diff --git a/.env.example b/.env.example deleted file mode 100644 index 2e4a6b7..0000000 --- a/.env.example +++ /dev/null @@ -1,16 +0,0 @@ -# Copy this to .env and fill in your values -# See OPENMINDWELL_PROJECT_GUIDE.md for instructions on obtaining these - -# Supabase (get from https://app.supabase.com) -SUPABASE_URL=https://your-project.supabase.co -SUPABASE_ANON_KEY=your-anon-key-here -SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here - -# HuggingFace (optional - get from https://huggingface.co/settings/tokens) -HUGGINGFACE_API_TOKEN=your-token-here - -# Frontend URL (for CORS) -FRONTEND_URL=http://localhost:3000 - -# Backend Port -PORT=3001 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index af8b428..0000000 --- a/.gitignore +++ /dev/null @@ -1,51 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -node_modules/ -.pnp -.pnp.js - -# testing -coverage/ - -# next.js -.next/ -out/ -build -dist/ - -# production -build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -.env -.env.local -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# ide -.idea -.vscode/* -!.vscode/extensions.json -*.swp -*.swo -*~ - -# os -Thumbs.db diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index b88d27e..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "bradlc.vscode-tailwindcss", - "ms-vscode.vscode-typescript-next" - ] -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 931d9ea..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -๏ปฟ# Contributing to OpenMindWell - -Thank you for your interest in contributing to OpenMindWell! - -## About This Project - -OpenMindWell is a **self-hosted, open-source** mental health support platform built by Team ZenYukti. - -**Important**: There is **no central hosted instance** of OpenMindWell. Each deployment is independent: -- Users deploy their own instances with their own infrastructure -- Each instance uses its own Supabase database and HuggingFace API keys -- Users maintain full control over their data and deployment -- No shared public demo exists - this is intentional for privacy and security - -## Ways to Contribute - -See full guidelines in the repository README.md - ---- - -**Connect with Team ZenYukti:** -- ๐ŸŒ [zenyukti.in](https://zenyukti.in) -- ๐Ÿ’ผ [LinkedIn](https://linkedin.com/company/zenyukti) -- ๐Ÿฆ [Twitter/X](https://x.com/zenyukti) -- ๐Ÿ’ฌ [Discord Community](https://go.zenyukti.in/discord) -- ๐Ÿ“ธ [Instagram](https://instagram.com/zenyukti) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5d7a8f9..0000000 --- a/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -MIT License - -Copyright (c) 2025 ZenYukti - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---- - -DISCLAIMER: OpenMindWell is NOT a substitute for professional mental health care. -If you are in crisis, please contact emergency services or a crisis hotline immediately. diff --git a/OPENMINDWELL_PROJECT_GUIDE.md b/OPENMINDWELL_PROJECT_GUIDE.md deleted file mode 100644 index 9e0dd77..0000000 --- a/OPENMINDWELL_PROJECT_GUIDE.md +++ /dev/null @@ -1,1235 +0,0 @@ -# ๐Ÿ“˜ OpenMindWell - Complete Project Guide - -> **Version 1.0** | Last Updated: November 2024 - ---- - -## โš ๏ธ CRITICAL SAFETY DISCLAIMER - -**OpenMindWell is NOT a substitute for professional mental health care.** - -This platform provides: -- โœ… Peer support and community connection -- โœ… Self-help resources and coping strategies -- โœ… A safe space to share experiences - -This platform does NOT provide: -- โŒ Professional therapy or counseling -- โŒ Medical diagnosis or treatment -- โŒ Emergency crisis intervention -- โŒ Licensed mental health services - -**IF YOU ARE IN CRISIS:** -- ๐Ÿ‡บ๐Ÿ‡ธ Call/Text **988** (Suicide & Crisis Lifeline) -- ๐Ÿ‡บ๐Ÿ‡ธ Text **HOME** to **741741** (Crisis Text Line) -- ๐ŸŒ Find international helplines: **findahelpline.com** -- ๐Ÿšจ Call emergency services (911/112/999) for immediate danger - ---- - -## ๐Ÿ“‹ Table of Contents - -1. [Project Overview](#project-overview) -2. [Features](#features) -3. [Tech Stack](#tech-stack) -4. [Architecture](#architecture) -5. [Folder Structure](#folder-structure) -6. [Environment Variables](#environment-variables) -7. [Local Development Setup](#local-development-setup) -8. [Free Service Accounts Setup](#free-service-accounts-setup) -9. [Deployment Guide](#deployment-guide) -10. [Security & Privacy](#security--privacy) -11. [Contributing](#contributing) -12. [Roadmap](#roadmap) - ---- - -## ๐ŸŒŸ Project Overview - -**OpenMindWell** is a free, open-source mental health support platform designed to provide anonymous peer support, self-help tools, and curated resources. Built with modern web technologies and deployed entirely on free-tier services. - -### Why OpenMindWell? - -- **Accessibility**: 100% free to use, no premium features -- **Privacy**: Anonymous accounts, no personal data required -- **Safety**: AI-powered crisis detection with automatic resource suggestions -- **Community**: Peer-to-peer support in moderated chat rooms -- **Open Source**: Transparent, auditable, and community-driven - -### Target Audience - -- Individuals seeking peer support for mental wellness -- People exploring self-help strategies -- Communities building mental health awareness -- Open-source contributors (GSoC, Hacktoberfest, etc.) - ---- - -## ๐ŸŽฏ Features - -### 1. **Anonymous Chat Rooms** ๐Ÿ’ฌ -- 6 pre-created support rooms (Anxiety, Depression, PTSD, etc.) -- **โœ… Real-time WebSocket messaging** (fully implemented) -- Anonymous/pseudonymous usernames -- Emoji avatars (no photos) -- Auto-reconnection with exponential backoff -- Message history (last 50 messages) -- User join/leave notifications -- Crisis alerts with helpline numbers - -### 2. **AI Crisis Detection** ๐Ÿค– -- **โœ… Active in real-time chat** - scans every message -- HuggingFace emotion analysis (twitter-roberta-base-emotion) -- Keyword-based fallback system (no API key required) -- Automatic crisis alerts with US & India helplines -- 4-tier risk levels (low, medium, high, critical) -- Visual highlighting of crisis messages (red background) -- Moderator notifications (backend ready) - -### 3. **Private Journaling** ๐Ÿ“ -- End-to-end private entries (only visible to user) -- Mood tracking (1-5 scale) -- Tagging system -- Reflection prompts - -### 4. **Habit Tracking** โœ… -- Custom habit creation -- Daily logging with notes -- Streak tracking -- Progress visualization (coming soon) - -### 5. **Resource Library** ๐Ÿ“š -- Curated mental health articles -- Crisis hotlines (US & International) -- Breathing exercises and guided meditations -- Categorized by type (hotline, article, exercise) - -### 6. **Moderation System** ๐Ÿ›ก๏ธ -- User reporting functionality -- Moderator dashboard (volunteer-only) -- Flagged message review -- Community guidelines enforcement - -### 7. **Volunteer Program** ๐Ÿค -- Trained peer support volunteers -- Moderator privileges -- Community safety oversight - ---- - -## ๐Ÿ› ๏ธ Tech Stack - -### **Frontend** -- **Framework**: React 18 with Vite 5 -- **Router**: React Router DOM 6 -- **Language**: TypeScript 5.3 -- **Styling**: Tailwind CSS 3.4 -- **Auth**: Supabase Auth (anonymous sign-in) -- **State**: React Hooks -- **HTTP Client**: Fetch API -- **WebSocket**: Native WebSocket API - -### **Backend** -- **Runtime**: Node.js 18+ -- **Framework**: Express.js 4.18 -- **Language**: TypeScript 5.3 -- **WebSocket**: ws library 8.16 -- **Database**: Supabase (PostgreSQL 15) -- **Auth**: JWT validation -- **AI**: HuggingFace Inference API -- **Security**: Helmet, CORS, Rate Limiting - -### **Database** -- **Service**: Supabase (managed PostgreSQL) -- **ORM**: None (direct SQL queries via Supabase client) -- **Security**: Row Level Security (RLS) policies -- **Tables**: 8 (profiles, rooms, messages, journal_entries, habits, habit_logs, resources, reports, volunteers) - -### **AI/ML** -- **Provider**: HuggingFace -- **Model**: `cardiffnlp/twitter-roberta-base-emotion` -- **Task**: Emotion classification (7 emotions) -- **Fallback**: Keyword-based pattern matching - -### **Deployment** -- **Hosting**: Self-hosted (VPS, home server, or Raspberry Pi) -- **Containerization**: Docker & Docker Compose -- **Database**: Supabase (free tier) or self-hosted PostgreSQL -- **Version Control**: Git/GitHub - -### **Development Tools** -- **Package Manager**: npm -- **Linting**: TypeScript compiler -- **Monorepo**: Workspaces with concurrently - ---- - -## ๐Ÿ—๏ธ Architecture - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ USER DEVICES โ”‚ -โ”‚ (Web Browsers - Desktop/Mobile) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ FRONTEND (React 18 + Vite) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Routes: / โ†’ /onboarding โ†’ /dashboard โ”‚ โ”‚ -โ”‚ โ”‚ Components: RoomsList, JournalForm, HabitTracker, etc. โ”‚ โ”‚ -โ”‚ โ”‚ API Client: lib/api.ts (REST) + WebSocket client โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ Self-hosted on your server โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ”‚ HTTP/REST โ”‚ WebSocket (wss://) - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ BACKEND (Express.js + ws) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ REST API Routes: โ”‚ โ”‚ -โ”‚ โ”‚ - /api/journal (GET/POST/PUT/DELETE) โ”‚ โ”‚ -โ”‚ โ”‚ - /api/habits (GET/POST/PUT/DELETE) โ”‚ โ”‚ -โ”‚ โ”‚ - /api/rooms (GET rooms, GET messages) โ”‚ โ”‚ -โ”‚ โ”‚ - /api/resources (GET by category) โ”‚ โ”‚ -โ”‚ โ”‚ - /api/moderation (GET reports, POST flag) โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ WebSocket Server: โ”‚ โ”‚ -โ”‚ โ”‚ - Real-time chat messaging โ”‚ โ”‚ -โ”‚ โ”‚ - Room join/leave events โ”‚ โ”‚ -โ”‚ โ”‚ - Crisis detection integration โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ Services: โ”‚ โ”‚ -โ”‚ โ”‚ - Crisis Detection (HuggingFace API + keywords) โ”‚ โ”‚ -โ”‚ โ”‚ - Chat Server (WebSocket management) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ Self-hosted on your server โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”‚ Supabase Client SDK - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ SUPABASE (Database + Auth) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ PostgreSQL Database (8 tables): โ”‚ โ”‚ -โ”‚ โ”‚ - profiles (user info) โ”‚ โ”‚ -โ”‚ โ”‚ - rooms (chat rooms) โ”‚ โ”‚ -โ”‚ โ”‚ - messages (chat history + risk_level) โ”‚ โ”‚ -โ”‚ โ”‚ - journal_entries (private notes) โ”‚ โ”‚ -โ”‚ โ”‚ - habits (user habits) โ”‚ โ”‚ -โ”‚ โ”‚ - habit_logs (daily tracking) โ”‚ โ”‚ -โ”‚ โ”‚ - resources (curated content) โ”‚ โ”‚ -โ”‚ โ”‚ - reports (moderation flags) โ”‚ โ”‚ -โ”‚ โ”‚ - volunteers (moderator access) โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ Row Level Security (RLS): โ”‚ โ”‚ -โ”‚ โ”‚ - Users can only see/edit their own data โ”‚ โ”‚ -โ”‚ โ”‚ - Messages visible to room members only โ”‚ โ”‚ -โ”‚ โ”‚ - Journal entries completely private โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ Authentication: โ”‚ โ”‚ -โ”‚ โ”‚ - Anonymous sign-in (no email required) โ”‚ โ”‚ -โ”‚ โ”‚ - JWT tokens for API authentication โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ Managed by: Supabase โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”‚ HTTPS API Calls - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HUGGINGFACE INFERENCE API โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Model: cardiffnlp/twitter-roberta-base-emotion โ”‚ โ”‚ -โ”‚ โ”‚ Input: Chat message text โ”‚ โ”‚ -โ”‚ โ”‚ Output: Emotion scores (anger, fear, sadness, etc.) โ”‚ โ”‚ -โ”‚ โ”‚ Rate Limit: 1000 calls/day (free tier) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ Managed by: HuggingFace โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### WebSocket Architecture (Real-Time Chat) - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ FRONTEND (React + useWebSocket) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ChatRoom Component: โ”‚ โ”‚ -โ”‚ โ”‚ - Message input & display โ”‚ โ”‚ -โ”‚ โ”‚ - Crisis alert banner โ”‚ โ”‚ -โ”‚ โ”‚ - Connection status indicator โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ useWebSocket Hook: โ”‚ โ”‚ -โ”‚ โ”‚ - Auto-connect on mount โ”‚ โ”‚ -โ”‚ โ”‚ - Auto-reconnect (exponential backoff, max 5 attempts) โ”‚ โ”‚ -โ”‚ โ”‚ - Event handlers: onMessage, onConnect, onDisconnect โ”‚ โ”‚ -โ”‚ โ”‚ - sendMessage() function โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”‚ WebSocket (ws:// or wss://) - โ”‚ Events: JOIN, LEAVE, CHAT - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ BACKEND (Express + ws WebSocket Server) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ChatServer Class: โ”‚ โ”‚ -โ”‚ โ”‚ - Room Map (roomId โ†’ Set<{ws, userId, nickname}>) โ”‚ โ”‚ -โ”‚ โ”‚ - Heartbeat/ping every 30s โ”‚ โ”‚ -โ”‚ โ”‚ - Message handlers: handleJoin, handleLeave, handleChat โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ Message Flow: โ”‚ โ”‚ -โ”‚ โ”‚ 1. Receive CHAT event โ”‚ โ”‚ -โ”‚ โ”‚ 2. Run detectCrisis(content) โ†’ riskLevel โ”‚ โ”‚ -โ”‚ โ”‚ 3. Save to DB: {content, risk_level, user_id, room_id} โ”‚ โ”‚ -โ”‚ โ”‚ 4. broadcastToRoom() โ†’ all connected clients โ”‚ โ”‚ -โ”‚ โ”‚ 5. If crisis: send CRISIS_ALERT to sender โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”‚ Supabase Client SDK - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ SUPABASE (PostgreSQL + Storage) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ messages table: โ”‚ โ”‚ -โ”‚ โ”‚ - id, room_id, user_id, content โ”‚ โ”‚ -โ”‚ โ”‚ - risk_level (none, low, medium, high, critical) โ”‚ โ”‚ -โ”‚ โ”‚ - created_at โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ Row Level Security: โ”‚ โ”‚ -โ”‚ โ”‚ - Users can read messages in rooms they've joined โ”‚ โ”‚ -โ”‚ โ”‚ - Messages persist for history (last 50 loaded on join) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Data Flow Examples - -**1. User Joins Chat Room (Full WebSocket Flow):** -``` -User clicks "Join Room" โ†’ ChatRoom modal opens - โ†’ useWebSocket.connect() โ†’ WebSocket to ws://localhost:3001 - โ†’ Send JOIN {roomId, userId, nickname} - โ†’ Backend: add to rooms Map - โ†’ Backend: SELECT last 50 messages WHERE room_id = ? - โ†’ Send HISTORY {messages: [...]} to client - โ†’ Frontend: setMessages(history) - โ†’ User sees chat interface with history -``` - -**2. User Sends Message with Crisis Content:** -``` -User types "I feel hopeless" โ†’ clicks Send - โ†’ useWebSocket.sendMessage(content) - โ†’ Send CHAT {roomId, userId, content, timestamp} - โ†’ Backend: detectCrisis(content) โ†’ {riskLevel: "medium", isCrisis: true} - โ†’ Backend: INSERT INTO messages (content, risk_level) - โ†’ Backend: broadcastToRoom(CHAT message with risk_level) - โ†’ All users receive message - โ†’ Frontend: render with red background (crisis styling) - โ†’ Backend: send CRISIS_ALERT to sender only - โ†’ Sender sees: "โš ๏ธ CRISIS DETECTED - Call 988 | 9152987821" -``` - -**3. User Creates Journal Entry:** -``` -User writes entry โ†’ Frontend form โ†’ HTTP POST /api/journal - โ†’ Backend validates JWT โ†’ Supabase insert (with RLS check) - โ†’ Return success โ†’ Update UI -``` - -**3. User Joins Chat Room (Full Implementation):** -``` -User clicks "Join Room" โ†’ ChatRoom modal opens - โ†’ useWebSocket hook connects to ws://localhost:3001 - โ†’ Send JOIN message {roomId, userId, nickname} - โ†’ Backend validates & adds user to room Map - โ†’ Backend fetches last 50 messages from DB - โ†’ Frontend receives HISTORY message โ†’ displays messages - โ†’ User types message โ†’ sends CHAT event - โ†’ Backend runs detectCrisis() on message content - โ†’ Backend saves to DB with risk_level - โ†’ Backend broadcasts to all room members - โ†’ If crisis detected: sends CRISIS_ALERT to sender - โ†’ Frontend shows red banner with helplines - โ†’ Auto-scroll to latest message -``` - ---- - -## ๐Ÿ“ Folder Structure - -``` -openmindwell/ -โ”‚ -โ”œโ”€โ”€ backend/ # Node.js Express backend -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ config/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Config validation & export -โ”‚ โ”‚ โ”œโ”€โ”€ lib/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ supabase.ts # Supabase client & types -โ”‚ โ”‚ โ”œโ”€โ”€ middleware/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ auth.ts # JWT authentication -โ”‚ โ”‚ โ”œโ”€โ”€ routes/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ journal.ts # Journal CRUD endpoints -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ habits.ts # Habits CRUD + logging -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ resources.ts # Resource listing -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ rooms.ts # Room & message queries -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ moderation.ts # Reporting & flagging -โ”‚ โ”‚ โ”œโ”€โ”€ services/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ crisisDetection.ts # โœ… AI + keyword crisis detection -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ chatServer.ts # โœ… WebSocket server (COMPLETE) -โ”‚ โ”‚ โ”œโ”€โ”€ scripts/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ setupDatabase.ts # Helper for DB setup -โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Main Express server + WS init -โ”‚ โ”œโ”€โ”€ database/ -โ”‚ โ”‚ โ””โ”€โ”€ schema.sql # PostgreSQL schema (CRITICAL) -โ”‚ โ”œโ”€โ”€ .env.example # Backend env template -โ”‚ โ”œโ”€โ”€ Dockerfile # Docker container config -โ”‚ โ”œโ”€โ”€ package.json # Backend dependencies -โ”‚ โ””โ”€โ”€ tsconfig.json # TypeScript config -โ”‚ -โ”œโ”€โ”€ frontend/ # React + Vite frontend -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ components/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ChatRoom.tsx # โœ… Real-time chat UI (NEW) -โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ useWebSocket.ts # โœ… WebSocket client hook (NEW) -โ”‚ โ”‚ โ”œโ”€โ”€ pages/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Home.tsx # Landing page -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Onboarding.tsx # Nickname setup -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Dashboard.tsx # โœ… Updated with ChatRoom -โ”‚ โ”‚ โ”œโ”€โ”€ lib/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ api.ts # REST API client -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ supabase.ts # Supabase auth client -โ”‚ โ”‚ โ”œโ”€โ”€ App.tsx # React Router config -โ”‚ โ”‚ โ”œโ”€โ”€ main.tsx # App entry point -โ”‚ โ”‚ โ””โ”€โ”€ index.css # Tailwind styles -โ”‚ โ”œโ”€โ”€ .env.example # Frontend env template -โ”‚ โ”œโ”€โ”€ Dockerfile # โœ… Container config (NEW) -โ”‚ โ”œโ”€โ”€ nginx.conf # โœ… Production server (NEW) -โ”‚ โ”œโ”€โ”€ vite.config.ts # Vite configuration -โ”‚ โ”œโ”€โ”€ tailwind.config.ts # Tailwind config -โ”‚ โ”œโ”€โ”€ postcss.config.js # PostCSS config -โ”‚ โ”œโ”€โ”€ package.json # Frontend dependencies -โ”‚ โ””โ”€โ”€ tsconfig.json # TypeScript config -โ”‚ -โ”œโ”€โ”€ .github/ # (Future) CI/CD workflows -โ”œโ”€โ”€ docker-compose.yml # โœ… Self-hosting deployment (NEW) -โ”œโ”€โ”€ .gitignore -โ”œโ”€โ”€ OPENMINDWELL_PROJECT_GUIDE.md # ๐Ÿ“– Complete guide (UPDATED) -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ CONTRIBUTING.md -โ”œโ”€โ”€ PROJECT_SUMMARY.md # Quick reference -โ”œโ”€โ”€ LICENSE -โ””โ”€โ”€ package.json # Root scripts (npm run dev) -โ”œโ”€โ”€ .gitignore # Git ignore rules -โ”œโ”€โ”€ package.json # Monorepo scripts -โ”œโ”€โ”€ README.md # Project README -โ”œโ”€โ”€ LICENSE # MIT License -โ”œโ”€โ”€ CONTRIBUTING.md # Contribution guide -โ”œโ”€โ”€ OPENMINDWELL_PROJECT_GUIDE.md # โ† YOU ARE HERE -โ””โ”€โ”€ PROJECT_SUMMARY.md # Quick reference checklist -``` - -### Key File Descriptions - -| File | Purpose | -|------|---------| -| `backend/database/schema.sql` | **MOST IMPORTANT** - Defines all database tables, RLS policies, and seed data. Run this in Supabase SQL editor. | -| `backend/src/index.ts` | Main backend entry point. Starts Express server and WebSocket server. | -| `backend/src/services/crisisDetection.ts` | Analyzes messages for mental health crises using HuggingFace AI and keyword patterns. | -| `backend/src/services/chatServer.ts` | Manages WebSocket connections, room memberships, message broadcasting. | -| `frontend/src/app/dashboard/page.tsx` | Main application interface with tabs for Rooms, Journal, Habits, Resources. | -| `frontend/src/lib/api.ts` | HTTP client for backend API calls (journal, habits, etc.). | -| `.env.example` files | Templates for environment variables. Copy to `.env` and fill in. | - ---- - -## ๐Ÿ” Environment Variables - -### Backend Variables (`backend/.env`) - -| Variable | Description | Example | Required? | -|----------|-------------|---------|-----------| -| `SUPABASE_URL` | Your Supabase project URL | `https://abc123.supabase.co` | โœ… Yes | -| `SUPABASE_ANON_KEY` | Supabase anonymous/public key | `eyJhbG...` | โœ… Yes | -| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key (admin) | `eyJhbG...` | โœ… Yes | -| `HUGGINGFACE_API_TOKEN` | HuggingFace API token | `hf_abc123...` | โš ๏ธ Optional* | -| `FRONTEND_URL` | Frontend domain for CORS | `http://localhost:3000` | โœ… Yes | -| `PORT` | Backend server port | `3001` | โš ๏ธ Optional** | -| `RATE_LIMIT_WINDOW_MS` | Rate limit window (ms) | `900000` (15 min) | โŒ No | -| `RATE_LIMIT_MAX_REQUESTS` | Max requests per window | `100` | โŒ No | - -*Falls back to keyword-based crisis detection if not provided. -**Defaults to `3001` if not set. Configure this on your server as needed. - -### Frontend Variables (`frontend/.env`) - -| Variable | Description | Example | Required? | -|----------|-------------|---------|-----------| -| `VITE_API_BASE_URL` | Backend API URL | `http://localhost:3001` | โœ… Yes | -| `VITE_WS_URL` | WebSocket server URL | `ws://localhost:3001` | โœ… Yes | -| `VITE_SUPABASE_URL` | Supabase project URL | `https://abc123.supabase.co` | โœ… Yes | -| `VITE_SUPABASE_ANON_KEY` | Supabase anon key | `eyJhbG...` | โœ… Yes | - -**Production values:** -- `VITE_API_BASE_URL`: `https://your-domain.com` (or your server IP) -- `VITE_WS_URL`: `wss://your-domain.com` (or your server IP) - ---- - -## ๐Ÿš€ Local Development Setup - -### Prerequisites - -- **Node.js** 18+ (check with `node -v`) -- **npm** 9+ (check with `npm -v`) -- **Git** (check with `git --version`) -- **Supabase account** (free tier) -- **HuggingFace account** (optional, free tier) - -### Step-by-Step Setup - -#### 1. Clone the Repository - -```bash -git clone https://github.com/yourusername/openmindwell.git -cd openmindwell -``` - -#### 2. Install Root Dependencies - -```bash -npm install -``` - -This installs `concurrently` for running multiple servers. - -#### 3. Install Backend Dependencies - -```bash -cd backend -npm install -cd .. -``` - -#### 4. Install Frontend Dependencies - -```bash -cd frontend -npm install -cd .. -``` - -#### 5. Set Up Supabase - -1. Go to [supabase.com](https://supabase.com) and create a free account -2. Click **"New Project"** -3. Choose organization, name your project (e.g., `openmindwell`) -4. Set a strong database password (save it!) -5. Select a region (closest to you) -6. Wait ~2 minutes for project to provision - -#### 6. Apply Database Schema - -1. In Supabase dashboard, click **"SQL Editor"** (left sidebar) -2. Open `backend/database/schema.sql` in your code editor -3. **Copy the entire file** (it's ~400 lines) -4. Paste into Supabase SQL Editor -5. Click **"Run"** (or press `Ctrl+Enter`) -6. You should see success message and 8 tables created -7. Click **"Table Editor"** to verify tables exist - -#### 7. Get Supabase Credentials - -1. In Supabase dashboard, click **"Project Settings"** (gear icon) -2. Click **"API"** in left sidebar -3. Copy these values: - - **Project URL** (under "Config") - - **anon public** key (under "Project API keys") - - **service_role** key (under "Project API keys" - click "Reveal") - -#### 8. Configure Backend Environment - -```bash -cd backend -cp .env.example .env -``` - -Edit `backend/.env`: -```env -SUPABASE_URL=https://your-project-id.supabase.co -SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -HUGGINGFACE_API_TOKEN=hf_YourTokenHere # Optional for now -FRONTEND_URL=http://localhost:3000 -PORT=3001 -``` - -#### 9. Configure Frontend Environment - -```bash -cd ../frontend -cp .env.example .env -``` - -Edit `frontend/.env`: -```env -VITE_API_BASE_URL=http://localhost:3001 -VITE_WS_URL=ws://localhost:3001 -VITE_SUPABASE_URL=https://your-project-id.supabase.co -VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -``` - -#### 10. Run the Application - -From the **root directory**: - -```bash -npm run dev -``` - -This starts: -- Backend on http://localhost:3001 -- Frontend on http://localhost:3000 - -#### 11. Test the Application - -1. Open http://localhost:3000 in your browser -2. You should see the landing page with crisis disclaimers -3. Click **"Get Started"** -4. Enter a nickname (e.g., `TestUser123`) -5. Select an avatar emoji -6. Click **"Continue"** -7. You should see the dashboard with 4 tabs -8. Click **"Support Rooms"** tab โ†’ see 6 pre-created rooms -9. **โœ… Click "Join Room โ†’"** on any room -10. **โœ… Chat modal opens** with real-time WebSocket connection -11. **โœ… Type a message** and press Send โ†’ see it appear instantly -12. **โœ… Test crisis detection**: Type "I feel hopeless" โ†’ see message highlighted -13. **โœ… Test crisis alert**: Type "I want to hurt myself" โ†’ red banner appears with helplines -14. Open a second browser window (incognito) and join the same room with a different nickname -15. **โœ… Send messages between windows** โ†’ see real-time sync -16. Close one window โ†’ see "User left the room" notification - -#### 12. Verify Database - -In Supabase Table Editor, check: -- **profiles** table has your new user -- **rooms** table has 6 rooms -- **resources** table has 8 resources - ---- - -## ๐Ÿ†“ Free Service Accounts Setup - -### Supabase (Database + Auth) - -**Free Tier Limits:** -- 500 MB database storage -- 2 GB bandwidth/month -- 50,000 monthly active users -- Unlimited API requests - -**Setup:** -1. Go to [supabase.com](https://supabase.com) -2. Click "Start your project" -3. Sign up with GitHub (recommended) -4. Create new organization (free) -5. Create new project -6. Save database password -7. Wait for provisioning (~2 min) -8. Apply schema from `backend/database/schema.sql` - -**Get Credentials:** -- Project Settings โ†’ API -- Copy URL and both API keys - -### HuggingFace (AI Crisis Detection) - -**Free Tier Limits:** -- 1,000 API calls/day -- Rate limit: 30 requests/min -- Public models only - -**Setup:** -1. Go to [huggingface.co](https://huggingface.co) -2. Click "Sign Up" (use Google/GitHub) -3. Verify email -4. Click profile icon โ†’ Settings -5. Click "Access Tokens" (left sidebar) -6. Click "New token" -7. Name: `openmindwell-crisis-detection` -8. Role: **Read** -9. Click "Generate" -10. Copy token (starts with `hf_...`) -11. Save in `backend/.env` as `HUGGINGFACE_API_TOKEN` - -**Optional:** If you skip this, the backend will use keyword-based detection (less accurate but functional). - ---- - -## ๐ŸŒ Self-Hosting Guide - -### Why Self-Host? - -- **100% Privacy**: Your data stays on your server -- **No Vendor Lock-in**: Full control over your infrastructure -- **Zero Recurring Costs**: Run on home server or cheap VPS (~$5/month) -- **True Open Source**: Own your mental health platform - -### Hosting Options - -| Option | Cost | Difficulty | Best For | -|--------|------|------------|----------| -| **Home Server / Raspberry Pi** | $0 | Medium | Tech enthusiasts, full control | -| **DigitalOcean Droplet** | $6/mo | Easy | Reliable, simple setup | -| **Linode / Vultr VPS** | $5/mo | Easy | Budget-friendly | -| **AWS EC2 Free Tier** | $0 (1 year) | Hard | Existing AWS users | -| **Oracle Cloud Free Tier** | $0 (forever) | Medium | Free ARM instance | - -### Prerequisites - -- Linux server (Ubuntu 22.04 recommended) -- Docker & Docker Compose installed -- Domain name (optional, can use IP) -- SSL certificate (Let's Encrypt free) - ---- - -## ๐Ÿ“ฆ Docker Deployment (Recommended) - -### Step 1: Prepare Your Server - -```bash -# Update system -sudo apt update && sudo apt upgrade -y - -# Install Docker -curl -fsSL https://get.docker.com -o get-docker.sh -sudo sh get-docker.sh - -# Install Docker Compose -sudo apt install docker-compose -y - -# Add your user to docker group -sudo usermod -aG docker $USER -newgrp docker -``` - -### Step 2: Clone Repository - -```bash -git clone https://github.com/ZenYukti/OpenMindWell.git -cd OpenMindWell -``` - -### Step 3: Configure Environment Variables - -```bash -# Backend environment -cp backend/.env.example backend/.env -nano backend/.env -# Fill in your Supabase credentials - -# Frontend environment -cp frontend/.env.example frontend/.env -nano frontend/.env -# Update API URLs to your server domain/IP -``` - -### Step 4: Create Docker Compose File - -Create `docker-compose.yml` in project root: - -```yaml -version: '3.8' - -services: - backend: - build: - context: ./backend - dockerfile: Dockerfile - ports: - - "3001:3001" - env_file: - - ./backend/.env - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3001/health"] - interval: 30s - timeout: 10s - retries: 3 - - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - ports: - - "80:80" - env_file: - - ./frontend/.env - depends_on: - - backend - restart: unless-stopped - -volumes: - backend_data: -``` - -### Step 5: Deploy - -```bash -# Build and start containers -docker-compose up -d - -# Check logs -docker-compose logs -f - -# Verify running -docker-compose ps -``` - -Your app is now live at `http://your-server-ip`! - ---- - -## ๐Ÿ” SSL Setup (Production) - -### Using Nginx + Let's Encrypt - -```bash -# Install Nginx -sudo apt install nginx certbot python3-certbot-nginx -y - -# Create Nginx config -sudo nano /etc/nginx/sites-available/openmindwell -``` - -Add this configuration: - -```nginx -server { - listen 80; - server_name your-domain.com www.your-domain.com; - - # Frontend - location / { - proxy_pass http://localhost:80; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - - # Backend API - location /api { - proxy_pass http://localhost:3001; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - # WebSocket - location /ws { - proxy_pass http://localhost:3001; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - proxy_set_header Host $host; - } -} -``` - -Enable and get SSL: - -```bash -# Enable site -sudo ln -s /etc/nginx/sites-available/openmindwell /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl restart nginx - -# Get SSL certificate -sudo certbot --nginx -d your-domain.com -d www.your-domain.com -``` - ---- - -## ๐Ÿ  Home Server / Raspberry Pi Deployment - -### Requirements -- Raspberry Pi 4 (4GB+ RAM recommended) -- MicroSD card (32GB+) -- Stable internet connection -- Static local IP or DDNS service - -### Setup - -1. **Install Raspberry Pi OS Lite (64-bit)** - - Use Raspberry Pi Imager - - Enable SSH in settings - -2. **Follow Docker deployment steps above** - -3. **Port Forwarding** - - Router settings: Forward ports 80 (HTTP) and 443 (HTTPS) to Pi's local IP - -4. **Dynamic DNS (if no static IP)** - - Use DuckDNS, No-IP, or Cloudflare - - Update DNS automatically with cron job - -### Power Management - -```bash -# Auto-restart on reboot -sudo systemctl enable docker -docker update --restart unless-stopped $(docker ps -aq) -``` - ---- - -## โœ… Post-Deployment Checklist - -- [ ] Frontend loads without errors -- [ ] Backend health check passes (`/health` or `/api/health`) -- [ ] CORS is configured correctly -- [ ] WebSocket connects successfully -- [ ] Database queries work (check Supabase logs) -- [ ] Crisis detection triggers (test with keyword "suicide") -- [ ] Anonymous sign-in works -- [ ] SSL certificate is valid (if using HTTPS) -- [ ] Environment variables are set correctly -- [ ] Firewall allows ports 80, 443, 3001 - -### Monitoring Your Deployment - -```bash -# Check container status -docker-compose ps - -# View live logs -docker-compose logs -f - -# Restart services -docker-compose restart - -# Update to latest code -git pull -docker-compose down -docker-compose up -d --build -``` - -### Troubleshooting - -**Frontend won't load:** -- Check Docker logs: `docker-compose logs frontend` -- Verify environment variables in `.env` -- Ensure port 80 is not blocked by firewall - -**Backend errors:** -- Check logs: `docker-compose logs backend` -- Verify Supabase credentials are correct -- Test database connection in Supabase dashboard -- Ensure `schema.sql` was applied - -**WebSocket won't connect:** -- Check Nginx configuration for WebSocket upgrade headers -- Verify `wss://` protocol in frontend env vars -- Test direct connection to port 3001 - -**CORS errors:** -- Check `FRONTEND_URL` in backend `.env` -- Ensure it matches your domain exactly (include `https://`) -- Restart backend after env changes - ---- - -## ๐Ÿ”’ Security & Privacy - -### Row Level Security (RLS) - -OpenMindWell uses PostgreSQL Row Level Security to ensure data privacy: - -**Profiles:** -- Users can only read/update their own profile -- Enforced by: `auth.uid() = user_id` - -**Journal Entries:** -- **Completely private** - only visible to entry owner -- No admin access -- Enforced by: `auth.uid() = user_id` - -**Messages:** -- Visible to all users in the same room -- Enforced by: `room_id IN (SELECT id FROM rooms)` - -**Habits & Habit Logs:** -- Users can only see/edit their own habits -- Enforced by: `auth.uid() = user_id` - -**Resources:** -- Public read access for all users -- Only admins can insert/update - -**Reports:** -- Users can create reports -- Only moderators can view all reports - -### Authentication Flow - -1. User clicks "Get Started" -2. Frontend calls `supabase.auth.signInAnonymously()` -3. Supabase creates anonymous session (JWT token) -4. Frontend receives session with `access_token` -5. All API calls include: `Authorization: Bearer ` -6. Backend validates JWT using Supabase public key -7. Backend extracts `user_id` from token claims -8. Database RLS policies enforce access based on `auth.uid()` - -### Anonymous vs Pseudonymous - -- **Anonymous**: No personal data (email, phone, real name) -- **Pseudonymous**: Users choose a nickname + emoji avatar -- **Session-based**: If user clears browser data, they lose access (by design for privacy) - -### Crisis Detection Privacy - -- Messages are analyzed for crisis keywords/emotions -- No data is sent to third parties except HuggingFace (temporary processing) -- HuggingFace does NOT store messages -- Risk levels are stored in database for moderation only - -### Data Retention - -- **Messages**: Kept indefinitely (for moderation/context) -- **Journal Entries**: Kept until user deletes -- **Accounts**: Anonymous accounts are permanent (no deletion flow yet) -- **Logs**: Backend logs rotate after 7 days (Render/Railway default) - -### Moderation Best Practices - -- All moderators should complete training (TODO: create guide) -- Review flagged messages within 24 hours -- Escalate critical risk messages to platform admins -- Never share user data outside platform -- Ban users only for severe violations (spam, abuse, illegal content) - -### Vulnerabilities to Monitor - -- **SQL Injection**: Mitigated by parameterized queries via Supabase client -- **XSS**: Mitigated by React's auto-escaping -- **CSRF**: Mitigated by SameSite cookies + JWT -- **Rate Limiting**: Implemented in backend (100 req/15min per IP) -- **DDoS**: Mitigated by Vercel/Render infrastructure - ---- - -## ๐Ÿค Contributing - -### Ways to Contribute - -- ๐Ÿ› **Bug Reports**: Open GitHub issues -- โœจ **Feature Requests**: Use issue templates -- ๐Ÿ“ **Documentation**: Improve this guide -- ๐Ÿ’ป **Code**: Submit pull requests -- ๐ŸŽจ **Design**: UI/UX improvements -- ๐ŸŒ **Localization**: Translate to other languages - -### Development Workflow - -1. **Fork** the repository on GitHub -2. **Clone** your fork: `git clone https://github.com/yourusername/openmindwell.git` -3. **Install dependencies**: `npm install` (root, then backend, then frontend) -4. **Set up environment**: Copy `.env.example` files and configure -5. **Apply DB schema**: Run `schema.sql` in Supabase SQL Editor -6. **Create branch**: `git checkout -b feature/your-feature` -7. **Start dev servers**: `npm run dev` from root directory -8. **Make changes** and test locally: - - Backend changes: Check http://localhost:3001/health - - Frontend changes: Hot reload at http://localhost:3000 - - WebSocket changes: Test in chat rooms with multiple browser tabs - - Database changes: Verify in Supabase Table Editor -9. **Test crisis detection**: Send messages with keywords like "hopeless", "suicide" -10. **Commit**: `git commit -m "feat: add new feature"` -11. **Push**: `git push origin feature/your-feature` -12. **Open Pull Request** on GitHub with description of changes - -### Commit Message Convention - -Use [Conventional Commits](https://www.conventionalcommits.org/): - -- `feat:` - New feature -- `fix:` - Bug fix -- `docs:` - Documentation changes -- `style:` - Code style (formatting, no logic change) -- `refactor:` - Code restructuring -- `test:` - Adding tests -- `chore:` - Maintenance tasks - -Examples: -``` -feat: add breathing exercise timer -fix: resolve WebSocket reconnection bug -docs: update deployment guide for Railway -``` - -### Code of Conduct - -**We are committed to:** -- Respectful and inclusive communication -- Constructive feedback -- Prioritizing user safety and privacy -- Transparency in decision-making - -**Zero tolerance for:** -- Harassment, hate speech, or discrimination -- Sharing private user data -- Malicious code or security exploits -- Spam or off-topic content - -**Reporting:** Email conduct@openmindwell.org (TODO: set up) - -### Getting Help - -- ๐Ÿ’ฌ **GitHub Discussions**: Ask questions, share ideas -- ๐Ÿ“ง **Email**: support@openmindwell.org (TODO) -- ๐Ÿ’ป **Discord**: Coming soon - ---- - -## ๐Ÿ—บ๏ธ Roadmap - -### Phase 1: Foundation โœ… COMPLETE -- [x] Anonymous authentication -- [x] Basic chat rooms (6 pre-created) -- [x] **Real-time chat UI (WebSocket client)** โœ… NEW -- [x] **WebSocket auto-reconnection** โœ… NEW -- [x] **Message history loading** โœ… NEW -- [x] **User join/leave events** โœ… NEW -- [x] AI crisis detection (HuggingFace + keywords) -- [x] **Crisis alerts in chat** โœ… NEW -- [x] Private journaling -- [x] Habit tracking -- [x] Resource library -- [x] Moderation system (backend ready) -- [x] **Self-hosting deployment (Docker)** โœ… NEW -- [x] **Production Nginx config** โœ… NEW - -### Phase 2: Enhanced UX (Next 3 Months) -- [ ] Notification system (new messages, @mentions) -- [ ] User profiles (bio, status, preferred pronouns) -- [ ] Direct messaging (1-on-1 private chats) -- [ ] Emoji reactions on messages -- [ ] Message editing/deletion -- [ ] Dark mode toggle -- [ ] Mobile-responsive chat improvements -- [ ] Voice messages (optional) -- [ ] File sharing (images only, moderated) - -### Phase 3: Community Features (3-6 Months) -- [ ] Guided meditation audio -- [ ] Breathing exercise timer -- [ ] Mood tracking visualizations -- [ ] Habit streak leaderboard (opt-in) -- [ ] Volunteer application flow -- [ ] Peer support badge system -- [ ] Weekly wellness challenges - -### Phase 4: Scale & Localization (6-12 Months) -- [ ] Internationalization (Spanish, French, Hindi, etc.) -- [ ] Mobile apps (React Native) -- [ ] Advanced moderation (auto-ban repeat offenders) -- [ ] Analytics dashboard (aggregate stats) -- [ ] Professional referral network -- [ ] Integration with external crisis lines -- [ ] Offline mode (PWA) - -### Long-Term Vision -- [ ] AI therapy chatbot (ethical, limited scope) -- [ ] Video/audio chat rooms -- [ ] Support groups with facilitators -- [ ] Research partnerships (anonymized data) -- [ ] Fundraising for free tier expansion -- [ ] Certification program for moderators - ---- - -## ๐Ÿ“ž Support & Contact - -### For Users -- **In Crisis?** Call 988 (US) or visit findahelpline.com -- **Technical Issues**: GitHub Issues -- **General Questions**: support@openmindwell.org (TODO) - -### For Contributors -- **GitHub**: [github.com/yourusername/openmindwell](https://github.com/yourusername/openmindwell) -- **Discussions**: GitHub Discussions tab -- **Discord**: Coming soon - -### For Researchers -- **Data Access**: Contact research@openmindwell.org (TODO) -- **Partnerships**: partnerships@openmindwell.org (TODO) - ---- - -## ๐Ÿ“„ License - -MIT License - See [LICENSE](./LICENSE) file. - -**Ethical Use Clause:** -While this software is open-source, we ask that derivative works: -1. Maintain prominent mental health crisis disclaimers -2. Do NOT claim to provide professional medical services -3. Respect user privacy and anonymity -4. Contribute improvements back to the community - ---- - -## ๐Ÿ™ Acknowledgments - -- **Supabase** - For generous free tier and excellent DX -- **HuggingFace** - For democratizing AI/ML access -- **Vercel** - For seamless Next.js hosting -- **Render/Railway** - For free backend hosting -- **Mental health advocates** - For inspiration and guidance -- **Open-source community** - For tools and support - ---- - -## ๐Ÿ“š Additional Resources - -### Mental Health Organizations -- **NAMI** (National Alliance on Mental Illness): nami.org -- **Mental Health America**: mhanational.org -- **Crisis Text Line**: crisistextline.org - -### Development Resources -- **Next.js Docs**: nextjs.org/docs -- **Supabase Docs**: supabase.com/docs -- **TypeScript Handbook**: typescriptlang.org/docs - -### Similar Projects -- **7 Cups**: 7cups.com (peer support chat) -- **TalkLife**: talklife.com (anonymous community) -- **Wysa**: wysa.io (AI chatbot) - ---- - -**Last Updated**: November 23, 2024 -**Version**: 1.0.0 -**Maintainers**: OpenMindWell Core Team - ---- - -*Built with ๐Ÿ’™ by people who care about mental wellness* - -*Remember: It's okay to not be okay. Seeking help is a sign of strength.* diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md deleted file mode 100644 index 897d67a..0000000 --- a/PROJECT_SUMMARY.md +++ /dev/null @@ -1,215 +0,0 @@ -# Project Summary - -**OpenMindWell** - Complete open-source mental health support platform - -## โœ… What Has Been Created - -### 1. **Backend** (Node.js + Express + TypeScript + WebSocket) -- โœ… Complete REST API for journal, habits, resources, rooms, moderation -- โœ… **WebSocket chat server with real-time messaging** (FULLY IMPLEMENTED) -- โœ… **Room-based chat architecture** with user join/leave events -- โœ… **Auto-reconnection with heartbeat/ping** (30s interval) -- โœ… **Message history** (last 50 messages loaded on room join) -- โœ… AI-powered crisis detection (HuggingFace API + keyword fallback) -- โœ… **Crisis alerts broadcast in real-time** with helpline numbers -- โœ… Supabase integration (PostgreSQL + Auth) -- โœ… Rate limiting and security middleware -- โœ… Deployment configs (Dockerfile for self-hosting) -- โœ… Database schema with Row Level Security - -### 2. **Frontend** (React 18 + Vite + TypeScript + Tailwind CSS) -- โœ… Landing page with crisis disclaimers -- โœ… Anonymous onboarding flow -- โœ… Dashboard with tabbed navigation -- โœ… **Real-time chat UI (ChatRoom component)** - FULLY FUNCTIONAL -- โœ… **useWebSocket custom hook** with auto-reconnect -- โœ… **Crisis alert banners** with US & India helplines -- โœ… **Message history display** with auto-scroll -- โœ… **Connection status indicators** -- โœ… **Visual crisis highlighting** (red background for high-risk messages) -- โœ… Support rooms interface with "Join Room" functionality -- โœ… Journal, habits, resources tabs -- โœ… Responsive design with Tailwind CSS -- โœ… Supabase Auth integration -- โœ… React Router for navigation - -### 3. **Database** (Supabase PostgreSQL) -- โœ… Complete schema with 8 tables: - - profiles, rooms, messages, journal_entries - - habits, habit_logs, resources, reports, volunteers -- โœ… Row Level Security policies -- โœ… Seed data (6 rooms, 8 resources) -- โœ… Automatic timestamps and triggers - -### 4. **Documentation** -- โœ… **OPENMINDWELL_PROJECT_GUIDE.md** - Comprehensive 800+ line guide - - Project overview and safety disclaimers - - Complete tech stack documentation - - Architecture diagrams - - Environment variable reference - - Step-by-step local setup - - Free service account creation guides - - Self-hosting deployment instructions (Docker, VPS) - - Security and privacy guidelines - - Contribution guide with code of conduct - - Future roadmap -- โœ… README.md - Quick project overview -- โœ… CONTRIBUTING.md - Contributor guidelines -- โœ… LICENSE - MIT License - -### 5. **Deployment Ready** -- โœ… All environment variable configs -- โœ… **Docker Compose setup** (frontend + backend) -- โœ… **Frontend Dockerfile** (multi-stage build with Nginx) -- โœ… **Backend Dockerfile** with health checks -- โœ… **Nginx configuration** for production -- โœ… WebSocket proxy support (ws:// and wss://) -- โœ… Self-hosting configuration (VPS, home server, Raspberry Pi) -- โœ… Health check endpoint -- โœ… CORS properly configured - -## ๐Ÿš€ Quick Start - -```bash -# Install dependencies -npm install - -# Set up environment variables -cp backend/.env.example backend/.env -cp frontend/.env.example frontend/.env -# (Edit .env files with your Supabase credentials) - -# Run both servers -npm run dev -``` - -Visit: http://localhost:3000 - -## ๐Ÿ“‹ Next Steps - -1. **Set up free accounts** (see guide): - - Supabase (database + auth) - - HuggingFace (AI detection) - -2. **Apply database schema**: - - Copy `backend/database/schema.sql` - - Paste into Supabase SQL Editor - - Run - -3. **Test locally**: - - Create anonymous account - - **Join a chat room** โ†’ Real-time WebSocket chat - - **Send messages** โ†’ See instant delivery - - **Test crisis detection** โ†’ Type "I feel hopeless" - - **Multi-tab test** โ†’ Open 2 browsers, chat between them - - Create journal entry - - Log a habit - -4. **Deploy** (optional): - - Self-host on VPS (DigitalOcean, Linode, AWS EC2) - - Or run on home server / Raspberry Pi - -## ๐Ÿ”’ Safety Features - -- โœ… Prominent crisis disclaimers throughout app -- โœ… AI crisis detection on all chat messages -- โœ… Automatic crisis resource warnings -- โœ… Moderator flagging system -- โœ… User reporting functionality -- โœ… Row-level security on all data -- โœ… Anonymous/pseudonymous accounts only - -## ๐ŸŒŸ Key Features - -- **โœ… Anonymous Chat Rooms** - 6 pre-created support topics with REAL-TIME messaging -- **โœ… WebSocket Communication** - Instant message delivery, auto-reconnection, presence tracking -- **โœ… AI Crisis Detection** - HuggingFace emotion analysis + keyword patterns (active in chat) -- **โœ… Crisis Alerts** - Real-time red banners with US (988) & India (9152987821) helplines -- **Private Journaling** - Mood tracking and tags -- **Habit Tracking** - Streaks and completion logs -- **Resource Library** - Hotlines, exercises, articles -- **Volunteer System** - Moderation and support roles (backend ready) - -## ๐Ÿ“Š Self-Hosted Stack - -- **Database**: Supabase (free tier: 500MB DB, 2GB bandwidth/month) or self-hosted PostgreSQL -- **AI Detection**: HuggingFace (free tier: 1000 API calls/day) or keyword-based fallback -- **Hosting**: Your own server (VPS ~$5/month or free on home server) - -**Cost: $0-5/month depending on hosting choice** - -## ๐Ÿ“ File Structure - -``` -openmindwell/ -โ”œโ”€โ”€ backend/ # Node.js + Express + WebSocket -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # Main server + WebSocket init -โ”‚ โ”‚ โ”œโ”€โ”€ routes/ # REST API endpoints -โ”‚ โ”‚ โ”œโ”€โ”€ services/ # โœ… chatServer.ts + crisisDetection.ts -โ”‚ โ”‚ โ””โ”€โ”€ middleware/ # Auth, security -โ”‚ โ”œโ”€โ”€ database/ -โ”‚ โ”‚ โ””โ”€โ”€ schema.sql # Complete DB schema -โ”‚ โ””โ”€โ”€ Dockerfile # Container config -โ”‚ -โ”œโ”€โ”€ frontend/ # React + Vite + WebSocket -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ components/ # โœ… ChatRoom.tsx (NEW) -โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # โœ… useWebSocket.ts (NEW) -โ”‚ โ”‚ โ”œโ”€โ”€ pages/ # Home, Dashboard, Onboarding -โ”‚ โ”‚ โ””โ”€โ”€ lib/ # API clients -โ”‚ โ”œโ”€โ”€ Dockerfile # โœ… Multi-stage build (NEW) -โ”‚ โ””โ”€โ”€ nginx.conf # โœ… Production server (NEW) -โ”‚ -โ”œโ”€โ”€ docker-compose.yml # โœ… Full stack deployment (NEW) -โ”œโ”€โ”€ OPENMINDWELL_PROJECT_GUIDE.md # ๐Ÿ“– Complete guide (UPDATED) -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ CONTRIBUTING.md -โ”œโ”€โ”€ PROJECT_SUMMARY.md # This file -โ””โ”€โ”€ package.json -``` - -## ๐ŸŽฏ Ready for - -- โœ… Local development -- โœ… Production deployment -- โœ… Open source collaboration -- โœ… GSoC/Hacktoberfest/etc. -- โœ… Portfolio demonstration - -## โš ๏ธ Important Notes - -1. **NOT medical software** - Peer support only -2. **Apply DB schema** before running backend -3. **Set all env variables** in `.env` files -4. **Review security settings** before production deploy -5. **Test crisis detection** to understand limitations - -## ๐Ÿ“š Read This First - -**โ†’ [OPENMINDWELL_PROJECT_GUIDE.md](./OPENMINDWELL_PROJECT_GUIDE.md)** - -This 800+ line guide contains EVERYTHING you need to: -- Set up locally -- Create free accounts -- Deploy to production -- Contribute to the project -- Understand security considerations - -## ๐Ÿค Contributing - -See [CONTRIBUTING.md](./CONTRIBUTING.md) - -All contributions welcome - from typo fixes to major features! - -## ๐Ÿ“ž Support - -- GitHub Issues: Bug reports and feature requests -- GitHub Discussions: Questions and ideas -- Email: support@zenyukti.in (TODO: set up) - ---- - -**Built with ๐Ÿ’™ for mental wellness** - -*Remember: This platform supplements but never replaces professional mental health care.* diff --git a/README.md b/README.md deleted file mode 100644 index ce69c0c..0000000 --- a/README.md +++ /dev/null @@ -1,254 +0,0 @@ -# OpenMindWell ๐ŸŒฑ - -![Landing Page Glimpse for OpenMindWell](image.png) - -![License](https://img.shields.io/badge/license-MIT-blue.svg) -![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg) -![Built by](https://img.shields.io/badge/Built%20by-Team%20ZenYukti-purple) -![Self-Hosted](https://img.shields.io/badge/deployment-self--hosted-orange) - -**A compassionate, AI-powered mental health support platform** - -> ๐Ÿ  **Self-Hosted Project**: OpenMindWell is designed to be deployed by each user with their own infrastructure. There is no central hosted instance - users maintain full control of their data and deployment. - -> โš ๏ธ **IMPORTANT DISCLAIMER**: OpenMindWell is NOT a substitute for professional mental health care. If you are in crisis, please contact emergency services or a crisis hotline immediately. - -## ๐ŸŒŸ Features - -- **Anonymous Chat Rooms** - Join peer support groups without revealing identity -- **AI Crisis Detection** - Automatic detection of concerning messages with resource suggestions -- **Private Journaling** - Track mood, thoughts, and personal reflections -- **Habit Tracking** - Build positive daily habits with streak tracking -- **Resource Library** - Curated mental health resources, hotlines, and exercises -- **Volunteer Moderation** - Community-driven safety and support - -## ๐Ÿš€ Quick Start - -### Prerequisites -- Node.js 18+ -- Supabase account (free tier) -- HuggingFace account (free tier) - -### Installation - -1. **Clone the repository** -```bash -git clone https://github.com/YOUR_USERNAME/OpenMindWell.git -cd OpenMindWell -``` - -2. **Install dependencies** -```bash -npm install -cd backend && npm install && cd .. -cd frontend && npm install && cd .. -``` - -3. **Set up Supabase** - - Create a free account at [supabase.com](https://supabase.com) - - Create a new project - - Go to SQL Editor and run `database/schema.sql` - - Enable Anonymous authentication: Authentication โ†’ Providers โ†’ Anonymous (toggle ON) - - Disable CAPTCHA for development: Authentication โ†’ Settings โ†’ Disable CAPTCHA - -4. **Configure environment variables** - -**Backend** (`backend/.env`): -```env -SUPABASE_URL=your_supabase_project_url -SUPABASE_ANON_KEY=your_anon_key -SUPABASE_SERVICE_KEY=your_service_role_key -HUGGINGFACE_API_KEY=your_hf_token -FRONTEND_URL=http://localhost:3000 -PORT=3001 -``` - -**Frontend** (`frontend/.env`): -```env -VITE_SUPABASE_URL=your_supabase_project_url -VITE_SUPABASE_ANON_KEY=your_anon_key -VITE_API_BASE_URL=http://localhost:3001 -VITE_WS_URL=ws://localhost:3001 -``` - -5. **Get API keys** - - **Supabase**: Project Settings โ†’ API (URL, anon key, service_role key) - - **HuggingFace**: [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) โ†’ New Token (Read access) - -6. **Run the application** -```bash -# From root directory -npm run dev -``` - -- Frontend: http://localhost:3000 -- Backend: http://localhost:3001 - -## ๐ŸŒ Deployment - -OpenMindWell is a **self-hosted application**. Each deployment requires: -- Your own Supabase account (free tier available) -- Your own HuggingFace API token (free tier available) -- Hosting platform of your choice - -### Recommended Deployment Options: - -**Option 1: Cloud Hosting (Recommended for production)** -- **Frontend**: [Vercel](https://vercel.com) (free tier) - 1. Import GitHub repository - 2. Add environment variables from `frontend/.env.example` - 3. Deploy automatically from main branch - -- **Backend**: [Render](https://render.com) or [Railway](https://railway.app) (free tier) - 1. Connect GitHub repository - 2. Set build command: `cd backend && npm install && npm run build` - 3. Set start command: `cd backend && npm start` - 4. Add environment variables from `backend/.env.example` - -**Option 2: Self-Hosted (Full control)** -- Deploy on your own VPS (DigitalOcean, AWS, etc.) -- Use Docker containers (Dockerfile included in backend) -- Run with PM2 or systemd for process management - -**Option 3: Local Network** -- Run on local machine for personal use -- Great for testing and development - -**Important Notes:** -- ๐Ÿ”’ Each user maintains their own database and API keys -- ๐ŸŒ No central hosted instance exists -- ๐Ÿ’ฐ All infrastructure costs are borne by the deployer -- ๐Ÿ›ก๏ธ You control data privacy and security - -See [OPENMINDWELL_PROJECT_GUIDE.md](OPENMINDWELL_PROJECT_GUIDE.md) for detailed deployment instructions. - -## ๐Ÿš€ Quick Start - -```bash -# Clone the repository -git clone https://github.com/yourusername/openmindwell.git -cd openmindwell - -# Install dependencies -npm install - -# Set up environment variables -cp backend/.env.example backend/.env -cp frontend/.env.example frontend/.env -# Edit .env files with your credentials - -# Run both servers -npm run dev -``` - -Visit http://localhost:3000 - -## ๐Ÿ“š Documentation - -**READ THIS FIRST:** [OPENMINDWELL_PROJECT_GUIDE.md](./OPENMINDWELL_PROJECT_GUIDE.md) - -This comprehensive guide contains: -- Complete setup instructions -- Free service account creation -- Deployment guides -- Security considerations -- Contribution guidelines - -## ๐Ÿ› ๏ธ Tech Stack - -**100% Free Services:** -- **Frontend**: React 18, Vite, React Router, TypeScript, Tailwind CSS โ†’ Vercel/Netlify -- **Backend**: Node.js, Express, WebSocket, TypeScript โ†’ Render/Railway -- **Database**: Supabase (PostgreSQL + Auth) -- **AI**: HuggingFace Inference API (emotion detection) - -## ๐Ÿ“ Project Structure - -``` -openmindwell/ -โ”œโ”€โ”€ backend/ # Express API + WebSocket server -โ”œโ”€โ”€ frontend/ # React + Vite application -โ”œโ”€โ”€ OPENMINDWELL_PROJECT_GUIDE.md -โ”œโ”€โ”€ CONTRIBUTING.md -โ””โ”€โ”€ package.json # Monorepo scripts -``` - -## ๐Ÿ”’ Safety Features - -- Prominent crisis disclaimers throughout the app -- AI-powered crisis detection on all messages -- Automatic resource suggestions -- User reporting and moderation system -- Anonymous/pseudonymous accounts only -- Row-level security on all data - -## ๐Ÿค Contributing - -We welcome contributions! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. - -Perfect for: -- ๐ŸŽ“ GSoC, Hacktoberfest, WoC programs -- ๐Ÿ’ผ Portfolio projects -- ๐ŸŒ Making a social impact - -## ๐Ÿ“ž Crisis Resources - -**If you're in crisis:** - -**๐Ÿ‡บ๐Ÿ‡ธ United States:** -- **988 Suicide & Crisis Lifeline**: Call/Text 988 -- **Crisis Text Line**: Text HOME to 741741 - -**๐Ÿ‡ฎ๐Ÿ‡ณ India:** -- **iCall Psychosocial Helpline**: 9152987821 (Mon-Sat, 8 AM - 10 PM IST) -- **KIRAN Mental Health Helpline**: 1800-599-0019 (24/7, Toll-free) - -**๐ŸŒ International**: [findahelpline.com](https://findahelpline.com) - -## ๐Ÿ“„ License - -MIT License - See [LICENSE](./LICENSE) for details - -## โš ๏ธ Ethical Use - -This platform is designed to: -- โœ… Provide peer support and community -- โœ… Share coping strategies and resources -- โœ… Reduce stigma around mental health - -This platform is NOT: -- โŒ A replacement for therapy or medical treatment -- โŒ Qualified to diagnose or treat mental health conditions -- โŒ A crisis intervention service - ---- - -## ๐Ÿค Contributing - -We welcome contributions from the community! OpenMindWell is built with the mission to make mental health support accessible to everyone. - -**Ways to Contribute:** -- ๐Ÿ› Report bugs and issues -- ๐Ÿ’ก Suggest new features -- ๐Ÿ”ง Submit pull requests -- ๐Ÿ“– Improve documentation -- ๐ŸŒ Translate to other languages - -See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. - ---- - -## ๐Ÿ’œ Built by Team ZenYukti - -**[ZenYukti](https://zenyukti.in)** - Building innovative solutions for mental wellness and personal growth. - -**Connect with us:** -- ๐ŸŒ Website: [zenyukti.in](https://zenyukti.in) -- ๐Ÿ’ผ LinkedIn: [linkedin.com/company/zenyukti](https://linkedin.com/company/zenyukti) -- ๐Ÿฆ Twitter/X: [@zenyukti](https://x.com/zenyukti) -- ๐Ÿ’ฌ Discord: [Join our community](https://go.zenyukti.in/discord) -- ๐Ÿ“ธ Instagram: [@zenyukti](https://instagram.com/zenyukti) - ---- - -*Remember: Seeking professional help is a sign of strength, not weakness.* ๐Ÿ’™ diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 8fa3f1d..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,21 +0,0 @@ -# Backend Environment Variables -# Copy this to .env and fill in your actual values - -# Supabase Configuration (get from https://app.supabase.com โ†’ Project Settings โ†’ API) -SUPABASE_URL=https://your-project-id.supabase.co -SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - -# HuggingFace API Token (optional - get from https://huggingface.co/settings/tokens) -# If not provided, crisis detection will use keyword-based fallback -HUGGINGFACE_API_TOKEN=hf_YourTokenHere - -# Frontend URL (for CORS whitelist) -FRONTEND_URL=http://localhost:3000 - -# Server Configuration -PORT=3001 - -# Rate Limiting (optional - defaults shown) -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX_REQUESTS=100 diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index bd38cb6..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -# Multi-stage build for smaller production image -FROM node:18-alpine AS builder - -WORKDIR /app - -# Copy package files -COPY package*.json ./ -COPY tsconfig.json ./ - -# Install all dependencies (including dev) -RUN npm ci - -# Copy source code -COPY src ./src - -# Build TypeScript -RUN npm run build - -# Production stage -FROM node:18-alpine - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install only production dependencies -RUN npm ci --only=production - -# Copy built code from builder -COPY --from=builder /app/dist ./dist - -# Expose port (configure as needed for your server) -EXPOSE 3001 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" - -# Start server -CMD ["node", "dist/index.js"] diff --git a/backend/database/schema.sql b/backend/database/schema.sql deleted file mode 100644 index 5265007..0000000 --- a/backend/database/schema.sql +++ /dev/null @@ -1,319 +0,0 @@ --- OpenMindWell Database Schema --- PostgreSQL 15+ with Row Level Security (RLS) --- Apply this in Supabase SQL Editor - --- Enable UUID extension -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- ======================================== --- TABLES --- ======================================== - --- Profiles table (user information) -CREATE TABLE IF NOT EXISTS profiles ( - user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, - nickname VARCHAR(50) NOT NULL, - avatar VARCHAR(10) NOT NULL, - is_moderator BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Rooms table (chat rooms) -CREATE TABLE IF NOT EXISTS rooms ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name VARCHAR(100) NOT NULL, - description TEXT, - category VARCHAR(50) NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Messages table (chat messages with crisis detection) -CREATE TABLE IF NOT EXISTS messages ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - room_id UUID NOT NULL REFERENCES rooms(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - content TEXT NOT NULL, - risk_level VARCHAR(20) DEFAULT 'none' CHECK (risk_level IN ('none', 'low', 'medium', 'high', 'critical')), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Journal entries table (private journaling) -CREATE TABLE IF NOT EXISTS journal_entries ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - title VARCHAR(200) NOT NULL, - content TEXT NOT NULL, - mood INT CHECK (mood >= 1 AND mood <= 5), - tags TEXT[] DEFAULT '{}', - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Habits table (user habits) -CREATE TABLE IF NOT EXISTS habits ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - name VARCHAR(100) NOT NULL, - description TEXT, - frequency VARCHAR(20) DEFAULT 'daily' CHECK (frequency IN ('daily', 'weekly')), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Habit logs table (habit completion tracking) -CREATE TABLE IF NOT EXISTS habit_logs ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - habit_id UUID NOT NULL REFERENCES habits(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - completed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - notes TEXT -); - --- Resources table (mental health resources) -CREATE TABLE IF NOT EXISTS resources ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - title VARCHAR(200) NOT NULL, - description TEXT NOT NULL, - url TEXT, - category VARCHAR(50) NOT NULL CHECK (category IN ('hotline', 'article', 'exercise', 'video')), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Reports table (user reports for moderation) -CREATE TABLE IF NOT EXISTS reports ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE, - reported_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - reason TEXT NOT NULL, - status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'reviewed', 'resolved')), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Volunteers table (moderators/peer supporters) -CREATE TABLE IF NOT EXISTS volunteers ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - role VARCHAR(50) DEFAULT 'moderator', - is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- ======================================== --- ROW LEVEL SECURITY (RLS) POLICIES --- ======================================== - --- Enable RLS on all tables -ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; -ALTER TABLE rooms ENABLE ROW LEVEL SECURITY; -ALTER TABLE messages ENABLE ROW LEVEL SECURITY; -ALTER TABLE journal_entries ENABLE ROW LEVEL SECURITY; -ALTER TABLE habits ENABLE ROW LEVEL SECURITY; -ALTER TABLE habit_logs ENABLE ROW LEVEL SECURITY; -ALTER TABLE resources ENABLE ROW LEVEL SECURITY; -ALTER TABLE reports ENABLE ROW LEVEL SECURITY; -ALTER TABLE volunteers ENABLE ROW LEVEL SECURITY; - --- Profiles policies -CREATE POLICY "Users can view all profiles" - ON profiles FOR SELECT - USING (TRUE); - -CREATE POLICY "Users can insert their own profile" - ON profiles FOR INSERT - WITH CHECK (auth.uid() = user_id); - -CREATE POLICY "Users can update their own profile" - ON profiles FOR UPDATE - USING (auth.uid() = user_id); - --- Rooms policies (public read) -CREATE POLICY "Anyone can view rooms" - ON rooms FOR SELECT - USING (TRUE); - --- Messages policies -CREATE POLICY "Users can view messages in any room" - ON messages FOR SELECT - USING (TRUE); - -CREATE POLICY "Authenticated users can insert messages" - ON messages FOR INSERT - WITH CHECK (auth.uid() = user_id); - --- Journal entries policies (completely private) -CREATE POLICY "Users can only view their own journal entries" - ON journal_entries FOR SELECT - USING (auth.uid() = user_id); - -CREATE POLICY "Users can insert their own journal entries" - ON journal_entries FOR INSERT - WITH CHECK (auth.uid() = user_id); - -CREATE POLICY "Users can update their own journal entries" - ON journal_entries FOR UPDATE - USING (auth.uid() = user_id); - -CREATE POLICY "Users can delete their own journal entries" - ON journal_entries FOR DELETE - USING (auth.uid() = user_id); - --- Habits policies -CREATE POLICY "Users can view their own habits" - ON habits FOR SELECT - USING (auth.uid() = user_id); - -CREATE POLICY "Users can insert their own habits" - ON habits FOR INSERT - WITH CHECK (auth.uid() = user_id); - -CREATE POLICY "Users can update their own habits" - ON habits FOR UPDATE - USING (auth.uid() = user_id); - -CREATE POLICY "Users can delete their own habits" - ON habits FOR DELETE - USING (auth.uid() = user_id); - --- Habit logs policies -CREATE POLICY "Users can view their own habit logs" - ON habit_logs FOR SELECT - USING (auth.uid() = user_id); - -CREATE POLICY "Users can insert their own habit logs" - ON habit_logs FOR INSERT - WITH CHECK (auth.uid() = user_id); - --- Resources policies (public read, admin write) -CREATE POLICY "Anyone can view resources" - ON resources FOR SELECT - USING (TRUE); - --- Reports policies -CREATE POLICY "Users can create reports" - ON reports FOR INSERT - WITH CHECK (auth.uid() = reported_by); - -CREATE POLICY "Moderators can view all reports" - ON reports FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM volunteers - WHERE volunteers.user_id = auth.uid() - AND volunteers.is_active = TRUE - ) - ); - --- Volunteers policies -CREATE POLICY "Users can view active volunteers" - ON volunteers FOR SELECT - USING (is_active = TRUE); - --- ======================================== --- INDEXES FOR PERFORMANCE --- ======================================== - -CREATE INDEX IF NOT EXISTS idx_messages_room_id ON messages(room_id); -CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at DESC); -CREATE INDEX IF NOT EXISTS idx_messages_risk_level ON messages(risk_level) WHERE risk_level IN ('high', 'critical'); -CREATE INDEX IF NOT EXISTS idx_journal_entries_user_id ON journal_entries(user_id); -CREATE INDEX IF NOT EXISTS idx_habits_user_id ON habits(user_id); -CREATE INDEX IF NOT EXISTS idx_habit_logs_habit_id ON habit_logs(habit_id); -CREATE INDEX IF NOT EXISTS idx_habit_logs_user_id ON habit_logs(user_id); -CREATE INDEX IF NOT EXISTS idx_reports_status ON reports(status); - --- ======================================== --- TRIGGERS --- ======================================== - --- Auto-update updated_at for journal entries -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER update_journal_entries_updated_at - BEFORE UPDATE ON journal_entries - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); - --- ======================================== --- SEED DATA --- ======================================== - --- Insert default rooms -INSERT INTO rooms (id, name, description, category) VALUES - ('11111111-1111-1111-1111-111111111111', 'Anxiety Support', 'A safe space to discuss anxiety, panic attacks, and coping strategies', 'anxiety'), - ('22222222-2222-2222-2222-222222222222', 'Depression Support', 'Share experiences and find understanding about depression', 'depression'), - ('33333333-3333-3333-3333-333333333333', 'PTSD & Trauma', 'Support for those dealing with trauma and PTSD', 'trauma'), - ('44444444-4444-4444-4444-444444444444', 'General Wellness', 'Discuss mental health, self-care, and daily challenges', 'general'), - ('55555555-5555-5555-5555-555555555555', 'Student Support', 'Mental health support specifically for students', 'students'), - ('66666666-6666-6666-6666-666666666666', 'Grief & Loss', 'Find support while grieving and processing loss', 'grief') -ON CONFLICT (id) DO NOTHING; - --- Insert default resources -INSERT INTO resources (title, description, url, category) VALUES - ( - '988 Suicide & Crisis Lifeline', - 'Free, confidential 24/7 support for people in distress. Call or text 988.', - 'https://988lifeline.org', - 'hotline' - ), - ( - 'Crisis Text Line', - 'Free 24/7 crisis support via text. Text HOME to 741741.', - 'https://www.crisistextline.org', - 'hotline' - ), - ( - 'International Helplines', - 'Find crisis helplines in your country.', - 'https://findahelpline.com', - 'hotline' - ), - ( - 'NAMI - National Alliance on Mental Illness', - 'Education, support, and advocacy for mental health.', - 'https://www.nami.org', - 'article' - ), - ( - '4-7-8 Breathing Exercise', - 'A simple breathing technique to reduce anxiety and promote calm.', - 'https://www.healthline.com/health/4-7-8-breathing', - 'exercise' - ), - ( - 'Progressive Muscle Relaxation', - 'Learn to release physical tension and mental stress.', - 'https://www.anxietycanada.com/articles/progressive-muscle-relaxation/', - 'exercise' - ), - ( - 'Grounding Techniques for Anxiety', - '5-4-3-2-1 method and other grounding exercises.', - 'https://www.urmc.rochester.edu/behavioral-health-partners/bhp-blog/april-2018/5-4-3-2-1-coping-technique-for-anxiety.aspx', - 'article' - ), - ( - 'Mental Health America Resources', - 'Screening tools, peer support, and educational materials.', - 'https://www.mhanational.org', - 'article' - ) -ON CONFLICT DO NOTHING; - --- ======================================== --- VERIFICATION QUERIES --- ======================================== - --- Count tables (should return 9) --- SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'; - --- Verify RLS is enabled --- SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; - --- Check seed data --- SELECT COUNT(*) FROM rooms; -- Should be 6 --- SELECT COUNT(*) FROM resources; -- Should be 8 diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index 8e7a659..0000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,1905 +0,0 @@ -{ - "name": "openmindwell-backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "openmindwell-backend", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@supabase/supabase-js": "^2.39.0", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-rate-limit": "^7.1.5", - "helmet": "^7.1.0", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/node": "^20.10.6", - "@types/ws": "^8.5.10", - "ts-node": "^10.9.2", - "ts-node-dev": "^2.0.0", - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@supabase/auth-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.84.0.tgz", - "integrity": "sha512-J6XKbqqg1HQPMfYkAT9BrC8anPpAiifl7qoVLsYhQq5B/dnu/lxab1pabnxtJEsvYG5rwI5HEVEGXMjoQ6Wz2Q==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.84.0.tgz", - "integrity": "sha512-2oY5QBV4py/s64zMlhPEz+4RTdlwxzmfhM1k2xftD2v1DruRZKfoe7Yn9DCz1VondxX8evcvpc2udEIGzHI+VA==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.84.0.tgz", - "integrity": "sha512-oplc/3jfJeVW4F0J8wqywHkjIZvOVHtqzF0RESijepDAv5Dn/LThlGW1ftysoP4+PXVIrnghAbzPHo88fNomPQ==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.84.0.tgz", - "integrity": "sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==", - "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.84.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.84.0.tgz", - "integrity": "sha512-vXvAJ1euCuhryOhC6j60dG8ky+lk0V06ubNo+CbhuoUv+sl39PyY0lc+k+qpQhTk/VcI6SiM0OECLN83+nyJ5A==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.84.0.tgz", - "integrity": "sha512-byMqYBvb91sx2jcZsdp0qLpmd4Dioe80e4OU/UexXftCkpTcgrkoENXHf5dO8FCSai8SgNeq16BKg10QiDI6xg==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.84.0", - "@supabase/functions-js": "2.84.0", - "@supabase/postgrest-js": "2.84.0", - "@supabase/realtime-js": "2.84.0", - "@supabase/storage-js": "2.84.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.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/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true, - "license": "MIT" - }, - "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/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "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/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "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", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/helmet": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", - "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node-dev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", - "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.6", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^10.4.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" - }, - "engines": { - "node": ">=0.8.0" - }, - "peerDependencies": { - "node-notifier": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "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-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "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/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index 1c6f262..0000000 --- a/backend/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "openmindwell-backend", - "version": "1.0.0", - "description": "Backend API and WebSocket server for OpenMindWell", - "main": "dist/index.js", - "scripts": { - "dev": "ts-node-dev --respawn --transpile-only src/index.ts", - "build": "tsc", - "start": "node dist/index.js", - "setup-db": "ts-node src/scripts/setupDatabase.ts" - }, - "keywords": ["mental-health", "websocket", "express", "crisis-detection"], - "author": "OpenMindWell Contributors", - "license": "MIT", - "dependencies": { - "@supabase/supabase-js": "^2.39.0", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-rate-limit": "^7.1.5", - "helmet": "^7.1.0", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/node": "^20.10.6", - "@types/ws": "^8.5.10", - "ts-node": "^10.9.2", - "ts-node-dev": "^2.0.0", - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=18.0.0" - } -} diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts deleted file mode 100644 index 3337f48..0000000 --- a/backend/src/config/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import dotenv from 'dotenv'; - -dotenv.config(); - -interface Config { - supabase: { - url: string; - anonKey: string; - serviceRoleKey: string; - }; - huggingface: { - apiToken?: string; - }; - server: { - port: number; - frontendUrl: string; - }; - rateLimit: { - windowMs: number; - maxRequests: number; - }; -} - -const config: Config = { - supabase: { - url: process.env.SUPABASE_URL || '', - anonKey: process.env.SUPABASE_ANON_KEY || '', - serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY || '', - }, - huggingface: { - apiToken: process.env.HUGGINGFACE_API_TOKEN, - }, - server: { - port: parseInt(process.env.PORT || '3001', 10), - frontendUrl: process.env.FRONTEND_URL || 'http://localhost:3000', - }, - rateLimit: { - windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10), - maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10), - }, -}; - -// Validation -const requiredEnvVars = [ - 'SUPABASE_URL', - 'SUPABASE_ANON_KEY', - 'SUPABASE_SERVICE_ROLE_KEY', - 'FRONTEND_URL', -]; - -const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]); - -if (missingVars.length > 0) { - throw new Error( - `Missing required environment variables: ${missingVars.join(', ')}\n` + - 'Please check your .env file and ensure all required variables are set.' - ); -} - -export default config; diff --git a/backend/src/index.ts b/backend/src/index.ts deleted file mode 100644 index 980d714..0000000 --- a/backend/src/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -import express from 'express'; -import http from 'http'; -import cors from 'cors'; -import helmet from 'helmet'; -import rateLimit from 'express-rate-limit'; -import config from './config'; -import { ChatServer } from './services/chatServer'; -import journalRoutes from './routes/journal'; -import habitsRoutes from './routes/habits'; -import resourcesRoutes from './routes/resources'; -import roomsRoutes from './routes/rooms'; -import moderationRoutes from './routes/moderation'; - -const app = express(); -const server = http.createServer(app); - -// Initialize WebSocket server -new ChatServer(server); - -// Middleware -app.use(helmet()); -app.use( - cors({ - origin: config.server.frontendUrl, - credentials: true, - }) -); -app.use(express.json()); - -// Rate limiting -const limiter = rateLimit({ - windowMs: config.rateLimit.windowMs, - max: config.rateLimit.maxRequests, - message: 'Too many requests from this IP, please try again later.', -}); -app.use('/api/', limiter); - -// Health check -app.get('/health', (req, res) => { - res.json({ status: 'ok', timestamp: new Date().toISOString() }); -}); - -// API Routes -app.use('/api/journal', journalRoutes); -app.use('/api/habits', habitsRoutes); -app.use('/api/resources', resourcesRoutes); -app.use('/api/rooms', roomsRoutes); -app.use('/api/moderation', moderationRoutes); - -// 404 handler -app.use((req, res) => { - res.status(404).json({ error: 'Route not found' }); -}); - -// Error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - console.error('Unhandled error:', err); - res.status(500).json({ error: 'Internal server error' }); -}); - -// Start server -const PORT = config.server.port; -server.listen(PORT, () => { - console.log(`๐Ÿš€ Server running on port ${PORT}`); - console.log(`๐Ÿ“ก WebSocket server ready`); - console.log(`๐ŸŒ Frontend URL: ${config.server.frontendUrl}`); - console.log(`๐Ÿ’š Health check: http://localhost:${PORT}/health`); -}); - -// Graceful shutdown -process.on('SIGTERM', () => { - console.log('SIGTERM received, closing server gracefully'); - server.close(() => { - console.log('Server closed'); - process.exit(0); - }); -}); - -process.on('SIGINT', () => { - console.log('SIGINT received, closing server gracefully'); - server.close(() => { - console.log('Server closed'); - process.exit(0); - }); -}); diff --git a/backend/src/lib/supabase.ts b/backend/src/lib/supabase.ts deleted file mode 100644 index de66e86..0000000 --- a/backend/src/lib/supabase.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { createClient } from '@supabase/supabase-js'; -import config from '../config'; - -// Create Supabase client with service role key (for backend) -export const supabase = createClient( - config.supabase.url, - config.supabase.serviceRoleKey, - { - auth: { - autoRefreshToken: false, - persistSession: false, - }, - } -); - -// Create Supabase client with anon key (for auth validation) -export const supabaseAnon = createClient( - config.supabase.url, - config.supabase.anonKey -); - -// Database Types -export interface Profile { - user_id: string; - nickname: string; - avatar: string; - is_moderator: boolean; - created_at: string; -} - -export interface Room { - id: string; - name: string; - description: string; - category: string; - created_at: string; -} - -export interface Message { - id: string; - room_id: string; - user_id: string; - content: string; - risk_level: 'none' | 'low' | 'medium' | 'high' | 'critical'; - created_at: string; - profile?: Profile; -} - -export interface JournalEntry { - id: string; - user_id: string; - title: string; - content: string; - mood: number; - tags: string[]; - created_at: string; - updated_at: string; -} - -export interface Habit { - id: string; - user_id: string; - name: string; - description: string; - frequency: 'daily' | 'weekly'; - created_at: string; -} - -export interface HabitLog { - id: string; - habit_id: string; - user_id: string; - completed_at: string; - notes: string; -} - -export interface Resource { - id: string; - title: string; - description: string; - url: string; - category: 'hotline' | 'article' | 'exercise' | 'video'; - created_at: string; -} - -export interface Report { - id: string; - message_id: string; - reported_by: string; - reason: string; - status: 'pending' | 'reviewed' | 'resolved'; - created_at: string; -} diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts deleted file mode 100644 index 887e19a..0000000 --- a/backend/src/middleware/auth.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { supabaseAnon } from '../lib/supabase'; - -export interface AuthRequest extends Request { - user?: { - id: string; - email?: string; - }; -} - -export const authenticate = async ( - req: AuthRequest, - res: Response, - next: NextFunction -): Promise => { - try { - const authHeader = req.headers.authorization; - - if (!authHeader || !authHeader.startsWith('Bearer ')) { - res.status(401).json({ error: 'Missing or invalid authorization header' }); - return; - } - - const token = authHeader.substring(7); - - const { - data: { user }, - error, - } = await supabaseAnon.auth.getUser(token); - - if (error || !user) { - res.status(401).json({ error: 'Invalid or expired token' }); - return; - } - - req.user = { - id: user.id, - email: user.email, - }; - - next(); - } catch (error) { - console.error('Authentication error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}; diff --git a/backend/src/routes/habits.ts b/backend/src/routes/habits.ts deleted file mode 100644 index f434c34..0000000 --- a/backend/src/routes/habits.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { Router } from 'express'; -import { supabase } from '../lib/supabase'; -import { authenticate, AuthRequest } from '../middleware/auth'; - -const router = Router(); - -// Get all habits for authenticated user -router.get('/', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - - const { data, error } = await supabase - .from('habits') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }); - - if (error) { - console.error('Error fetching habits:', error); - return res.status(500).json({ error: 'Failed to fetch habits' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Create new habit -router.post('/', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { name, description, frequency } = req.body; - - if (!name) { - return res.status(400).json({ error: 'Name is required' }); - } - - const { data, error } = await supabase - .from('habits') - .insert({ - user_id: userId, - name, - description: description || '', - frequency: frequency || 'daily', - }) - .select() - .single(); - - if (error) { - console.error('Error creating habit:', error); - return res.status(500).json({ error: 'Failed to create habit' }); - } - - res.status(201).json(data); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Log habit completion -router.post('/:id/log', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { id } = req.params; - const { notes } = req.body; - - const { data, error } = await supabase - .from('habit_logs') - .insert({ - habit_id: id, - user_id: userId, - notes: notes || '', - completed_at: new Date().toISOString(), - }) - .select() - .single(); - - if (error) { - console.error('Error logging habit:', error); - return res.status(500).json({ error: 'Failed to log habit' }); - } - - res.status(201).json(data); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get habit logs -router.get('/:id/logs', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { id } = req.params; - - const { data, error } = await supabase - .from('habit_logs') - .select('*') - .eq('habit_id', id) - .eq('user_id', userId) - .order('completed_at', { ascending: false }); - - if (error) { - console.error('Error fetching habit logs:', error); - return res.status(500).json({ error: 'Failed to fetch logs' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Update habit -router.put('/:id', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { id } = req.params; - const { name, description, frequency } = req.body; - - const { data, error } = await supabase - .from('habits') - .update({ - name, - description, - frequency, - }) - .eq('id', id) - .eq('user_id', userId) - .select() - .single(); - - if (error) { - console.error('Error updating habit:', error); - return res.status(500).json({ error: 'Failed to update habit' }); - } - - if (!data) { - return res.status(404).json({ error: 'Habit not found' }); - } - - res.json(data); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Delete habit -router.delete('/:id', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { id } = req.params; - - const { error } = await supabase - .from('habits') - .delete() - .eq('id', id) - .eq('user_id', userId); - - if (error) { - console.error('Error deleting habit:', error); - return res.status(500).json({ error: 'Failed to delete habit' }); - } - - res.status(204).send(); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -export default router; diff --git a/backend/src/routes/journal.ts b/backend/src/routes/journal.ts deleted file mode 100644 index 119bd64..0000000 --- a/backend/src/routes/journal.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Router } from 'express'; -import { supabase } from '../lib/supabase'; -import { authenticate, AuthRequest } from '../middleware/auth'; - -const router = Router(); - -// Get all journal entries for authenticated user -router.get('/', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - - const { data, error } = await supabase - .from('journal_entries') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }); - - if (error) { - console.error('Error fetching journal entries:', error); - return res.status(500).json({ error: 'Failed to fetch entries' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Create new journal entry -router.post('/', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { title, content, mood, tags } = req.body; - - if (!title || !content) { - return res.status(400).json({ error: 'Title and content are required' }); - } - - const { data, error } = await supabase - .from('journal_entries') - .insert({ - user_id: userId, - title, - content, - mood: mood || 3, - tags: tags || [], - }) - .select() - .single(); - - if (error) { - console.error('Error creating journal entry:', error); - return res.status(500).json({ error: 'Failed to create entry' }); - } - - res.status(201).json(data); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Update journal entry -router.put('/:id', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { id } = req.params; - const { title, content, mood, tags } = req.body; - - const { data, error } = await supabase - .from('journal_entries') - .update({ - title, - content, - mood, - tags, - updated_at: new Date().toISOString(), - }) - .eq('id', id) - .eq('user_id', userId) - .select() - .single(); - - if (error) { - console.error('Error updating journal entry:', error); - return res.status(500).json({ error: 'Failed to update entry' }); - } - - if (!data) { - return res.status(404).json({ error: 'Entry not found' }); - } - - res.json(data); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Delete journal entry -router.delete('/:id', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { id } = req.params; - - const { error } = await supabase - .from('journal_entries') - .delete() - .eq('id', id) - .eq('user_id', userId); - - if (error) { - console.error('Error deleting journal entry:', error); - return res.status(500).json({ error: 'Failed to delete entry' }); - } - - res.status(204).send(); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -export default router; diff --git a/backend/src/routes/moderation.ts b/backend/src/routes/moderation.ts deleted file mode 100644 index 5aad43e..0000000 --- a/backend/src/routes/moderation.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Router } from 'express'; -import { supabase } from '../lib/supabase'; -import { authenticate, AuthRequest } from '../middleware/auth'; - -const router = Router(); - -// Get flagged messages (moderators only) -router.get('/flagged', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - - // Check if user is a moderator - const { data: volunteer } = await supabase - .from('volunteers') - .select('*') - .eq('user_id', userId) - .eq('is_active', true) - .single(); - - if (!volunteer) { - return res.status(403).json({ error: 'Moderator access required' }); - } - - const { data, error } = await supabase - .from('messages') - .select('*, profile:profiles(nickname, avatar), room:rooms(name)') - .in('risk_level', ['high', 'critical']) - .order('created_at', { ascending: false }) - .limit(100); - - if (error) { - console.error('Error fetching flagged messages:', error); - return res.status(500).json({ error: 'Failed to fetch flagged messages' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Create a report -router.post('/reports', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - const { messageId, reason } = req.body; - - if (!messageId || !reason) { - return res.status(400).json({ error: 'Message ID and reason are required' }); - } - - const { data, error } = await supabase - .from('reports') - .insert({ - message_id: messageId, - reported_by: userId, - reason, - status: 'pending', - }) - .select() - .single(); - - if (error) { - console.error('Error creating report:', error); - return res.status(500).json({ error: 'Failed to create report' }); - } - - res.status(201).json(data); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get all reports (moderators only) -router.get('/reports', authenticate, async (req: AuthRequest, res) => { - try { - const userId = req.user!.id; - - // Check if user is a moderator - const { data: volunteer } = await supabase - .from('volunteers') - .select('*') - .eq('user_id', userId) - .eq('is_active', true) - .single(); - - if (!volunteer) { - return res.status(403).json({ error: 'Moderator access required' }); - } - - const { data, error } = await supabase - .from('reports') - .select('*, message:messages(content, risk_level), reporter:profiles!reported_by(nickname)') - .order('created_at', { ascending: false }); - - if (error) { - console.error('Error fetching reports:', error); - return res.status(500).json({ error: 'Failed to fetch reports' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -export default router; diff --git a/backend/src/routes/resources.ts b/backend/src/routes/resources.ts deleted file mode 100644 index 7c840c5..0000000 --- a/backend/src/routes/resources.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Router } from 'express'; -import { supabase } from '../lib/supabase'; - -const router = Router(); - -// Get all resources (public, no auth required) -router.get('/', async (req, res) => { - try { - const { category } = req.query; - - let query = supabase.from('resources').select('*').order('created_at', { ascending: false }); - - if (category) { - query = query.eq('category', category); - } - - const { data, error } = await query; - - if (error) { - console.error('Error fetching resources:', error); - return res.status(500).json({ error: 'Failed to fetch resources' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -export default router; diff --git a/backend/src/routes/rooms.ts b/backend/src/routes/rooms.ts deleted file mode 100644 index 57ee27f..0000000 --- a/backend/src/routes/rooms.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Router } from 'express'; -import { supabase } from '../lib/supabase'; -import { authenticate, AuthRequest } from '../middleware/auth'; - -const router = Router(); - -// Get all rooms -router.get('/', authenticate, async (req: AuthRequest, res) => { - try { - const { data, error } = await supabase - .from('rooms') - .select('*') - .order('created_at', { ascending: true }); - - if (error) { - console.error('Error fetching rooms:', error); - return res.status(500).json({ error: 'Failed to fetch rooms' }); - } - - res.json(data || []); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get messages for a room -router.get('/:roomId/messages', authenticate, async (req: AuthRequest, res) => { - try { - const { roomId } = req.params; - const limit = parseInt(req.query.limit as string) || 50; - - // Fetch messages without joining profiles (will get user data from WebSocket) - const { data, error } = await supabase - .from('messages') - .select('*') - .eq('room_id', roomId) - .order('created_at', { ascending: false }) - .limit(limit); - - if (error) { - console.error('Error fetching messages:', error); - return res.status(500).json({ error: 'Failed to fetch messages' }); - } - - res.json((data || []).reverse()); - } catch (error) { - console.error('Error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -export default router; diff --git a/backend/src/scripts/setupDatabase.ts b/backend/src/scripts/setupDatabase.ts deleted file mode 100644 index 81a79b5..0000000 --- a/backend/src/scripts/setupDatabase.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { supabase } from '../lib/supabase'; - -/** - * Helper script to test database connection and setup - * Run with: npm run setup-db - */ - -async function testConnection() { - console.log('Testing Supabase connection...'); - - try { - const { data, error } = await supabase.from('profiles').select('count').single(); - - if (error) { - console.error('โŒ Database connection failed:', error.message); - console.log('\nMake sure you have:'); - console.log('1. Created a Supabase project'); - console.log('2. Applied the schema.sql file'); - console.log('3. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env'); - process.exit(1); - } - - console.log('โœ… Database connection successful!'); - console.log('\nNext steps:'); - console.log('1. Run: npm run dev'); - console.log('2. Open frontend and create a test user'); - } catch (error) { - console.error('โŒ Unexpected error:', error); - process.exit(1); - } -} - -testConnection(); diff --git a/backend/src/services/chatServer.ts b/backend/src/services/chatServer.ts deleted file mode 100644 index 58127df..0000000 --- a/backend/src/services/chatServer.ts +++ /dev/null @@ -1,270 +0,0 @@ -import WebSocket from 'ws'; -import { supabase } from '../lib/supabase'; -import { detectCrisis, getCrisisResourcesMessage } from './crisisDetection'; - -interface ChatMessage { - type: 'join' | 'leave' | 'chat' | 'crisis_alert'; - roomId?: string; - userId?: string; - nickname?: string; - content?: string; - riskLevel?: string; - timestamp?: string; -} - -interface RoomMember { - ws: WebSocket; - userId: string; - nickname: string; -} - -export class ChatServer { - private wss: WebSocket.Server; - private rooms: Map>; - - constructor(server: any) { - this.wss = new WebSocket.Server({ server }); - this.rooms = new Map(); - - this.wss.on('connection', this.handleConnection.bind(this)); - - // Heartbeat to detect dead connections - setInterval(() => { - this.wss.clients.forEach((ws: any) => { - if (ws.isAlive === false) { - return ws.terminate(); - } - ws.isAlive = false; - ws.ping(); - }); - }, 30000); - } - - private handleConnection(ws: WebSocket) { - console.log('New WebSocket connection'); - - (ws as any).isAlive = true; - ws.on('pong', () => { - (ws as any).isAlive = true; - }); - - ws.on('message', async (data: string) => { - try { - const message: ChatMessage = JSON.parse(data.toString()); - await this.handleMessage(ws, message); - } catch (error) { - console.error('Error handling message:', error); - // Send error but don't close connection - try { - ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' })); - } catch (sendError) { - console.error('Error sending error message:', sendError); - } - } - }); - - ws.on('close', () => { - this.handleDisconnect(ws); - }); - } - - private async handleMessage(ws: WebSocket, message: ChatMessage) { - switch (message.type) { - case 'join': - await this.handleJoin(ws, message); - break; - case 'leave': - this.handleLeave(ws, message); - break; - case 'chat': - await this.handleChatMessage(ws, message); - break; - default: - ws.send(JSON.stringify({ type: 'error', message: 'Unknown message type' })); - } - } - - private async handleJoin(ws: WebSocket, message: ChatMessage) { - const { roomId, userId, nickname } = message; - - if (!roomId || !userId || !nickname) { - ws.send(JSON.stringify({ type: 'error', message: 'Missing required fields' })); - return; - } - - try { - // Add user to room - if (!this.rooms.has(roomId)) { - this.rooms.set(roomId, new Set()); - } - - const room = this.rooms.get(roomId)!; - room.add({ ws, userId, nickname }); - - // Store connection metadata - (ws as any).roomId = roomId; - (ws as any).userId = userId; - (ws as any).nickname = nickname; - - // Fetch recent messages from database (without profile join - nicknames come from messages) - const { data: messages, error } = await supabase - .from('messages') - .select('*') - .eq('room_id', roomId) - .order('created_at', { ascending: false }) - .limit(50); - - if (error) { - console.error('Error fetching messages:', error); - // Send error but continue - don't crash the connection - ws.send(JSON.stringify({ - type: 'error', - message: 'Could not load message history' - })); - } else { - ws.send( - JSON.stringify({ - type: 'history', - messages: messages?.reverse() || [], - }) - ); - } - - // Broadcast join event - this.broadcastToRoom(roomId, { - type: 'join', - userId, - nickname, - timestamp: new Date().toISOString(), - }); - - console.log(`${nickname} joined room ${roomId}`); - } catch (error) { - console.error('Error in handleJoin:', error); - ws.send(JSON.stringify({ - type: 'error', - message: 'Failed to join room' - })); - } - } - - private handleLeave(ws: WebSocket, message: ChatMessage) { - const roomId = (ws as any).roomId; - const userId = (ws as any).userId; - const nickname = (ws as any).nickname; - - if (roomId && userId) { - const room = this.rooms.get(roomId); - if (room) { - // Remove user from room - room.forEach((member) => { - if (member.userId === userId) { - room.delete(member); - } - }); - - // Broadcast leave event - this.broadcastToRoom(roomId, { - type: 'leave', - userId, - nickname, - timestamp: new Date().toISOString(), - }); - - console.log(`${nickname} left room ${roomId}`); - } - } - } - - private async handleChatMessage(ws: WebSocket, message: ChatMessage) { - const { content } = message; - const roomId = (ws as any).roomId; - const userId = (ws as any).userId; - const nickname = (ws as any).nickname; - - if (!content || !roomId || !userId) { - ws.send(JSON.stringify({ type: 'error', message: 'Missing required fields' })); - return; - } - - // Detect crisis in message - const crisisResult = await detectCrisis(content); - - // Save message to database with risk level - const { data: savedMessage, error } = await supabase - .from('messages') - .insert({ - room_id: roomId, - user_id: userId, - content, - risk_level: crisisResult.riskLevel, - }) - .select('*') - .single(); - - if (error) { - console.error('Error saving message:', error); - ws.send(JSON.stringify({ type: 'error', message: 'Failed to save message' })); - return; - } - - // Broadcast message to room with nickname from WebSocket metadata - this.broadcastToRoom(roomId, { - type: 'chat', - userId: savedMessage.user_id, - nickname: nickname, // Use nickname from WebSocket connection - content: savedMessage.content, - timestamp: savedMessage.created_at, - riskLevel: savedMessage.risk_level, - }); - - // Send crisis alert if detected - if (crisisResult.isCrisis && crisisResult.riskLevel !== 'none') { - const resourcesMessage = getCrisisResourcesMessage(crisisResult.riskLevel); - - // Send private crisis resources to the user - ws.send( - JSON.stringify({ - type: 'crisis_alert', - riskLevel: crisisResult.riskLevel, - message: resourcesMessage, - timestamp: new Date().toISOString(), - }) - ); - - console.log( - `Crisis detected (${crisisResult.riskLevel}) in room ${roomId} by ${nickname}` - ); - } - } - - private handleDisconnect(ws: WebSocket) { - const roomId = (ws as any).roomId; - const userId = (ws as any).userId; - - if (roomId && userId) { - const room = this.rooms.get(roomId); - if (room) { - room.forEach((member) => { - if (member.userId === userId) { - room.delete(member); - } - }); - } - } - - console.log('WebSocket disconnected'); - } - - private broadcastToRoom(roomId: string, message: any) { - const room = this.rooms.get(roomId); - if (!room) return; - - const messageStr = JSON.stringify(message); - room.forEach((member) => { - if (member.ws.readyState === WebSocket.OPEN) { - member.ws.send(messageStr); - } - }); - } -} diff --git a/backend/src/services/crisisDetection.ts b/backend/src/services/crisisDetection.ts deleted file mode 100644 index 796bab2..0000000 --- a/backend/src/services/crisisDetection.ts +++ /dev/null @@ -1,258 +0,0 @@ -import config from '../config'; - -interface EmotionScore { - label: string; - score: number; -} - -interface HuggingFaceResponse { - label: string; - score: number; -} - -interface CrisisDetectionResult { - isCrisis: boolean; - riskLevel: 'none' | 'low' | 'medium' | 'high' | 'critical'; - detectedEmotions?: string[]; - triggeredKeywords?: string[]; - confidence: number; -} - -// Crisis keywords categorized by severity -const CRISIS_KEYWORDS = { - critical: [ - 'suicide', - 'kill myself', - 'end my life', - 'want to die', - 'better off dead', - 'no reason to live', - 'goodbye world', - 'final goodbye', - ], - high: [ - 'self harm', - 'cut myself', - 'hurt myself', - 'overdose', - 'jump off', - 'hang myself', - 'planning to', - 'going to hurt', - ], - medium: [ - 'hopeless', - 'worthless', - 'cant go on', - 'no point', - 'give up', - 'ending it', - 'rather be dead', - 'disappear forever', - ], - low: [ - 'depressed', - 'anxious', - 'scared', - 'alone', - 'struggling', - 'hard time', - 'overwhelming', - 'cant cope', - ], -}; - -// High-risk emotions from the AI model -const HIGH_RISK_EMOTIONS = ['sadness', 'fear', 'anger']; -const MEDIUM_RISK_EMOTIONS = ['disgust', 'surprise']; - -/** - * Analyze message using HuggingFace emotion detection model - */ -async function analyzeWithHuggingFace( - message: string -): Promise { - const apiToken = config.huggingface.apiToken; - - if (!apiToken) { - console.warn('HuggingFace API token not configured, using keyword fallback'); - return null; - } - - try { - const response = await fetch( - 'https://api-inference.huggingface.co/models/cardiffnlp/twitter-roberta-base-emotion', - { - method: 'POST', - headers: { - Authorization: `Bearer ${apiToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ inputs: message }), - } - ); - - if (!response.ok) { - console.error('HuggingFace API error:', response.statusText); - return null; - } - - const result: HuggingFaceResponse[][] = await response.json(); - return result[0] || null; - } catch (error) { - console.error('Error calling HuggingFace API:', error); - return null; - } -} - -/** - * Analyze message using keyword matching (fallback) - */ -function analyzeWithKeywords(message: string): CrisisDetectionResult { - const lowerMessage = message.toLowerCase(); - const triggeredKeywords: string[] = []; - let highestRiskLevel: 'none' | 'low' | 'medium' | 'high' | 'critical' = 'none'; - - // Check critical keywords - for (const keyword of CRISIS_KEYWORDS.critical) { - if (lowerMessage.includes(keyword)) { - triggeredKeywords.push(keyword); - highestRiskLevel = 'critical'; - } - } - - // Check high-risk keywords - if (highestRiskLevel !== 'critical') { - for (const keyword of CRISIS_KEYWORDS.high) { - if (lowerMessage.includes(keyword)) { - triggeredKeywords.push(keyword); - if (highestRiskLevel !== 'high') { - highestRiskLevel = 'high'; - } - } - } - } - - // Check medium-risk keywords - if (highestRiskLevel === 'none' || highestRiskLevel === 'low') { - for (const keyword of CRISIS_KEYWORDS.medium) { - if (lowerMessage.includes(keyword)) { - triggeredKeywords.push(keyword); - if (highestRiskLevel !== 'medium' && highestRiskLevel !== 'high') { - highestRiskLevel = 'medium'; - } - } - } - } - - // Check low-risk keywords - if (highestRiskLevel === 'none') { - for (const keyword of CRISIS_KEYWORDS.low) { - if (lowerMessage.includes(keyword)) { - triggeredKeywords.push(keyword); - highestRiskLevel = 'low'; - } - } - } - - return { - isCrisis: highestRiskLevel !== 'none', - riskLevel: highestRiskLevel, - triggeredKeywords, - confidence: triggeredKeywords.length > 0 ? 0.7 : 0.0, - }; -} - -/** - * Main crisis detection function - */ -export async function detectCrisis( - message: string -): Promise { - // First, try HuggingFace AI analysis - const emotions = await analyzeWithHuggingFace(message); - - if (emotions && emotions.length > 0) { - // Analyze emotion scores - const detectedEmotions = emotions.map((e) => e.label); - let riskLevel: 'none' | 'low' | 'medium' | 'high' | 'critical' = 'none'; - let maxScore = 0; - - for (const emotion of emotions) { - if (emotion.score > maxScore) { - maxScore = emotion.score; - } - - if (HIGH_RISK_EMOTIONS.includes(emotion.label) && emotion.score > 0.5) { - riskLevel = emotion.score > 0.7 ? 'high' : 'medium'; - } else if ( - MEDIUM_RISK_EMOTIONS.includes(emotion.label) && - emotion.score > 0.6 - ) { - if (riskLevel === 'none') { - riskLevel = 'low'; - } - } - } - - // Also run keyword analysis and take the higher risk level - const keywordResult = analyzeWithKeywords(message); - const riskLevels = ['none', 'low', 'medium', 'high', 'critical']; - const aiRiskIndex = riskLevels.indexOf(riskLevel); - const keywordRiskIndex = riskLevels.indexOf(keywordResult.riskLevel); - - if (keywordRiskIndex > aiRiskIndex) { - riskLevel = keywordResult.riskLevel; - } - - return { - isCrisis: riskLevel !== 'none', - riskLevel, - detectedEmotions, - triggeredKeywords: keywordResult.triggeredKeywords, - confidence: maxScore, - }; - } - - // Fallback to keyword analysis - return analyzeWithKeywords(message); -} - -/** - * Get crisis resources message based on risk level - */ -export function getCrisisResourcesMessage( - riskLevel: 'low' | 'medium' | 'high' | 'critical' -): string { - const baseMessage = - '๐Ÿ†˜ **Crisis Resources** ๐Ÿ†˜\n\n' + - '**If you are in immediate danger, please call emergency services (911/112/999).**\n\n'; - - const resources = - '**24/7 Crisis Hotlines:**\n' + - 'โ€ข US: Call/Text **988** (Suicide & Crisis Lifeline)\n' + - 'โ€ข US: Text **HOME** to **741741** (Crisis Text Line)\n' + - 'โ€ข International: **findahelpline.com**\n\n' + - '**You are not alone. Help is available.**'; - - if (riskLevel === 'critical' || riskLevel === 'high') { - return ( - baseMessage + - 'โš ๏ธ **This message may indicate a mental health crisis.** โš ๏ธ\n\n' + - 'Please reach out to a crisis professional immediately:\n\n' + - resources - ); - } else if (riskLevel === 'medium') { - return ( - baseMessage + - 'If you\'re struggling, consider reaching out:\n\n' + - resources - ); - } else { - return ( - '๐Ÿ’™ **Support Resources** ๐Ÿ’™\n\n' + - 'If you need support, here are some resources:\n\n' + - resources - ); - } -} diff --git a/backend/tsconfig.json b/backend/tsconfig.json deleted file mode 100644 index 2f6a024..0000000 --- a/backend/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": ["ES2020"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "declaration": true, - "declarationMap": true, - "sourceMap": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f259b7f..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,45 +0,0 @@ -version: '3.8' - -services: - backend: - build: - context: ./backend - dockerfile: Dockerfile - container_name: openmindwell-backend - ports: - - "3001:3001" - env_file: - - ./backend/.env - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3001/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s - networks: - - openmindwell - - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - container_name: openmindwell-frontend - ports: - - "80:80" - depends_on: - backend: - condition: service_healthy - restart: unless-stopped - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"] - interval: 30s - timeout: 3s - retries: 3 - start_period: 5s - networks: - - openmindwell - -networks: - openmindwell: - driver: bridge diff --git a/frontend/.env.example b/frontend/.env.example index eedc8d4..b0c422e 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,16 +1,3 @@ -# Frontend Environment Variables -# Copy this to .env and fill in your values - -# Backend API URL -VITE_API_BASE_URL=http://localhost:3001 - -# WebSocket URL -VITE_WS_URL=ws://localhost:3001 - -# Supabase Configuration (same as backend) -VITE_SUPABASE_URL=https://your-project-id.supabase.co -VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - -# Production values: -# VITE_API_BASE_URL=https://your-domain.com -# VITE_WS_URL=wss://your-domain.com +# Supabase Configuration +VITE_SUPABASE_URL=your_supabase_project_url +VITE_SUPABASE_ANON_KEY=your_supabase_anon_key diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..71da7d5 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +.env.local +*.log diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index fd60e69..0000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# Build stage -FROM node:18-alpine AS build - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies -RUN npm ci - -# Copy source code -COPY . . - -# Build the application -RUN npm run build - -# Production stage -FROM nginx:alpine - -# Copy built files from build stage -COPY --from=build /app/dist /usr/share/nginx/html - -# Copy nginx configuration -COPY nginx.conf /etc/nginx/conf.d/default.conf - -# Expose port (configure as needed for your server) -EXPOSE 80 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..8490c9a --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,175 @@ +# ๐Ÿง  OpenMindWell + +> A free, anonymous, privacy-first mental health peer support platform. + +![React](https://img.shields.io/badge/React-18.2-blue) +![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue) +![Tailwind CSS](https://img.shields.io/badge/Tailwind-3.4-38B2AC) +![Supabase](https://img.shields.io/badge/Supabase-Backend-green) + +## โœจ Features + +### ๐Ÿ” Privacy First +- **Anonymous Authentication** - No email, no password, no tracking +- **Encrypted Data** - Your journal entries are private and secure +- **No Personal Data Collection** - We don't know who you are + +### ๐Ÿ’ฌ Peer Support Chat +- Join topic-based chat rooms (Anxiety, Depression, Stress, etc.) +- Real-time messaging with fellow community members +- Safe, moderated environment +- Create your own support rooms + +### ๐Ÿ“” Private Journal +- Express your thoughts freely +- Daily prompts to guide your writing +- Search and organize entries +- Only you can access your journal + +### ๐ŸŽฏ Habit Tracking +- Create and track healthy habits +- Visual progress indicators +- Daily check-ins with streak tracking +- Customizable habit emojis + +### ๐Ÿ“Š Mood Tracking +- Log your mood with simple emoji scale +- Track energy and anxiety levels +- Add notes for context +- View mood trends over time + +### ๐Ÿ†˜ Crisis Support +- One-click access to crisis helplines +- Automatic crisis keyword detection +- US and India helpline numbers +- IASP international resources + +## ๐Ÿš€ Getting Started + +### Prerequisites +- Node.js 18+ +- npm or yarn +- Supabase account (free tier works!) + +### Installation + +1. **Clone the repository** +```bash +git clone https://github.com/yourusername/openmindwell.git +cd openmindwell/frontend +``` + +2. **Install dependencies** +```bash +npm install +``` + +3. **Setup Supabase** + - Create a new project at [supabase.com](https://supabase.com) + - Go to **SQL Editor** and run the schema from `supabase/schema.sql` + - Enable **Anonymous Authentication** in Authentication > Providers + - Copy your project URL and anon key + +4. **Configure environment** +```bash +cp .env.example .env +``` +Edit `.env`: +```env +VITE_SUPABASE_URL=your_supabase_url +VITE_SUPABASE_ANON_KEY=your_supabase_anon_key +``` + +5. **Start development server** +```bash +npm run dev +``` + +Visit `http://localhost:5173` ๐ŸŽ‰ + +## ๐Ÿ“ Project Structure + +``` +frontend/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ components/ +โ”‚ โ”‚ โ”œโ”€โ”€ layout/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ DashboardLayout.tsx # Main app layout +โ”‚ โ”‚ โ””โ”€โ”€ CrisisModal.tsx # Crisis resources modal +โ”‚ โ”œโ”€โ”€ lib/ +โ”‚ โ”‚ โ”œโ”€โ”€ supabase.ts # Supabase client & helpers +โ”‚ โ”‚ โ”œโ”€โ”€ database.types.ts # TypeScript types +โ”‚ โ”‚ โ””โ”€โ”€ utils.ts # Utility functions +โ”‚ โ”œโ”€โ”€ pages/ +โ”‚ โ”‚ โ”œโ”€โ”€ Landing.tsx # Homepage +โ”‚ โ”‚ โ”œโ”€โ”€ Dashboard.tsx # User dashboard +โ”‚ โ”‚ โ”œโ”€โ”€ ChatRooms.tsx # Chat room list +โ”‚ โ”‚ โ”œโ”€โ”€ ChatRoom.tsx # Individual chat room +โ”‚ โ”‚ โ”œโ”€โ”€ Journal.tsx # Journal entries +โ”‚ โ”‚ โ”œโ”€โ”€ Habits.tsx # Habit tracker +โ”‚ โ”‚ โ”œโ”€โ”€ Mood.tsx # Mood tracker +โ”‚ โ”‚ โ””โ”€โ”€ Resources.tsx # Mental health resources +โ”‚ โ”œโ”€โ”€ store/ +โ”‚ โ”‚ โ”œโ”€โ”€ authStore.ts # Auth state (Zustand) +โ”‚ โ”‚ โ””โ”€โ”€ uiStore.ts # UI state (Zustand) +โ”‚ โ”œโ”€โ”€ App.tsx # Routes & main app +โ”‚ โ”œโ”€โ”€ main.tsx # Entry point +โ”‚ โ””โ”€โ”€ index.css # Global styles +โ”œโ”€โ”€ supabase/ +โ”‚ โ””โ”€โ”€ schema.sql # Database schema +โ””โ”€โ”€ package.json +``` + +## ๐Ÿ› ๏ธ Tech Stack + +| Technology | Purpose | +|------------|---------| +| React 18 | UI Framework | +| TypeScript | Type Safety | +| Vite | Build Tool | +| Tailwind CSS | Styling | +| Supabase | Backend (Auth, DB, Realtime) | +| Zustand | State Management | +| React Router | Routing | +| Lucide React | Icons | + +## ๐Ÿค Contributing + +Contributions are welcome! Please read our contributing guidelines first. + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit changes (`git commit -m 'Add amazing feature'`) +4. Push to branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## ๐Ÿ“œ License + +This project is open source and available under the [MIT License](LICENSE). + +## ๐Ÿ’š Support + +If you find this project helpful, please consider: +- โญ Starring the repository +- ๐Ÿ› Reporting bugs +- ๐Ÿ’ก Suggesting features +- ๐Ÿค Contributing code + +## ๐Ÿ†˜ Crisis Resources + +If you or someone you know is in crisis: + +**United States** +- 988 Suicide & Crisis Lifeline: Call or text **988** +- Crisis Text Line: Text **HOME** to **741741** + +**India** +- KIRAN Helpline: **1800-599-0019** (24/7, free) +- Vandrevala Foundation: **1860-2662-345** + +**International** +- [IASP Crisis Centres](https://www.iasp.info/resources/Crisis_Centres/) + +--- + +Built with ๐Ÿ’š for mental health awareness diff --git a/frontend/index.html b/frontend/index.html index 63f3987..771b86b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,12 +1,12 @@ - + - + - - - OpenMindWell - Anonymous Mental Health Support + + + OpenMindWell - Mental Health Support
diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index 2f2c7eb..0000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,31 +0,0 @@ -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - # Enable gzip compression - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - - # React Router support - all routes should serve index.html - location / { - try_files $uri $uri/ /index.html; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } - - # Don't cache index.html - location = /index.html { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - } -} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0f88a72..6ef490a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,27 +1,36 @@ { - "name": "openmindwell-frontend", + "name": "openmindwell", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "openmindwell-frontend", + "name": "openmindwell", "version": "1.0.0", "dependencies": { "@supabase/supabase-js": "^2.39.0", + "clsx": "^2.0.0", + "date-fns": "^2.30.0", + "lucide-react": "^0.294.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.20.1" + "react-hot-toast": "^2.4.1", + "react-router-dom": "^6.21.0", + "zustand": "^4.4.7" }, "devDependencies": { - "@types/node": "^20.10.6", - "@types/react": "^18.2.46", - "@types/react-dom": "^18.2.18", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.16", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", - "typescript": "^5.3.3", + "typescript": "^5.2.2", "vite": "^5.0.8" } }, @@ -94,6 +103,16 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", @@ -128,6 +147,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -272,6 +301,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -711,6 +749,155 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "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/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1124,9 +1311,9 @@ ] }, "node_modules/@supabase/auth-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.84.0.tgz", - "integrity": "sha512-J6XKbqqg1HQPMfYkAT9BrC8anPpAiifl7qoVLsYhQq5B/dnu/lxab1pabnxtJEsvYG5rwI5HEVEGXMjoQ6Wz2Q==", + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.86.0.tgz", + "integrity": "sha512-3xPqMvBWC6Haqpr6hEWmSUqDq+6SA1BAEdbiaHdAZM9QjZ5uiQJ+6iD9pZOzOa6MVXZh4GmwjhC9ObIG0K1NcA==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -1136,9 +1323,9 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.84.0.tgz", - "integrity": "sha512-2oY5QBV4py/s64zMlhPEz+4RTdlwxzmfhM1k2xftD2v1DruRZKfoe7Yn9DCz1VondxX8evcvpc2udEIGzHI+VA==", + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.86.0.tgz", + "integrity": "sha512-AlOoVfeaq9XGlBFIyXTmb+y+CZzxNO4wWbfgRM6iPpNU5WCXKawtQYSnhivi3UVxS7GA0rWovY4d6cIAxZAojA==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -1148,9 +1335,9 @@ } }, "node_modules/@supabase/postgrest-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.84.0.tgz", - "integrity": "sha512-oplc/3jfJeVW4F0J8wqywHkjIZvOVHtqzF0RESijepDAv5Dn/LThlGW1ftysoP4+PXVIrnghAbzPHo88fNomPQ==", + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.86.0.tgz", + "integrity": "sha512-QVf+wIXILcZJ7IhWhWn+ozdf8B+oO0Ulizh2AAPxD/6nQL+x3r9lJ47a+fpc/jvAOGXMbkeW534Kw6jz7e8iIA==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -1160,9 +1347,9 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.84.0.tgz", - "integrity": "sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==", + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.86.0.tgz", + "integrity": "sha512-dyS8bFoP29R/sj5zLi0AP3JfgG8ar1nuImcz5jxSx7UIW7fbFsXhUCVrSY2Ofo0+Ev6wiATiSdBOzBfWaiFyPA==", "license": "MIT", "dependencies": { "@types/phoenix": "^1.6.6", @@ -1175,11 +1362,12 @@ } }, "node_modules/@supabase/storage-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.84.0.tgz", - "integrity": "sha512-vXvAJ1euCuhryOhC6j60dG8ky+lk0V06ubNo+CbhuoUv+sl39PyY0lc+k+qpQhTk/VcI6SiM0OECLN83+nyJ5A==", + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.86.0.tgz", + "integrity": "sha512-PM47jX/Mfobdtx7NNpoj9EvlrkapAVTQBZgGGslEXD6NS70EcGjhgRPBItwHdxZPM5GwqQ0cGMN06uhjeY2mHQ==", "license": "MIT", "dependencies": { + "iceberg-js": "^0.8.0", "tslib": "2.8.1" }, "engines": { @@ -1187,16 +1375,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.84.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.84.0.tgz", - "integrity": "sha512-byMqYBvb91sx2jcZsdp0qLpmd4Dioe80e4OU/UexXftCkpTcgrkoENXHf5dO8FCSai8SgNeq16BKg10QiDI6xg==", + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.86.0.tgz", + "integrity": "sha512-BaC9sv5+HGNy1ulZwY8/Ev7EjfYYmWD4fOMw9bDBqTawEj6JHAiOHeTwXLRzVaeSay4p17xYLN2NSCoGgXMQnw==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.84.0", - "@supabase/functions-js": "2.84.0", - "@supabase/postgrest-js": "2.84.0", - "@supabase/realtime-js": "2.84.0", - "@supabase/storage-js": "2.84.0" + "@supabase/auth-js": "2.86.0", + "@supabase/functions-js": "2.86.0", + "@supabase/postgrest-js": "2.86.0", + "@supabase/realtime-js": "2.86.0", + "@supabase/storage-js": "2.86.0" }, "engines": { "node": ">=20.0.0" @@ -1254,13 +1442,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "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": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/phoenix": { @@ -1273,14 +1468,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1297,6 +1492,13 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -1306,135 +1508,440 @@ "@types/node": "*" } }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 8" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "postcss": "^8.1.0" + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", - "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, "engines": { - "node": ">=8" + "node": "^16.0.0 || >=18.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=8" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "funding": [ + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" @@ -1463,6 +1970,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1474,9 +1991,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001756", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", - "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -1494,6 +2011,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1532,6 +2066,35 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1542,6 +2105,13 @@ "node": ">= 6" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1549,6 +2119,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1566,9 +2151,24 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1587,6 +2187,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1594,6 +2201,19 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -1601,62 +2221,293 @@ "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "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" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "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/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "ISC" + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "node": ">=4.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1687,6 +2538,20 @@ "node": ">= 6" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -1697,6 +2562,19 @@ "reusify": "^1.0.4" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1710,6 +2588,45 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "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": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -1724,6 +2641,13 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1759,6 +2683,28 @@ "node": ">=6.9.0" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1772,6 +2718,93 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1785,6 +2818,71 @@ "node": ">= 0.4" } }, + "node_modules/iceberg-js": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.0.tgz", + "integrity": "sha512-kmgmea2nguZEvRqW79gDqNXyxA3OS5WIgMVffrHpqXV4F/J4UmNIw2vstixioLTNSkd5rFB8G0s3Lwzogm6OFw==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1847,6 +2945,23 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -1863,6 +2978,19 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1876,6 +3004,27 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1889,6 +3038,30 @@ "node": ">=6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -1909,6 +3082,29 @@ "dev": true, "license": "MIT" }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "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", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1931,6 +3127,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.294.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.294.0.tgz", + "integrity": "sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1952,7 +3157,23 @@ "picomatch": "^2.3.1" }, "engines": { - "node": ">=8.6" + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -1993,6 +3214,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -2040,6 +3268,109 @@ "node": ">= 6" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "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", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "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", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "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", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2047,6 +3378,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2250,6 +3591,26 @@ "dev": true, "license": "MIT" }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2296,6 +3657,23 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -2382,6 +3760,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -2393,6 +3781,23 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", @@ -2469,13 +3874,49 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/source-map-js": { @@ -2488,6 +3929,32 @@ "node": ">=0.10.0" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -2511,6 +3978,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2562,6 +4042,13 @@ "node": ">=14.0.0" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2646,6 +4133,19 @@ "node": ">=8.0" } }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -2659,6 +4159,32 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -2674,9 +4200,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "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": { @@ -2710,6 +4236,25 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2777,6 +4322,39 @@ } } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "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", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -2804,6 +4382,47 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index 13eb284..93ecee6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,29 +1,38 @@ { - "name": "openmindwell-frontend", - "version": "1.0.0", + "name": "openmindwell", "private": true, + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" }, "dependencies": { "@supabase/supabase-js": "^2.39.0", + "lucide-react": "^0.294.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.20.1" + "react-router-dom": "^6.21.0", + "react-hot-toast": "^2.4.1", + "zustand": "^4.4.7", + "date-fns": "^2.30.0", + "clsx": "^2.0.0" }, "devDependencies": { - "@types/node": "^20.10.6", - "@types/react": "^18.2.46", - "@types/react-dom": "^18.2.18", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.16", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", - "typescript": "^5.3.3", + "typescript": "^5.2.2", "vite": "^5.0.8" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 2aa7205..2e7af2b 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -}; +} diff --git a/frontend/public/heart.svg b/frontend/public/heart.svg new file mode 100644 index 0000000..99d9c08 --- /dev/null +++ b/frontend/public/heart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 0000000..39c031a Binary files /dev/null and b/frontend/public/logo.png differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 99f74c5..6887b9a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,17 +1,81 @@ -import { Routes, Route, Navigate } from 'react-router-dom'; -import Home from './pages/Home'; -import Onboarding from './pages/Onboarding'; -import Dashboard from './pages/Dashboard'; +import { Routes, Route } from 'react-router-dom' +import { Toaster } from 'react-hot-toast' +import { useEffect } from 'react' +import { useAuthStore } from './store/authStore' +import { useUIStore } from './store/uiStore' +import { supabase } from './lib/supabase' + +// Pages +import Landing from './pages/Landing' +import Dashboard from './pages/Dashboard' +import ChatRooms from './pages/ChatRooms' +import ChatRoom from './pages/ChatRoom' +import Journal from './pages/Journal' +import Habits from './pages/Habits' +import Mood from './pages/Mood' +import Resources from './pages/Resources' + +// Layout +import DashboardLayout from './components/layout/DashboardLayout' function App() { + const { setUser, setLoading } = useAuthStore() + const { darkMode } = useUIStore() + + useEffect(() => { + // Check for existing session + supabase.auth.getSession().then(({ data: { session } }) => { + setUser(session?.user ?? null) + setLoading(false) + }) + + // Listen for auth changes + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setUser(session?.user ?? null) + }) + + return () => subscription.unsubscribe() + }, [setUser, setLoading]) + + useEffect(() => { + // Apply dark mode class to document + if (darkMode) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + }, [darkMode]) + return ( - - } /> - } /> - } /> - } /> - - ); + <> + + {/* Public */} + } /> + + {/* Dashboard */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + ) } -export default App; +export default App diff --git a/frontend/src/components/ChatRoom.tsx b/frontend/src/components/ChatRoom.tsx deleted file mode 100644 index 07a1652..0000000 --- a/frontend/src/components/ChatRoom.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; -import { useWebSocket } from '../hooks/useWebSocket'; - -interface Message { - id?: string; - userId: string; - nickname: string; - content: string; - timestamp: string; - riskLevel?: string; - type?: string; -} - -interface ChatRoomProps { - room: { - id: string; - name: string; - description: string; - }; - currentUser: { - id: string; - nickname: string; - }; - onClose: () => void; -} - -export default function ChatRoom({ room, currentUser, onClose }: ChatRoomProps) { - const [messages, setMessages] = useState([]); - const [inputValue, setInputValue] = useState(''); - const [showCrisisAlert, setShowCrisisAlert] = useState(false); - const messagesEndRef = useRef(null); - - const handleMessage = useCallback((message: any) => { - if (message.type === 'history') { - // Load message history - setMessages(message.messages || []); - } else if (message.type === 'chat') { - // Add new chat message - setMessages((prev) => [ - ...prev, - { - userId: message.userId!, - nickname: message.nickname!, - content: message.content!, - timestamp: message.timestamp!, - riskLevel: message.riskLevel, - }, - ]); - - // Show crisis alert if detected - if (message.riskLevel === 'high' || message.riskLevel === 'critical') { - setShowCrisisAlert(true); - setTimeout(() => setShowCrisisAlert(false), 10000); - } - } else if (message.type === 'join') { - // User joined notification - setMessages((prev) => [ - ...prev, - { - userId: 'system', - nickname: 'System', - content: `${message.nickname} joined the room`, - timestamp: message.timestamp!, - type: 'system', - }, - ]); - } else if (message.type === 'leave') { - // User left notification - setMessages((prev) => [ - ...prev, - { - userId: 'system', - nickname: 'System', - content: `${message.nickname} left the room`, - timestamp: message.timestamp!, - type: 'system', - }, - ]); - } else if (message.type === 'crisis_alert') { - setShowCrisisAlert(true); - setTimeout(() => setShowCrisisAlert(false), 10000); - } - }, []); - - const { isConnected, connectionError, sendMessage } = useWebSocket({ - roomId: room.id, - userId: currentUser.id, - nickname: currentUser.nickname, - onMessage: handleMessage, - onConnect: () => { - console.log('Connected to chat room:', room.name); - }, - onDisconnect: () => { - console.log('Disconnected from chat room'); - }, - }); - - useEffect(() => { - // Scroll to bottom when new messages arrive - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); - - const handleSendMessage = (e: React.FormEvent) => { - e.preventDefault(); - - if (!inputValue.trim() || !isConnected) { - return; - } - - const success = sendMessage(inputValue.trim()); - if (success) { - setInputValue(''); - } - }; - - return ( -
-
- {/* Header */} -
-
-

{room.name}

-

{room.description}

-
-
-
-
- {isConnected ? 'Connected' : 'Disconnected'} -
- -
-
- - {/* Crisis Alert Banner */} - {showCrisisAlert && ( -
-
-
- - - -
-
-

- โš ๏ธ CRISIS DETECTED - Moderators have been notified -

-

- ๐Ÿ‡บ๐Ÿ‡ธ Call 988 | Text HOME to 741741 | ๐Ÿ‡ฎ๐Ÿ‡ณ Call 9152987821 (iCall) | KIRAN 1800-599-0019 -

-
-
-
- )} - - {/* Connection Error */} - {connectionError && ( -
-

โš ๏ธ {connectionError}

-
- )} - - {/* Messages */} -
- {messages.length === 0 && ( -
-

No messages yet. Start the conversation!

-
- )} - - {messages.map((msg, index) => ( -
- {msg.type === 'system' ? ( -
{msg.content}
- ) : ( -
-
- - {msg.userId === currentUser.id ? 'You' : msg.nickname} - - - {new Date(msg.timestamp).toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - })} - -
-

- {msg.content} -

- {(msg.riskLevel === 'high' || msg.riskLevel === 'critical') && ( -
โš ๏ธ Crisis language detected
- )} -
- )} -
- ))} -
-
- - {/* Input */} -
-
- setInputValue(e.target.value)} - placeholder={isConnected ? 'Type your message...' : 'Connecting...'} - disabled={!isConnected} - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 disabled:bg-gray-100 disabled:cursor-not-allowed" - maxLength={500} - /> - -
-

- ๐Ÿ’ก Be kind and supportive. All messages are monitored for safety. -

-
-
-
- ); -} diff --git a/frontend/src/components/CrisisModal.tsx b/frontend/src/components/CrisisModal.tsx new file mode 100644 index 0000000..d8563c2 --- /dev/null +++ b/frontend/src/components/CrisisModal.tsx @@ -0,0 +1,122 @@ +import { X, Phone, Globe, ExternalLink } from 'lucide-react' + +interface CrisisModalProps { + open: boolean + onClose: () => void +} + +const crisisResources = { + us: [ + { name: '988 Suicide & Crisis Lifeline', phone: '988', desc: 'Call or text 24/7' }, + { name: 'Crisis Text Line', phone: '741741', desc: 'Text HOME to 741741' }, + { name: 'SAMHSA Helpline', phone: '1-800-662-4357', desc: 'Mental health & substance abuse' }, + ], + india: [ + { name: 'KIRAN Helpline', phone: '1800-599-0019', desc: 'Government free helpline 24/7' }, + { name: 'Vandrevala Foundation', phone: '1860-2662-345', desc: '24/7 multilingual support' }, + { name: 'iCall', phone: '9152987821', desc: 'Mon-Sat 8am-10pm' }, + ], +} + +export default function CrisisModal({ open, onClose }: CrisisModalProps) { + if (!open) return null + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
+ {/* Header */} +
+
+
+

Crisis Resources

+

Help is available 24/7

+
+ +
+
+ + {/* Content */} +
+

+ If you are in crisis, please reach out. You matter. +

+ + {/* US */} +
+
+ +

United States

+
+
+ {crisisResources.us.map((r) => ( + + +
+

{r.name}

+

{r.desc}

+
+ {r.phone} +
+ ))} +
+
+ + {/* India */} +
+
+ +

India

+
+
+ {crisisResources.india.map((r) => ( + + +
+

{r.name}

+

{r.desc}

+
+ {r.phone} +
+ ))} +
+
+ + {/* International */} + + + Find crisis centers worldwide (IASP) + +
+ + {/* Footer */} +
+

Reaching out is a sign of strength

+
+
+
+ ) +} diff --git a/frontend/src/components/layout/DashboardLayout.tsx b/frontend/src/components/layout/DashboardLayout.tsx new file mode 100644 index 0000000..ee93857 --- /dev/null +++ b/frontend/src/components/layout/DashboardLayout.tsx @@ -0,0 +1,302 @@ +import { Outlet, NavLink, useNavigate } from 'react-router-dom' +import { useState, useEffect } from 'react' +import { + MessageCircle, BookOpen, Target, TrendingUp, Zap, + Menu, X, Moon, Sun, LogOut, Phone, Home +} from 'lucide-react' +import { useAuthStore } from '../../store/authStore' +import { useUIStore } from '../../store/uiStore' +import { supabase, signInAnonymously, generateUsername, signOut } from '../../lib/supabase' +import { getAvatarUrl } from '../../lib/utils' +import CrisisModal from '@/components/CrisisModal' +import toast from 'react-hot-toast' + +const navItems = [ + { to: '/app', icon: Home, label: 'Dashboard', end: true }, + { to: '/app/chat', icon: MessageCircle, label: 'Chat' }, + { to: '/app/journal', icon: BookOpen, label: 'Journal' }, + { to: '/app/habits', icon: Target, label: 'Habits' }, + { to: '/app/mood', icon: TrendingUp, label: 'Mood' }, + { to: '/app/resources', icon: Zap, label: 'Resources' }, +] + +export default function DashboardLayout() { + const navigate = useNavigate() + const { user, profile, setProfile, logout } = useAuthStore() + const { sidebarOpen, darkMode, setSidebarOpen, toggleDarkMode } = useUIStore() + const [mobileOpen, setMobileOpen] = useState(false) + const [showCrisis, setShowCrisis] = useState(false) + + useEffect(() => { + document.documentElement.classList.toggle('dark', darkMode) + }, [darkMode]) + + useEffect(() => { + const initUser = async () => { + if (!user) { + try { + await signInAnonymously() + toast.success('Welcome! You are anonymous.') + } catch (error) { + toast.error('Failed to connect') + return + } + } + + if (user && !profile) { + const { data: existingProfile } = await supabase + .from('profiles') + .select('*') + .eq('id', user.id) + .single() + + if (existingProfile) { + setProfile(existingProfile) + } else { + const newProfile = { + id: user.id, + username: generateUsername(), + avatar_seed: Math.random().toString(36).substring(7), + is_online: true, + } + + const { data, error } = await supabase + .from('profiles') + .insert(newProfile) + .select() + .single() + + if (!error && data) { + setProfile(data) + } + } + } + } + + initUser() + }, [user, profile, setProfile]) + + const handleSignOut = async () => { + try { + await signOut() + logout() + navigate('/') + toast.success('Signed out') + } catch { + toast.error('Failed to sign out') + } + } + + return ( +
+ {/* Mobile Header */} +
+
+ +
+ OpenMindWell Logo + OpenMindWell +
+ +
+
+ +
+ {/* Sidebar - Desktop */} + + + {/* Mobile Menu */} + {mobileOpen && ( +
+
setMobileOpen(false)} /> +
e.stopPropagation()}> +
+ {/* Mobile Logo */} +
+ OpenMindWell Logo + OpenMindWell +
+ + {profile && ( +
+
+ + +
+
+

{profile.username}

+

Anonymous

+
+
+ )} + + + +
+ + +
+ + +
+
+
+
+
+ )} + + {/* Main Content */} +
+
+ +
+
+
+ + setShowCrisis(false)} /> +
+ ) +} diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx new file mode 100644 index 0000000..9e17667 --- /dev/null +++ b/frontend/src/components/ui/Button.tsx @@ -0,0 +1,95 @@ +import { forwardRef, ButtonHTMLAttributes } from 'react' +import { Loader2 } from 'lucide-react' +import { cn } from '@/lib/utils' + +interface ButtonProps extends ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' + size?: 'sm' | 'md' | 'lg' + isLoading?: boolean + leftIcon?: React.ReactNode + rightIcon?: React.ReactNode +} + +const Button = forwardRef( + ( + { + className, + variant = 'primary', + size = 'md', + isLoading = false, + leftIcon, + rightIcon, + children, + disabled, + ...props + }, + ref + ) => { + const baseStyles = ` + inline-flex items-center justify-center font-medium rounded-xl + transition-all duration-300 transform + focus:outline-none focus:ring-2 focus:ring-offset-2 + disabled:opacity-60 disabled:cursor-not-allowed disabled:transform-none + hover:scale-[1.02] active:scale-[0.98] + ` + + const variants = { + primary: ` + bg-accent-600 text-white + hover:bg-accent-700 + shadow-lg shadow-accent-600/25 hover:shadow-xl hover:shadow-accent-600/30 + focus:ring-accent-500 + `, + secondary: ` + bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white + hover:bg-gray-200 dark:hover:bg-gray-700 + border border-gray-200 dark:border-gray-700 + focus:ring-gray-500 + `, + outline: ` + bg-transparent border-2 border-primary-500 text-primary-600 dark:text-primary-400 + hover:bg-primary-50 dark:hover:bg-primary-900/20 + focus:ring-primary-500 + `, + ghost: ` + bg-transparent text-gray-700 dark:text-gray-300 + hover:bg-gray-100 dark:hover:bg-gray-800 + focus:ring-gray-500 + `, + danger: ` + bg-gradient-to-r from-red-500 to-red-600 text-white + hover:from-red-600 hover:to-red-700 + shadow-lg shadow-red-500/25 hover:shadow-xl hover:shadow-red-500/30 + focus:ring-red-500 + `, + } + + const sizes = { + sm: 'px-3 py-1.5 text-sm gap-1.5', + md: 'px-5 py-2.5 text-base gap-2', + lg: 'px-7 py-3.5 text-lg gap-2.5', + } + + return ( + + ) + } +) + +Button.displayName = 'Button' + +export { Button } +export type { ButtonProps } diff --git a/frontend/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx new file mode 100644 index 0000000..33619fe --- /dev/null +++ b/frontend/src/components/ui/Card.tsx @@ -0,0 +1,135 @@ +import { forwardRef, HTMLAttributes } from 'react' +import { cn } from '@/lib/utils' + +interface CardProps extends HTMLAttributes { + variant?: 'default' | 'glass' | 'gradient' | 'outlined' + hover?: boolean + padding?: 'none' | 'sm' | 'md' | 'lg' +} + +const Card = forwardRef( + ( + { + className, + variant = 'default', + hover = true, + padding = 'md', + children, + ...props + }, + ref + ) => { + const baseStyles = ` + rounded-2xl transition-all duration-300 + ` + + const variants = { + default: ` + bg-white dark:bg-gray-800 + border border-gray-100 dark:border-gray-700 + shadow-sm + `, + glass: ` + glass + `, + gradient: ` + bg-white dark:bg-gray-800 + border border-gray-100 dark:border-gray-700 + shadow-sm + `, + outlined: ` + bg-transparent + border-2 border-gray-200 dark:border-gray-700 + `, + } + + const paddings = { + none: '', + sm: 'p-4', + md: 'p-6', + lg: 'p-8', + } + + const hoverStyles = hover + ? 'hover:shadow-xl hover:scale-[1.01] cursor-default' + : '' + + return ( +
+ {children} +
+ ) + } +) + +Card.displayName = 'Card' + +interface CardHeaderProps extends HTMLAttributes { + icon?: React.ReactNode + iconColor?: 'primary' | 'purple' | 'blue' | 'green' | 'orange' | 'red' + action?: React.ReactNode +} + +const CardHeader = forwardRef( + ({ className, icon, iconColor = 'primary', action, children, ...props }, ref) => { + const iconColors = { + primary: 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400', + purple: 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400', + blue: 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400', + green: 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400', + orange: 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400', + red: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400', + } + + return ( +
+

+ {icon && ( +
+ {icon} +
+ )} + {children} +

+ {action} +
+ ) + } +) + +CardHeader.displayName = 'CardHeader' + +interface CardContentProps extends HTMLAttributes {} + +const CardContent = forwardRef( + ({ className, ...props }, ref) => ( +
+ ) +) + +CardContent.displayName = 'CardContent' + +interface CardFooterProps extends HTMLAttributes {} + +const CardFooter = forwardRef( + ({ className, ...props }, ref) => ( +
+ ) +) + +CardFooter.displayName = 'CardFooter' + +export { Card, CardHeader, CardContent, CardFooter } +export type { CardProps, CardHeaderProps, CardContentProps, CardFooterProps } diff --git a/frontend/src/components/ui/Input.tsx b/frontend/src/components/ui/Input.tsx new file mode 100644 index 0000000..166fad3 --- /dev/null +++ b/frontend/src/components/ui/Input.tsx @@ -0,0 +1,110 @@ +import { forwardRef, InputHTMLAttributes, TextareaHTMLAttributes } from 'react' +import { cn } from '@/lib/utils' + +interface InputProps extends InputHTMLAttributes { + label?: string + error?: string + hint?: string + leftIcon?: React.ReactNode + rightIcon?: React.ReactNode +} + +const Input = forwardRef( + ({ className, label, error, hint, leftIcon, rightIcon, ...props }, ref) => { + return ( +
+ {label && ( + + )} +
+ {leftIcon && ( +
+ {leftIcon} +
+ )} + + {rightIcon && ( +
+ {rightIcon} +
+ )} +
+ {error && ( +

{error}

+ )} + {hint && !error && ( +

{hint}

+ )} +
+ ) + } +) + +Input.displayName = 'Input' + +interface TextareaProps extends TextareaHTMLAttributes { + label?: string + error?: string + hint?: string +} + +const Textarea = forwardRef( + ({ className, label, error, hint, ...props }, ref) => { + return ( +
+ {label && ( + + )} +