Skip to content

Commit 647205b

Browse files
authored
App config util (#147)
* initial zod config utility * Refactor configuration handling: remove ConfigContext and related hooks, update environment variable timestamps to UTC format * Update testing guidelines: specify test file placement in __tests__ directory * Add config schema validation tests using Zod * Enhance configuration access by introducing a type-safe `config` utility with Zod validation for environment variables
1 parent 9d31755 commit 647205b

24 files changed

Lines changed: 583 additions & 215 deletions

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
## Provided by Pipeline (Simulated)
77
VITE_BUILD_DATE=1970-01-01
88
VITE_BUILD_TIME=00:00:00
9-
VITE_BUILD_TS=1970-01-01T00:00:00+0000
9+
VITE_BUILD_TS=1970-01-01T00:00:00Z
1010
VITE_BUILD_COMMIT_SHA=local
1111
VITE_BUILD_ENV_CODE=local
1212
VITE_BUILD_WORKFLOW_NAME=local

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ package.json # Project dependencies and scripts
173173
## Testing Guidelines
174174

175175
- Use **Vitest**.
176-
- Place test files next to the source file, with `.test.ts` suffix.
176+
- Place test files in the `__tests__` directory adjacent to the source file, with `.test.ts` suffix.
177177
- Use Arrange - Act - Assert (AAA) pattern for test structure:
178178
- **Arrange:** Set up the test environment and inputs.
179179
- **Act:** Call the function being tested.

.github/workflows/reusable-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
echo "${{ inputs.env_file }}" > .env
5151
echo "VITE_BUILD_DATE=$(date +'%Y-%m-%d')" >> .env
5252
echo "VITE_BUILD_TIME=$(date +'%H:%M:%S%z')" >> .env
53-
echo "VITE_BUILD_TS=$(date +'%Y-%m-%dT%H:%M:%S%z')" >> .env
53+
echo "VITE_BUILD_TS=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> .env
5454
echo "VITE_BUILD_COMMIT_SHA=${{ github.sha }}" >> .env
5555
echo "VITE_BUILD_ENV_CODE=${{ inputs.env }}" >> .env
5656
echo "VITE_BUILD_WORKFLOW_NAME=${{ github.workflow }}" >> .env

docs/CONFIGURATION_GUIDE.md

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The following environment variables are available for configuring the React appl
4242
```env
4343
VITE_BUILD_DATE=2026-02-10
4444
VITE_BUILD_TIME=14:30:00
45-
VITE_BUILD_TS=2026-02-10T14:30:00+0000
45+
VITE_BUILD_TS=2026-02-10T14:30:00Z
4646
VITE_BUILD_COMMIT_SHA=abc123def456
4747
VITE_BUILD_ENV_CODE=dev
4848
VITE_BUILD_WORKFLOW_NAME=Build
@@ -52,20 +52,35 @@ The following environment variables are available for configuring the React appl
5252

5353
### Accessing Configuration
5454

55-
Application configuration values are accessible directly in your React components since Vite automatically injects them:
55+
Application configuration values are accessed through the `config` utility, which provides type-safe, validated configuration throughout your React components and utilities. The utility validates all environment variables at runtime using Zod schema validation, ensuring type safety and early error detection.
56+
57+
Import the `config` object from the common utilities:
5658

5759
```typescript
58-
// API base URL
59-
const apiUrl = import.meta.env.VITE_BASE_URL_API;
60+
import { config } from 'common/utils/config';
61+
62+
// API base URL (type-safe string)
63+
const apiUrl = config.VITE_BASE_URL_API;
6064

61-
// Toast settings
62-
const toastDuration = import.meta.env.VITE_TOAST_AUTO_DISMISS_MILLIS;
65+
// Toast settings (type-safe number)
66+
const toastDuration = config.VITE_TOAST_AUTO_DISMISS_MILLIS;
6367

64-
// Build information
65-
const buildDate = import.meta.env.VITE_BUILD_DATE;
66-
const buildCommit = import.meta.env.VITE_BUILD_COMMIT_SHA;
68+
// Build information (type-safe strings)
69+
const buildDate = config.VITE_BUILD_DATE;
70+
const buildCommit = config.VITE_BUILD_COMMIT_SHA;
71+
const envCode = config.VITE_BUILD_ENV_CODE;
6772
```
6873

74+
**Benefits of using the `config` utility:**
75+
76+
- **Type Safety**: All configuration values are validated against a Zod schema, ensuring correct types
77+
- **Validation**: Environment variables are validated on application startup, catching missing or invalid configuration early
78+
- **IDE Support**: Full TypeScript autocomplete and type checking for configuration values
79+
- **Single Source of Truth**: Configuration is centralized and consistently accessed throughout the application
80+
81+
**Configuration Schema Location:**
82+
The Zod schema that validates all environment variables is defined in [src/common/utils/config.ts](../src/common/utils/config.ts). This file also exports the `Config` type for use in type annotations when needed.
83+
6984
### Local Development
7085

7186
For local development, create a `.env` file in the root directory with local values:
@@ -75,7 +90,7 @@ VITE_BASE_URL_API=http://localhost:3000
7590
VITE_TOAST_AUTO_DISMISS_MILLIS=5000
7691
VITE_BUILD_DATE=1970-01-01
7792
VITE_BUILD_TIME=00:00:00
78-
VITE_BUILD_TS=1970-01-01T00:00:00+0000
93+
VITE_BUILD_TS=1970-01-01T00:00:00Z
7994
VITE_BUILD_COMMIT_SHA=local
8095
VITE_BUILD_ENV_CODE=local
8196
VITE_BUILD_WORKFLOW_NAME=local
@@ -99,7 +114,7 @@ For running unit tests, create a `.env.test.local` file in the root directory wi
99114
# Provided by Pipeline (Simulated)
100115
VITE_BUILD_DATE=1970-01-01
101116
VITE_BUILD_TIME=00:00:00
102-
VITE_BUILD_TS=1970-01-01T00:00:00+0000
117+
VITE_BUILD_TS=1970-01-01T00:00:00Z
103118
VITE_BUILD_COMMIT_SHA=test
104119
VITE_BUILD_ENV_CODE=test
105120
VITE_BUILD_WORKFLOW_NAME=test
@@ -432,15 +447,19 @@ Ensure all required configuration variables are set as the correct type.
432447
2. Ensure AWS CLI is configured with `aws configure`
433448
3. Verify with: `aws sts get-caller-identity`
434449

435-
### Application Build Variables Not Available
450+
### Application Configuration Validation Error
436451

437-
**Problem**: `import.meta.env.VITE_*` variables are undefined at runtime.
452+
**Problem**: Application fails to start with a configuration validation error message.
438453

439454
**Solution**:
440455

441-
1. Ensure variables in `.env` are prefixed with `VITE_`
442-
2. Restart the development server after changing `.env`
443-
3. Verify with: `echo $VITE_BASE_URL_API`
456+
1. Ensure all required variables in `.env` are present (see Environment Variables table above)
457+
2. Ensure variables are prefixed with `VITE_` for Vite compatibility
458+
3. Restart the development server after changing `.env`
459+
4. Verify variables are set: `echo $VITE_BASE_URL_API`
460+
5. The config utility will provide detailed error messages indicating which variables are missing or invalid
461+
462+
**Note**: The application uses the `config` utility from `common/utils/config` which validates all environment variables at startup using Zod schema validation. If any required variables are missing or have invalid values, the application will fail with a clear error message indicating the issue.
444463

445464
### CDK Configuration Validation Errors
446465

src/App.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
44

55
import ErrorBoundary from 'common/components/Errors/ErrorBoundary';
66
import ErrorFallback from 'common/components/Errors/ErrorFallback';
7-
import ConfigContextProvider from 'common/providers/ConfigProvider';
87
import SettingsContextProvider from 'common/providers/SettingsProvider';
98
import AxiosContextProvider from 'common/providers/AxiosProvider';
109
import { router } from 'common/components/Router/Router';
@@ -33,22 +32,20 @@ function App() {
3332
return (
3433
<div id="app" data-testid="app">
3534
<ErrorBoundary fallback={<ErrorFallback />}>
36-
<ConfigContextProvider>
37-
<QueryClientProvider client={queryClient}>
38-
<SettingsContextProvider>
39-
<Theme>
40-
<AuthContextProvider>
41-
<AxiosContextProvider>
42-
<ToastsProvider>
43-
<RouterProvider router={router} />
44-
</ToastsProvider>
45-
</AxiosContextProvider>
46-
</AuthContextProvider>
47-
</Theme>
48-
<ReactQueryDevtools initialIsOpen={false} />
49-
</SettingsContextProvider>
50-
</QueryClientProvider>
51-
</ConfigContextProvider>
35+
<QueryClientProvider client={queryClient}>
36+
<SettingsContextProvider>
37+
<Theme>
38+
<AuthContextProvider>
39+
<AxiosContextProvider>
40+
<ToastsProvider>
41+
<RouterProvider router={router} />
42+
</ToastsProvider>
43+
</AxiosContextProvider>
44+
</AuthContextProvider>
45+
</Theme>
46+
<ReactQueryDevtools initialIsOpen={false} />
47+
</SettingsContextProvider>
48+
</QueryClientProvider>
5249
</ErrorBoundary>
5350
</div>
5451
);

src/common/api/useGetUser.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { UseQueryResult, useQuery } from '@tanstack/react-query';
22

33
import { useAxios } from 'common/hooks/useAxios';
4-
import { useConfig } from 'common/hooks/useConfig';
4+
import { config } from 'common/utils/config';
55
import { QueryKey } from 'common/utils/constants';
66

77
/**
@@ -56,7 +56,6 @@ interface UseGetUserProps {
5656
*/
5757
export const useGetUser = ({ userId }: UseGetUserProps): UseQueryResult<User, Error> => {
5858
const axios = useAxios();
59-
const config = useConfig();
6059

6160
const getUser = async (): Promise<User> => {
6261
const response = await axios.request({

src/common/components/Toast/Toast.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { cva, VariantProps } from 'class-variance-authority';
55

66
import { cn } from 'common/utils/css';
77
import { BaseComponentProps } from 'common/utils/types';
8+
import { config } from 'common/utils/config';
89
import { ToastDetail } from 'common/providers/ToastsContext';
9-
import { useConfig } from 'common/hooks/useConfig';
1010
import Button from 'common/components/Button/Button';
1111
import FAIcon from 'common/components/Icon/FAIcon';
1212

@@ -48,8 +48,6 @@ export interface ToastProps extends BaseComponentProps {
4848
* used when some adverse action happens, such as an error.
4949
*/
5050
const Toast = ({ className, dismiss, testId = 'toast', toast }: ToastProps) => {
51-
const config = useConfig();
52-
5351
const [springs, api] = useSpring(() => ({
5452
from: { opacity: 1, x: 0 },
5553
}));
@@ -74,7 +72,7 @@ const Toast = ({ className, dismiss, testId = 'toast', toast }: ToastProps) => {
7472

7573
return () => clearInterval(dismissInterval);
7674
}
77-
}, [toast, config.VITE_TOAST_AUTO_DISMISS_MILLIS]);
75+
}, [toast]);
7876

7977
return (
8078
<animated.div

src/common/components/Toast/__stories__/Toast.stories.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Meta, StoryObj } from '@storybook/react-vite';
22
import dayjs from 'dayjs';
33

4-
import ConfigContextProvider from 'common/providers/ConfigProvider';
54
import { ToastDetail } from 'common/providers/ToastsContext';
65

76
import Toast from '../Toast';
@@ -11,10 +10,8 @@ const meta = {
1110
component: Toast,
1211
decorators: [
1312
(Story) => (
14-
<div className="w-[480px]">
15-
<ConfigContextProvider>
16-
<Story />
17-
</ConfigContextProvider>
13+
<div className="w-120">
14+
<Story />
1815
</div>
1916
),
2017
],

src/common/hooks/__tests__/useConfig.test.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/common/hooks/useConfig.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)