diff --git a/.github/workflows/testing-client.yml b/.github/workflows/testing-client.yml index d13e8bf7b..7835cbff7 100644 --- a/.github/workflows/testing-client.yml +++ b/.github/workflows/testing-client.yml @@ -49,7 +49,6 @@ jobs: command-prefix: yarn dlx command: "yarn test:e2e" env: - NEXT_PUBLIC_MAPBOX_API_TOKEN: ${{ secrets.NEXT_PUBLIC_MAPBOX_API_TOKEN }} NEXT_PUBLIC_API_URL: ${{ secrets.CYPRESS_API_URL }} NEXT_TELEMETRY_DISABLED: 1 NEXTAUTH_URL: http://localhost:3000 diff --git a/client/.env.sample b/client/.env.sample index bae9f2079..70340230b 100644 --- a/client/.env.sample +++ b/client/.env.sample @@ -1,6 +1,3 @@ -NEXT_PUBLIC_MAPBOX_API_TOKEN="[mapbox-token]" NEXT_PUBLIC_API_URL="http://[api-url]" NEXTAUTH_URL="http://localhost:3000" -NEXTAUTH_SECRET="nyancat" -NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE=10000000 NEXT_TELEMETRY_DISABLED=1 diff --git a/client/Dockerfile b/client/Dockerfile index 4a758dbf4..ec690949c 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -4,7 +4,6 @@ ARG UID=5000 ARG GID=5000 ARG NEXTAUTH_URL ARG NEXTAUTH_SECRET -ARG NEXT_PUBLIC_MAPBOX_API_TOKEN ARG NEXT_PUBLIC_API_URL ARG CYPRESS_USERNAME ARG CYPRESS_PASSWORD @@ -16,7 +15,6 @@ ENV USER $NAME ENV APP_HOME /opt/$NAME ENV NEXTAUTH_URL $NEXTAUTH_URL ENV NEXTAUTH_SECRET $NEXTAUTH_SECRET -ENV NEXT_PUBLIC_MAPBOX_API_TOKEN $NEXT_PUBLIC_MAPBOX_API_TOKEN ENV NEXT_PUBLIC_API_URL $NEXT_PUBLIC_API_URL ENV NEXT_TELEMETRY_DISABLED 1 ENV CYPRESS_USERNAME $CYPRESS_USERNAME diff --git a/client/ENV_VARS.md b/client/ENV_VARS.md deleted file mode 100644 index 1a4b3225c..000000000 --- a/client/ENV_VARS.md +++ /dev/null @@ -1,13 +0,0 @@ -# Environment variables - -This document covers the different [environment variables](https://en.wikipedia.org/wiki/Environment_variable) supported -by Landgriffon client application. - -* `NEXT_PUBLIC_MAPBOX_API_TOKEN`: Mapbox API token for the frontend app. -* `NEXT_PUBLIC_API_URL`: URL including protocol of the api application. -* `NEXTAUTH_URL`: URL including protocol of the client application. -* `NEXTAUTH_SECRET`: Secret used for cryptographic operations in the client application. Generate using `openssl rand -base64 32` - -* `CYPRESS_USERNAME`: email for login in the test tasks -* `CYPRESS_PASSWORD`: password for login in the test tasks -* `CYPRESS_API_URL`: URL including protocol of the api application, only for testing. diff --git a/client/README.md b/client/README.md index 348491904..53afa1aec 100644 --- a/client/README.md +++ b/client/README.md @@ -21,6 +21,17 @@ Run the development server: ```bash yarn dev ``` + +### Environmental variables +The application handles environmental variables using [@t3-oss/env-nextjs](https://env.t3.gg/docs). You can see the available (and required) variables in the `./src/env` file. **NOTE**: the application will NOT start if the required variables are not set previously. + +#### Testing +Additionally, and exclusively for testing purposes, you can set the following environmental variables: + +- `CYPRESS_USERNAME`: email to authenticate for the e2e tests. +- `CYPRESS_PASSWORD`: password to authenticate for the e2e tests. +- `CYPRESS_API_URL`: API used to run the e2e tests. + ### Running tests Run the tests locally: diff --git a/client/docker-compose.test.yml b/client/docker-compose.test.yml index f1d9fa2df..b19eeadc5 100644 --- a/client/docker-compose.test.yml +++ b/client/docker-compose.test.yml @@ -7,7 +7,6 @@ services: args: - NEXTAUTH_URL=http://localhost:3000 - NEXTAUTH_SECRET=secret - - NEXT_PUBLIC_MAPBOX_API_TOKEN=token ports: - "3000:3000" container_name: landgriffon-client diff --git a/client/next.config.js b/client/next.config.js index b962da05f..b81579bc7 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -1,3 +1,12 @@ +import { fileURLToPath } from 'node:url'; + +import createJiti from 'jiti'; + +import { env } from './src/env.mjs'; + +const jiti = createJiti(fileURLToPath(import.meta.url)); +jiti('./src/env.mjs'); + /** @type {import('next').NextConfig} */ const nextConfig = { poweredByHeader: false, @@ -21,7 +30,7 @@ const nextConfig = { destination: '/auth/signin', permanent: false, }, - ...(process.env.NEXT_PUBLIC_ENABLE_EUDR !== 'true' + ...(!env.NEXT_PUBLIC_ENABLE_EUDR ? [ { source: '/eudr', @@ -32,15 +41,6 @@ const nextConfig = { : []), ]; }, - env: { - NEXT_PUBLIC_PLANET_API_KEY: 'PLAK6679039df83f414faf798ba4ad4530db', - NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN: - 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjY2JlMjUyYSJ9.LoqzuDp076ESVYmHm1mZNtfhnqOVGmSxzp60Fht8PQw', - NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN: - 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjZDk0ZWIyZSJ9.oqLagnOEc-j7Z4hY-MTP1yoZA_vJ7WYYAkOz_NUmCJo', - NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN: - 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiI3NTFkNzA1YSJ9.jrVugV7HYfhmjxj-p2Iks8nL_AjHR91Q37JVP2fNmtc', - }, }; export default nextConfig; diff --git a/client/package.json b/client/package.json index d7aa892d5..a9cb3915a 100644 --- a/client/package.json +++ b/client/package.json @@ -97,9 +97,11 @@ "tailwindcss": "3.4.1", "tailwindcss-animate": "1.0.7", "uuid": "8.3.2", - "yup": "0.32.11" + "yup": "0.32.11", + "zod": "3.22.4" }, "devDependencies": { + "@t3-oss/env-nextjs": "0.9.2", "@types/chroma-js": "2.1.3", "@types/d3-format": "3.0.1", "@types/d3-scale": "4.0.2", @@ -116,6 +118,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", "istanbul-reports": "3.0.0", + "jiti": "1.21.0", "nyc": "15.1.0", "nyc-report-lcov-absolute": "1.0.0", "prettier": "^3.1.1", diff --git a/client/src/components/map/layers/maplibre/raster/utils.ts b/client/src/components/map/layers/maplibre/raster/utils.ts index f3c082cb2..9dcb31997 100644 --- a/client/src/components/map/layers/maplibre/raster/utils.ts +++ b/client/src/components/map/layers/maplibre/raster/utils.ts @@ -1,5 +1,7 @@ import queryString from 'query-string'; +import { env } from '@/env.mjs'; + import type { ContextualLayerApiResponse } from 'hooks/layers/getContextualLayers'; export const getTiler = ( @@ -9,7 +11,7 @@ export const getTiler = ( return queryString.stringifyUrl({ url: tilerPath.match(/^(http|https):\/\//) ? tilerPath - : `${process.env.NEXT_PUBLIC_API_URL}${tilerPath}`, + : `${env.NEXT_PUBLIC_API_URL}${tilerPath}`, query: tilerParams, }); }; diff --git a/client/src/containers/analysis-eudr/map/compare.tsx b/client/src/containers/analysis-eudr/map/compare.tsx index 88de837a8..8875a5cfc 100644 --- a/client/src/containers/analysis-eudr/map/compare.tsx +++ b/client/src/containers/analysis-eudr/map/compare.tsx @@ -17,6 +17,7 @@ import { useAppSelector } from '@/store/hooks'; import { INITIAL_VIEW_STATE, MAP_STYLES } from '@/components/map'; import { useEUDRData, usePlotGeometries } from '@/hooks/eudr'; import { formatNumber } from '@/utils/number-format'; +import { env } from '@/env.mjs'; import type { PickingInfo, MapViewState } from '@deck.gl/core/typed'; @@ -174,7 +175,7 @@ const EUDRCompareMap = () => { credentials: { apiVersion: API_VERSIONS.V3, apiBaseUrl: 'https://gcp-us-east1.api.carto.com', - accessToken: process.env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN, + accessToken: env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN, }, }); @@ -192,7 +193,7 @@ const EUDRCompareMap = () => { credentials: { apiVersion: API_VERSIONS.V3, apiBaseUrl: 'https://gcp-us-east1.api.carto.com', - accessToken: process.env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN, + accessToken: env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN, }, }); @@ -217,7 +218,7 @@ const EUDRCompareMap = () => { credentials: { apiVersion: API_VERSIONS.V3, apiBaseUrl: 'https://gcp-us-east1.api.carto.com', - accessToken: process.env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN, + accessToken: env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN, }, }); @@ -298,7 +299,7 @@ const EUDRCompareMap = () => { planetLayer.year }_${monthFormatter( planetLayer.month.toString(), - )}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${process.env.NEXT_PUBLIC_PLANET_API_KEY}`, + )}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${env.NEXT_PUBLIC_PLANET_API_KEY}`, ]} tileSize={256} > @@ -318,7 +319,7 @@ const EUDRCompareMap = () => { planetCompareLayer.year }_${monthFormatter( planetCompareLayer.month.toString(), - )}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${process.env.NEXT_PUBLIC_PLANET_API_KEY}`, + )}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${env.NEXT_PUBLIC_PLANET_API_KEY}`, ]} tileSize={256} > diff --git a/client/src/containers/analysis-eudr/map/component.tsx b/client/src/containers/analysis-eudr/map/component.tsx index 5adbb2b5a..2b0de4c85 100644 --- a/client/src/containers/analysis-eudr/map/component.tsx +++ b/client/src/containers/analysis-eudr/map/component.tsx @@ -16,6 +16,7 @@ import { useAppSelector } from '@/store/hooks'; import { INITIAL_VIEW_STATE, MAP_STYLES } from '@/components/map'; import { useEUDRData, usePlotGeometries } from '@/hooks/eudr'; import { formatNumber } from '@/utils/number-format'; +import { env } from '@/env.mjs'; import type { PickingInfo, MapViewState } from '@deck.gl/core/typed'; @@ -169,7 +170,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => { credentials: { apiVersion: API_VERSIONS.V3, apiBaseUrl: 'https://gcp-us-east1.api.carto.com', - accessToken: process.env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN, + accessToken: env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN, }, }); @@ -187,7 +188,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => { credentials: { apiVersion: API_VERSIONS.V3, apiBaseUrl: 'https://gcp-us-east1.api.carto.com', - accessToken: process.env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN, + accessToken: env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN, }, }); @@ -212,7 +213,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => { credentials: { apiVersion: API_VERSIONS.V3, apiBaseUrl: 'https://gcp-us-east1.api.carto.com', - accessToken: process.env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN, + accessToken: env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN, }, }); @@ -277,7 +278,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => { planetLayer.year }_${monthFormatter( planetLayer.month.toString(), - )}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${process.env.NEXT_PUBLIC_PLANET_API_KEY}`, + )}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${env.NEXT_PUBLIC_PLANET_API_KEY}`, ]} tileSize={256} > diff --git a/client/src/containers/uploader/component.tsx b/client/src/containers/uploader/component.tsx index 076ab5559..5a1ab080e 100644 --- a/client/src/containers/uploader/component.tsx +++ b/client/src/containers/uploader/component.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/router'; import { useUploadDataSource } from 'hooks/sourcing-data'; import { useLasTask } from 'hooks/tasks'; import FileDropzone from 'components/file-dropzone'; +import { env } from '@/env.mjs'; import type { FileDropZoneProps } from 'components/file-dropzone/types'; import type { Task } from 'types'; @@ -15,14 +16,12 @@ type DataUploaderProps = { onUploadInProgress?: (inProgress: boolean) => void; }; -const MAX_SIZE = Number(process.env.NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE || '10000000'); - const uploadOptions = { accept: { 'application/vnd.ms-excel': ['.xls'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], }, - maxSize: MAX_SIZE, + maxSize: env.NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE, }; const DataUploader: React.FC = ({ variant = 'default', onUploadInProgress }) => { diff --git a/client/src/env.mjs b/client/src/env.mjs new file mode 100644 index 000000000..ed18b700c --- /dev/null +++ b/client/src/env.mjs @@ -0,0 +1,63 @@ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; + +export const env = createEnv({ + shared: { + NODE_ENV: z.enum(['development', 'production', 'test']), + }, + + /* + * Serverside Environment variables, not available on the client. + * Will throw if you access these variables on the client. + */ + server: { + // ? URL (including protocol) of the client application + NEXTAUTH_URL: z + .string() + .url() + .default(`http://localhost:${process.env.PORT || 3000}`), + // ? Secret used for cryptographic operations in the client application. Generate using `openssl rand -base64 32` + NEXTAUTH_SECRET: z.string().min(1), + }, + /* + * Environment variables available on the client (and server). + * + * 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_. + */ + client: { + // ? URL (including protocol) of the API + NEXT_PUBLIC_API_URL: z.string().url(), + // ? enables access to EUDR page + NEXT_PUBLIC_ENABLE_EUDR: z.coerce.boolean().optional().default(false), + NEXT_PUBLIC_PLANET_API_KEY: z.string().default('PLAK6679039df83f414faf798ba4ad4530db'), + NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN: z + .string() + .default( + 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjY2JlMjUyYSJ9.LoqzuDp076ESVYmHm1mZNtfhnqOVGmSxzp60Fht8PQw', + ), + NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN: z + .string() + .default( + 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjZDk0ZWIyZSJ9.oqLagnOEc-j7Z4hY-MTP1yoZA_vJ7WYYAkOz_NUmCJo', + ), + NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN: z + .string() + .default( + 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiI3NTFkNzA1YSJ9.jrVugV7HYfhmjxj-p2Iks8nL_AjHR91Q37JVP2fNmtc', + ), + NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE: z.coerce.number().default(10000000), + }, + runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + NEXT_PUBLIC_ENABLE_EUDR: process.env.NEXT_PUBLIC_ENABLE_EUDR, + NEXT_PUBLIC_PLANET_API_KEY: process.env.NEXT_PUBLIC_PLANET_API_KEY, + NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN: process.env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN, + NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN: + process.env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN, + NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN: process.env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN, + NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE: process.env.NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE, + }, +}); diff --git a/client/src/layouts/application/component.tsx b/client/src/layouts/application/component.tsx index c8de60cdc..4006afa70 100644 --- a/client/src/layouts/application/component.tsx +++ b/client/src/layouts/application/component.tsx @@ -5,6 +5,7 @@ import { CollectionIcon as CollectionIconSolid } from '@heroicons/react/solid'; import { ChartBarIconOutline, ChartBarIconSolid } from './icons/chart-bar'; +import { env } from '@/env.mjs'; import { useLasTask } from 'hooks/tasks'; import Navigation from 'containers/navigation/desktop'; import UserDropdown from 'containers/user-dropdown'; @@ -31,7 +32,7 @@ const ApplicationLayout: React.FC = ({ children }) => { icon: { default: ChartBarIconOutline, active: ChartBarIconSolid }, disabled: !!(!lastTask || lastTask?.status === 'processing'), }, - ...(process.env.NEXT_PUBLIC_ENABLE_EUDR === 'true' + ...(env.NEXT_PUBLIC_ENABLE_EUDR ? [ { name: 'EUDR', diff --git a/client/src/services/api.ts b/client/src/services/api.ts index 43fd82b8e..bb7900e7b 100644 --- a/client/src/services/api.ts +++ b/client/src/services/api.ts @@ -3,6 +3,8 @@ import Jsona from 'jsona'; import { getSession, signOut } from 'next-auth/react'; import toast from 'react-hot-toast'; +import { env } from '@/env.mjs'; + import type { ApiError, ErrorResponse } from 'types'; import type { AxiosRequestConfig, AxiosResponse } from 'axios'; @@ -14,7 +16,7 @@ import type { AxiosRequestConfig, AxiosResponse } from 'axios'; const dataFormatter = new Jsona(); const defaultConfig: AxiosRequestConfig = { - baseURL: `${process.env.NEXT_PUBLIC_API_URL}/api/v1`, + baseURL: `${env.NEXT_PUBLIC_API_URL}/api/v1`, headers: { 'Content-Type': 'application/json' }, }; diff --git a/client/src/services/authentication.ts b/client/src/services/authentication.ts index 9f364455b..294b6c55d 100644 --- a/client/src/services/authentication.ts +++ b/client/src/services/authentication.ts @@ -1,7 +1,9 @@ import axios from 'axios'; +import { env } from '@/env.mjs'; + export const authService = axios.create({ - baseURL: `${process.env.NEXT_PUBLIC_API_URL}/auth`, + baseURL: `${env.NEXT_PUBLIC_API_URL}/auth`, headers: { 'Content-Type': 'application/json' }, }); diff --git a/client/src/services/ssr.ts b/client/src/services/ssr.ts index 2a5335357..fec3a6685 100644 --- a/client/src/services/ssr.ts +++ b/client/src/services/ssr.ts @@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios'; import { getServerSession } from 'next-auth/next'; import { options as authOptions } from 'pages/api/auth/[...nextauth]'; +import { env } from '@/env.mjs'; export const sessionSSR = async ({ req, res }) => await getServerSession(req, res, authOptions); @@ -14,7 +15,7 @@ export const tasksSSR = async ({ req, res }) => { return await axios({ method: 'GET', - url: `${process.env.NEXT_PUBLIC_API_URL}/api/v1/tasks`, + url: `${env.NEXT_PUBLIC_API_URL}/api/v1/tasks`, params: { 'page[size]': 1, 'page[number]': 1, sort: '-createdAt' }, headers: { Authorization: `Bearer ${session.accessToken}`, diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 48f714987..b79c73868 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -18,6 +18,7 @@ import analysisScenarios, { } from 'store/features/analysis/scenarios'; import eudr from 'store/features/eudr'; import eudrDetail from 'store/features/eudr-detail'; +import { env } from '@/env.mjs'; import type { Action, ReducersMapObject, Middleware } from '@reduxjs/toolkit'; import type { AnalysisState } from './features/analysis'; @@ -158,7 +159,7 @@ const querySyncMiddleware: Middleware = () => (next) => (action) => { const createStore = (query = {}, currentState?: AnalysisState) => configureStore({ reducer: createReducer(asyncReducers), - devTools: process.env.NODE_ENV !== 'production', + devTools: env.NODE_ENV !== 'production', preloadedState: getPreloadedState(query, currentState), middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(querySyncMiddleware), }); diff --git a/client/yarn.lock b/client/yarn.lock index 66f125d27..e9cdd9d02 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2572,6 +2572,34 @@ __metadata: languageName: node linkType: hard +"@t3-oss/env-core@npm:0.9.2": + version: 0.9.2 + resolution: "@t3-oss/env-core@npm:0.9.2" + peerDependencies: + typescript: ">=5.0.0" + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 9488814c7243081a3d732e6504fdc6638582e57bba6f1e8be3a917d5cc8be9436372cb7cdf6d99125ea45942f451274817d0a965cfc2bf30e055ab7f073e643d + languageName: node + linkType: hard + +"@t3-oss/env-nextjs@npm:0.9.2": + version: 0.9.2 + resolution: "@t3-oss/env-nextjs@npm:0.9.2" + dependencies: + "@t3-oss/env-core": 0.9.2 + peerDependencies: + typescript: ">=5.0.0" + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 44cae81c09c34702a81c6150f2f352e4adbcfe3d4ea2464693a077fffbbdb95c1c191e3334a44e961e1dca0f95beacbf975c87aa049b7a37c99826d3dfa30e45 + languageName: node + linkType: hard + "@tailwindcss/forms@npm:0.4.0": version: 0.4.0 resolution: "@tailwindcss/forms@npm:0.4.0" @@ -7577,7 +7605,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.19.1": +"jiti@npm:1.21.0, jiti@npm:^1.19.1": version: 1.21.0 resolution: "jiti@npm:1.21.0" bin: @@ -7852,6 +7880,7 @@ __metadata: "@radix-ui/react-switch": ^1.0.3 "@radix-ui/react-tooltip": ^1.0.7 "@reduxjs/toolkit": 1.8.2 + "@t3-oss/env-nextjs": 0.9.2 "@tailwindcss/forms": 0.4.0 "@tailwindcss/typography": 0.5.0 "@tanstack/react-query": ^4.2.1 @@ -7885,6 +7914,7 @@ __metadata: eslint-plugin-prettier: 5.1.3 fuse.js: 6.4.6 istanbul-reports: 3.0.0 + jiti: 1.21.0 jsona: 1.9.2 lodash-es: 4.17.21 lottie-react: 2.4.0 @@ -7922,6 +7952,7 @@ __metadata: uuid: 8.3.2 webpack: 5 yup: 0.32.11 + zod: 3.22.4 languageName: unknown linkType: soft @@ -12290,3 +12321,10 @@ __metadata: checksum: 43a16786b47cc910fed4891cebdd89df6d6e31702e9462e8f969c73eac88551ce750732608012201ea6b93802c8847cb0aa27b5d57370640f4ecf30f9f97d4b0 languageName: node linkType: hard + +"zod@npm:3.22.4": + version: 3.22.4 + resolution: "zod@npm:3.22.4" + checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f + languageName: node + linkType: hard