diff --git a/.env.example b/.env.example index 0cba496..ae7b99c 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,12 @@ -HOSTNAME=localhost +# Network configuration +HOSTNAME=localhost # hostname for service discovery across docker network -# PORTS -KAFKA_PORT=9092 -REDIS_PORT=6377 +# Application configuration +STORE_MODE=hotel # platform mode: 'hotel' or 'airline' - determines product catalog and UI theme +NEXT_PUBLIC_API_BASE=http://localhost:3000 # base URL for API endpoints, must be valid URL format +NEXT_PUBLIC_APP_ENV=dev # application environment: 'dev' or 'prod' - controls logging, error handling + +# Service ports - used by docker-compose and service communication +KAFKA_PORT=9092 # kafka broker port for event streaming +REDIS_PORT=6377 # redis port for worker queue and caching +REDPANDA_CONSOLE_PORT=8084 # redpanda console UI port for kafka monitoring diff --git a/web/package-lock.json b/web/package-lock.json index 33bebd6..8de31b4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,7 +11,8 @@ "kafkajs": "^2.2.4", "next": "16.0.0", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "zod": "^4.1.12" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -1616,6 +1617,15 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/web/package.json b/web/package.json index 9b83e2d..c46656c 100644 --- a/web/package.json +++ b/web/package.json @@ -11,7 +11,8 @@ "kafkajs": "^2.2.4", "next": "16.0.0", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "zod": "^4.1.12" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/web/src/lib/config.ts b/web/src/lib/config.ts new file mode 100644 index 0000000..8be3088 --- /dev/null +++ b/web/src/lib/config.ts @@ -0,0 +1,30 @@ +import { z } from 'zod'; + +type Env = z.infer; +const envSchema = z.object({ + STORE_MODE: z.enum(['hotel', 'airline'], { + errorMap: () => ({ message: 'STORE_MODE must be either "hotel" or "airline"' }) + }), + NEXT_PUBLIC_API_BASE: z.string().url({ + message: 'NEXT_PUBLIC_API_BASE must be a valid URL (e.g., http://localhost:3000)' + }), + NEXT_PUBLIC_APP_ENV: z.enum(['dev', 'prod'], { + errorMap: () => ({ message: 'NEXT_PUBLIC_APP_ENV must be either "dev" or "prod"' }) + }), +}); + +// parse and validate env at module load, fail fast with descriptive errors +const parseEnv = (): Env => { + const result = envSchema.safeParse({ + STORE_MODE: process.env.STORE_MODE, + NEXT_PUBLIC_API_BASE: process.env.NEXT_PUBLIC_API_BASE, + NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV, + }); + if (!result.success) { + const errors = result.error.errors.map((err) => `${err.path.join('.')}: ${err.message}`).join('\n'); + throw new Error(`Environment validation failed:\n${errors}`); + } + return result.data; +}; + +export const config: Env = parseEnv();