diff --git a/.github/workflows/betasite.yml b/.github/workflows/betasite.yml index 98dfd166..e0df6b4d 100644 --- a/.github/workflows/betasite.yml +++ b/.github/workflows/betasite.yml @@ -7,6 +7,11 @@ on: env: AWS_DEFAULT_REGION: us-east-2 VITE_DB_URL: https://wantycfbnzzocsbthqzs.supabase.co + VITE_DB_API_KEY: ${{ secrets.SUPABASE_API_KEY }} + VITE_REACT_GOOGLE_MAPS_API_KEY: ${{ secrets.REACT_GOOGLE_MAPS_API_KEY }} + VITE_OPEN_ROUTE_SERVICE_API_KEY: ${{ secrets.OPEN_ROUTE_SERVICE_API_KEY }} + VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + VITE_VERIFICATION_PASSWORD: ${{ secrets.VERIFICATION_PASSWORD }} permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout diff --git a/.github/workflows/build_testsite.yml b/.github/workflows/build_testsite.yml index 8367fed1..eacc785a 100644 --- a/.github/workflows/build_testsite.yml +++ b/.github/workflows/build_testsite.yml @@ -1,12 +1,18 @@ name: Deploy Test Site on S3 on: [workflow_call] -env: - AWS_DEFAULT_REGION: us-east-2 jobs: build: runs-on: ubuntu-latest + env: + AWS_DEFAULT_REGION: us-east-2 + VITE_DB_URL: https://wantycfbnzzocsbthqzs.supabase.co + VITE_DB_API_KEY: ${{ secrets.SUPABASE_API_KEY }} + VITE_REACT_GOOGLE_MAPS_API_KEY: ${{ secrets.REACT_GOOGLE_MAPS_API_KEY }} + VITE_OPEN_ROUTE_SERVICE_API_KEY: ${{ secrets.OPEN_ROUTE_SERVICE_API_KEY }} + VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + VITE_VERIFICATION_PASSWORD: ${{ secrets.VERIFICATION_PASSWORD }} steps: - uses: actions/checkout@v1 - name: Configure AWS Credentials diff --git a/.github/workflows/cypress_testing.yml b/.github/workflows/cypress_testing.yml index 1ad0fc4a..e00ca151 100644 --- a/.github/workflows/cypress_testing.yml +++ b/.github/workflows/cypress_testing.yml @@ -4,6 +4,14 @@ on: [workflow_call] jobs: cypress_tests: + runs-on: ubuntu-latest + env: + VITE_DB_URL: https://wantycfbnzzocsbthqzs.supabase.co + VITE_DB_API_KEY: ${{ secrets.SUPABASE_API_KEY }} + VITE_REACT_GOOGLE_MAPS_API_KEY: ${{ secrets.REACT_GOOGLE_MAPS_API_KEY }} + VITE_OPEN_ROUTE_SERVICE_API_KEY: ${{ secrets.OPEN_ROUTE_SERVICE_API_KEY }} + VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + VITE_VERIFICATION_PASSWORD: ${{ secrets.VERIFICATION_PASSWORD }} strategy: matrix: include: @@ -13,7 +21,6 @@ jobs: - spec: 'cypress/e2e/mobile/*.cy.ts' config_file: 'cypress.mobile.config.ts' video_path: 'cypress/videos/mobile' - runs-on: ubuntu-latest steps: - uses: actions/setup-node@v6 with: diff --git a/.github/workflows/lighthouse_testing.yml b/.github/workflows/lighthouse_testing.yml index bb112947..b9a82e11 100644 --- a/.github/workflows/lighthouse_testing.yml +++ b/.github/workflows/lighthouse_testing.yml @@ -4,8 +4,8 @@ on: [workflow_call] env: AWS_DEFAULT_REGION: us-east-2 permissions: - id-token: write # This is required for requesting the JWT - contents: read # This is required for actions/checkout + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout jobs: lighthouse-check: diff --git a/.github/workflows/prodsite.yml b/.github/workflows/prodsite.yml index 07366f3e..b09f6180 100644 --- a/.github/workflows/prodsite.yml +++ b/.github/workflows/prodsite.yml @@ -7,6 +7,11 @@ on: env: AWS_DEFAULT_REGION: us-east-2 VITE_DB_URL: https://wantycfbnzzocsbthqzs.supabase.co + VITE_DB_API_KEY: ${{ secrets.SUPABASE_API_KEY }} + VITE_REACT_GOOGLE_MAPS_API_KEY: ${{ secrets.REACT_GOOGLE_MAPS_API_KEY }} + VITE_OPEN_ROUTE_SERVICE_API_KEY: ${{ secrets.OPEN_ROUTE_SERVICE_API_KEY }} + VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + VITE_VERIFICATION_PASSWORD: ${{ secrets.VERIFICATION_PASSWORD }} permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout diff --git a/.github/workflows/testsite.yml b/.github/workflows/testsite.yml index 6c8ce53b..a074ea28 100644 --- a/.github/workflows/testsite.yml +++ b/.github/workflows/testsite.yml @@ -15,3 +15,4 @@ jobs: secrets: inherit cypress-testing: uses: ./.github/workflows/cypress_testing.yml + secrets: inherit diff --git a/docker-compose.yml b/docker-compose.yml index 9ae4f4c5..9432610a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,11 @@ services: environment: CI: 'false' VITE_DB_URL: + VITE_DB_API_KEY: + VITE_REACT_GOOGLE_MAPS_API_KEY: + VITE_OPEN_ROUTE_SERVICE_API_KEY: + VITE_PUBLIC_POSTHOG_KEY: + VITE_VERIFICATION_PASSWORD: volumes: - './docker/build:/usr/src/app/build' - './docker/testResults:/usr/src/app/testResults' diff --git a/src/.example.env b/src/.example.env new file mode 100644 index 00000000..7269349e --- /dev/null +++ b/src/.example.env @@ -0,0 +1,8 @@ +# Below is how your .env file should look. Make sure to replace the placeholder values with your actual API keys. +# To get these details please message us in the #phlask-data channel on Slack + +VITE_DB_API_KEY=db_api_key_here +VITE_REACT_GOOGLE_MAPS_API_KEY=google_maps_api_key_here +VITE_OPEN_ROUTE_SERVICE_API_KEY=open_route_service_api_key_here +VITE_PUBLIC_POSTHOG_KEY=posthog_api_key_here +VITE_VERIFICATION_PASSWORD=verification_password_here \ No newline at end of file diff --git a/src/components/Providers/AnalyticsProvider.tsx b/src/components/Providers/AnalyticsProvider.tsx index d120ef8a..2a46cd99 100644 --- a/src/components/Providers/AnalyticsProvider.tsx +++ b/src/components/Providers/AnalyticsProvider.tsx @@ -1,9 +1,12 @@ import { PostHogProvider } from 'posthog-js/react'; import type { PropsWithChildren } from 'react'; +import { env } from 'config'; + +const APIHOST = 'https://us.i.posthog.com'; +const APIKEY = env.VITE_PUBLIC_POSTHOG_KEY; const postHogOptions = { - api_host: - import.meta.env.VITE_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com', + api_host: APIHOST, mask_all_text: true }; @@ -17,13 +20,7 @@ const AnalyticsProvider = ({ children }: PropsWithChildren) => { return children; return ( - + {children} ); diff --git a/src/components/Providers/Providers.tsx b/src/components/Providers/Providers.tsx index e4dad1d5..47532fb0 100644 --- a/src/components/Providers/Providers.tsx +++ b/src/components/Providers/Providers.tsx @@ -5,21 +5,23 @@ import type { PropsWithChildren } from 'react'; import ToolbarContextProvider from './ToolbarContextProvider'; import queryClient from 'services/queryClient'; import ThemeProvider from './ThemeProvider'; +import { env } from 'config'; -const Providers = ({ children }: PropsWithChildren) => ( - - - - - - {children} - - - - -); +const REACT_GOOGLE_MAPS_API_KEY = env.VITE_REACT_GOOGLE_MAPS_API_KEY; + +const Providers = ({ children }: PropsWithChildren) => { + return ( + + + + + + {children} + + + + + ); +}; export default Providers; diff --git a/src/components/Verification/VerificationButton.tsx b/src/components/Verification/VerificationButton.tsx index 6c5c4a35..e077397c 100644 --- a/src/components/Verification/VerificationButton.tsx +++ b/src/components/Verification/VerificationButton.tsx @@ -5,8 +5,9 @@ import Dialog from '@mui/material/Dialog'; import { updateResource } from 'services/db'; import type { ResourceEntry, Verification } from 'types/ResourceEntry'; import useSelectedResource from 'hooks/useSelectedResource'; +import { env } from 'config'; -const PASSWORD = 'ZnJlZXdhdGVy'; // Ask in Slack if you want the real password +const PASSWORD = env.VITE_VERIFICATION_PASSWORD; type VerificationButtonProps = { resource: ResourceEntry; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..15561b5a --- /dev/null +++ b/src/config.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +const envSchema = z.object({ + VITE_DB_API_KEY: z.string().min(1), + VITE_REACT_GOOGLE_MAPS_API_KEY: z.string().min(1), + VITE_OPEN_ROUTE_SERVICE_API_KEY: z.string().min(1), + VITE_PUBLIC_POSTHOG_KEY: z.string().min(1), + VITE_VERIFICATION_PASSWORD: z.string().min(1) +}); + +const result = envSchema.safeParse(import.meta.env); + +if (!result.success) { + console.error(result.error); + throw new Error( + `Environment variables are missing or invalid in your .env file. Check the error details above.` + ); +} + +export const env = result.data; diff --git a/src/hooks/queries/useWalkingDurationQuery.ts b/src/hooks/queries/useWalkingDurationQuery.ts index 5814396d..1735ee73 100644 --- a/src/hooks/queries/useWalkingDurationQuery.ts +++ b/src/hooks/queries/useWalkingDurationQuery.ts @@ -2,9 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import type { ResourceEntry } from 'types/ResourceEntry'; import getUserLocation from 'utils/getUserLocation'; import useActiveSearchLocation from 'hooks/useActiveSearchLocation'; - -const OPEN_ROUTE_SERVICE_API_KEY = - '5b3ce3597851110001cf6248ac903cdbe0364ca9850aa85cb64d8dfc'; +import { env } from 'config'; const BASE_URL = 'https://api.openrouteservice.org/v2'; const PATH = '/directions/foot-walking'; @@ -77,7 +75,7 @@ export const useWalkingDurationQuery = ({ const endingLocation = [longitude, latitude].join(','); const params = new URLSearchParams({ - api_key: OPEN_ROUTE_SERVICE_API_KEY, + api_key: env.VITE_OPEN_ROUTE_SERVICE_API_KEY, start: startingLocation, end: endingLocation }); diff --git a/src/services/db.ts b/src/services/db.ts index 348f4d64..351d9ede 100644 --- a/src/services/db.ts +++ b/src/services/db.ts @@ -2,14 +2,12 @@ import { createClient } from '@supabase/supabase-js'; import type { Provider, ResourceEntry } from 'types/ResourceEntry'; import type { ResourceTypeOption } from 'hooks/useResourceType'; import type { Contributor } from 'types/Contributor'; +import { env } from 'config'; -// Need access to the database? Message us in the #phlask-data channel on Slack -const databaseUrl = - import.meta.env?.VITE_DB_URL || 'https://wantycfbnzzocsbthqzs.supabase.co'; +// Need access to the database? Please refer to .example.env and message us in the #phlask-data channel on Slack +const databaseUrl = 'https://wantycfbnzzocsbthqzs.supabase.co'; +const databaseApiKey = env.VITE_DB_API_KEY; const resourceDatabaseName = 'resources'; -const databaseApiKey = - import.meta.env?.VITE_DB_API_KEY || - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndhbnR5Y2Zibnp6b2NzYnRocXpzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzcwNDY2OTgsImV4cCI6MjA1MjYyMjY5OH0.yczsMOx3Y-zsWu-GjYEajIb0yw9fYWEIUglmmfM1zCY'; const contributorDatabaseName = 'airtable_contributors'; const providersDatabaseName = 'providers'; @@ -127,11 +125,15 @@ export const getContributors = async (): Promise => { return data; }; -export const getResourceProviders = async (resourceId: string): Promise => { +export const getResourceProviders = async ( + resourceId: string +): Promise => { const { data, error } = await supabase .from(providersDatabaseName) - .select('name, logo_url, url:website_url, resource_providers!inner(resource_id)') - .eq('resource_providers.resource_id', resourceId) + .select( + 'name, logo_url, url:website_url, resource_providers!inner(resource_id)' + ) + .eq('resource_providers.resource_id', resourceId); if (error) { throw error; } diff --git a/vite.config.ts b/vite.config.ts index 377b92f7..402ca777 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,61 @@ import { defineConfig } from 'vite'; +import type { Plugin } from 'vite'; import react from '@vitejs/plugin-react'; import tsconfigPaths from 'vite-tsconfig-paths'; +const REQUIRED_ENV_VARS = [ + 'VITE_DB_API_KEY', + 'VITE_REACT_GOOGLE_MAPS_API_KEY', + 'VITE_OPEN_ROUTE_SERVICE_API_KEY', + 'VITE_PUBLIC_POSTHOG_KEY', + 'VITE_VERIFICATION_PASSWORD', +]; + +function envCheckPlugin(): Plugin { + let missingVars: string[] = []; + + return { + name: 'env-check', + configResolved(config) { + missingVars = REQUIRED_ENV_VARS.filter(key => !config.env[key]); + if (missingVars.length > 0) { + console.warn( + '\n\x1b[33m[env-check] Missing required environment variables:\x1b[0m\n' + + missingVars.map(v => ` \x1b[31m✗ ${v}\x1b[0m`).join('\n') + + '\n\x1b[33m Copy src/.example.env to .env and fill in the values following the guidance described in the example file.\x1b[0m\n' + ); + } + }, + transformIndexHtml(html) { + if (missingVars.length === 0) return html; + + const listItems = missingVars.map(v => `
  • ${v}
  • `).join(''); + const overlay = ` +`; + + return html.replace('', overlay + '\n'); + }, + }; +} + export default defineConfig(() => ({ base: './', // This is set to allow for deployments on dynamic subpaths (i.e. - test.phlask.me) build: { @@ -9,6 +63,7 @@ export default defineConfig(() => ({ target: 'es2022' }, plugins: [ + envCheckPlugin(), react(), tsconfigPaths(), ]