diff --git a/.env.development b/.env.development index 95418a9..cdd7a1a 100644 --- a/.env.development +++ b/.env.development @@ -1 +1,2 @@ -VITE_AUTH_SERVER_URL=https://auth.dearborncodingclub.com \ No newline at end of file +VITE_API_BASE_URL=http://localhost:8000 +VITE_AUTH_SERVER_URL=http://localhost:8080 diff --git a/.env.production b/.env.production index 95418a9..6b01716 100644 --- a/.env.production +++ b/.env.production @@ -1 +1,2 @@ -VITE_AUTH_SERVER_URL=https://auth.dearborncodingclub.com \ No newline at end of file +VITE_API_BASE_URL=https://api.dearborncodingclub.com +VITE_AUTH_SERVER_URL=https://auth.dearborncodingclub.com diff --git a/README.md b/README.md index c2df62a..cf738c6 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,15 @@ Dearborn Coding Club Frontend is a dynamic, React-based website for the [Dearbor ## Table of Contents 1. [Architecture](#architecture) -2. [Running](#running) +2. [Environment Configuration](#environment-configuration) +3. [Running](#running) * [Running locally](#running-locally) * [Running from Docker Container](#running-from-the-gostatic-docker-container-locally) -3. [Building](#building-locally) -4. [Deploying](#deploying) +4. [Building](#building-locally) +5. [Deploying](#deploying) * [Deploying to Fly.io](#deploying-to-flyio) * [Deploying to Staging](#deploying-to-staging) -5. [(Re)generating TLS Certificates](#regenerating-tls-certificates) +6. [(Re)generating TLS Certificates](#regenerating-tls-certificates) ## Architecture --- @@ -43,11 +44,40 @@ sequenceDiagram end ``` +## Environment Configuration +--- + +The frontend uses environment variables to configure API endpoints for different environments. Create a `.env` file in the root directory with the following variables: + +### Local Development + +For local development, create a `.env` file with: + +```bash +VITE_API_BASE_URL=http://localhost:8000 +VITE_AUTH_SERVER_URL=http://localhost:8080 +``` + +These are the default values used if the environment variables are not set, so you can run the app locally without a `.env` file. + +### Production + +For production builds, set these environment variables: + +```bash +VITE_API_BASE_URL=https://api.dearborncodingclub.com +VITE_AUTH_SERVER_URL=https://auth.dearborncodingclub.com +``` + +> [!NOTE] +> Environment variables prefixed with `VITE_` are exposed to the client-side code. Make sure these only contain public API endpoints, not sensitive credentials. + ## Running --- ### Running locally - Run `npm install` +- (Optional) Create a `.env` file with your local configuration (see [Environment Configuration](#environment-configuration)) - Run `npm run dev` ### Running from the goStatic docker container locally @@ -61,6 +91,9 @@ sequenceDiagram - Run `npm run build` +> [!NOTE] +> When building for production, ensure the environment variables `VITE_API_BASE_URL` and `VITE_AUTH_SERVER_URL` are set to production values. These are embedded at build time. + ## Deploying --- diff --git a/package-lock.json b/package-lock.json index 897eb76..66d2460 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.8.tgz", "integrity": "sha512-6AWcmZC/MZCO0yKys4uhg5NlxL0ESF3K6IAaoQ+xSXvPyPyxNWRafP+GDbI88Oh68O7QkJgmEtedWPM9U0pZNg==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -3572,6 +3573,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3957,6 +3959,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "dev": true, + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3967,6 +3970,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", "dev": true, + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -4045,6 +4049,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.16.0", "@typescript-eslint/types": "7.16.0", @@ -4229,6 +4234,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4816,6 +4822,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -5894,6 +5901,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5949,6 +5957,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7752,6 +7761,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -10528,6 +10538,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10673,6 +10684,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10684,6 +10696,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -11975,6 +11988,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12044,6 +12058,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz", "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.7.0", "@typescript-eslint/types": "8.7.0", @@ -12329,6 +12344,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/src/config/api.ts b/src/config/api.ts new file mode 100644 index 0000000..e073806 --- /dev/null +++ b/src/config/api.ts @@ -0,0 +1,16 @@ +/** + * API Configuration + * + * Centralized configuration for API endpoints. + * Uses environment variables with sensible defaults for local development. + */ + +// Helper to get env var with fallback, explicitly checking for undefined/empty +const getEnvVar = (key: string, defaultValue: string): string => { + const value = (import.meta.env as Record)[key]; + return (value && typeof value === 'string' && value.trim() !== '') ? value : defaultValue; +}; + +export const API_BASE_URL = getEnvVar('VITE_API_BASE_URL', 'http://localhost:8000'); +export const AUTH_SERVER_URL = getEnvVar('VITE_AUTH_SERVER_URL', 'http://localhost:8080'); + diff --git a/src/hooks/useRegisterForm.tsx b/src/hooks/useRegisterForm.tsx index b263e65..ca4ce7e 100644 --- a/src/hooks/useRegisterForm.tsx +++ b/src/hooks/useRegisterForm.tsx @@ -1,8 +1,8 @@ import { useState } from "react" import { toastType, useToastProvider } from "../providers/ToastProvider" +import { AUTH_SERVER_URL } from "../config/api" const useRegisterForm = () => { - const AUTH_SERVER_URL = import.meta.env.VITE_AUTH_SERVER_URL; const { pushToast } = useToastProvider(); const [errorMessage, setErrorMessage] = useState(""); diff --git a/src/pages/Notes.tsx b/src/pages/Notes.tsx index f92f439..8472f2f 100644 --- a/src/pages/Notes.tsx +++ b/src/pages/Notes.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom" import "../css/theme.css" import { CircleArrowLeft } from "lucide-react" import { useAuthServiceProvider } from "../providers/AuthServiceProvider" +import { API_BASE_URL } from "../config/api" interface ServerResponse { message: string @@ -31,7 +32,7 @@ const Notes: React.FC = () => { const onClick = async (e: React.MouseEvent) => { e.preventDefault(); - const response = await fetch("https://api.dearborncodingclub.com/v2/notes/", { + const response = await fetch(`${API_BASE_URL}/v2/notes/`, { method: "POST", body: JSON.stringify({title: title, content: notes}), headers: { @@ -57,7 +58,7 @@ const Notes: React.FC = () => { const fetchMessage = async (): Promise => { try { const response = await fetch( - "https://api.dearborncodingclub.com/v2/notes/", { + `${API_BASE_URL}/v2/notes/`, { method: "GET", headers: { "Content-type": "application/json; charset=UTF-8", diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 94be7bc..25a645d 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -2,12 +2,13 @@ import "../css/Profile.css" import DummyProfilePicture from "../assets/dummy-profile.png" import CircularProgressBar from "../components/CircularProgressBar" import React, { useEffect } from "react" +import { API_BASE_URL } from "../config/api" const Profile: React.FC = () => { useEffect(() => { const fetchProfileData = async () => { - const response = await fetch("https://api.dearborncodingclub.com/profile") + const response = await fetch(`${API_BASE_URL}/profile`) const data = await response.json() console.log(data) }; diff --git a/src/providers/AuthServiceProvider.tsx b/src/providers/AuthServiceProvider.tsx index 2542c53..1b03ce3 100644 --- a/src/providers/AuthServiceProvider.tsx +++ b/src/providers/AuthServiceProvider.tsx @@ -1,5 +1,6 @@ import React, { createContext, useState, useContext, useEffect } from 'react' import { useLoadingProvider } from './LoadingProvider'; +import { AUTH_SERVER_URL } from '../config/api'; interface AuthServiceContextType { token: string | null; @@ -11,8 +12,6 @@ const AuthServiceContext = createContext(null) export const AuthServiceProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const AUTH_SERVER_URL = import.meta.env.VITE_AUTH_SERVER_URL - const { startLoading, stopLoading } = useLoadingProvider() const [token, setToken] = useState(localStorage.getItem("token"))