diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3b0b40372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env \ No newline at end of file diff --git a/AUTH_SIMULATION.md b/AUTH_SIMULATION.md new file mode 100644 index 000000000..5adcd8701 --- /dev/null +++ b/AUTH_SIMULATION.md @@ -0,0 +1,126 @@ +# Authentication Simulation Guide + +## Current Setup: Mock Authentication + +Since your teammate is working on real authentication, we've set up a **mock auth system** that simulates being logged in as "George Demo". + +## How It Works + +### 1. Mock Auth Context (`client/src/context/AuthContext.jsx`) +- Simulates a logged-in user +- Provides user data throughout the app +- **Default user**: `george-demo-id` (change this to match your DB) + +### 2. Getting User Info Anywhere + +In any component: + +```jsx +import { useAuth } from "../context/AuthContext"; + +function MyComponent() { + const { user, isAuthenticated } = useAuth(); + + // user.id → "george-demo-id" + // user.username → "George Demo" + // user.email → "george@example.com" + + // Use user.id when creating posts, joining groups, etc. + const createSomething = async () => { + await api.post("/something", { + user_id: user.id, // Uses George's ID + // ... other data + }); + }; +} +``` + +### 3. Current Implementations + +**Posts** (`Home.jsx`): +- ✅ Uses `user.id` when creating posts +- Posts are attributed to "George Demo" + +## How to Change the Mock User + +Open `client/src/context/AuthContext.jsx` and update: + +```jsx +const [user, setUser] = useState({ + id: "YOUR_ACTUAL_USER_ID_FROM_DB", // ← Change this + username: "George Demo", + email: "george@example.com", +}); +``` + +**To find George's actual ID from Supabase:** +1. Go to Supabase Dashboard +2. Open the `user` table +3. Find George Demo's row +4. Copy the `id` value +5. Paste it in the code above + +## When Real Auth is Ready + +Your teammate will replace the mock system with real authentication: + +1. **Login/Signup** will call real API endpoints +2. **JWT token** will be stored in localStorage +3. **Token** will be sent with every API request +4. **Backend** will validate token and extract real user ID + +## What You Can Do Now + +You can implement features that require a logged-in user: + +- **Create posts** → Uses George's ID +- **Join groups** → Add George to group members +- **Send messages** → Attribute to George +- **Like/comment** → Track George's interactions + +Just use `user.id` from the `useAuth()` hook everywhere! + +## Backend Side (Optional Enhancement) + +If you want the backend to validate the user, you could add middleware later: + +```javascript +// server/middleware/auth.js (for future) +export const authMiddleware = (req, res, next) => { + // For now, trust the user_id in the request + // Later: validate JWT token here + next(); +}; +``` + +## Testing Different Users + +To test as a different user, change the ID in `AuthContext.jsx`: + +```jsx +// Test as User 1 +const [user, setUser] = useState({ + id: "user-1-id", + username: "User One", + // ... +}); + +// Test as User 2 +const [user, setUser] = useState({ + id: "user-2-id", + username: "User Two", + // ... +}); +``` + +## Transition Plan + +When real auth is implemented: + +1. Keep `AuthContext.jsx` structure the same +2. Replace mock `login()` with real API call +3. Add token storage/retrieval +4. Update `user` state from API response +5. Add token to API requests (already set up in `api.js`) + +The rest of your code won't need to change! 🎉 diff --git a/INTEGRATION_README.md b/INTEGRATION_README.md new file mode 100644 index 000000000..337f29725 --- /dev/null +++ b/INTEGRATION_README.md @@ -0,0 +1,137 @@ +# Frontend-Backend Integration - Posts Feature + +## ✅ Completed Integration + +The frontend and backend are now connected for **Post CRUD operations**. All users (even unauthenticated) can create, read, update, and delete posts. + +## 🏗️ What Was Implemented + +### Backend (Already Complete) +- ✅ Express server with CORS enabled +- ✅ Supabase database integration +- ✅ Full CRUD API endpoints at `/api/posts` + - `GET /api/posts` - Get all posts + - `GET /api/posts/:id` - Get single post + - `POST /api/posts` - Create new post + - `PUT /api/posts/:id` - Update post + - `DELETE /api/posts/:id` - Delete post + +### Frontend (Newly Added) +- ✅ Axios installed and configured +- ✅ API service layer created +- ✅ Vite proxy configured +- ✅ Post UI components integrated into Home page +- ✅ Full CRUD operations in UI + +## 📁 New Files Created + +``` +client/ +├── .env # Environment variables +├── src/ +│ ├── services/ +│ │ ├── api.js # Axios base configuration +│ │ └── postService.js # Post API calls +│ └── pages/ +│ └── Posts.css # Post styling +``` + +## 🚀 How to Run + +### 1. Start the Backend Server +```bash +cd server +npm install +npm run dev +``` +Server runs on: `http://localhost:3000` + +### 2. Start the Frontend +```bash +cd client +npm install +npm run dev +``` +Client runs on: `http://localhost:5173` + +## 🎯 Features Implemented + +### Create Post +- Fill in caption, content, and optional photo URL +- Click "Post" button +- Post is created in Supabase database + +### Read Posts +- All posts automatically load when Home page opens +- Posts display with user, timestamp, caption, content, and image + +### Update Post +- Click "Edit" button on any post +- Modify caption, content, or photo URL +- Click "Update" to save changes + +### Delete Post +- Click "Delete" button on any post +- Confirm deletion in popup +- Post is removed from database + +## 🔧 Post Data Structure + +Based on `server/tables/post.json`: +```json +{ + "id": "auto-generated", + "user_id": "anonymous (for now)", + "photo_url": "optional image URL", + "caption": "post title/caption", + "content": "post body text", + "user_ids": {}, + "group_limit": "" +} +``` + +## 🌐 API Endpoints Used + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/posts` | Fetch all posts | +| GET | `/api/posts/:id` | Fetch single post | +| POST | `/api/posts` | Create new post | +| PUT | `/api/posts/:id` | Update existing post | +| DELETE | `/api/posts/:id` | Delete post | + +## 🎨 UI Location + +Posts are displayed on the **Home page** (`/home`) below the groups section. + +## ⚠️ Important Notes + +1. **Authentication is disabled** - As requested, anyone can create/edit/delete posts +2. **Supabase must be configured** - Make sure `server/.env` has valid Supabase credentials +3. **Proxy is configured** - Frontend `/api` calls are proxied to `http://localhost:3000` +4. **Both servers must run** - Backend on port 3000, frontend on port 5173 + +## 🔮 Next Steps (When Ready) + +- Add authentication (your teammate's work) +- Restrict edit/delete to post owners +- Add user profiles +- Implement groups integration +- Add real-time updates +- Image upload functionality + +## 🐛 Troubleshooting + +### "Failed to load posts" +- Ensure backend server is running on port 3000 +- Check Supabase credentials in `server/.env` +- Verify `post` table exists in Supabase + +### "Network Error" +- Backend server not running +- Check console for CORS errors + +### Posts not showing after creation +- Check browser console for errors +- Verify Supabase table has correct schema +- Check network tab to see API responses diff --git a/README.md b/README.md index 0e1211217..2dab3be51 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,71 @@ -# [your app name here] +# The Lexington Link CodePath WEB103 Final Project -Designed and developed by: [your names here] +Designed and developed by: Evan Lu, James Chen, Eric Azayev -🔗 Link to deployed app: +🔗 Link to deployed app: -## About +## About / Description and Purpose -### Description and Purpose +The Lexington Link is a full-stack web application designed exclusively for Hunter College students to combat social isolation and foster a more connected campus community. + +The app serves as a focused friend and hangout matching platform, moving beyond superficial connections by matching users based on shared academic courses, compatible majors, and preferred local hangout spots. + +The primary purpose is to provide students with the tools to easily: +* Discover and connect with peers who share interests or classes. +* Form and manage casual hangout groups for socializing, studying, or exploring the city. +* Schedule and communicate plans within a safe, university-verified environment. + +By implementing essential features like school email verification, a familiar swipe interface, and robust Group Formation and Chat Functionality, The Lexington Link aims to directly address the high rates of student loneliness by making healthy, local social connection simple and intuitive. -[text goes here] ### Inspiration -[text goes here] +* Majority of Hunter College students are anti-social, so this build to bridge connections and create a more healthy environment for college lifestyle. +* Dating Apps: swipe feature allows an easy experience for users. +* "More broadly, loneliness is a significant issue among college students in the U.S., with a survey indicating nearly two-thirds (64.7%) of college students report feeling lonely. This data highlights the mental health impact of loneliness, including psychological distress." - ActiveMinds (Nonprofit organization) ## Tech Stack -Frontend: +Frontend: ReactJS, Tailwind CSS -Backend: +Backend: Express, Postgres ## Features -### [Name of Feature 1] +### User registration + +To connect with other students, users need to create an account with their email. + +### Profile creation + +✅Users can add personal details, their major, and favorite hangout spots to find compatible hangout buddies. +profile_creation + + +### Swipe Functionality + +Users can swipe for hangout buddies based on their courses, interests, location, or preferred hangout time. + +### Group formation -[short description goes here] +Users can form hangout groups, invite friends, and schedule hangout plans. -[gif goes here] +### Chat functionality -### [Name of Feature 2] +✅Users can chat with their hangout buddies and discuss their hangout plan. user has a list of GCIDs. When they open messages, they're shown all the gcs they're in. +messages_page -[short description goes here] -[gif goes here] +### Home Functionality -### [Name of Feature 3] +✅Home can be used to view Clubs, posts. +✅Brew Random Group in /home +✅User can access messages from /home +home_page -[short description goes here] -[gif goes here] ### [ADDITIONAL FEATURES GO HERE - ADD ALL FEATURES HERE IN THE FORMAT ABOVE; you will check these off and add gifs as you complete them] diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 000000000..3b0b40372 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env \ No newline at end of file diff --git a/client/README.md b/client/README.md new file mode 100644 index 000000000..18bc70ebe --- /dev/null +++ b/client/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 000000000..cee1e2c78 --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/client/index.html b/client/index.html new file mode 100644 index 000000000..f07b3d04d --- /dev/null +++ b/client/index.html @@ -0,0 +1,13 @@ + + + + + + + client + + +
+ + + diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 000000000..ca32a790d --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,2216 @@ +{ + "name": "client", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client", + "version": "0.0.0", + "dependencies": { + "@supabase/supabase-js": "^2.81.1", + "axios": "^1.13.2", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.5" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "vite": "^7.2.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.43", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@supabase/auth-js": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.83.0.tgz", + "integrity": "sha512-xmyFcglbAo6C2ox5T9FjZryqk50xU23QqoNKnEYn7mjgxghP/A13W64lL3/TF8HtbuCt3Esk9d3Jw5afXTO/ew==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.83.0.tgz", + "integrity": "sha512-fRfPbyWB6MsovTINpSC21HhU1hfY/4mcXLsDV34sC2b/5i0mZYTBaCbuy4yfTG1vcxCzKDqMgAIC//lewnafrg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.83.0.tgz", + "integrity": "sha512-qjVwbP9JXwgd/YbOj/soWvOUl5c/jyI/L7zs7VDxl5HEq64Gs4ZI5OoDcml+HcOwxFFxVytYeyQLd0rSWWNRIQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.83.0.tgz", + "integrity": "sha512-mT+QeXAD2gLoqNeQFLjTloDM62VR+VFV8OVdF8RscYpXZriBhabTLE2Auff5lkEJetFFclP1B8j+YtgrWqSmeA==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.83.0.tgz", + "integrity": "sha512-qmOM8E6HH/+dm6tW0Tu9Q/TuM035pI3AuKegvQERZRLLk3HtPms5O8UaYh6zi5LZaPtM9u5fldv1W6AUKkKLDQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.83.0.tgz", + "integrity": "sha512-X0OOgJQfD9BDNhxfslozuq/26fPyBt+TsMX+YkI2T6Hc4M2bkCDho/D4LC8nV9gNtviuejWdhit8YzHwnKOQoQ==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.83.0", + "@supabase/functions-js": "2.83.0", + "@supabase/postgrest-js": "2.83.0", + "@supabase/realtime-js": "2.83.0", + "@supabase/storage-js": "2.83.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.43", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.26", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.250", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.9.5", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.5", + "license": "MIT", + "dependencies": { + "react-router": "7.9.5" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 000000000..cabbd154c --- /dev/null +++ b/client/package.json @@ -0,0 +1,30 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@supabase/supabase-js": "^2.81.1", + "axios": "^1.13.2", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.5" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "vite": "^7.2.2" + } +} diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 000000000..8041d8056 --- /dev/null +++ b/client/src/App.css @@ -0,0 +1 @@ +/* App.css - Minimal global app styles */ diff --git a/client/src/App.jsx b/client/src/App.jsx new file mode 100644 index 000000000..c5bcb0c49 --- /dev/null +++ b/client/src/App.jsx @@ -0,0 +1,43 @@ +import { + BrowserRouter as Router, + Routes, + Route, + Navigate, +} from "react-router-dom"; +import { useAuth } from "./context/AuthContext"; +import SignUp from "./pages/SignUp"; +import Home from "./pages/Home"; +import Messages from "./pages/Messages"; +import UserLookup from "./pages/UserLookup"; +import Profile from "./pages/Profile"; +import Login from "./pages/Login"; +import "./App.css"; + +// Protected route wrapper +function ProtectedRoute({ children }) { + const { isAuthenticated, loading } = useAuth(); + + if (loading) { + return
Loading...
; + } + + return isAuthenticated ? children : ; +} + +function App() { + return ( + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} + +export default App; diff --git a/client/src/assets/default.jpg b/client/src/assets/default.jpg new file mode 100644 index 000000000..93c4cc0d6 Binary files /dev/null and b/client/src/assets/default.jpg differ diff --git a/client/src/assets/react.svg b/client/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/client/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/context/AuthContext.jsx b/client/src/context/AuthContext.jsx new file mode 100644 index 000000000..9294d742a --- /dev/null +++ b/client/src/context/AuthContext.jsx @@ -0,0 +1,149 @@ +import { createContext, useContext, useState, useEffect } from "react"; +import { supabase } from "../supabaseClient"; + +const AuthContext = createContext(); + +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within AuthProvider"); + } + return context; +}; + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [loading, setLoading] = useState(true); + + // Initialize auth state on mount + useEffect(() => { + // Check active sessions and sets the user + supabase.auth.getSession().then(({ data: { session } }) => { + if (session?.user) { + loadUserData(session.user.id); + } else { + setLoading(false); + } + }); + + // Listen for auth changes + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_event, session) => { + if (session?.user) { + loadUserData(session.user.id); + } else { + setUser(null); + setIsAuthenticated(false); + setLoading(false); + } + }); + + return () => subscription.unsubscribe(); + }, []); + + // Load user data from your 'user' table + const loadUserData = async (authUserId) => { + try { + // First check if user exists in 'user' table + const { data: userData, error } = await supabase + .from("user") + .select("*") + .eq("id", authUserId) + .single(); + + if (error && error.code === 'PGRST116') { + // User doesn't exist in user table yet, create them + const { data: authUser } = await supabase.auth.getUser(); + const { data: newUser, error: insertError } = await supabase + .from("user") + .insert([{ + id: authUserId, + username: authUser.user.email.split('@')[0], // Default username from email + display_name: null, + pfp: null, + bio: null, + borough: null, + year: null, + interests: {}, + follows_ids: {}, + }]) + .select() + .single(); + + if (insertError) throw insertError; + + setUser(newUser); + setIsAuthenticated(true); + } else if (error) { + throw error; + } else { + setUser(userData); + setIsAuthenticated(true); + } + } catch (error) { + console.error("Error loading user data:", error); + setUser(null); + setIsAuthenticated(false); + } finally { + setLoading(false); + } + }; + + // Login function + const login = async (email, password) => { + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + if (error) throw error; + // User will be loaded via onAuthStateChange listener + }; + + // Signup function + const signup = async (email, password, username) => { + const { data, error } = await supabase.auth.signUp({ + email, + password, + }); + if (error) throw error; + + // Create user in 'user' table + if (data.user) { + await supabase.from("user").insert([{ + id: data.user.id, + username: username || email.split('@')[0], + display_name: null, + pfp: null, + bio: null, + borough: null, + year: null, + interests: {}, + follows_ids: {}, + }]); + } + + return data; + }; + + // Logout function + const logout = async () => { + const { error } = await supabase.auth.signOut(); + if (error) throw error; + setUser(null); + setIsAuthenticated(false); + }; + + const value = { + user, + isAuthenticated, + loading, + login, + signup, + logout, + setUser, + }; + + return {children}; +}; diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 000000000..7cedd77e4 --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,115 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"); + +:root { + /* Color Palette */ + --primary-purple: #2b004a; + --white: #ffffff; + --light-purple: #4a0070; + --hover-purple: #3a0058; + --text-gray: #e0e0e0; + --border-gray: #d1d1d1; + --shadow: rgba(0, 0, 0, 0.2); + + /* Typography */ + font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + line-height: 1.6; + font-weight: 400; + + /* Smooth rendering */ + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background-color: var(--primary-purple); + color: var(--white); +} + +#root { + min-height: 100vh; +} + +a { + color: var(--white); + text-decoration: none; + transition: opacity 0.3s ease; +} + +a:hover { + opacity: 0.8; +} + +button { + border-radius: 12px; + border: none; + padding: 12px 24px; + font-size: 1rem; + font-weight: 500; + font-family: "Poppins", sans-serif; + background-color: var(--white); + color: var(--primary-purple); + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 12px var(--shadow); +} + +button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px var(--shadow); +} + +button:active { + transform: translateY(0); +} + +input, +select, +textarea { + width: 100%; + padding: 12px 16px; + border-radius: 8px; + border: 1px solid var(--border-gray); + font-family: "Poppins", sans-serif; + font-size: 1rem; + transition: border-color 0.3s ease; +} + +input:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--light-purple); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; + line-height: 1.2; +} + +h1 { + font-size: 2.5rem; +} + +h2 { + font-size: 2rem; +} + +h3 { + font-size: 1.5rem; +} diff --git a/client/src/main.jsx b/client/src/main.jsx new file mode 100644 index 000000000..cd583f517 --- /dev/null +++ b/client/src/main.jsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' +import { AuthProvider } from './context/AuthContext.jsx' + +createRoot(document.getElementById('root')).render( + + + + + , +) diff --git a/client/src/pages/Home.css b/client/src/pages/Home.css new file mode 100644 index 000000000..4a10d719a --- /dev/null +++ b/client/src/pages/Home.css @@ -0,0 +1,271 @@ +.home-container { + min-height: 100vh; + background-color: var(--primary-purple); +} + +.navbar { + background-color: var(--light-purple); + padding: 16px 32px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 12px var(--shadow); + position: sticky; + top: 0; + z-index: 100; +} + +.nav-brand h2 { + color: var(--white); + font-size: 1.5rem; + margin: 0; +} + +.nav-links { + display: flex; + align-items: center; + gap: 16px; +} + +.nav-button { + background-color: var(--white); + color: var(--primary-purple); + padding: 10px 20px; + border-radius: 8px; + border: none; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; + display: inline-block; +} + +.nav-button:hover { + background-color: var(--text-gray); + transform: translateY(-2px); +} + +.search-container { + position: relative; +} + +.search-input { + padding: 10px 16px; + border-radius: 8px; + border: none; + width: 200px; + font-size: 0.95rem; + transition: all 0.3s ease; +} + +.search-input:focus { + width: 250px; + outline: none; + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.3); +} + +.main-content { + padding: 48px 32px; + max-width: 1200px; + margin: 0 auto; +} + +.content-header { + text-align: center; + margin-bottom: 48px; + animation: fadeIn 0.5s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.content-header h1 { + color: var(--white); + margin-bottom: 12px; + font-size: 2.5rem; +} + +.content-header p { + color: var(--text-gray); + font-size: 1.1rem; +} + +.groups-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; + animation: fadeIn 0.7s ease; +} + +.group-card { + background-color: var(--white); + border-radius: 16px; + padding: 32px; + text-align: center; + transition: all 0.3s ease; + box-shadow: 0 4px 12px var(--shadow); +} + +.group-card:hover { + transform: translateY(-8px); + box-shadow: 0 8px 24px var(--shadow); +} + +.group-icon { + font-size: 4rem; + margin-bottom: 16px; +} + +.group-card h3 { + color: var(--primary-purple); + margin-bottom: 8px; + font-size: 1.3rem; +} + +.member-count { + color: var(--hover-purple); + font-size: 0.9rem; + margin-bottom: 20px; +} + +.join-button { + width: 100%; + background-color: var(--primary-purple); + color: var(--white); + padding: 12px; + border-radius: 8px; + border: none; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.join-button:hover { + background-color: var(--light-purple); + transform: translateY(-2px); +} + +@media (max-width: 768px) { + .navbar { + flex-direction: column; + gap: 16px; + padding: 16px; + } + + .nav-links { + flex-wrap: wrap; + justify-content: center; + } + + .search-input { + width: 100%; + } + + .search-input:focus { + width: 100%; + } + + .main-content { + padding: 24px 16px; + } + + .content-header h1 { + font-size: 2rem; + } +} + +/* Friends Section */ +.friends-section { + background: white; + border-radius: 16px; + padding: 24px; + margin: 48px 0; + box-shadow: 0 4px 12px var(--shadow); +} + +.friends-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; +} + +.friends-header h2 { + color: var(--primary-purple); + margin: 0; +} + +.add-friend-link { + background-color: var(--primary-purple); + color: white; + padding: 10px 20px; + border-radius: 8px; + text-decoration: none; + font-weight: 600; + transition: all 0.3s ease; +} + +.add-friend-link:hover { + background-color: var(--light-purple); + transform: translateY(-2px); +} + +.friends-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} + +.friend-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border: 2px solid #e2e8f0; + border-radius: 12px; + transition: all 0.3s ease; +} + +.friend-card:hover { + border-color: var(--primary-purple); + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.friend-pfp { + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; +} + +.friend-info { + flex: 1; + min-width: 0; +} + +.friend-info h4 { + margin: 0; + color: var(--primary-purple); + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.friend-interests { + font-size: 0.85rem; + color: #64748b; + margin-top: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/client/src/pages/Home.jsx b/client/src/pages/Home.jsx new file mode 100644 index 000000000..08436dc17 --- /dev/null +++ b/client/src/pages/Home.jsx @@ -0,0 +1,393 @@ +import { Link } from "react-router-dom"; +import { useState, useEffect } from "react"; +import { + getAllPosts, + createPost, + updatePost, + deletePost, +} from "../services/postService"; +import { getFriends } from "../services/userService"; +import { useAuth } from "../context/AuthContext"; +import "./Home.css"; +import "./Posts.css"; + +function Home() { + const { user } = useAuth(); // Get current user from auth context + + const groups = [ + { id: 1, name: "CS_Club", image: "💻", members: 45 }, + { id: 2, name: "Art_Lovers", image: "🎨", members: 32 }, + { id: 3, name: "Foodies_NYC", image: "🍕", members: 67 }, + { id: 4, name: "Book_Club", image: "📚", members: 28 }, + { id: 5, name: "Fitness_Gang", image: "💪", members: 54 }, + { id: 6, name: "Music_Makers", image: "🎵", members: 41 }, + ]; + + // Post state + const [posts, setPosts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [editingPostId, setEditingPostId] = useState(null); + + // Friends state + const [friends, setFriends] = useState([]); + const [friendsLoading, setFriendsLoading] = useState(false); + + // Form state + const [newPost, setNewPost] = useState({ + caption: "", + content: "", + image_url: "", + }); + + const [editForm, setEditForm] = useState({ + caption: "", + content: "", + image_url: "", + }); + + // Fetch posts and friends on component mount + useEffect(() => { + fetchPosts(); + if (user?.id) { + fetchFriends(); + } + }, [user?.id]); + + const fetchFriends = async () => { + try { + setFriendsLoading(true); + const data = await getFriends(user.id); + setFriends(data || []); + } catch (err) { + console.error("Error fetching friends:", err); + } finally { + setFriendsLoading(false); + } + }; + + const fetchPosts = async () => { + try { + setLoading(true); + setError(null); + const data = await getAllPosts(); + console.log("Frontend received posts:", data); // Debug log + setPosts(data || []); + } catch (err) { + setError("Failed to load posts. Make sure the server is running."); + console.error("Error fetching posts:", err); + } finally { + setLoading(false); + } + }; + + const handleCreatePost = async (e) => { + e.preventDefault(); + try { + const postData = { + caption: newPost.caption, + content: newPost.content, + user_id: user?.id || "anonymous", // Use actual user ID from auth context + }; + // Only include image_url if it's not empty + if (newPost.image_url && newPost.image_url.trim()) { + postData.image_url = newPost.image_url; + } + await createPost(postData); + setNewPost({ caption: "", content: "", image_url: "" }); + fetchPosts(); // Refresh the list + } catch (err) { + alert("Failed to create post: " + (err.response?.data?.error || err.message)); + } + }; + + const handleDeletePost = async (id) => { + if (window.confirm("Are you sure you want to delete this post?")) { + try { + await deletePost(id); + fetchPosts(); // Refresh the list + } catch (err) { + alert("Failed to delete post: " + (err.response?.data?.error || err.message)); + } + } + }; + + const handleEditClick = (post) => { + setEditingPostId(post.id); + setEditForm({ + caption: post.caption || "", + content: post.content || "", + image_url: post.image_url || "", + }); + }; + + const handleUpdatePost = async (e, id) => { + e.preventDefault(); + try { + const updateData = { + caption: editForm.caption, + content: editForm.content, + }; + // Only include image_url if it's not empty + if (editForm.image_url && editForm.image_url.trim()) { + updateData.image_url = editForm.image_url; + } + await updatePost(id, updateData); + setEditingPostId(null); + fetchPosts(); // Refresh the list + } catch (err) { + alert("Failed to update post: " + (err.response?.data?.error || err.message)); + } + }; + + const handleCancelEdit = () => { + setEditingPostId(null); + setEditForm({ caption: "", content: "", image_url: "" }); + }; + + + return ( +
+ + +
+
+

Discover Groups

+

Find your community and connect with like-minded students

+
+ +
+ {groups.map((group) => ( +
+
{group.image}
+

{group.name}

+

{group.members} members

+ +
+ ))} +
+ + {/* Friends Section */} +
+
+

My Friends

+ + + Add Friends + +
+ + {friendsLoading ? ( +
Loading friends...
+ ) : friends.length === 0 ? ( +
+

No friends yet. Start adding friends from User Lookup!

+
+ ) : ( +
+ {friends.map((friend) => ( +
+ {friend.pfp && ( + {friend.username} + )} +
+

{friend.username}

+ {friend.interests && ( +
+ {Object.keys(friend.interests).slice(0, 3).join(", ")} +
+ )} +
+
+ ))} +
+ )} +
+ + {/* Posts Section */} +
+

Community Posts

+ + {/* Create Post Form */} +
+

Create a New Post

+
+ + setNewPost({ ...newPost, caption: e.target.value }) + } + required + /> +