diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index 5ceba04d..cca1e740 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - feat/azure-deploy + - feat/azure-vercel-deployment paths: - "server/**" - ".github/workflows/azure-deploy.yml" @@ -26,18 +26,18 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: "22.x" + node-version: ${{ env.NODE_VERSION }} cache: "npm" cache-dependency-path: server/package-lock.json - - name: Install dependencies + - name: Create deployment package run: | cd server - npm ci --production + zip -r ../server-deploy.zip . -x "node_modules/*" ".env" ".env.*" "**/*.log" - name: Deploy to Azure Web App uses: azure/webapps-deploy@v3 with: app-name: ${{ secrets.AZURE_WEBAPP_NAME }} publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} + package: ./server-deploy.zip diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index 5382768c..6e726249 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -1,22 +1,71 @@ name: Deploy Frontend to Vercel + on: push: branches: - main - - feat/azure-deploy - paths: ["client/**"] + - feat/azure-vercel-deployment + paths: [ + "client/**", + "package.json", + "package-lock.json", + "vercel.json", + "client/vercel.json", + ] # Added Vercel configs to trigger + pull_request: + branches: + - main + - feat/azure-vercel-deployment + paths: + [ + "client/**", + "package.json", + "package-lock.json", + "vercel.json", + "client/vercel.json", + ] workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 + + - name: Use Node.js 18 + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: "npm" + # FIX: Point to the actual location of your lock file + cache-dependency-path: "package-lock.json" + + - name: Install dependencies + # FIX: Run in root because that's where package.json is + run: npm install + + - name: Build frontend + # If your build script is in the root package.json: + run: npm run build + # OR if you must run the build inside the client folder: + # working-directory: ./client + # run: npm run build + + - name: Choose Vercel deploy type + id: vercel + run: | + if [ "${{ github.event_name }}" = "push" ]; then + echo "args=--prod" >> $GITHUB_OUTPUT + else + echo "args=" >> $GITHUB_OUTPUT + fi + - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} - working-directory: ./client - vercel-args: "--prod" + working-directory: ./ # Usually root if package.json is there + vercel-args: ${{ steps.vercel.outputs.args }} diff --git a/client/package.json b/client/package.json new file mode 100644 index 00000000..e0b270f4 --- /dev/null +++ b/client/package.json @@ -0,0 +1,152 @@ +{ + "name": "rest-express", + "version": "1.0.0", + "type": "module", + "license": "MIT", + "scripts": { + "dev": "cross-env NODE_ENV=development tsx server/index.ts", + "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", + "start": "NODE_ENV=production node dist/index.js", + "check": "tsc", + "db:push": "drizzle-kit push", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "prepare": "husky" + }, + "dependencies": { + "@emailjs/browser": "^4.4.1", + "@emailjs/nodejs": "^5.0.2", + "@hookform/resolvers": "^3.10.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@neondatabase/serverless": "^0.10.4", + "@radix-ui/react-accordion": "^1.2.4", + "@radix-ui/react-alert-dialog": "^1.1.7", + "@radix-ui/react-aspect-ratio": "^1.1.3", + "@radix-ui/react-avatar": "^1.1.4", + "@radix-ui/react-checkbox": "^1.1.5", + "@radix-ui/react-collapsible": "^1.1.4", + "@radix-ui/react-context-menu": "^2.2.7", + "@radix-ui/react-dialog": "^1.1.7", + "@radix-ui/react-dropdown-menu": "^2.1.7", + "@radix-ui/react-hover-card": "^1.1.7", + "@radix-ui/react-label": "^2.1.3", + "@radix-ui/react-menubar": "^1.1.7", + "@radix-ui/react-navigation-menu": "^1.2.6", + "@radix-ui/react-popover": "^1.1.7", + "@radix-ui/react-progress": "^1.1.3", + "@radix-ui/react-radio-group": "^1.2.4", + "@radix-ui/react-scroll-area": "^1.2.4", + "@radix-ui/react-select": "^2.1.7", + "@radix-ui/react-separator": "^1.1.3", + "@radix-ui/react-slider": "^1.2.4", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-switch": "^1.1.4", + "@radix-ui/react-tabs": "^1.1.4", + "@radix-ui/react-toast": "^1.2.7", + "@radix-ui/react-toggle": "^1.1.3", + "@radix-ui/react-toggle-group": "^1.1.3", + "@radix-ui/react-tooltip": "^1.2.0", + "@react-oauth/google": "^0.12.2", + "@stripe/react-stripe-js": "^5.4.0", + "@stripe/stripe-js": "^8.5.2", + "@tanstack/react-query": "^5.60.5", + "@vercel/blob": "^2.0.0", + "axios": "^1.12.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "connect-pg-simple": "^10.0.0", + "consola": "^3.4.2", + "date-fns": "^3.6.0", + "drizzle-orm": "^0.39.1", + "drizzle-zod": "^0.7.0", + "embla-carousel-react": "^8.6.0", + "express": "^4.21.2", + "express-session": "^1.18.1", + "framer-motion": "^11.18.2", + "googleapis": "^168.0.0", + "ical-generator": "^10.0.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.554.0", + "memorystore": "^1.6.7", + "moment": "^2.30.1", + "next-themes": "^0.4.6", + "nodemailer": "^7.0.9", + "passport": "^0.7.0", + "passport-local": "^1.0.0", + "postcss-import": "^16.1.1", + "postcss-loader": "^8.2.0", + "react": "^18.3.1", + "react-big-calendar": "^1.19.4", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.55.0", + "react-icons": "^5.4.0", + "react-image-crop": "^11.0.10", + "react-resizable-panels": "^2.1.7", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.2", + "stripe": "^19.3.0", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "three": "^0.180.0", + "tw-animate-css": "^1.2.5", + "vaul": "^1.1.2", + "wouter": "^3.3.5", + "ws": "^8.18.0", + "xlsx": "^0.18.5", + "zod": "^3.24.2", + "zod-validation-error": "^3.4.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@replit/vite-plugin-cartographer": "^0.3.1", + "@replit/vite-plugin-dev-banner": "^0.1.1", + "@replit/vite-plugin-runtime-error-modal": "^0.0.3", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.1.3", + "@types/axios": "^0.9.36", + "@types/connect-pg-simple": "^7.0.3", + "@types/express": "4.17.21", + "@types/express-session": "^1.18.0", + "@types/node": "20.16.11", + "@types/passport": "^1.0.16", + "@types/passport-local": "^1.0.38", + "@types/react": "^18.3.11", + "@types/react-big-calendar": "^1.16.3", + "@types/react-dom": "^18.3.1", + "@types/three": "^0.180.0", + "@types/ws": "^8.5.13", + "@typescript-eslint/eslint-plugin": "^8.46.3", + "@typescript-eslint/parser": "^8.46.3", + "autoprefixer": "^10.4.21", + "baseline-browser-mapping": "^2.9.9", + "cross-env": "^10.1.0", + "drizzle-kit": "^0.31.4", + "esbuild": "^0.25.0", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-unused-imports": "^4.3.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "tailwindcss": "^3.4.17", + "tsx": "^4.20.5", + "typescript": "^5.9.3", + "vite": "^5.4.20" + }, + "optionalDependencies": { + "bufferutil": "^4.0.8" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ + "prettier --write" + ] + } +} diff --git a/client/src/components/BazaarList.tsx b/client/src/components/BazaarList.tsx index 8e21b912..3952fa1f 100644 --- a/client/src/components/BazaarList.tsx +++ b/client/src/components/BazaarList.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Search, Store } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; export interface Bazaar { _id: string; @@ -44,6 +45,7 @@ export default function BazaarList({ }: BazaarListProps) { const [searchTerm, setSearchTerm] = useState(""); const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const handleRegisterEvent = async (eventId: string) => { try { @@ -58,7 +60,7 @@ export default function BazaarList({ } const response = await fetch( - `http://localhost:4000/api/events/${eventId}/register`, + `${API_BASE_URL}/api/events/${eventId}/register`, { method: "POST", headers: { diff --git a/client/src/components/BigCalendarView.tsx b/client/src/components/BigCalendarView.tsx index a44354aa..5f0eab7a 100644 --- a/client/src/components/BigCalendarView.tsx +++ b/client/src/components/BigCalendarView.tsx @@ -5,10 +5,10 @@ import "react-big-calendar/lib/css/react-big-calendar.css"; import { Card } from "@/components/ui/card"; import EventDetailsDialog from "@/components/EventsDetailsDialog"; import { Button } from "@/components/ui/button"; +import { getApiBaseUrl } from "@/lib/apiBase"; const localizer = momentLocalizer(moment); -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Event { _id: string; diff --git a/client/src/components/CalendarPopover.tsx b/client/src/components/CalendarPopover.tsx index 67e5055a..a601eeb0 100644 --- a/client/src/components/CalendarPopover.tsx +++ b/client/src/components/CalendarPopover.tsx @@ -8,8 +8,9 @@ import { } from "@/components/ui/popover"; import MiniCalendar from "./MiniCalendar"; import { useLocation } from "wouter"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Event { _id: string; diff --git a/client/src/components/CreateEventForm.tsx b/client/src/components/CreateEventForm.tsx index fcaaea86..2f4527cd 100644 --- a/client/src/components/CreateEventForm.tsx +++ b/client/src/components/CreateEventForm.tsx @@ -15,6 +15,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Info, Clock, CalendarIcon } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; export interface CreateEventFormValues { name: string; @@ -104,8 +106,7 @@ export default function CreateEventForm({ (async () => { try { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const res = await fetch(`${baseUrl}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/client/src/components/CreateGymSessionDialog.tsx b/client/src/components/CreateGymSessionDialog.tsx index 6d07892a..f1e75839 100644 --- a/client/src/components/CreateGymSessionDialog.tsx +++ b/client/src/components/CreateGymSessionDialog.tsx @@ -27,6 +27,8 @@ import { } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { cn } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; type CreateGymSessionDialogProps = { open: boolean; @@ -48,6 +50,7 @@ export default function CreateGymSessionDialog({ onOpenChange, onSuccess, }: CreateGymSessionDialogProps) { + const API_BASE_URL = getApiBaseUrl(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState<{ date: Date | undefined; @@ -96,7 +99,7 @@ export default function CreateGymSessionDialog({ }; const response = await fetch( - "http://localhost:4000/api/facilities/admin/gym/sessions", + `${API_BASE_URL}/api/facilities/admin/gym/sessions`, { method: "POST", headers: { diff --git a/client/src/components/CreatePrivilegedUserForm.tsx b/client/src/components/CreatePrivilegedUserForm.tsx index 2d26070e..12d90cf8 100644 --- a/client/src/components/CreatePrivilegedUserForm.tsx +++ b/client/src/components/CreatePrivilegedUserForm.tsx @@ -10,6 +10,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Props { onSuccess?: () => void; @@ -20,6 +21,7 @@ export default function CreatePrivilegedUserForm({ onSuccess, onCancel, }: Props) { + const API_BASE_URL = getApiBaseUrl(); const [form, setForm] = useState({ firstName: "", lastName: "", @@ -37,7 +39,7 @@ export default function CreatePrivilegedUserForm({ setError(null); try { await axios.post( - "http://localhost:4000/api/users/create-management-account", + `${API_BASE_URL}/api/users/create-management-account`, form, { headers: { diff --git a/client/src/components/EditGymSessionDialog.tsx b/client/src/components/EditGymSessionDialog.tsx index 3ee0d933..577853ca 100644 --- a/client/src/components/EditGymSessionDialog.tsx +++ b/client/src/components/EditGymSessionDialog.tsx @@ -20,6 +20,8 @@ import { import { Calendar } from "@/components/ui/calendar"; import { cn } from "@/lib/utils"; import { CalendarIcon } from "lucide-react"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; type GymSession = { _id: string; @@ -76,6 +78,7 @@ export default function EditGymSessionDialog({ onSuccess, }: EditGymSessionDialogProps) { const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const [isSubmitting, setIsSubmitting] = useState(false); const { @@ -122,7 +125,7 @@ export default function EditGymSessionDialog({ updateData.duration = Number(data.durationMinutes); const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions/${session._id}`, + `${API_BASE_URL}/api/facilities/gym/sessions/${session._id}`, { method: "PATCH", headers: { diff --git a/client/src/components/EventCard.tsx b/client/src/components/EventCard.tsx index fbcfa5a9..dfa758c6 100644 --- a/client/src/components/EventCard.tsx +++ b/client/src/components/EventCard.tsx @@ -35,9 +35,9 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import EventDetailsDialog from "@/components/EventsDetailsDialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); async function deleteEvent(eventId: string) { const token = localStorage.getItem("token"); diff --git a/client/src/components/EventFeedbackDialog.tsx b/client/src/components/EventFeedbackDialog.tsx index 1b72d15f..7b81dfba 100644 --- a/client/src/components/EventFeedbackDialog.tsx +++ b/client/src/components/EventFeedbackDialog.tsx @@ -11,6 +11,7 @@ import { Textarea } from "@/components/ui/textarea"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Star } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface EventFeedbackDialogProps { open: boolean; @@ -63,7 +64,7 @@ export default function EventFeedbackDialog({ setCheckingSubmission(true); const token = localStorage.getItem("token"); const response = await fetch( - `http://localhost:4000/api/feedback/events/${eventId}/me`, + `${getApiBaseUrl()}/api/feedback/events/${eventId}/me`, { headers: { Authorization: `Bearer ${token}`, @@ -97,7 +98,7 @@ export default function EventFeedbackDialog({ setLoading(true); const token = localStorage.getItem("token"); const response = await fetch( - `http://localhost:4000/api/feedback/events/${eventId}`, + `${getApiBaseUrl()}/api/feedback/events/${eventId}`, { headers: { Authorization: `Bearer ${token}`, @@ -193,7 +194,7 @@ export default function EventFeedbackDialog({ if (comment.trim().length > 0) body.comment = comment; const response = await fetch( - `http://localhost:4000/api/feedback/events/${eventId}`, + `${getApiBaseUrl()}/api/feedback/events/${eventId}`, { method: "POST", headers: { diff --git a/client/src/components/EventListItem.tsx b/client/src/components/EventListItem.tsx index 2cb5a921..1307066f 100644 --- a/client/src/components/EventListItem.tsx +++ b/client/src/components/EventListItem.tsx @@ -3,9 +3,9 @@ import { Card, CardContent } from "@/components/ui/card"; import CategoryBadge, { type EventCategory } from "./CategoryBadge"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); async function deleteEvent(eventId: string) { const token = localStorage.getItem("token"); diff --git a/client/src/components/EventSearch.tsx b/client/src/components/EventSearch.tsx index 6b8266f0..1255ddc9 100644 --- a/client/src/components/EventSearch.tsx +++ b/client/src/components/EventSearch.tsx @@ -1,6 +1,8 @@ import { useState, useEffect, useRef, useMemo } from "react"; import { Search, X, Loader2 } from "lucide-react"; import { Input } from "@/components/ui/input"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; export interface EventSearchFilters { type?: string; @@ -34,8 +36,7 @@ export default function EventSearch({ const hasInitiallyFetched = useRef(false); const scrollPositionRef = useRef(0); - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const API_URL = `${API_BASE_URL}/api/events/upcoming`; const SEARCH_URL = `${API_BASE_URL}/api/events/search`; const token = localStorage.getItem("token"); diff --git a/client/src/components/EventsDetailsDialog.tsx b/client/src/components/EventsDetailsDialog.tsx index 68e768b7..64d0e205 100644 --- a/client/src/components/EventsDetailsDialog.tsx +++ b/client/src/components/EventsDetailsDialog.tsx @@ -19,6 +19,8 @@ import { import CategoryBadge from "./CategoryBadge"; import { motion, AnimatePresence } from "framer-motion"; import { useEffect } from "react"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface EventDetailsDialogProps { open: boolean; @@ -46,8 +48,7 @@ export default function EventDetailsDialog({ if (open && event?._id) { const token = localStorage.getItem("token"); if (token) { - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); fetch(`${API_BASE_URL}/api/events/${event._id}/view`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, diff --git a/client/src/components/EventsReport.tsx b/client/src/components/EventsReport.tsx index 781e00ff..136b0f6c 100644 --- a/client/src/components/EventsReport.tsx +++ b/client/src/components/EventsReport.tsx @@ -40,9 +40,9 @@ import { import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface EventStat { _id: string; diff --git a/client/src/components/GoogleCalendarIntegration.tsx b/client/src/components/GoogleCalendarIntegration.tsx index a04a1c86..f15df940 100644 --- a/client/src/components/GoogleCalendarIntegration.tsx +++ b/client/src/components/GoogleCalendarIntegration.tsx @@ -11,8 +11,9 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Card, CardContent } from "@/components/ui/card"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function GoogleCalendarIntegration() { const [isConnected, setIsConnected] = useState(false); diff --git a/client/src/components/GymScheduleViewer.tsx b/client/src/components/GymScheduleViewer.tsx index 7a90dee5..60dab167 100644 --- a/client/src/components/GymScheduleViewer.tsx +++ b/client/src/components/GymScheduleViewer.tsx @@ -22,6 +22,7 @@ import { } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; import EditGymSessionDialog from "@/components/EditGymSessionDialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; type GymSession = { _id: string; @@ -78,6 +79,7 @@ export default function GymScheduleViewer({ ); const [isCancelling, setIsCancelling] = useState(false); const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const month = currentDate.getMonth() + 1; // JavaScript months are 0-indexed const year = currentDate.getFullYear(); @@ -98,7 +100,7 @@ export default function GymScheduleViewer({ try { const token = localStorage.getItem("token"); const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions?month=${month}&year=${year}`, + `${API_BASE_URL}/api/facilities/gym/sessions?month=${month}&year=${year}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, } @@ -143,7 +145,7 @@ export default function GymScheduleViewer({ const handleRegister = async (sessionId: string) => { //gym session registration logic const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions/${sessionId}/register`, + `${API_BASE_URL}/api/facilities/gym/sessions/${sessionId}/register`, { method: "POST", headers: { @@ -196,7 +198,7 @@ export default function GymScheduleViewer({ } const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions/${selectedSession._id}/cancel`, + `${API_BASE_URL}/api/facilities/gym/sessions/${selectedSession._id}/cancel`, { method: "PATCH", headers: { diff --git a/client/src/components/LoyaltyProgramDialog.tsx b/client/src/components/LoyaltyProgramDialog.tsx index 471388dc..46156c03 100644 --- a/client/src/components/LoyaltyProgramDialog.tsx +++ b/client/src/components/LoyaltyProgramDialog.tsx @@ -21,6 +21,7 @@ import { } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface LoyaltyProgramDialogProps { open: boolean; @@ -67,7 +68,7 @@ export default function LoyaltyProgramDialog({ } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/status", + `${getApiBaseUrl()}/api/loyalty-partners/status`, { method: "GET", headers: { @@ -107,7 +108,7 @@ export default function LoyaltyProgramDialog({ } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/cancel", + `${getApiBaseUrl()}/api/loyalty-partners/cancel`, { method: "DELETE", headers: { @@ -166,7 +167,7 @@ export default function LoyaltyProgramDialog({ } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/apply", + `${getApiBaseUrl()}/api/loyalty-partners/apply`, { method: "POST", headers: { diff --git a/client/src/components/NotificationsPopover.tsx b/client/src/components/NotificationsPopover.tsx index a1f5c3b9..a992a1e2 100644 --- a/client/src/components/NotificationsPopover.tsx +++ b/client/src/components/NotificationsPopover.tsx @@ -18,6 +18,7 @@ import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useToast } from "@/hooks/use-toast"; import { formatDistanceToNow, format } from "date-fns"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Notification { _id: string; @@ -69,7 +70,7 @@ export default function NotificationsPopover() { const token = localStorage.getItem("token"); if (!token) return; - const res = await fetch("http://localhost:4000/api/notifications/me", { + const res = await fetch(`${getApiBaseUrl()}/api/notifications/me`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -96,7 +97,7 @@ export default function NotificationsPopover() { if (!token) return; const res = await fetch( - `http://localhost:4000/api/notifications/${notificationId}`, + `${getApiBaseUrl()}/api/notifications/${notificationId}`, { method: "PATCH", headers: { @@ -132,7 +133,7 @@ export default function NotificationsPopover() { if (!token) return; const res = await fetch( - `http://localhost:4000/api/notifications/${notificationToDelete}`, + `${getApiBaseUrl()}/api/notifications/${notificationToDelete}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` }, diff --git a/client/src/components/Recommendations.tsx b/client/src/components/Recommendations.tsx index 7b0becbf..ff7cc001 100644 --- a/client/src/components/Recommendations.tsx +++ b/client/src/components/Recommendations.tsx @@ -8,6 +8,8 @@ import { Button } from "@/components/ui/button"; import EventDetailsDialog from "@/components/EventsDetailsDialog"; import { useToast } from "@/hooks/use-toast"; import { EventPaymentDialog } from "./EventPaymentDialog"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface RecommendationResponse { type: "popular" | "personalized"; @@ -90,8 +92,7 @@ function CompactEventCard({ event }: { event: any }) { } setIsRegistering(true); try { - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const res = await fetch( `${API_BASE_URL}/api/events/${event._id}/register`, { @@ -286,8 +287,7 @@ export default function Recommendations() { // const handleReset = async () => { // try { // const token = localStorage.getItem("token"); - // const API_BASE_URL = - // import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + // const API_BASE_URL = getApiBaseUrl(); // // await fetch(`${API_BASE_URL}/api/recommendations/reset`, { // method: "POST", @@ -313,8 +313,7 @@ export default function Recommendations() { return; } - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const res = await fetch(`${API_BASE_URL}/api/recommendations`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/client/src/components/RestrictAccessDialog.tsx b/client/src/components/RestrictAccessDialog.tsx index d5029340..49970711 100644 --- a/client/src/components/RestrictAccessDialog.tsx +++ b/client/src/components/RestrictAccessDialog.tsx @@ -11,9 +11,9 @@ import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface RestrictAccessDialogProps { open: boolean; diff --git a/client/src/components/WorkshopParticipants.tsx b/client/src/components/WorkshopParticipants.tsx index 8bbfd558..0c522268 100644 --- a/client/src/components/WorkshopParticipants.tsx +++ b/client/src/components/WorkshopParticipants.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Users } from "lucide-react"; import { Badge } from "@/components/ui/badge"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Participant { _id: string; @@ -24,8 +25,7 @@ const WorkshopParticipants: React.FC = ({ workshopId }) => { setLoading(true); try { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const res = await axios.get( `${baseUrl}/api/events/workshops/${workshopId}/participants`, { diff --git a/client/src/components/ui/SalesReport.tsx b/client/src/components/ui/SalesReport.tsx index f21ff867..91409416 100644 --- a/client/src/components/ui/SalesReport.tsx +++ b/client/src/components/ui/SalesReport.tsx @@ -25,9 +25,9 @@ import { import { Calendar } from "@/components/ui/calendar"; import { Loader2, Download, CalendarIcon } from "lucide-react"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface EventSale { _id: string; diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 98ba6973..c505bb44 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -1,7 +1,8 @@ import axios from "axios"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +import { getApiBaseUrl } from "./apiBase"; + +const API_BASE_URL = getApiBaseUrl(); export const api = axios.create({ baseURL: `${API_BASE_URL}/api`, diff --git a/client/src/lib/apiBase.ts b/client/src/lib/apiBase.ts new file mode 100644 index 00000000..b9d1a87d --- /dev/null +++ b/client/src/lib/apiBase.ts @@ -0,0 +1,7 @@ +export function getApiBaseUrl(): string { + return ( + import.meta.env.VITE_API_BASE_URL || + (import.meta as any).env?.VITE_API_URL || + "http://localhost:4000" + ); +} diff --git a/client/src/lib/bazaarApi.ts b/client/src/lib/bazaarApi.ts index 7e2d44b1..1715fc58 100644 --- a/client/src/lib/bazaarApi.ts +++ b/client/src/lib/bazaarApi.ts @@ -1,6 +1,7 @@ +import { getApiBaseUrl } from "./apiBase"; + // API service for bazaars -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export interface Bazaar { _id: string; diff --git a/client/src/pages/AdminDashboardPage.tsx b/client/src/pages/AdminDashboardPage.tsx index 764c6635..e25e643e 100644 --- a/client/src/pages/AdminDashboardPage.tsx +++ b/client/src/pages/AdminDashboardPage.tsx @@ -13,9 +13,10 @@ import EventSort from "@/components/EventSort"; import EventFilters, { EventFilterState } from "@/components/EventFilters"; import EventCountBadge from "@/components/EventCountBadge"; import EventsDetailsDialog from "@/components/EventsDetailsDialog"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Conference { _id: string; @@ -226,12 +227,10 @@ export default function AdminDashboardPage() { }, []); useEffect(() => { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; const token = localStorage.getItem("token"); const fetchProfessors = async () => { try { - const res = await fetch(`${baseUrl}/api/users/professors`, { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), "Content-Type": "application/json", diff --git a/client/src/pages/AdminUsers.tsx b/client/src/pages/AdminUsers.tsx index ed07bb86..f012f769 100644 --- a/client/src/pages/AdminUsers.tsx +++ b/client/src/pages/AdminUsers.tsx @@ -15,6 +15,7 @@ import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import CreatePrivilegedUserForm from "@/components/CreatePrivilegedUserForm"; +import { getApiBaseUrl } from "@/lib/apiBase"; import { Table, @@ -59,6 +60,7 @@ interface User { export default function AdminUsers() { const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const [users, setUsers] = useState([]); const [pendingUsers, setPendingUsers] = useState([]); const [loading, setLoading] = useState(true); @@ -103,7 +105,7 @@ export default function AdminUsers() { ) => { try { await axios.patch( - `http://localhost:4000/api/users/${userId}/block-status`, + `${API_BASE_URL}/api/users/${userId}/block-status`, { action }, { headers: { @@ -132,7 +134,7 @@ export default function AdminUsers() { const handleDeleteUser = async (userId: string) => { try { - await axios.delete(`http://localhost:4000/api/users/${userId}/delete`, { + await axios.delete(`${API_BASE_URL}/api/users/${userId}/delete`, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}`, }, @@ -177,14 +179,11 @@ export default function AdminUsers() { try { setLoading(true); setError(null); - const response = await axios.get( - "http://localhost:4000/api/users/getusers", - { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - } - ); + const response = await axios.get(`${API_BASE_URL}/api/users/getusers`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); setUsers(response.data.data); } catch (err) { logger.error("Failed to fetch users:", err); @@ -198,14 +197,11 @@ export default function AdminUsers() { const fetchPendingUsers = async () => { try { setPendingLoading(true); - const response = await axios.get( - "http://localhost:4000/api/users/pending", - { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - } - ); + const response = await axios.get(`${API_BASE_URL}/api/users/pending`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); setPendingUsers(response.data.data); } catch (err) { logger.error("Failed to fetch pending users:", err); @@ -219,7 +215,7 @@ export default function AdminUsers() { try { setAssigningRole(userId); await axios.patch( - `http://localhost:4000/api/users/${userId}/assign-role`, + `${API_BASE_URL}/api/users/${userId}/assign-role`, { role }, { headers: { diff --git a/client/src/pages/ApprovedLoyaltyPartnersPage.tsx b/client/src/pages/ApprovedLoyaltyPartnersPage.tsx index b15512a7..a7819a11 100644 --- a/client/src/pages/ApprovedLoyaltyPartnersPage.tsx +++ b/client/src/pages/ApprovedLoyaltyPartnersPage.tsx @@ -24,6 +24,8 @@ import StaffHeader from "@/components/StaffHeader"; import ProfessorHeader from "@/components/ProfessorHeader"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import AdminHeader from "@/components/AdminHeader"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Partner { vendorId: string; @@ -39,6 +41,7 @@ interface Partner { export default function ApprovedLoyaltyPartnersPage() { const [, setLocation] = useLocation(); const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const [partners, setPartners] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -86,7 +89,7 @@ export default function ApprovedLoyaltyPartnersPage() { } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/partners", + `${API_BASE_URL}/api/loyalty-partners/partners`, { method: "GET", headers: { diff --git a/client/src/pages/ArchivedEvents.tsx b/client/src/pages/ArchivedEvents.tsx index 3b070a94..845d2607 100644 --- a/client/src/pages/ArchivedEvents.tsx +++ b/client/src/pages/ArchivedEvents.tsx @@ -7,9 +7,9 @@ import EventDetailsDialog from "@/components/EventsDetailsDialog"; import { useToast } from "@/hooks/use-toast"; import EmptyState from "@/components/EmptyState"; import { Archive } from "lucide-react"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function ArchivedEvents() { const [events, setEvents] = useState([]); diff --git a/client/src/pages/AttendeeDetails.tsx b/client/src/pages/AttendeeDetails.tsx index 71eb17c1..bec00678 100644 --- a/client/src/pages/AttendeeDetails.tsx +++ b/client/src/pages/AttendeeDetails.tsx @@ -24,6 +24,7 @@ import { import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import Logo from "@/components/Logo"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface AttendeeData { attendee: { @@ -54,8 +55,7 @@ export default function AttendeeDetails() { const [data, setData] = useState(null); const [errorMessage, setErrorMessage] = useState(""); - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); useEffect(() => { const fetchAttendeeData = async () => { diff --git a/client/src/pages/BoothVotePage.tsx b/client/src/pages/BoothVotePage.tsx index 6922d18b..a7dcee5a 100644 --- a/client/src/pages/BoothVotePage.tsx +++ b/client/src/pages/BoothVotePage.tsx @@ -10,9 +10,10 @@ import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { useToast } from "@/hooks/use-toast"; import { CheckCircle2 } from "lucide-react"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface PollOption { _id: string; diff --git a/client/src/pages/CalendarPage.tsx b/client/src/pages/CalendarPage.tsx index 1e222482..76506956 100644 --- a/client/src/pages/CalendarPage.tsx +++ b/client/src/pages/CalendarPage.tsx @@ -14,7 +14,9 @@ import { SelectValue, } from "@/components/ui/select"; -const API_BASE_URL = "http://localhost:4000"; +import { getApiBaseUrl } from "@/lib/apiBase"; + +const API_BASE_URL = getApiBaseUrl(); interface Event { _id: string; diff --git a/client/src/pages/CreateBazaar.tsx b/client/src/pages/CreateBazaar.tsx index 82585588..ab0f3344 100644 --- a/client/src/pages/CreateBazaar.tsx +++ b/client/src/pages/CreateBazaar.tsx @@ -25,6 +25,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; @@ -56,8 +57,7 @@ export default function CreateBazaar() { { value: "vendor", label: "Vendors" }, ]; - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); diff --git a/client/src/pages/CreateConference.tsx b/client/src/pages/CreateConference.tsx index dc021ad7..74678df1 100644 --- a/client/src/pages/CreateConference.tsx +++ b/client/src/pages/CreateConference.tsx @@ -6,9 +6,9 @@ import CreateEventForm, { } from "@/components/CreateEventForm"; import { useLocation } from "wouter"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function CreateConference() { const [, setLocation] = useLocation(); diff --git a/client/src/pages/CreateTrip.tsx b/client/src/pages/CreateTrip.tsx index abd90557..0ad4b881 100644 --- a/client/src/pages/CreateTrip.tsx +++ b/client/src/pages/CreateTrip.tsx @@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import { useLocation } from "wouter"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function TripManagement() { const [, setLocation] = useLocation(); @@ -15,8 +16,7 @@ export default function TripManagement() { const [showDescriptionModal, setShowDescriptionModal] = useState(false); const [selectedTrip, setSelectedTrip] = useState(null); - const apiBase = - (import.meta.env.VITE_API_URL as string) || "http://localhost:4000"; + const apiBase = getApiBaseUrl(); // Fetch trips from the backend admin trips endpoint and handle ApiResponse wrapper const fetchTrips = async () => { diff --git a/client/src/pages/CreateTripForm.tsx b/client/src/pages/CreateTripForm.tsx index 000cb21b..2364c617 100644 --- a/client/src/pages/CreateTripForm.tsx +++ b/client/src/pages/CreateTripForm.tsx @@ -18,6 +18,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import { useLocation } from "wouter"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function CreateTripForm() { const { toast } = useToast(); @@ -44,8 +45,7 @@ export default function CreateTripForm() { { value: "professor", label: "Professors" }, ]; - const apiBase = - (import.meta.env.VITE_API_URL as string) || "http://localhost:4000"; + const apiBase = getApiBaseUrl(); const handleSubmit = async (e?: FormEvent) => { if (e) { diff --git a/client/src/pages/CreateWorkshop.tsx b/client/src/pages/CreateWorkshop.tsx index 5cf97bab..e11c7b34 100644 --- a/client/src/pages/CreateWorkshop.tsx +++ b/client/src/pages/CreateWorkshop.tsx @@ -24,6 +24,7 @@ import { Calendar as CalendarComponent } from "@/components/ui/calendar"; import { Checkbox } from "@/components/ui/checkbox"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface WorkshopFormData { name: string; @@ -45,6 +46,7 @@ interface WorkshopFormData { export default function CreateWorkshop() { const [, setLocation] = useLocation(); + const apiBaseUrl = getApiBaseUrl(); const [formData, setFormData] = useState({ name: "", @@ -108,9 +110,7 @@ export default function CreateWorkshop() { setLoading(false); return; } - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; - const res = await fetch(`${baseUrl}/api/users/professors`, { + const res = await fetch(`${apiBaseUrl}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { @@ -181,9 +181,7 @@ export default function CreateWorkshop() { return; } - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; - const res = await fetch(`${baseUrl}/api/events/workshops`, { + const res = await fetch(`${apiBaseUrl}/api/events/workshops`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/client/src/pages/EditConference.tsx b/client/src/pages/EditConference.tsx index d6d9a70f..ee93a42a 100644 --- a/client/src/pages/EditConference.tsx +++ b/client/src/pages/EditConference.tsx @@ -17,9 +17,9 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Conference { _id: string; diff --git a/client/src/pages/EditTrip.tsx b/client/src/pages/EditTrip.tsx index 99bdae80..0bfe4370 100644 --- a/client/src/pages/EditTrip.tsx +++ b/client/src/pages/EditTrip.tsx @@ -27,9 +27,9 @@ import { } from "@/components/ui/alert-dialog"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function EditTrip() { const [, params] = useRoute("/events-office/events/trip/edit/:id"); diff --git a/client/src/pages/EditWorkshop.tsx b/client/src/pages/EditWorkshop.tsx index 36229f2d..cef1012e 100644 --- a/client/src/pages/EditWorkshop.tsx +++ b/client/src/pages/EditWorkshop.tsx @@ -31,6 +31,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface WorkshopFormData { name: string; @@ -54,6 +56,7 @@ export default function EditWorkshop() { const [, setLocation] = useLocation(); const { toast } = useToast(); const workshopId = params?.id; + const API_BASE_URL = getApiBaseUrl(); const [formData, setFormData] = useState({ name: "", @@ -95,7 +98,7 @@ export default function EditWorkshop() { if (!token) return; try { - const res = await fetch("http://localhost:4000/api/users/professors", { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { @@ -124,12 +127,9 @@ export default function EditWorkshop() { return; } - const res = await fetch( - `http://localhost:4000/api/events/${workshopId}`, - { - headers: { Authorization: `Bearer ${token}` }, - } - ); + const res = await fetch(`${API_BASE_URL}/api/events/${workshopId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); if (!res.ok) { throw new Error("Failed to fetch workshop details"); @@ -222,7 +222,7 @@ export default function EditWorkshop() { if (!token) throw new Error("Not authenticated. Please log in first."); const res = await fetch( - `http://localhost:4000/api/events/workshops/${workshopId}`, + `${API_BASE_URL}/api/events/workshops/${workshopId}`, { method: "PATCH", headers: { diff --git a/client/src/pages/EmailVerified.tsx b/client/src/pages/EmailVerified.tsx index 35e1deb6..20e68797 100644 --- a/client/src/pages/EmailVerified.tsx +++ b/client/src/pages/EmailVerified.tsx @@ -8,10 +8,13 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function EmailVerified() { const [, params] = useRoute("/verify-email/:token"); const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [status, setStatus] = useState<"loading" | "success" | "error">( "loading" ); @@ -33,7 +36,7 @@ export default function EmailVerified() { `/api/auth/verify-email/${token}` ); const response = await fetch( - `http://localhost:4000/api/auth/verify-email/${token}` + `${API_BASE_URL}/api/auth/verify-email/${token}` ); const data = await response.json(); logger.info("Verification response:", response.status, data); diff --git a/client/src/pages/EventsOfficeDashboard.tsx b/client/src/pages/EventsOfficeDashboard.tsx index 4626a0f6..95d06786 100644 --- a/client/src/pages/EventsOfficeDashboard.tsx +++ b/client/src/pages/EventsOfficeDashboard.tsx @@ -46,9 +46,10 @@ import CreateGymSessionDialog from "@/components/CreateGymSessionDialog"; import { getEventImage } from "@/lib/eventImages"; import { useToast } from "@/hooks/use-toast"; import RestrictAccessDialog from "@/components/RestrictAccessDialog"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Bazaar { _id: string; @@ -725,11 +726,9 @@ export default function EventsOfficeDashboard() { // Fetch professors for dropdown useEffect(() => { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_BASE_URL || "http://localhost:4000"; const fetchProfessors = async () => { try { - const res = await fetch(`${baseUrl}/api/users/professors`, { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), "Content-Type": "application/json", diff --git a/client/src/pages/EventsOfficePolls.tsx b/client/src/pages/EventsOfficePolls.tsx index 1b5c3c49..a694e385 100644 --- a/client/src/pages/EventsOfficePolls.tsx +++ b/client/src/pages/EventsOfficePolls.tsx @@ -22,9 +22,9 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Checkbox } from "@/components/ui/checkbox"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface PollOption { _id: string; diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index ba057dad..bcb83836 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -10,6 +10,8 @@ import EmptyState from "@/components/EmptyState"; import Recommendations from "@/components/Recommendations"; import { useToast } from "@/hooks/use-toast"; import { getEventImage } from "@/lib/eventImages"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; // Define Event type for type safety interface Event { @@ -228,8 +230,7 @@ export default function Home() { ]); useEffect(() => { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const token = localStorage.getItem("token"); const fetchProfessors = async () => { try { diff --git a/client/src/pages/LiveMoments.tsx b/client/src/pages/LiveMoments.tsx index 8b657271..cbcd14bd 100644 --- a/client/src/pages/LiveMoments.tsx +++ b/client/src/pages/LiveMoments.tsx @@ -24,6 +24,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Event { _id: string; @@ -50,8 +51,7 @@ interface EventImage { uploadedAt: string; } -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); // Component for displaying a single event with sliding images function LiveEventCard({ event }: { event: Event }) { diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx index 4f1bff2c..db13447a 100644 --- a/client/src/pages/Login.tsx +++ b/client/src/pages/Login.tsx @@ -16,6 +16,7 @@ import { import Logo from "@/components/Logo"; import { useToast } from "@/hooks/use-toast"; import { ToastProvider, ToastViewport } from "@/components/ui/toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function Login() { const [location] = useLocation(); @@ -62,7 +63,7 @@ export default function Login() { e.preventDefault(); try { - const res = await fetch("http://localhost:4000/api/auth/login", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), diff --git a/client/src/pages/MyEvents.tsx b/client/src/pages/MyEvents.tsx index b66b21bf..08c256a5 100644 --- a/client/src/pages/MyEvents.tsx +++ b/client/src/pages/MyEvents.tsx @@ -9,6 +9,7 @@ import ProfessorHeader from "@/components/ProfessorHeader"; import StaffHeader from "@/components/StaffHeader"; import EventCard from "@/components/EventCard"; import EventFeedbackDialog from "@/components/EventFeedbackDialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; // Helper to get token const getToken = () => localStorage.getItem("token"); @@ -32,6 +33,7 @@ interface RegisteredEvent { export default function MyEvents() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [registeredEvents, setRegisteredEvents] = useState( [] ); @@ -54,9 +56,7 @@ export default function MyEvents() { const fetchEvents = async () => { setLoading(true); try { - const apiBaseUrl = "http://localhost:4000"; - - const res = await fetch(`${apiBaseUrl}/api/events/me/events`, { + const res = await fetch(`${API_BASE_URL}/api/events/me/events`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}`, @@ -130,8 +130,7 @@ export default function MyEvents() { setDetailsLoading(true); setDialogOpen(true); try { - const apiBaseUrl = "http://localhost:4000"; - const res = await fetch(`${apiBaseUrl}/api/events/${eventId}`, { + const res = await fetch(`${API_BASE_URL}/api/events/${eventId}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}`, diff --git a/client/src/pages/ProfessorDashboard.tsx b/client/src/pages/ProfessorDashboard.tsx index ff29edfb..d39e025a 100644 --- a/client/src/pages/ProfessorDashboard.tsx +++ b/client/src/pages/ProfessorDashboard.tsx @@ -16,6 +16,7 @@ import EventCard from "@/components/EventCard"; import EmptyState from "@/components/EmptyState"; import { useToast } from "@/hooks/use-toast"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Workshop { _id: string; @@ -26,6 +27,7 @@ interface Workshop { export default function ProfessorDashboard() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [workshops, setWorkshops] = useState([]); const [loading, setLoading] = useState(true); const [userName, setUserName] = useState(""); @@ -90,7 +92,7 @@ export default function ProfessorDashboard() { return; } - const res = await fetch("http://localhost:4000/api/events/me/workshops", { + const res = await fetch(`${API_BASE_URL}/api/events/me/workshops`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -109,7 +111,7 @@ export default function ProfessorDashboard() { try { const token = localStorage.getItem("token"); if (!token) return; - const res = await fetch("http://localhost:4000/api/users/professors", { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) return; diff --git a/client/src/pages/SignUp.tsx b/client/src/pages/SignUp.tsx index 05398685..eca725ba 100644 --- a/client/src/pages/SignUp.tsx +++ b/client/src/pages/SignUp.tsx @@ -17,6 +17,7 @@ import Logo from "@/components/Logo"; import { bazaarApiService } from "@/lib/bazaarApi"; import { useToast } from "@/hooks/use-toast"; import { FileUploadWithCrop } from "@/components/ui/file-upload-with-crop"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function SignUp() { const [, setLocation] = useLocation(); @@ -97,7 +98,7 @@ export default function SignUp() { } try { - const res = await fetch("http://localhost:4000/api/auth/signup", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -148,7 +149,7 @@ export default function SignUp() { } try { - const res = await fetch("http://localhost:4000/api/auth/signup", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(staffForm), @@ -177,7 +178,7 @@ export default function SignUp() { e.preventDefault(); try { - const res = await fetch("http://localhost:4000/api/auth/signup", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/client/src/pages/SportsFacilities.tsx b/client/src/pages/SportsFacilities.tsx index b61910af..f14a8bc0 100644 --- a/client/src/pages/SportsFacilities.tsx +++ b/client/src/pages/SportsFacilities.tsx @@ -14,6 +14,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import GymScheduleViewer from "@/components/GymScheduleViewer"; import CreateGymSessionDialog from "@/components/CreateGymSessionDialog"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; type Slot = { startTime: string; @@ -51,6 +52,7 @@ const formatTimeToAMPM = (time: string) => { export default function SportsFacilities() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [courtSchedules, setCourtSchedules] = useState< Record >({ @@ -91,7 +93,7 @@ export default function SportsFacilities() { const token = localStorage.getItem("token"); try { setLoading(true); - const res = await fetch("http://localhost:4000/api/facilities/courts", { + const res = await fetch(`${API_BASE_URL}/api/facilities/courts`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); @@ -218,22 +220,19 @@ export default function SportsFacilities() { try { const token = localStorage.getItem("token"); - const res = await fetch( - "http://localhost:4000/api/facilities/courts/reserve", - { - method: "POST", - headers: { - "Content-Type": "application/json", - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }, - body: JSON.stringify({ - date: bodyDate, - courtType, - startTime, - endTime, - }), - } - ); + const res = await fetch(`${API_BASE_URL}/api/facilities/courts/reserve`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + date: bodyDate, + courtType, + startTime, + endTime, + }), + }); const data = await res.json(); diff --git a/client/src/pages/StaffTADashboard.tsx b/client/src/pages/StaffTADashboard.tsx index 9099d719..8d2604e1 100644 --- a/client/src/pages/StaffTADashboard.tsx +++ b/client/src/pages/StaffTADashboard.tsx @@ -25,6 +25,7 @@ import EventSort from "@/components/EventSort"; import EmptyState from "@/components/EmptyState"; import { useToast } from "@/hooks/use-toast"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface RegisteredEvent { _id: string; @@ -47,6 +48,7 @@ interface MyEvent { export default function StaffTADashboard() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [events, setEvents] = useState([]); const [registeredEvents, setRegisteredEvents] = useState([]); const [loading, setLoading] = useState(true); @@ -100,7 +102,7 @@ export default function StaffTADashboard() { return; } - const res = await fetch("http://localhost:4000/api/events/me/events", { + const res = await fetch(`${API_BASE_URL}/api/events/me/events`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -120,7 +122,7 @@ export default function StaffTADashboard() { const token = localStorage.getItem("token"); if (!token) return; - const res = await fetch("http://localhost:4000/api/events/me/events", { + const res = await fetch(`${API_BASE_URL}/api/events/me/events`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, @@ -156,7 +158,7 @@ export default function StaffTADashboard() { setDialogOpen(true); try { const token = localStorage.getItem("token"); - const res = await fetch(`http://localhost:4000/api/events/${eventId}`, { + const res = await fetch(`${API_BASE_URL}/api/events/${eventId}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, @@ -190,8 +192,7 @@ export default function StaffTADashboard() { // Fetch professors for filter useEffect(() => { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_BASE_URL || "http://localhost:4000"; + const baseUrl = API_BASE_URL; const fetchProfessors = async () => { try { diff --git a/client/src/pages/StaffUpcomingEvents.tsx b/client/src/pages/StaffUpcomingEvents.tsx index b9156a80..c7a32b3b 100644 --- a/client/src/pages/StaffUpcomingEvents.tsx +++ b/client/src/pages/StaffUpcomingEvents.tsx @@ -6,6 +6,8 @@ import EventSearch, { EventSearchFilters } from "@/components/EventSearch"; import EventSort from "@/components/EventSort"; import EmptyState from "@/components/EmptyState"; import { useToast } from "@/hooks/use-toast"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Event { _id: string; @@ -202,8 +204,7 @@ export default function StaffUpcomingEvents() { ]); useEffect(() => { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const token = localStorage.getItem("token"); const fetchProfessors = async () => { try { diff --git a/client/src/pages/VendorRequests.tsx b/client/src/pages/VendorRequests.tsx index f73aa23e..06ced981 100644 --- a/client/src/pages/VendorRequests.tsx +++ b/client/src/pages/VendorRequests.tsx @@ -30,6 +30,7 @@ import { Label } from "@/components/ui/label"; import { useState, useEffect } from "react"; import { logger } from "@/lib/logger"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Table, @@ -68,7 +69,7 @@ async function updateVendorStatus( try { const response = await fetch( - `http://localhost:4000/api/applications/${requestId}/status`, + `${getApiBaseUrl()}/api/applications/${requestId}/status`, { method: "PATCH", headers: { @@ -195,7 +196,7 @@ export default function VendorRequests() { try { setLoading(true); setError(null); - const res = await fetch(`http://localhost:4000/api/applications`, { + const res = await fetch(`${getApiBaseUrl()}/api/applications`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/client/src/pages/WorkshopApprovals.tsx b/client/src/pages/WorkshopApprovals.tsx index b9e092ee..c34ee2e3 100644 --- a/client/src/pages/WorkshopApprovals.tsx +++ b/client/src/pages/WorkshopApprovals.tsx @@ -45,6 +45,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function WorkshopApprovals() { const [allWorkshops, setAllWorkshops] = useState([]); @@ -59,8 +60,7 @@ export default function WorkshopApprovals() { const [searchInput, setSearchInput] = useState(""); const searchTimerRef = useRef(null); - const apiBase = - (import.meta.env.VITE_API_URL as string) || "http://localhost:4000"; + const apiBase = getApiBaseUrl(); // Compute unique faculties dynamically from workshops data const facultyOptions = useMemo(() => { diff --git a/client/src/pages/WorkshopManagement.tsx b/client/src/pages/WorkshopManagement.tsx index 5c5781a6..637d4df3 100644 --- a/client/src/pages/WorkshopManagement.tsx +++ b/client/src/pages/WorkshopManagement.tsx @@ -13,6 +13,7 @@ import { } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Workshop { _id: string; @@ -32,6 +33,7 @@ interface Workshop { export default function WorkshopManagement() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [workshops, setWorkshops] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); @@ -49,10 +51,8 @@ export default function WorkshopManagement() { setParticipantsError(""); try { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; const res = await fetch( - `${baseUrl}/api/events/workshops/${workshopId}/participants`, + `${API_BASE_URL}/api/events/workshops/${workshopId}/participants`, { headers: token ? { Authorization: `Bearer ${token}` } : undefined, } @@ -89,9 +89,7 @@ export default function WorkshopManagement() { return; } - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; - const res = await fetch(`${baseUrl}/api/events/me/workshops`, { + const res = await fetch(`${API_BASE_URL}/api/events/me/workshops`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/client/vercel.json b/client/vercel.json new file mode 100644 index 00000000..02b5eee0 --- /dev/null +++ b/client/vercel.json @@ -0,0 +1,7 @@ +{ + "version": 2, + "framework": "vite", + "buildCommand": "npm run build", + "outputDirectory": "dist/public", + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} diff --git a/server/package.json b/server/package.json index aa3d3db9..010a87c0 100644 --- a/server/package.json +++ b/server/package.json @@ -4,6 +4,9 @@ "description": "The backend server for the Eventy project", "main": "src/index.js", "type": "module", + "engines": { + "node": ">=22 <23" + }, "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js", diff --git a/server/src/app.js b/server/src/app.js index f3261ff5..c91f9802 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -17,14 +17,71 @@ app.set("trust proxy", 1); // 1. Setup CORS before anything else // Since you haven't deployed the frontend yet, // we add localhost so you can still test locally. -const allowedOrigins = [process.env.CLIENT_URL, "http://localhost:5000"].filter( - Boolean -); // Removes undefined values if CLIENT_URL isn't set yet +const isProd = process.env.NODE_ENV === "production"; + +const envAllowed = [process.env.CLIENT_URL, process.env.CLIENT_ORIGIN] + .filter(Boolean) + .flatMap((v) => String(v).split(",")) + .map((v) => v.trim()) + .filter(Boolean); + +const allowVercelPreviews = + String(process.env.ALLOW_VERCEL_PREVIEWS || "").toLowerCase() === "true"; + +const vercelProjectSlug = (() => { + const candidate = process.env.CLIENT_URL || process.env.CLIENT_ORIGIN; + if (!candidate) return null; + const first = String(candidate).split(",")[0]?.trim(); + if (!first) return null; + + try { + const { hostname } = new URL(first); + if (!hostname.endsWith(".vercel.app")) return null; + return hostname.replace(/\.vercel\.app$/i, ""); + } catch (_e) { + return null; + } +})(); + +const allowedOrigins = [...envAllowed, "http://localhost:5000"].filter(Boolean); // Global Middlewares app.use( cors({ - origin: allowedOrigins, + origin: (origin, callback) => { + if (!origin) return callback(null, true); + + if (allowedOrigins.includes(origin)) return callback(null, true); + + if (vercelProjectSlug) { + try { + const { hostname, protocol } = new URL(origin); + if ( + protocol === "https:" && + hostname.endsWith(".vercel.app") && + (hostname === `${vercelProjectSlug}.vercel.app` || + hostname.startsWith(`${vercelProjectSlug}-`)) + ) { + return callback(null, true); + } + } catch (_e) { + // ignore invalid origin + } + } + + if (allowVercelPreviews) { + try { + const { hostname, protocol } = new URL(origin); + if (protocol === "https:" && hostname.endsWith(".vercel.app")) { + return callback(null, true); + } + } catch (_e) { + // ignore invalid origin + } + } + + return callback(new Error(`CORS blocked for origin: ${origin}`)); + }, credentials: true, }) ); @@ -35,7 +92,11 @@ app.use( secret: process.env.JWT_SECRET || "calendar-secret-key", resave: false, saveUninitialized: false, - cookie: { secure: false, maxAge: 600000 }, // 10 minutes + cookie: { + secure: isProd, + sameSite: isProd ? "none" : "lax", + maxAge: 600000, + }, }) ); diff --git a/server/src/routes/index.js b/server/src/routes/index.js index acb8afee..8ca2024a 100644 --- a/server/src/routes/index.js +++ b/server/src/routes/index.js @@ -22,6 +22,15 @@ router.get("/", (req, res) => { res.json({ message: "Welcome to the Eventy API!" }); }); +// Health check endpoint for uptime monitoring / deployment verification +router.get("/health", (_req, res) => { + res.json({ + ok: true, + service: "eventy-api", + timestamp: new Date().toISOString(), + }); +}); + // import authRoutes from '../features/auth/auth.route.js'; router.use("/auth", authRoutes); diff --git a/server/src/utils/logger.js b/server/src/utils/logger.js index 61cc76a7..9f0e894d 100644 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.js @@ -16,12 +16,51 @@ * - apiLogger, authLogger, dbLogger, emailLogger, paymentLogger */ -import { createConsola } from "consola"; +import * as consolaImport from "consola"; const isDevelopment = process.env.NODE_ENV === "development"; const isProduction = process.env.NODE_ENV === "production"; -// Create logger instance with environment-specific configuration +const consolaDefault = consolaImport?.default; +const consolaInstance = consolaImport.consola ?? consolaDefault; +const createConsolaFn = + consolaImport.createConsola ?? consolaDefault?.createConsola ?? null; + +// Create logger instance with environment-specific configuration. +// In some Azure/Oryx installs, consola may be resolved as a CommonJS module +// where `createConsola` isn't exposed as a named export. In that case, fall +// back to the default consola instance so the server can still boot. +export const logger = + (typeof createConsolaFn === "function" + ? createConsolaFn({ + level: isDevelopment ? 5 : 3, // 5=trace in dev, 3=info in prod + fancy: isDevelopment, // Colored output in development + formatOptions: { + colors: isDevelopment, + compact: isProduction, + date: true, // Always include timestamps on server + }, + }) + : consolaInstance) ?? console; + +if (!logger || typeof logger.info !== "function") { + // Extremely defensive fallback + // eslint-disable-next-line no-console + console.warn("Logger initialization fell back to console"); +} + +if (typeof logger.withTag !== "function") { + // Provide a minimal shim so existing code can keep calling withTag. + logger.withTag = () => logger; +} + +// If we're using the consola instance fallback, apply some of the same config. +if (logger?.options && typeof logger.options === "object") { + logger.options.level = isDevelopment ? 5 : 3; +} + +/* +// Previous code (kept for reference): export const logger = createConsola({ level: isDevelopment ? 5 : 3, // 5=trace in dev, 3=info in prod fancy: isDevelopment, // Colored output in development @@ -31,6 +70,7 @@ export const logger = createConsola({ date: true, // Always include timestamps on server }, }); +*/ // In production, be more conservative with logging and sanitize output if (isProduction) { diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..02b5eee0 --- /dev/null +++ b/vercel.json @@ -0,0 +1,7 @@ +{ + "version": 2, + "framework": "vite", + "buildCommand": "npm run build", + "outputDirectory": "dist/public", + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +}