diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 85b9131b8..5c9904f2d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -7,6 +7,7 @@ on: [pull_request] env: VERSION: ${{ github.event.pull_request.number }} + HUSKY: 0 jobs: build: diff --git a/app/core/config.ts b/app/core/config.ts new file mode 100644 index 000000000..60b70cf5f --- /dev/null +++ b/app/core/config.ts @@ -0,0 +1,27 @@ +/* SPDX-FileCopyrightText: 2014-present Kriasoft */ +/* SPDX-License-Identifier: MIT */ + +export type EnvName = "prod" | "test" | "local"; +export type Config = { + app: { + env: EnvName; + name: string; + origin: string; + hostname: string; + }; + firebase: { + projectId: string; + appId: string; + apiKey: string; + authDomain: string; + measurementId: string; + }; +}; + +export const configs = JSON.parse(import.meta.env.VITE_CONFIG); +export const config: Config = + location.hostname === configs.prod.hostname + ? configs.prod + : location.hostname === configs.test.hostname + ? configs.test + : configs.local; diff --git a/app/core/firebase.ts b/app/core/firebase.ts index 7f1418485..5decb93c7 100644 --- a/app/core/firebase.ts +++ b/app/core/firebase.ts @@ -14,17 +14,11 @@ import { type Auth, type UserCredential, } from "firebase/auth"; +import { config } from "./config.js"; export { AuthErrorCodes, linkWithCredential } from "firebase/auth"; export { FirebaseError }; -export const app = initializeApp({ - projectId: import.meta.env.VITE_GOOGLE_CLOUD_PROJECT, - appId: import.meta.env.VITE_FIREBASE_APP_ID, - apiKey: import.meta.env.VITE_FIREBASE_API_KEY, - authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, - measurementId: import.meta.env.VITE_GA_MEASUREMENT_ID, -}); - +export const app = initializeApp(config.firebase); export const auth = getAuth(app); export const analytics = getAnalytics(app); diff --git a/app/core/page.ts b/app/core/page.ts index 3746b1409..245f4c605 100644 --- a/app/core/page.ts +++ b/app/core/page.ts @@ -4,6 +4,7 @@ import { getAnalytics, logEvent } from "firebase/analytics"; import * as React from "react"; import { useLocation } from "react-router-dom"; +import { config } from "./config.js"; export function usePageEffect( options?: Options, @@ -17,10 +18,10 @@ export function usePageEffect( document.title = location.pathname === "/" - ? options?.title ?? import.meta.env.VITE_APP_NAME + ? options?.title ?? config.app.name : options?.title - ? `${options.title} - ${import.meta.env.VITE_APP_NAME}` - : import.meta.env.VITE_APP_NAME; + ? `${options.title} - ${config.app.name}` + : config.app.name; return function () { document.title = previousTitle; @@ -36,7 +37,7 @@ export function usePageEffect( React.useEffect(() => { if (!(options?.trackPageView === false)) { logEvent(getAnalytics(), "page_view", { - page_title: options?.title ?? import.meta.env.VITE_APP_NAME, + page_title: options?.title ?? config.app.name, page_path: `${location.pathname}${location.search}`, }); } diff --git a/app/global.d.ts b/app/global.d.ts index 10d3b974a..8d5233fca 100644 --- a/app/global.d.ts +++ b/app/global.d.ts @@ -1,14 +1,22 @@ /* SPDX-FileCopyrightText: 2014-present Kriasoft */ /* SPDX-License-Identifier: MIT */ -declare const APP_NAME: string; -declare const APP_HOSTNAME: string; -declare const GOOGLE_CLOUD_PROJECT: string; -declare const FIREBASE_APP_ID: string; -declare const FIREBASE_API_KEY: string; -declare const FIREBASE_AUTH_DOMAIN: string; -declare const GA_MEASUREMENT_ID: string; - interface Window { dataLayer: unknown[]; } + +interface ImportMetaEnv { + /** + * Client-side configuration for the production, test/QA, and local + * development environments. See `core/config.ts`, `vite.config.ts`. + */ + readonly VITE_CONFIG: string; +} + +declare module "relay-runtime" { + interface PayloadError { + errors?: Record; + } +} + +declare module "*.css"; diff --git a/app/layout/components/Logo.tsx b/app/layout/components/Logo.tsx index cc3696494..59c760993 100644 --- a/app/layout/components/Logo.tsx +++ b/app/layout/components/Logo.tsx @@ -2,6 +2,7 @@ /* SPDX-License-Identifier: MIT */ import { Typography, TypographyProps } from "@mui/material"; +import { config } from "../../core/config.js"; export function Logo(props: TypographyProps): JSX.Element { const { sx, ...other } = props; @@ -18,7 +19,7 @@ export function Logo(props: TypographyProps): JSX.Element { variant="h1" {...other} > - {import.meta.env.VITE_APP_NAME} + {config.app.name} ); } diff --git a/app/package.json b/app/package.json index 6684a9744..7e70b19b4 100644 --- a/app/package.json +++ b/app/package.json @@ -41,8 +41,5 @@ "typescript": "^4.9.5", "vite": "^4.1.4", "vitest": "^0.28.5" - }, - "envars": { - "cwd": "../env" } } diff --git a/app/routes/auth/Notice.tsx b/app/routes/auth/Notice.tsx index 468b4bfed..c1344101b 100644 --- a/app/routes/auth/Notice.tsx +++ b/app/routes/auth/Notice.tsx @@ -2,6 +2,7 @@ /* SPDX-License-Identifier: MIT */ import { Link, Typography, TypographyProps } from "@mui/material"; +import { config } from "../../core/config.js"; export function Notice(props: NoticeProps): JSX.Element { const { sx, ...other } = props; @@ -20,7 +21,7 @@ export function Notice(props: NoticeProps): JSX.Element { > By clicking Continue above, your acknowledge that your have read and - understood, and agree to {import.meta.env.VITE_APP_NAME}'s + understood, and agree to {config.app.name}'s {" "} Terms & Conditions diff --git a/app/routes/legal/Privacy.tsx b/app/routes/legal/Privacy.tsx index 1a22b53ac..51ab089a0 100644 --- a/app/routes/legal/Privacy.tsx +++ b/app/routes/legal/Privacy.tsx @@ -2,18 +2,19 @@ /* SPDX-License-Identifier: MIT */ import { Container, Link, Typography } from "@mui/material"; +import { config } from "../../core/config.js"; import { usePageEffect } from "../../core/page.js"; -const appName = import.meta.env.VITE_APP_NAME; -const appOrigin = `https://${import.meta.env.VITE_APP_HOSTNAME}`; -const email = `support@${import.meta.env.VITE_APP_HOSTNAME}`; - /** * Generated by https://getterms.io */ export default function Privacy(): JSX.Element { usePageEffect({ title: "Privacy Policy" }); + const appName = config.app.name; + const appOrigin = config.app.origin; + const email = `hello@${config.app.hostname}`; + return ( { + const env = envars.config({ env: envName, cwd: "../env" }); + return [ + envName, + { + app: { + env: envName, + name: env.APP_NAME, + origin: env.APP_ORIGIN, + hostname: new URL(env.APP_ORIGIN).hostname, + }, + firebase: { + projectId: env.GOOGLE_CLOUD_PROJECT, + appId: env.FIREBASE_APP_ID, + apiKey: env.FIREBASE_API_KEY, + authDomain: env.FIREBASE_AUTH_DOMAIN, + measurementId: env.GA_MEASUREMENT_ID, + }, + }, + ]; +}); + +// Pass client-side configuration to the web app // https://vitejs.dev/guide/env-and-mode.html#env-variables-and-modes -[ - "APP_ENV", - "APP_NAME", - "APP_ORIGIN", - "APP_HOSTNAME", - "GOOGLE_CLOUD_PROJECT", - "FIREBASE_APP_ID", - "FIREBASE_API_KEY", - "FIREBASE_AUTH_DOMAIN", - "GA_MEASUREMENT_ID", -].forEach((key) => (process.env[`VITE_${key}`] = process.env[key])); +process.env.VITE_CONFIG = JSON.stringify(Object.fromEntries(configs)); /** * Vite configuration