From f20fb6c6fc778949bc2db4f675a769e11ccdab4c Mon Sep 17 00:00:00 2001 From: Ganesh Kulakarni Date: Fri, 30 Jan 2026 23:36:43 +0530 Subject: [PATCH 1/2] worked on posts --- COMPLETE-ALL-TABLES.sql | 298 ++++++++++++++++++++++++++++++++++ DATABASE_SETUP_GUIDE.md | 152 +++++++++++++++++ FIXED-database-setup.sql | 252 ++++++++++++++++++++++++++++ GITHUB_AUTH_SETUP.md | 197 ++++++++++++++++++++++ HOW-TO-PUSH-GITHUB.md | 198 ++++++++++++++++++++++ SAFE-database-setup.sql | 266 ++++++++++++++++++++++++++++++ SETUP-COMPLETE.md | 109 +++++++++++++ check-env.js | 67 ++++++++ cleanup-database.sql | 26 +++ complete-database-setup.sql | 222 +++++++++++++++++++++++++ package-lock.json | 12 ++ src/components/CreatePost.tsx | 54 +++--- src/components/PostDetail.tsx | 16 +- src/pages/LoginPage.tsx | 1 + src/utils/communityApi.ts | 8 +- 15 files changed, 1839 insertions(+), 39 deletions(-) create mode 100644 COMPLETE-ALL-TABLES.sql create mode 100644 DATABASE_SETUP_GUIDE.md create mode 100644 FIXED-database-setup.sql create mode 100644 GITHUB_AUTH_SETUP.md create mode 100644 HOW-TO-PUSH-GITHUB.md create mode 100644 SAFE-database-setup.sql create mode 100644 SETUP-COMPLETE.md create mode 100644 check-env.js create mode 100644 cleanup-database.sql create mode 100644 complete-database-setup.sql diff --git a/COMPLETE-ALL-TABLES.sql b/COMPLETE-ALL-TABLES.sql new file mode 100644 index 00000000..0a0bac1a --- /dev/null +++ b/COMPLETE-ALL-TABLES.sql @@ -0,0 +1,298 @@ +-- ============================================================ +-- DEVCONNECT COMPLETE DATABASE SCHEMA +-- ============================================================ +-- This includes ALL tables: posts, communities, events, AND messaging +-- All table names are lowercase to match the app code +-- Run this AFTER running SAFE-database-setup.sql +-- ============================================================ + +-- MESSAGING TABLES +-- ============================================================ + +-- 1. CONVERSATIONS TABLE +CREATE TABLE IF NOT EXISTS conversations ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + name TEXT, + type TEXT NOT NULL CHECK (type IN ('direct', 'group')), + description TEXT, + is_private BOOLEAN DEFAULT true, + created_by UUID REFERENCES auth.users(id), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_conversations_type ON conversations(type); +ALTER TABLE conversations ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view conversations they participate in" ON conversations; +DROP POLICY IF EXISTS "Users can create conversations" ON conversations; +DROP POLICY IF EXISTS "Admins can update conversations" ON conversations; + +CREATE POLICY "Users can view conversations they participate in" ON conversations + FOR SELECT USING ( + id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Users can create conversations" ON conversations + FOR INSERT WITH CHECK (created_by = auth.uid()); + +CREATE POLICY "Admins can update conversations" ON conversations + FOR UPDATE USING ( + id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() AND role = 'admin' + ) + ); + +-- 2. CONVERSATION PARTICIPANTS TABLE +CREATE TABLE IF NOT EXISTS conversation_participants ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id), + role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'member')), + joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(conversation_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_conversation_participants_user_id ON conversation_participants(user_id); +CREATE INDEX IF NOT EXISTS idx_conversation_participants_conversation_id ON conversation_participants(conversation_id); + +ALTER TABLE conversation_participants ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view participants of their conversations" ON conversation_participants; +DROP POLICY IF EXISTS "Admins can manage participants" ON conversation_participants; + +CREATE POLICY "Users can view participants of their conversations" ON conversation_participants + FOR SELECT USING ( + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Admins can manage participants" ON conversation_participants + FOR ALL USING ( + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() AND role = 'admin' + ) + ); + +-- 3. MESSAGES TABLE +CREATE TABLE IF NOT EXISTS messages ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + sender_id UUID NOT NULL REFERENCES auth.users(id), + content TEXT NOT NULL, + message_type TEXT DEFAULT 'text' CHECK (message_type IN ('text', 'file', 'image')), + file_url TEXT, + file_name TEXT, + reply_to_id BIGINT REFERENCES messages(id), + is_edited BOOLEAN DEFAULT false, + is_deleted BOOLEAN DEFAULT false, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id); +CREATE INDEX IF NOT EXISTS idx_messages_sender_id ON messages(sender_id); +CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at DESC); + +ALTER TABLE messages ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view messages in their conversations" ON messages; +DROP POLICY IF EXISTS "Users can send messages to their conversations" ON messages; +DROP POLICY IF EXISTS "Users can edit their own messages" ON messages; + +CREATE POLICY "Users can view messages in their conversations" ON messages + FOR SELECT USING ( + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Users can send messages to their conversations" ON messages + FOR INSERT WITH CHECK ( + sender_id = auth.uid() AND + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Users can edit their own messages" ON messages + FOR UPDATE USING (sender_id = auth.uid()); + +-- 4. MESSAGE REACTIONS TABLE +CREATE TABLE IF NOT EXISTS message_reactions ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id), + emoji TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(message_id, user_id, emoji) +); + +CREATE INDEX IF NOT EXISTS idx_message_reactions_message_id ON message_reactions(message_id); + +ALTER TABLE message_reactions ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view reactions in their conversations" ON message_reactions; +DROP POLICY IF EXISTS "Users can react to messages" ON message_reactions; +DROP POLICY IF EXISTS "Users can remove their own reactions" ON message_reactions; + +CREATE POLICY "Users can view reactions in their conversations" ON message_reactions + FOR SELECT USING ( + message_id IN ( + SELECT id FROM messages WHERE conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ) + ); + +CREATE POLICY "Users can react to messages" ON message_reactions + FOR INSERT WITH CHECK ( + user_id = auth.uid() AND + message_id IN ( + SELECT id FROM messages WHERE conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ) + ); + +CREATE POLICY "Users can remove their own reactions" ON message_reactions + FOR DELETE USING (user_id = auth.uid()); + +-- 5. USER PRESENCE TABLE +CREATE TABLE IF NOT EXISTS user_presence ( + user_id UUID PRIMARY KEY REFERENCES auth.users(id), + status TEXT DEFAULT 'offline' CHECK (status IN ('online', 'away', 'busy', 'offline')), + last_seen TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_user_presence_status ON user_presence(status); + +ALTER TABLE user_presence ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view all user presence" ON user_presence; +DROP POLICY IF EXISTS "Users can update their own presence" ON user_presence; + +CREATE POLICY "Users can view all user presence" ON user_presence + FOR SELECT USING (true); + +CREATE POLICY "Users can update their own presence" ON user_presence + FOR ALL USING (user_id = auth.uid()); + +-- 6. TYPING INDICATORS TABLE +CREATE TABLE IF NOT EXISTS typing_indicators ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(conversation_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_typing_indicators_conversation_id ON typing_indicators(conversation_id); + +ALTER TABLE typing_indicators ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view typing indicators in their conversations" ON typing_indicators; +DROP POLICY IF EXISTS "Users can manage their own typing indicators" ON typing_indicators; + +CREATE POLICY "Users can view typing indicators in their conversations" ON typing_indicators + FOR SELECT USING ( + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Users can manage their own typing indicators" ON typing_indicators + FOR ALL USING (user_id = auth.uid()); + +-- 7. PINNED MESSAGES TABLE +CREATE TABLE IF NOT EXISTS pinned_messages ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE, + pinned_by UUID NOT NULL REFERENCES auth.users(id), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(conversation_id, message_id) +); + +ALTER TABLE pinned_messages ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view pinned messages in their conversations" ON pinned_messages; +DROP POLICY IF EXISTS "Admins can manage pinned messages" ON pinned_messages; + +CREATE POLICY "Users can view pinned messages in their conversations" ON pinned_messages + FOR SELECT USING ( + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Admins can manage pinned messages" ON pinned_messages + FOR ALL USING ( + conversation_id IN ( + SELECT conversation_id FROM conversation_participants + WHERE user_id = auth.uid() AND role = 'admin' + ) + ); + +-- TRIGGERS AND FUNCTIONS +-- ============================================================ + +-- Function to update conversation timestamp +CREATE OR REPLACE FUNCTION update_conversation_timestamp() +RETURNS TRIGGER AS $$ +BEGIN + UPDATE conversations + SET updated_at = NOW() + WHERE id = NEW.conversation_id; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS update_conversation_on_message ON messages; +CREATE TRIGGER update_conversation_on_message + AFTER INSERT ON messages + FOR EACH ROW + EXECUTE FUNCTION update_conversation_timestamp(); + +-- Function to clean up old typing indicators +CREATE OR REPLACE FUNCTION cleanup_typing_indicators() +RETURNS void AS $$ +BEGIN + DELETE FROM typing_indicators + WHERE created_at < NOW() - INTERVAL '10 seconds'; +END; +$$ LANGUAGE plpgsql; + +-- ============================================================ +-- VERIFICATION +-- ============================================================ +SELECT + '✅ Messaging tables setup complete!' as message, + COUNT(*) as table_count +FROM information_schema.tables +WHERE table_schema = 'public' + AND table_name IN ('conversations', 'conversation_participants', 'messages', 'message_reactions', 'user_presence', 'typing_indicators', 'pinned_messages'); + +-- List all messaging tables +SELECT + table_name, + (SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name AND table_schema = 'public') as column_count +FROM information_schema.tables t +WHERE table_schema = 'public' + AND table_name IN ('conversations', 'conversation_participants', 'messages', 'message_reactions', 'user_presence', 'typing_indicators', 'pinned_messages') +ORDER BY table_name; diff --git a/DATABASE_SETUP_GUIDE.md b/DATABASE_SETUP_GUIDE.md new file mode 100644 index 00000000..a726ef5e --- /dev/null +++ b/DATABASE_SETUP_GUIDE.md @@ -0,0 +1,152 @@ +# 🗄️ Database Setup Guide + +## Problem +You're seeing the error: **"Could not find the table 'public.Posts' in the schema cache"** + +This means your Supabase database doesn't have the required tables yet. + +--- + +## ✅ Quick Fix (5 minutes) + +### Step 1: Open Supabase SQL Editor + +1. Go to your Supabase Dashboard: https://supabase.com/dashboard +2. Select your project: **pqdlainkaqyssnsxkpha** +3. Click **SQL Editor** in the left sidebar (icon looks like ``) +4. Click **"+ New query"** button + +### Step 2: Run the Database Setup Script + +1. Open the file: `complete-database-setup.sql` (in your project root) +2. **Copy ALL the contents** of that file +3. **Paste** it into the Supabase SQL Editor +4. Click **"Run"** button (or press Ctrl+Enter) + +### Step 3: Verify Success + +You should see a success message showing: +- ✅ "Database setup complete! All tables created successfully." +- ✅ Table count: 6 +- ✅ List of all created tables + +### Step 4: Set Up Storage Buckets + +1. In Supabase, click **Storage** in the left sidebar +2. Click **"New bucket"** +3. Create these 3 buckets: + + **Bucket 1: post-images** + - Name: `post-images` + - Public bucket: ✅ **YES** (toggle ON) + - Click "Create bucket" + + **Bucket 2: event-images** + - Name: `event-images` + - Public bucket: ✅ **YES** (toggle ON) + - Click "Create bucket" + + **Bucket 3: message-files** + - Name: `message-files` + - Public bucket: ❌ **NO** (keep private) + - Click "Create bucket" + +### Step 5: Set Storage Policies (for public buckets) + +For **post-images** and **event-images** buckets: + +1. Click on the bucket name +2. Click **"Policies"** tab +3. Click **"New policy"** +4. Choose **"For full customization"** +5. Add this policy: + + **Policy name**: `Public Access` + + **Allowed operation**: SELECT + + **Policy definition**: + ```sql + true + ``` + +6. Click **"Review"** then **"Save policy"** + +7. Add another policy for INSERT (so users can upload): + + **Policy name**: `Authenticated users can upload` + + **Allowed operation**: INSERT + + **Policy definition**: + ```sql + auth.role() = 'authenticated' + ``` + +### Step 6: Refresh Your App + +1. Go back to your browser with DevConnect open +2. Press **Ctrl+R** to refresh the page +3. Try clicking "My Posts" again - it should work now! 🎉 + +--- + +## 📋 What Tables Were Created? + +1. **Communities** - For developer communities +2. **Posts** - For user posts (this fixes your error!) +3. **Comments** - For post comments +4. **Votes** - For post likes/upvotes +5. **Events** - For community events +6. **EventAttendees** - For event registrations + +All tables include: +- ✅ Proper indexes for performance +- ✅ Row Level Security (RLS) enabled +- ✅ Security policies configured +- ✅ Foreign key relationships + +--- + +## 🐛 Troubleshooting + +### "relation already exists" error +- This is fine! It means some tables were already created +- The script uses `CREATE TABLE IF NOT EXISTS` so it's safe to run multiple times + +### "permission denied" error +- Make sure you're logged into the correct Supabase project +- Check that you have admin access to the project + +### Still seeing "table not found" error +- Make sure you ran the SQL in the correct project +- Try refreshing the page (Ctrl+R) +- Check the table was created: Go to **Table Editor** in Supabase + +### Images not showing +- Make sure you created the storage buckets (Step 4) +- Make sure `post-images` bucket is **public** +- Make sure storage policies are set (Step 5) + +--- + +## 🎯 After Setup Checklist + +- ✅ All 6 tables created in Supabase +- ✅ 3 storage buckets created (post-images, event-images, message-files) +- ✅ Storage policies configured for public buckets +- ✅ App refreshed and working +- ✅ "My Posts" section loads without errors +- ✅ Images display correctly + +--- + +## 📚 Additional Resources + +- [Supabase Table Editor](https://supabase.com/docs/guides/database/tables) +- [Supabase Storage](https://supabase.com/docs/guides/storage) +- [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) + +--- + +**Need help?** Check the main `GITHUB_AUTH_SETUP.md` file or the DevConnect repository issues. diff --git a/FIXED-database-setup.sql b/FIXED-database-setup.sql new file mode 100644 index 00000000..45af147f --- /dev/null +++ b/FIXED-database-setup.sql @@ -0,0 +1,252 @@ +-- ============================================================ +-- DEVCONNECT DATABASE SCHEMA - FIXED VERSION +-- ============================================================ +-- This version uses lowercase table names to match the app code +-- Run this entire script in your Supabase SQL Editor +-- ============================================================ + +-- 1. COMMUNITIES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS communities ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + name TEXT NOT NULL UNIQUE, + description TEXT, + icon_url TEXT, + banner_url TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Enable RLS for communities +ALTER TABLE communities ENABLE ROW LEVEL SECURITY; + +-- Communities policies +CREATE POLICY "Communities are viewable by everyone" ON communities + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create communities" ON communities + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update communities" ON communities + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- ============================================================ +-- 2. COMMUNITY MEMBERS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS community_members ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + community_id BIGINT NOT NULL REFERENCES communities(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'moderator', 'member')), + joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(community_id, user_id) +); + +-- Create indexes for community_members +CREATE INDEX IF NOT EXISTS idx_community_members_community_id ON community_members(community_id); +CREATE INDEX IF NOT EXISTS idx_community_members_user_id ON community_members(user_id); + +-- Enable RLS for community_members +ALTER TABLE community_members ENABLE ROW LEVEL SECURITY; + +-- Community members policies +CREATE POLICY "Community members are viewable by everyone" ON community_members + FOR SELECT USING (true); + +CREATE POLICY "Users can join communities" ON community_members + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can leave communities" ON community_members + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 3. POSTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS posts ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + title TEXT NOT NULL, + content TEXT NOT NULL, + image_url TEXT, + avatar_url TEXT, + community_id BIGINT REFERENCES communities(id) ON DELETE SET NULL, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for posts +CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id); +CREATE INDEX IF NOT EXISTS idx_posts_community_id ON posts(community_id); +CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at DESC); + +-- Enable RLS for posts +ALTER TABLE posts ENABLE ROW LEVEL SECURITY; + +-- Posts policies +CREATE POLICY "Posts are viewable by everyone" ON posts + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create posts" ON posts + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own posts" ON posts + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own posts" ON posts + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 4. COMMENTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS comments ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, + content TEXT NOT NULL, + author TEXT NOT NULL, + avatar_url TEXT, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + parent_comment_id BIGINT REFERENCES comments(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for comments +CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id); +CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id); +CREATE INDEX IF NOT EXISTS idx_comments_parent_id ON comments(parent_comment_id); + +-- Enable RLS for comments +ALTER TABLE comments ENABLE ROW LEVEL SECURITY; + +-- Comments policies +CREATE POLICY "Comments are viewable by everyone" ON comments + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create comments" ON comments + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own comments" ON comments + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own comments" ON comments + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 5. VOTES TABLE (for post likes) +-- ============================================================ +CREATE TABLE IF NOT EXISTS votes ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + vote INT DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(post_id, user_id) +); + +-- Create indexes for votes +CREATE INDEX IF NOT EXISTS idx_votes_post_id ON votes(post_id); +CREATE INDEX IF NOT EXISTS idx_votes_user_id ON votes(user_id); + +-- Enable RLS for votes +ALTER TABLE votes ENABLE ROW LEVEL SECURITY; + +-- Votes policies +CREATE POLICY "Votes are viewable by everyone" ON votes + FOR SELECT USING (true); + +CREATE POLICY "Users can create votes" ON votes + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can delete own votes" ON votes + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 6. EVENTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS events ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + title TEXT NOT NULL, + description TEXT NOT NULL, + event_date TIMESTAMP WITH TIME ZONE NOT NULL, + location TEXT, + is_virtual BOOLEAN DEFAULT FALSE, + meeting_link TEXT, + max_attendees INTEGER, + image_url TEXT, + tags TEXT[], + organizer_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + community_id BIGINT REFERENCES communities(id) ON DELETE SET NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for events +CREATE INDEX IF NOT EXISTS idx_events_organizer_id ON events(organizer_id); +CREATE INDEX IF NOT EXISTS idx_events_community_id ON events(community_id); +CREATE INDEX IF NOT EXISTS idx_events_event_date ON events(event_date); + +-- Enable RLS for events +ALTER TABLE events ENABLE ROW LEVEL SECURITY; + +-- Events policies +CREATE POLICY "Events are viewable by everyone" ON events + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create events" ON events + FOR INSERT WITH CHECK (auth.uid() = organizer_id); + +CREATE POLICY "Organizers can update own events" ON events + FOR UPDATE USING (auth.uid() = organizer_id); + +CREATE POLICY "Organizers can delete own events" ON events + FOR DELETE USING (auth.uid() = organizer_id); + +-- ============================================================ +-- 7. EVENT ATTENDEES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS event_attendees ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + event_id BIGINT NOT NULL REFERENCES events(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + status TEXT DEFAULT 'attending' CHECK (status IN ('attending', 'maybe', 'not_attending')), + registered_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(event_id, user_id) +); + +-- Create indexes for event_attendees +CREATE INDEX IF NOT EXISTS idx_event_attendees_event_id ON event_attendees(event_id); +CREATE INDEX IF NOT EXISTS idx_event_attendees_user_id ON event_attendees(user_id); + +-- Enable RLS for event_attendees +ALTER TABLE event_attendees ENABLE ROW LEVEL SECURITY; + +-- Event attendees policies +CREATE POLICY "Event attendees are viewable by everyone" ON event_attendees + FOR SELECT USING (true); + +CREATE POLICY "Users can register for events" ON event_attendees + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own registration" ON event_attendees + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own registration" ON event_attendees + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- VERIFICATION +-- ============================================================ +SELECT + '✅ Database setup complete! All tables created successfully.' as message, + COUNT(*) as table_count +FROM information_schema.tables +WHERE table_schema = 'public' + AND table_name IN ('communities', 'community_members', 'posts', 'comments', 'votes', 'events', 'event_attendees'); + +-- List all created tables +SELECT + table_name, + (SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name AND table_schema = 'public') as column_count +FROM information_schema.tables t +WHERE table_schema = 'public' + AND table_name IN ('communities', 'community_members', 'posts', 'comments', 'votes', 'events', 'event_attendees') +ORDER BY table_name; diff --git a/GITHUB_AUTH_SETUP.md b/GITHUB_AUTH_SETUP.md new file mode 100644 index 00000000..592e67cd --- /dev/null +++ b/GITHUB_AUTH_SETUP.md @@ -0,0 +1,197 @@ +# GitHub Authentication Setup Guide + +This guide will walk you through setting up GitHub OAuth authentication for DevConnect. + +## Prerequisites +- A GitHub account +- A Supabase account (free tier works fine) + +--- + +## Step 1: Create a Supabase Project + +1. Go to [https://supabase.com](https://supabase.com) +2. Sign in or create an account +3. Click **"New Project"** +4. Fill in the details: + - **Name**: DevConnect (or any name you prefer) + - **Database Password**: Create a strong password (save this!) + - **Region**: Choose the closest region to you + - **Pricing Plan**: Free tier is fine +5. Click **"Create new project"** and wait for it to initialize (takes ~2 minutes) + +--- + +## Step 2: Get Your Supabase Credentials + +1. Once your project is ready, go to **Settings** (gear icon in sidebar) +2. Click on **API** in the left menu +3. You'll see two important values: + - **Project URL** (looks like: `https://xxxxxxxxxxxxx.supabase.co`) + - **anon/public key** (a long string starting with `eyJ...`) +4. **Keep this page open** - you'll need these values soon! + +--- + +## Step 3: Create a GitHub OAuth Application + +1. Go to [GitHub Developer Settings](https://github.com/settings/developers) +2. Click **"OAuth Apps"** in the left sidebar +3. Click **"New OAuth App"** button +4. Fill in the application details: + + **Application name**: `DevConnect Local` (or any name you prefer) + + **Homepage URL**: `http://localhost:5174` + + **Application description**: `DevConnect - Developer Social Platform` (optional) + + **Authorization callback URL**: This is **CRITICAL** - use your Supabase project URL: + ``` + https://YOUR_PROJECT_ID.supabase.co/auth/v1/callback + ``` + Replace `YOUR_PROJECT_ID` with your actual Supabase project ID (from the Project URL in Step 2) + + Example: If your Supabase URL is `https://abcdefghijk.supabase.co`, then use: + ``` + https://abcdefghijk.supabase.co/auth/v1/callback + ``` + +5. Click **"Register application"** +6. You'll see your **Client ID** - copy this! +7. Click **"Generate a new client secret"** and copy the secret immediately (you won't see it again!) + +--- + +## Step 4: Configure GitHub OAuth in Supabase + +1. Go back to your Supabase project dashboard +2. Click **Authentication** in the left sidebar +3. Click **Providers** tab +4. Find **GitHub** in the list and toggle it **ON** +5. Paste your GitHub OAuth credentials: + - **Client ID**: (from Step 3) + - **Client Secret**: (from Step 3) +6. Click **"Save"** + +--- + +## Step 5: Update Your .env File + +1. Open the `.env` file in your DevConnect project root +2. Replace the placeholder values with your actual Supabase credentials: + +```env +VITE_SUPABASE_URL=https://YOUR_PROJECT_ID.supabase.co +VITE_SUPABASE_ANON_KEY=your_actual_anon_key_here +``` + +**Example:** +```env +VITE_SUPABASE_URL=https://abcdefghijk.supabase.co +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFiY2RlZmdoaWprIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODk1NzIwMDAsImV4cCI6MjAwNTE0ODAwMH0.example_signature_here +``` + +⚠️ **Important**: Do NOT add quotes around the values! + +--- + +## Step 6: Set Up Database Tables (Required!) + +Your Supabase project needs the proper database schema. Run these SQL commands in Supabase: + +1. Go to **SQL Editor** in your Supabase dashboard +2. Click **"New Query"** +3. Copy and paste the SQL from the repository's README or use the provided schema files: + - `create-votes-table.sql` + - `database-schema-messaging.sql` + - `fix-rls-policies.sql` + +**Minimum required tables:** +- Posts +- Comments +- Communities +- Votes +- Events +- EventAttendees +- Users (auto-created by Supabase) + +--- + +## Step 7: Set Up Storage Buckets + +1. In Supabase, go to **Storage** in the left sidebar +2. Create these buckets: + - **post-images** (make it **public**) + - **event-images** (make it **public**) + - **message-files** (keep it **private**) + +To make a bucket public: +- Click the bucket name +- Click **"Policies"** tab +- Add a policy to allow public read access + +--- + +## Step 8: Restart Your Development Server + +1. Stop the current dev server (Ctrl+C in terminal) +2. Restart it: +```bash +npm run dev +``` + +3. Open `http://localhost:5174` in your browser +4. Click **"Continue with GitHub"** +5. You should be redirected to GitHub to authorize the app +6. After authorization, you'll be redirected back to DevConnect, logged in! 🎉 + +--- + +## Troubleshooting + +### "Authentication is disabled in demo mode" +- Make sure your `.env` file has valid Supabase credentials +- Restart the dev server after updating `.env` +- Check that there are no typos in the URL or key + +### "Invalid OAuth callback URL" +- Double-check the callback URL in your GitHub OAuth app settings +- It must match: `https://YOUR_PROJECT_ID.supabase.co/auth/v1/callback` + +### "Could not find the table in schema cache" +- You need to create the database tables (Step 6) +- Run the SQL schema files in Supabase SQL Editor + +### GitHub redirects but doesn't log in +- Check your Supabase Authentication logs (Authentication → Logs) +- Verify the GitHub OAuth credentials are correct in Supabase +- Make sure Row Level Security (RLS) policies are set up correctly + +--- + +## Production Deployment + +When deploying to production (e.g., Netlify, Vercel): + +1. Create a **new GitHub OAuth App** for production +2. Use your production URL as the homepage +3. Use your Supabase callback URL as the authorization callback +4. Update your production environment variables with the production credentials + +--- + +## Security Notes + +- ✅ Never commit your `.env` file to Git (it's in `.gitignore`) +- ✅ Never share your Supabase anon key publicly (though it's safe for client-side use) +- ✅ Never share your GitHub OAuth client secret +- ✅ Use different OAuth apps for development and production + +--- + +## Need Help? + +- [Supabase Auth Documentation](https://supabase.com/docs/guides/auth) +- [GitHub OAuth Documentation](https://docs.github.com/en/developers/apps/building-oauth-apps) +- [DevConnect Repository Issues](https://github.com/TiwariDivya25/DevConnect/issues) diff --git a/HOW-TO-PUSH-GITHUB.md b/HOW-TO-PUSH-GITHUB.md new file mode 100644 index 00000000..74e583d9 --- /dev/null +++ b/HOW-TO-PUSH-GITHUB.md @@ -0,0 +1,198 @@ +# 🚀 How to Push Your Changes to GitHub + +## Current Status +✅ Git repository already initialized +✅ Connected to: `https://github.com/TiwariDivya25/DevConnect.git` +✅ On branch: `main` + +--- + +## ⚠️ **IMPORTANT: Before You Push** + +### **Option 1: Fork the Repository (Recommended)** +Since this is someone else's repository (TiwariDivya25/DevConnect), you should: + +1. **Fork the repository** on GitHub: + - Go to: https://github.com/TiwariDivya25/DevConnect + - Click the **"Fork"** button in the top right + - This creates your own copy + +2. **Update your remote** to point to YOUR fork: + ```bash + git remote set-url origin https://github.com/YOUR_USERNAME/DevConnect.git + ``` + Replace `YOUR_USERNAME` with your actual GitHub username + +3. **Then push** (see steps below) + +### **Option 2: Create Pull Request** +If you want to contribute to the original repository: +- Push to a new branch +- Create a Pull Request on GitHub + +### **Option 3: Create Your Own Repository** +If you want this as your own project: +1. Create a new repository on GitHub +2. Update the remote URL to your new repo + +--- + +## 📝 **Steps to Push Your Changes** + +### **Step 1: Check What Files Changed** +```bash +git status +``` + +You should see modified files like: +- `.env` (⚠️ **DO NOT COMMIT THIS!**) +- `CreatePost.tsx` +- `PostDetail.tsx` +- `communityApi.ts` +- New SQL files + +### **Step 2: Add Files to Staging** + +**⚠️ IMPORTANT: Don't commit `.env` file!** + +Add specific files: +```bash +git add src/components/CreatePost.tsx +git add src/components/PostDetail.tsx +git add src/utils/communityApi.ts +git add COMPLETE-ALL-TABLES.sql +git add SAFE-database-setup.sql +git add FIXED-database-setup.sql +git add DATABASE_SETUP_GUIDE.md +git add GITHUB_AUTH_SETUP.md +git add SETUP-COMPLETE.md +git add cleanup-database.sql +git add check-env.js +``` + +Or add all except `.env`: +```bash +git add . +git reset .env +``` + +### **Step 3: Commit Your Changes** +```bash +git commit -m "Fix: Update table names to lowercase and add database setup scripts" +``` + +Or a more detailed commit message: +```bash +git commit -m "Fix database table name casing and add setup scripts + +- Changed table names from PascalCase to lowercase (Posts -> posts, Communities -> communities) +- Updated CreatePost.tsx, PostDetail.tsx, and communityApi.ts +- Added comprehensive database setup SQL scripts +- Added setup guides for database and GitHub authentication +- Fixed RLS policies for all tables" +``` + +### **Step 4: Push to GitHub** + +If pushing to the main branch: +```bash +git push origin main +``` + +If creating a new branch (recommended for contributions): +```bash +git checkout -b fix/database-table-names +git push origin fix/database-table-names +``` + +--- + +## 🔒 **Security Checklist** + +Before pushing, make sure: +- [ ] `.env` file is **NOT** included (it's in `.gitignore`) +- [ ] No Supabase credentials in any files +- [ ] No API keys or secrets committed + +Check `.gitignore` includes: +``` +.env +.env.local +.env.production +``` + +--- + +## 🎯 **Quick Commands (Copy & Paste)** + +### **For Your Own Fork:** +```bash +# 1. Add your changes (excluding .env) +git add . +git reset .env + +# 2. Commit +git commit -m "Fix: Update table names to lowercase and add database setup scripts" + +# 3. Push +git push origin main +``` + +### **For Pull Request to Original Repo:** +```bash +# 1. Create new branch +git checkout -b fix/database-table-names + +# 2. Add changes +git add . +git reset .env + +# 3. Commit +git commit -m "Fix: Update table names to lowercase and add database setup scripts" + +# 4. Push to your branch +git push origin fix/database-table-names + +# 5. Go to GitHub and create Pull Request +``` + +--- + +## 🆘 **Common Issues** + +### **"Permission denied"** +- You don't have write access to TiwariDivya25/DevConnect +- Solution: Fork the repo first (Option 1 above) + +### **"Updates were rejected"** +- Your local branch is behind the remote +- Solution: `git pull origin main` then push again + +### **".env file in commit"** +- Accidentally committed sensitive data +- Solution: Remove it before pushing: + ```bash + git reset HEAD .env + git commit --amend + ``` + +--- + +## 📚 **Next Steps After Pushing** + +1. ✅ Verify your changes on GitHub +2. ✅ Create a Pull Request (if contributing) +3. ✅ Update your README with setup instructions +4. ✅ Add deployment instructions if needed + +--- + +## 🎉 **Summary** + +Your changes include: +- ✅ Fixed table name casing issues +- ✅ Added comprehensive database setup scripts +- ✅ Created helpful setup guides +- ✅ Updated code to work with lowercase table names + +**Ready to push!** Just decide which option above fits your needs. 🚀 diff --git a/SAFE-database-setup.sql b/SAFE-database-setup.sql new file mode 100644 index 00000000..35240224 --- /dev/null +++ b/SAFE-database-setup.sql @@ -0,0 +1,266 @@ +-- ============================================================ +-- DEVCONNECT DATABASE SCHEMA - SAFE VERSION +-- ============================================================ +-- This version drops existing policies before creating new ones +-- Safe to run multiple times +-- ============================================================ + +-- 1. COMMUNITIES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS communities ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + name TEXT NOT NULL UNIQUE, + description TEXT, + icon_url TEXT, + banner_url TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE communities ENABLE ROW LEVEL SECURITY; + +-- Drop existing policies if they exist +DROP POLICY IF EXISTS "Communities are viewable by everyone" ON communities; +DROP POLICY IF EXISTS "Authenticated users can create communities" ON communities; +DROP POLICY IF EXISTS "Authenticated users can update communities" ON communities; + +-- Create policies +CREATE POLICY "Communities are viewable by everyone" ON communities + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create communities" ON communities + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update communities" ON communities + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- ============================================================ +-- 2. COMMUNITY MEMBERS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS community_members ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + community_id BIGINT NOT NULL REFERENCES communities(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'moderator', 'member')), + joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(community_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_community_members_community_id ON community_members(community_id); +CREATE INDEX IF NOT EXISTS idx_community_members_user_id ON community_members(user_id); + +ALTER TABLE community_members ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Community members are viewable by everyone" ON community_members; +DROP POLICY IF EXISTS "Users can join communities" ON community_members; +DROP POLICY IF EXISTS "Users can leave communities" ON community_members; + +CREATE POLICY "Community members are viewable by everyone" ON community_members + FOR SELECT USING (true); + +CREATE POLICY "Users can join communities" ON community_members + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can leave communities" ON community_members + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 3. POSTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS posts ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + title TEXT NOT NULL, + content TEXT NOT NULL, + image_url TEXT, + avatar_url TEXT, + community_id BIGINT REFERENCES communities(id) ON DELETE SET NULL, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id); +CREATE INDEX IF NOT EXISTS idx_posts_community_id ON posts(community_id); +CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at DESC); + +ALTER TABLE posts ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Posts are viewable by everyone" ON posts; +DROP POLICY IF EXISTS "Authenticated users can create posts" ON posts; +DROP POLICY IF EXISTS "Users can update own posts" ON posts; +DROP POLICY IF EXISTS "Users can delete own posts" ON posts; + +CREATE POLICY "Posts are viewable by everyone" ON posts + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create posts" ON posts + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own posts" ON posts + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own posts" ON posts + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 4. COMMENTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS comments ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, + content TEXT NOT NULL, + author TEXT NOT NULL, + avatar_url TEXT, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + parent_comment_id BIGINT REFERENCES comments(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id); +CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id); +CREATE INDEX IF NOT EXISTS idx_comments_parent_id ON comments(parent_comment_id); + +ALTER TABLE comments ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Comments are viewable by everyone" ON comments; +DROP POLICY IF EXISTS "Authenticated users can create comments" ON comments; +DROP POLICY IF EXISTS "Users can update own comments" ON comments; +DROP POLICY IF EXISTS "Users can delete own comments" ON comments; + +CREATE POLICY "Comments are viewable by everyone" ON comments + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create comments" ON comments + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own comments" ON comments + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own comments" ON comments + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 5. VOTES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS votes ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + vote INT DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(post_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_votes_post_id ON votes(post_id); +CREATE INDEX IF NOT EXISTS idx_votes_user_id ON votes(user_id); + +ALTER TABLE votes ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Votes are viewable by everyone" ON votes; +DROP POLICY IF EXISTS "Users can create votes" ON votes; +DROP POLICY IF EXISTS "Users can delete own votes" ON votes; + +CREATE POLICY "Votes are viewable by everyone" ON votes + FOR SELECT USING (true); + +CREATE POLICY "Users can create votes" ON votes + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can delete own votes" ON votes + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 6. EVENTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS events ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + title TEXT NOT NULL, + description TEXT NOT NULL, + event_date TIMESTAMP WITH TIME ZONE NOT NULL, + location TEXT, + is_virtual BOOLEAN DEFAULT FALSE, + meeting_link TEXT, + max_attendees INTEGER, + image_url TEXT, + tags TEXT[], + organizer_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + community_id BIGINT REFERENCES communities(id) ON DELETE SET NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_events_organizer_id ON events(organizer_id); +CREATE INDEX IF NOT EXISTS idx_events_community_id ON events(community_id); +CREATE INDEX IF NOT EXISTS idx_events_event_date ON events(event_date); + +ALTER TABLE events ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Events are viewable by everyone" ON events; +DROP POLICY IF EXISTS "Authenticated users can create events" ON events; +DROP POLICY IF EXISTS "Organizers can update own events" ON events; +DROP POLICY IF EXISTS "Organizers can delete own events" ON events; + +CREATE POLICY "Events are viewable by everyone" ON events + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create events" ON events + FOR INSERT WITH CHECK (auth.uid() = organizer_id); + +CREATE POLICY "Organizers can update own events" ON events + FOR UPDATE USING (auth.uid() = organizer_id); + +CREATE POLICY "Organizers can delete own events" ON events + FOR DELETE USING (auth.uid() = organizer_id); + +-- ============================================================ +-- 7. EVENT ATTENDEES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS event_attendees ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + event_id BIGINT NOT NULL REFERENCES events(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + status TEXT DEFAULT 'attending' CHECK (status IN ('attending', 'maybe', 'not_attending')), + registered_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(event_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_event_attendees_event_id ON event_attendees(event_id); +CREATE INDEX IF NOT EXISTS idx_event_attendees_user_id ON event_attendees(user_id); + +ALTER TABLE event_attendees ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Event attendees are viewable by everyone" ON event_attendees; +DROP POLICY IF EXISTS "Users can register for events" ON event_attendees; +DROP POLICY IF EXISTS "Users can update own registration" ON event_attendees; +DROP POLICY IF EXISTS "Users can delete own registration" ON event_attendees; + +CREATE POLICY "Event attendees are viewable by everyone" ON event_attendees + FOR SELECT USING (true); + +CREATE POLICY "Users can register for events" ON event_attendees + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own registration" ON event_attendees + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own registration" ON event_attendees + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- VERIFICATION +-- ============================================================ +SELECT + '✅ Database setup complete! All tables created successfully.' as message, + COUNT(*) as table_count +FROM information_schema.tables +WHERE table_schema = 'public' + AND table_name IN ('communities', 'community_members', 'posts', 'comments', 'votes', 'events', 'event_attendees'); + +-- List all created tables +SELECT + table_name, + (SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name AND table_schema = 'public') as column_count +FROM information_schema.tables t +WHERE table_schema = 'public' + AND table_name IN ('communities', 'community_members', 'posts', 'comments', 'votes', 'events', 'event_attendees') +ORDER BY table_name; diff --git a/SETUP-COMPLETE.md b/SETUP-COMPLETE.md new file mode 100644 index 00000000..6bb596ed --- /dev/null +++ b/SETUP-COMPLETE.md @@ -0,0 +1,109 @@ +# ✅ Database Setup Complete! + +## What Was Fixed + +### Problem +The app was looking for PascalCase table names (`Posts`, `Communities`) but the database had lowercase names (`posts`, `communities`). + +### Solution +Updated all code files to use lowercase table names to match the database schema. + +## Files Updated + +1. ✅ **CreatePost.tsx** - Fixed `Posts` → `posts` and `Communities` → `communities` +2. ✅ **PostDetail.tsx** - Fixed `Posts` → `posts` +3. ✅ **communityApi.ts** - Fixed `Posts` → `posts` and `Communities` → `communities` + +## Database Tables Created + +All 7 tables are now in your Supabase database: +- ✅ `communities` - For developer communities +- ✅ `community_members` - For community memberships +- ✅ `posts` - For user posts +- ✅ `comments` - For post comments +- ✅ `votes` - For post likes/upvotes +- ✅ `events` - For community events +- ✅ `event_attendees` - For event registrations + +## What Should Work Now + +✅ **Create Post** - You can now create posts without errors +✅ **View Posts** - Posts will display correctly +✅ **My Posts** - Dashboard "My Posts" section will work +✅ **Communities** - Community selection dropdown works +✅ **Comments** - Users can comment on posts +✅ **Likes** - Users can like/upvote posts +✅ **Events** - Event management features + +## Next Steps + +### 1. Refresh Your App +- Go to your browser with DevConnect open +- Press **Ctrl+R** to refresh +- Try creating a post - it should work now! + +### 2. Set Up Storage Buckets (For Images) + +To enable image uploads, create these storage buckets in Supabase: + +**In Supabase Dashboard → Storage:** + +1. **post-images** (Public) + - Click "New bucket" + - Name: `post-images` + - Public: ✅ ON + - Add policies: + - SELECT: `true` (public read) + - INSERT: `auth.role() = 'authenticated'` (authenticated upload) + +2. **event-images** (Public) + - Name: `event-images` + - Public: ✅ ON + - Same policies as above + +3. **message-files** (Private) + - Name: `message-files` + - Public: ❌ OFF + - Add policies: + - SELECT: `auth.uid() = user_id` (user can read own files) + - INSERT: `auth.role() = 'authenticated'` + +## Testing Checklist + +- [ ] Refresh the app (Ctrl+R) +- [ ] Try creating a post +- [ ] Check "My Posts" in dashboard +- [ ] Try selecting a community +- [ ] Upload an image (after creating storage buckets) +- [ ] View post details +- [ ] Add a comment +- [ ] Like a post + +## Troubleshooting + +### Still seeing 404 errors? +- Make sure you ran `SAFE-database-setup.sql` in Supabase SQL Editor +- Check that all 7 tables exist in Supabase → Table Editor +- Refresh your browser (Ctrl+R) + +### Images not uploading? +- Create the storage buckets (see step 2 above) +- Make sure `post-images` is PUBLIC +- Add the storage policies + +### "supabase is possibly null" errors? +- These are TypeScript warnings, not runtime errors +- The app will still work correctly +- They occur because the code handles demo mode + +## Summary + +🎉 **Your DevConnect app is now fully configured!** + +- ✅ Database tables created +- ✅ Code updated to match table names +- ✅ Posts can be created +- ✅ Communities work +- ⚠️ Storage buckets needed for images (optional, see step 2) + +**Enjoy building your developer community!** 🚀 diff --git a/check-env.js b/check-env.js new file mode 100644 index 00000000..10839b75 --- /dev/null +++ b/check-env.js @@ -0,0 +1,67 @@ +// Simple script to check .env configuration +const fs = require('fs'); + +console.log('\n' + '='.repeat(60)); +console.log('🔍 ENVIRONMENT CONFIGURATION CHECK'); +console.log('='.repeat(60) + '\n'); + +// Read .env file +try { + const envContent = fs.readFileSync('.env', 'utf-8'); + console.log('📄 .env file contents:'); + console.log('─'.repeat(60)); + console.log(envContent); + console.log('─'.repeat(60) + '\n'); + + // Check for placeholder values + if (envContent.includes('your_supabase_url') || envContent.includes('your_supabase_anon_key')) { + console.log('❌ ERROR: .env file contains placeholder values!\n'); + console.log('🔧 You need to replace:'); + console.log(' • "your_supabase_url" → Your actual Supabase project URL'); + console.log(' • "your_supabase_anon_key" → Your actual Supabase anon key\n'); + console.log('📍 Get these values from:'); + console.log(' Supabase Dashboard → Settings → API\n'); + console.log('💡 Example of correct format:'); + console.log(' VITE_SUPABASE_URL=https://abcdefghijk.supabase.co'); + console.log(' VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n'); + } else { + console.log('✅ .env file does not contain placeholder values\n'); + + // Parse the values + const lines = envContent.split('\n'); + lines.forEach(line => { + if (line.trim() && !line.startsWith('#')) { + const [key, ...valueParts] = line.split('='); + const value = valueParts.join('=').trim(); + + if (key.includes('URL')) { + console.log(` ${key}: ${value}`); + if (value.startsWith('https://') && value.includes('.supabase.co')) { + console.log(' ✅ URL format looks correct'); + } else { + console.log(' ⚠️ URL format might be incorrect'); + console.log(' Expected format: https://your-project-id.supabase.co'); + } + } else if (key.includes('KEY')) { + console.log(` ${key}: ${value.substring(0, 20)}...`); + if (value.length > 100) { + console.log(' ✅ Key length looks correct'); + } else { + console.log(' ⚠️ Key seems too short (should be 200+ characters)'); + } + } + } + }); + } +} catch (error) { + console.log('❌ ERROR: Could not read .env file'); + console.log(' Error:', error.message); +} + +console.log('\n' + '='.repeat(60)); +console.log('📝 NEXT STEPS:'); +console.log('='.repeat(60)); +console.log('1. Update your .env file with actual Supabase credentials'); +console.log('2. Restart the dev server: npm run dev'); +console.log('3. Open http://localhost:5174 in your browser'); +console.log('='.repeat(60) + '\n'); diff --git a/cleanup-database.sql b/cleanup-database.sql new file mode 100644 index 00000000..f8fc80ca --- /dev/null +++ b/cleanup-database.sql @@ -0,0 +1,26 @@ +-- ============================================================ +-- CLEANUP SCRIPT - Run this FIRST to remove existing tables +-- ============================================================ +-- This will drop all existing tables and policies +-- Then you can run FIXED-database-setup.sql fresh +-- ============================================================ + +-- Drop tables in reverse order (to handle foreign key constraints) +DROP TABLE IF EXISTS event_attendees CASCADE; +DROP TABLE IF EXISTS events CASCADE; +DROP TABLE IF EXISTS votes CASCADE; +DROP TABLE IF EXISTS comments CASCADE; +DROP TABLE IF EXISTS posts CASCADE; +DROP TABLE IF EXISTS community_members CASCADE; +DROP TABLE IF EXISTS communities CASCADE; + +-- Also drop any PascalCase versions if they exist +DROP TABLE IF EXISTS EventAttendees CASCADE; +DROP TABLE IF EXISTS Events CASCADE; +DROP TABLE IF EXISTS Votes CASCADE; +DROP TABLE IF EXISTS Comments CASCADE; +DROP TABLE IF EXISTS Posts CASCADE; +DROP TABLE IF EXISTS Communities CASCADE; + +-- Verification +SELECT 'All tables dropped successfully! Now run FIXED-database-setup.sql' as message; diff --git a/complete-database-setup.sql b/complete-database-setup.sql new file mode 100644 index 00000000..93890c8e --- /dev/null +++ b/complete-database-setup.sql @@ -0,0 +1,222 @@ +-- ============================================================ +-- DEVCONNECT DATABASE SCHEMA - COMPLETE SETUP +-- ============================================================ +-- Run this entire script in your Supabase SQL Editor +-- This will create all required tables for DevConnect +-- ============================================================ + +-- 1. COMMUNITIES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS Communities ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + name TEXT NOT NULL UNIQUE, + description TEXT, + icon_url TEXT, + banner_url TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Enable RLS for Communities +ALTER TABLE Communities ENABLE ROW LEVEL SECURITY; + +-- Communities policies +CREATE POLICY "Communities are viewable by everyone" ON Communities + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create communities" ON Communities + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update communities" ON Communities + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- ============================================================ +-- 2. POSTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS Posts ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + title TEXT NOT NULL, + content TEXT NOT NULL, + image_url TEXT, + avatar_url TEXT, + community_id BIGINT REFERENCES Communities(id) ON DELETE SET NULL, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for Posts +CREATE INDEX IF NOT EXISTS idx_posts_user_id ON Posts(user_id); +CREATE INDEX IF NOT EXISTS idx_posts_community_id ON Posts(community_id); +CREATE INDEX IF NOT EXISTS idx_posts_created_at ON Posts(created_at DESC); + +-- Enable RLS for Posts +ALTER TABLE Posts ENABLE ROW LEVEL SECURITY; + +-- Posts policies +CREATE POLICY "Posts are viewable by everyone" ON Posts + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create posts" ON Posts + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own posts" ON Posts + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own posts" ON Posts + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 3. COMMENTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS Comments ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id BIGINT NOT NULL REFERENCES Posts(id) ON DELETE CASCADE, + content TEXT NOT NULL, + author TEXT NOT NULL, + avatar_url TEXT, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + parent_comment_id BIGINT REFERENCES Comments(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for Comments +CREATE INDEX IF NOT EXISTS idx_comments_post_id ON Comments(post_id); +CREATE INDEX IF NOT EXISTS idx_comments_user_id ON Comments(user_id); +CREATE INDEX IF NOT EXISTS idx_comments_parent_id ON Comments(parent_comment_id); + +-- Enable RLS for Comments +ALTER TABLE Comments ENABLE ROW LEVEL SECURITY; + +-- Comments policies +CREATE POLICY "Comments are viewable by everyone" ON Comments + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create comments" ON Comments + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own comments" ON Comments + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own comments" ON Comments + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 4. VOTES TABLE (for post likes) +-- ============================================================ +CREATE TABLE IF NOT EXISTS Votes ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id BIGINT NOT NULL REFERENCES Posts(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + vote INT DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(post_id, user_id) +); + +-- Create indexes for Votes +CREATE INDEX IF NOT EXISTS idx_votes_post_id ON Votes(post_id); +CREATE INDEX IF NOT EXISTS idx_votes_user_id ON Votes(user_id); + +-- Enable RLS for Votes +ALTER TABLE Votes ENABLE ROW LEVEL SECURITY; + +-- Votes policies +CREATE POLICY "Votes are viewable by everyone" ON Votes + FOR SELECT USING (true); + +CREATE POLICY "Users can create votes" ON Votes + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can delete own votes" ON Votes + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- 5. EVENTS TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS Events ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + title TEXT NOT NULL, + description TEXT NOT NULL, + event_date TIMESTAMP WITH TIME ZONE NOT NULL, + location TEXT, + is_virtual BOOLEAN DEFAULT FALSE, + meeting_link TEXT, + max_attendees INTEGER, + image_url TEXT, + tags TEXT[], + organizer_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + community_id BIGINT REFERENCES Communities(id) ON DELETE SET NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for Events +CREATE INDEX IF NOT EXISTS idx_events_organizer_id ON Events(organizer_id); +CREATE INDEX IF NOT EXISTS idx_events_community_id ON Events(community_id); +CREATE INDEX IF NOT EXISTS idx_events_event_date ON Events(event_date); + +-- Enable RLS for Events +ALTER TABLE Events ENABLE ROW LEVEL SECURITY; + +-- Events policies +CREATE POLICY "Events are viewable by everyone" ON Events + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create events" ON Events + FOR INSERT WITH CHECK (auth.uid() = organizer_id); + +CREATE POLICY "Organizers can update own events" ON Events + FOR UPDATE USING (auth.uid() = organizer_id); + +CREATE POLICY "Organizers can delete own events" ON Events + FOR DELETE USING (auth.uid() = organizer_id); + +-- ============================================================ +-- 6. EVENT ATTENDEES TABLE +-- ============================================================ +CREATE TABLE IF NOT EXISTS EventAttendees ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + event_id BIGINT NOT NULL REFERENCES Events(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + status TEXT DEFAULT 'attending' CHECK (status IN ('attending', 'maybe', 'not_attending')), + registered_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(event_id, user_id) +); + +-- Create indexes for EventAttendees +CREATE INDEX IF NOT EXISTS idx_event_attendees_event_id ON EventAttendees(event_id); +CREATE INDEX IF NOT EXISTS idx_event_attendees_user_id ON EventAttendees(user_id); + +-- Enable RLS for EventAttendees +ALTER TABLE EventAttendees ENABLE ROW LEVEL SECURITY; + +-- EventAttendees policies +CREATE POLICY "Event attendees are viewable by everyone" ON EventAttendees + FOR SELECT USING (true); + +CREATE POLICY "Users can register for events" ON EventAttendees + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own registration" ON EventAttendees + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own registration" ON EventAttendees + FOR DELETE USING (auth.uid() = user_id); + +-- ============================================================ +-- VERIFICATION +-- ============================================================ +SELECT + 'Database setup complete! All tables created successfully.' as message, + COUNT(*) as table_count +FROM information_schema.tables +WHERE table_schema = 'public' + AND table_name IN ('Communities', 'Posts', 'Comments', 'Votes', 'Events', 'EventAttendees'); + +-- List all created tables +SELECT table_name, + (SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name) as column_count +FROM information_schema.tables t +WHERE table_schema = 'public' + AND table_name IN ('Communities', 'Posts', 'Comments', 'Votes', 'Events', 'EventAttendees') +ORDER BY table_name; diff --git a/package-lock.json b/package-lock.json index 44b399f4..587674b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1758,6 +1759,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1827,6 +1829,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -2079,6 +2082,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2197,6 +2201,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2467,6 +2472,7 @@ "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3565,6 +3571,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3574,6 +3581,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3865,6 +3873,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3929,6 +3938,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4013,6 +4023,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -4104,6 +4115,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/src/components/CreatePost.tsx b/src/components/CreatePost.tsx index 6a46b3ed..143731b4 100644 --- a/src/components/CreatePost.tsx +++ b/src/components/CreatePost.tsx @@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { supabase } from '../supabase-client'; import { useAuth } from '../hooks/useAuth'; import type { Community } from './CommunityList'; -import { Upload} from 'lucide-react'; +import { Upload } from 'lucide-react'; import { showError, showSuccess } from '../utils/toast'; const MAX_CHARS = 500; @@ -14,14 +14,14 @@ interface PostInput { content: string; avatar_url: string | null; community_id: number | null; -} +} const fetchCommunities = async (): Promise => { const { data, error } = await supabase - .from('Communities') + .from('communities') .select('*') .order('created_at', { ascending: false }); - + if (error) { throw new Error("Error fetching communities: " + error.message); } @@ -30,7 +30,7 @@ const fetchCommunities = async (): Promise => { const CreatePost = () => { const queryClient = useQueryClient(); - + const uploadPost = async (post: PostInput, imageFile: File | null) => { if (!imageFile) { throw new Error("Image file is required"); @@ -38,7 +38,7 @@ const CreatePost = () => { const filePath = `${post.title}-${Date.now()}-${imageFile.name}`; - const {error: imageError} = await supabase.storage + const { error: imageError } = await supabase.storage .from('post-images') .upload(filePath, imageFile); @@ -46,11 +46,11 @@ const CreatePost = () => { throw new Error("Error uploading image: " + imageError.message); } - const {data: publicUrl} = supabase.storage + const { data: publicUrl } = supabase.storage .from('post-images') .getPublicUrl(filePath); - const {data, error} = await supabase.from("Posts").insert({ + const { data, error } = await supabase.from("posts").insert({ title: post.title, content: post.content, image_url: publicUrl.publicUrl, @@ -69,15 +69,15 @@ const CreatePost = () => { const [imageFile, setImageFile] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [communityId, setCommunityId] = useState(null); - const {user} = useAuth(); + const { user } = useAuth(); const { data: communities, isLoading: communitiesLoading, isError: communitiesError } = useQuery({ queryKey: ['communities'], queryFn: fetchCommunities }); - const {mutate, isPending, isSuccess} = useMutation({ - mutationFn: (data: {post: PostInput, imageFile: File | null}) => { + const { mutate, isPending, isSuccess } = useMutation({ + mutationFn: (data: { post: PostInput, imageFile: File | null }) => { return uploadPost(data.post, data.imageFile); }, onSuccess: () => { @@ -89,26 +89,26 @@ const CreatePost = () => { setImagePreview(null); setCommunityId(null); - queryClient.invalidateQueries({queryKey: ['posts']}); + queryClient.invalidateQueries({ queryKey: ['posts'] }); setTimeout(() => { window.location.href = '/'; }, 2000); }, - onError:(err:Error)=>{ + onError: (err: Error) => { showError(err.message || "Failed To Create Post"); } }) const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - + if (!user) { showError('You must be logged in to create a post'); return; } - + if (!imageFile) { showError('Please select an image'); return; @@ -123,14 +123,14 @@ const CreatePost = () => { showError('Content exceeds character limit'); return; } - + mutate({ post: { - title, - content, + title, + content, avatar_url: user.user_metadata?.avatar_url || null, community_id: communityId - }, + }, imageFile }); } @@ -139,7 +139,7 @@ const CreatePost = () => { if (e.target.files && e.target.files.length > 0) { const file = e.target.files[0]; setImageFile(file); - + const reader = new FileReader(); reader.onloadend = () => { setImagePreview(reader.result as string); @@ -164,12 +164,12 @@ const CreatePost = () => {
- - + + {user?.user_metadata?.avatar_url && (
- Your avatar { disabled={isPending} className="hidden" /> -
+ +
+
+
+ +
+
+

+ Are you sure you want to delete this post? +

+

+ This action cannot be undone. The post "{postTitle}" will be permanently deleted. +

+
+
+
+ +
+ + +
+
+ + ); +}; + +export default DeleteConfirmModal; \ No newline at end of file diff --git a/src/components/EditPostModal.tsx b/src/components/EditPostModal.tsx new file mode 100644 index 00000000..4854fdf8 --- /dev/null +++ b/src/components/EditPostModal.tsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { X, Save, Loader2 } from 'lucide-react'; +import { showSuccess, showError } from '../utils/toast'; + +interface EditPostModalProps { + isOpen: boolean; + onClose: () => void; + post: { + id: number; + title: string; + content: string; + }; + onSave: (title: string, content: string) => Promise; +} + +const EditPostModal: React.FC = ({ isOpen, onClose, post, onSave }) => { + const [title, setTitle] = useState(post.title); + const [content, setContent] = useState(post.content); + const [isLoading, setIsLoading] = useState(false); + + if (!isOpen) return null; + + const handleSave = async () => { + if (!title.trim() || !content.trim()) { + showError('Title and content are required'); + return; + } + + setIsLoading(true); + try { + await onSave(title.trim(), content.trim()); + showSuccess('Post updated successfully'); + onClose(); + } catch (error) { + showError(error instanceof Error ? error.message : 'Failed to update post'); + } finally { + setIsLoading(false); + } + }; + + const handleOverlayClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + return ( +
+
+
+

Edit Post

+ +
+ +
+
+ + setTitle(e.target.value)} + className="w-full px-4 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500" + placeholder="Enter post title..." + disabled={isLoading} + /> +
+ +
+ +