Skip to content

Commit 933faf2

Browse files
AHA
1 parent fa65bf7 commit 933faf2

File tree

13 files changed

+1062
-116
lines changed

13 files changed

+1062
-116
lines changed

seed_media.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Seed Media Data Script
3+
*
4+
* This script simulates the upload of the exhibits provided by the user.
5+
* It populates the media_content and media_items tables.
6+
*/
7+
8+
import { mediaService } from './src/services/mediaService';
9+
import { supabase } from './src/integrations/supabase/client';
10+
11+
async function seedExhibits() {
12+
console.log('🚀 Starting Seeding of exhibits...');
13+
14+
// 1. Create the "Price of Compromise" Carousel/PDF Series
15+
const slug = 'price-of-compromise';
16+
const existing = await mediaService.getMediaContent(slug);
17+
18+
if (existing) {
19+
console.log('✨ Content already exists, skipping creation.');
20+
return;
21+
}
22+
23+
const content = await mediaService.createContent({
24+
type: 'carousel',
25+
title: 'The Price of Compromise',
26+
description: 'Corruption is perfected by our leaders but starts with you. A deep dive into the costs of compromise in Kenya.',
27+
slug: slug,
28+
status: 'published',
29+
metadata: {
30+
pdf_url: '/media/documents/The_Price_of_Compromise.pdf' // This would be the B2 URL
31+
}
32+
});
33+
34+
if (!content) {
35+
console.error('❌ Failed to create content container');
36+
return;
37+
}
38+
39+
console.log('✅ Created content container:', content.id);
40+
41+
// 2. Simulate adding the Carousel Image (Exhibit 2)
42+
// Note: In a real environment, we'd use the file objects.
43+
// Here we just insert the DB record to verify the frontend link.
44+
const { error: itemError } = await supabase
45+
.from('media_items')
46+
.insert([
47+
{
48+
content_id: content.id,
49+
type: 'image',
50+
file_path: 'media/carousel/2026/02/04/price-of-compromise/cover.png',
51+
file_url: 'https://placehold.co/800x800?text=Price+of+Compromise+1/8', // Placeholder for now
52+
order_index: 0,
53+
metadata: { alt: 'Title Slide' }
54+
},
55+
{
56+
content_id: content.id,
57+
type: 'image',
58+
file_path: 'media/carousel/2026/02/04/price-of-compromise/page-2.png',
59+
file_url: 'https://placehold.co/800x800?text=Corruption+Impact+2/8',
60+
order_index: 1,
61+
metadata: { alt: 'Impact Slide' }
62+
}
63+
]);
64+
65+
if (itemError) {
66+
console.error('❌ Failed to add media items:', itemError);
67+
} else {
68+
console.log('✅ Added test carousel items');
69+
}
70+
71+
console.log('🎉 Seeding complete!');
72+
}
73+
74+
// seedExhibits(); // Manual trigger

src/App.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ import SHAmbles from '@/pages/SHAmbles';
3232
import PeoplesAuditPage from '@/pages/PeoplesAuditPage';
3333
import NasakaPage from '@/pages/NasakaIEBCPage';
3434
// Volunteer pages removed - functionality merged into JoinCommunity
35-
import UserProfile from '@/pages/UserProfile';
36-
import ProfileSettings from '@/pages/ProfileSettings';
3735
import Notifications from '@/pages/Notifications';
3836
import AdvocacyToolkit from '@/pages/AdvocacyToolkit';
3937
import AdvocacyToolkitDetail from '@/pages/AdvocacyToolkitDetail';
@@ -50,9 +48,9 @@ import ResourceUpload from '@/pages/ResourceUpload';
5048
import PendingResources from '@/pages/PendingResources';
5149
import ThumbnailDemo from '@/pages/ThumbnailDemo';
5250
import SettingsLayout from '@/pages/settings/SettingsLayout';
53-
import Settings from '@/pages/Settings';
5451
import AccountSettings from '@/pages/settings/AccountSettings';
5552
import NotificationSettings from '@/pages/settings/NotificationSettings';
53+
import AppearanceSettings from '@/pages/settings/AppearanceSettings';
5654
import PrivacySettings from '@/pages/settings/PrivacySettings';
5755
import PrivacyPolicy from '@/pages/PrivacyPolicy';
5856
import TermsConditions from '@/pages/TermsConditions';
@@ -151,16 +149,8 @@ const AppContent = () => {
151149
{/* Volunteer routes redirect to join-community */}
152150
<Route path="/volunteer" element={<Navigate to="/join-community?tab=volunteer" replace />} />
153151
<Route path="/volunteer/apply/:id" element={<Navigate to="/join-community?tab=volunteer" replace />} />
154-
<Route path="/profile" element={
155-
<ProtectedRoute>
156-
<UserProfile />
157-
</ProtectedRoute>
158-
} />
159-
<Route path="/profile/settings" element={
160-
<ProtectedRoute>
161-
<ProfileSettings />
162-
</ProtectedRoute>
163-
} />
152+
<Route path="/profile" element={<Navigate to="/settings/account" replace />} />
153+
<Route path="/profile/settings" element={<Navigate to="/settings/account" replace />} />
164154
<Route path="/notifications" element={
165155
<ProtectedRoute>
166156
<Notifications />
@@ -181,9 +171,10 @@ const AppContent = () => {
181171
<Route path="/privacy" element={<PrivacyPolicy />} />
182172
<Route path="/terms" element={<TermsConditions />} />
183173
<Route path="/settings" element={<SettingsLayout />}>
184-
<Route index element={<Settings />} />
174+
<Route index element={<Navigate to="/settings/account" replace />} />
185175
<Route path="account" element={<AccountSettings />} />
186176
<Route path="notifications" element={<NotificationSettings />} />
177+
<Route path="appearance" element={<AppearanceSettings />} />
187178
<Route path="privacy" element={<PrivacySettings />} />
188179
</Route>
189180
<Route path="*" element={<NotFound />} />
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
3+
import { ChevronLeft, ChevronRight, Download, Maximize2, Share2 } from 'lucide-react';
4+
import { cn } from '@/lib/utils';
5+
import { Button } from '@/components/ui/button';
6+
import { type MediaContent, type MediaItem } from '@/services/mediaService';
7+
8+
interface InstagramCarouselProps {
9+
content: MediaContent;
10+
className?: string;
11+
}
12+
13+
export const InstagramCarousel: React.FC<InstagramCarouselProps> = ({ content, className }) => {
14+
const [currentIndex, setCurrentIndex] = useState(0);
15+
const [direction, setDirection] = useState(0);
16+
const items = content.items || [];
17+
18+
const nextSlide = () => {
19+
if (currentIndex < items.length - 1) {
20+
setDirection(1);
21+
setCurrentIndex(currentIndex + 1);
22+
}
23+
};
24+
25+
const prevSlide = () => {
26+
if (currentIndex > 0) {
27+
setDirection(-1);
28+
setCurrentIndex(currentIndex - 1);
29+
}
30+
};
31+
32+
const handleDownloadPDF = () => {
33+
const pdfUrl = content.metadata?.pdf_url;
34+
if (pdfUrl) {
35+
window.open(pdfUrl, '_blank');
36+
} else {
37+
console.warn('No PDF URL found for this content');
38+
}
39+
};
40+
41+
// Keyboard navigation
42+
useEffect(() => {
43+
const handleKeyDown = (e: KeyboardEvent) => {
44+
if (e.key === 'ArrowLeft') prevSlide();
45+
if (e.key === 'ArrowRight') nextSlide();
46+
};
47+
window.addEventListener('keydown', handleKeyDown);
48+
return () => window.removeEventListener('keydown', handleKeyDown);
49+
}, [currentIndex]);
50+
51+
if (items.length === 0) return null;
52+
53+
const currentItem = items[currentIndex];
54+
55+
return (
56+
<div className={cn("relative group max-w-xl mx-auto bg-black rounded-xl overflow-hidden shadow-2xl", className)}>
57+
{/* Aspect Ratio Container (Square for IG feel) */}
58+
<div className="relative aspect-square w-full">
59+
<AnimatePresence initial={false} custom={direction}>
60+
<motion.div
61+
key={currentIndex}
62+
custom={direction}
63+
variants={{
64+
enter: (direction: number) => ({
65+
x: direction > 0 ? '100%' : '-100%',
66+
opacity: 0
67+
}),
68+
center: {
69+
x: 0,
70+
opacity: 1
71+
},
72+
exit: (direction: number) => ({
73+
x: direction < 0 ? '100%' : '-100%',
74+
opacity: 0
75+
})
76+
}}
77+
initial="enter"
78+
animate="center"
79+
exit="exit"
80+
transition={{
81+
x: { type: "spring", stiffness: 300, damping: 30 },
82+
opacity: { duration: 0.2 }
83+
}}
84+
className="absolute inset-0 flex items-center justify-center"
85+
>
86+
{currentItem.type === 'image' ? (
87+
<img
88+
src={currentItem.file_url || ''}
89+
alt={`${content.title} - Slide ${currentIndex + 1}`}
90+
className="w-full h-full object-cover"
91+
/>
92+
) : currentItem.type === 'video' ? (
93+
<video
94+
src={currentItem.file_url || ''}
95+
controls
96+
className="w-full h-full object-contain"
97+
/>
98+
) : (
99+
<div className="flex flex-col items-center justify-center text-white/50">
100+
<Maximize2 size={48} className="mb-2" />
101+
<span>Unsupported Media Type</span>
102+
</div>
103+
)}
104+
</motion.div>
105+
</AnimatePresence>
106+
107+
{/* Overlays / Controls */}
108+
<div className="absolute inset-0 pointer-events-none">
109+
{/* Navigation Buttons */}
110+
<div className="absolute inset-y-0 left-0 flex items-center pl-2 pointer-events-auto opacity-0 group-hover:opacity-100 transition-opacity">
111+
{currentIndex > 0 && (
112+
<Button
113+
variant="ghost"
114+
size="icon"
115+
onClick={prevSlide}
116+
className="rounded-full bg-black/30 hover:bg-black/50 text-white border-none h-8 w-8"
117+
>
118+
<ChevronLeft size={20} />
119+
</Button>
120+
)}
121+
</div>
122+
<div className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-auto opacity-0 group-hover:opacity-100 transition-opacity">
123+
{currentIndex < items.length - 1 && (
124+
<Button
125+
variant="ghost"
126+
size="icon"
127+
onClick={nextSlide}
128+
className="rounded-full bg-black/30 hover:bg-black/50 text-white border-none h-8 w-8"
129+
>
130+
<ChevronRight size={20} />
131+
</Button>
132+
)}
133+
</div>
134+
135+
{/* Content Header (Floating) */}
136+
<div className="absolute top-0 inset-x-0 p-4 bg-gradient-to-b from-black/60 to-transparent flex justify-between items-start text-white">
137+
<div className="flex flex-col">
138+
<span className="text-xs font-semibold uppercase tracking-wider text-kenya-red">{content.type}</span>
139+
<h3 className="text-sm font-bold line-clamp-1">{content.title}</h3>
140+
</div>
141+
<div className="flex gap-2 pointer-events-auto">
142+
<Button variant="ghost" size="icon" className="h-8 w-8 text-white hover:bg-white/20 rounded-full">
143+
<Share2 size={16} />
144+
</Button>
145+
</div>
146+
</div>
147+
148+
{/* Indicators (Instagram Style) */}
149+
<div className="absolute bottom-4 inset-x-0 flex justify-center gap-1.5">
150+
{items.length > 1 && items.map((_, i) => (
151+
<div
152+
key={i}
153+
className={cn(
154+
"h-1.5 w-1.5 rounded-full transition-all duration-300",
155+
i === currentIndex ? "bg-white scale-125" : "bg-white/40"
156+
)}
157+
/>
158+
))}
159+
</div>
160+
</div>
161+
</div>
162+
163+
{/* Bottom Panel (Download Action) */}
164+
<div className="bg-background border-t p-4 flex items-center justify-between">
165+
<div className="flex-1 mr-4">
166+
<p className="text-sm text-foreground/80 line-clamp-1 italic">
167+
{currentIndex + 1} / {items.length}
168+
</p>
169+
</div>
170+
{content.metadata?.pdf_url && (
171+
<Button
172+
onClick={handleDownloadPDF}
173+
className="bg-kenya-red hover:bg-kenya-red/90 text-white gap-2 rounded-full h-9 px-4 text-xs font-bold"
174+
>
175+
<Download size={14} />
176+
DOWNLOAD PDF
177+
</Button>
178+
)}
179+
</div>
180+
</div>
181+
);
182+
};
183+
184+
export default InstagramCarousel;

src/components/layout/Navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ const Navbar = () => {
184184
</DropdownMenuTrigger>
185185
<DropdownMenuContent align="end" className="w-56 p-2 rounded-[24px] bg-white/95 dark:bg-[#1C1C1E]/95 backdrop-blur-3xl border-none shadow-2xl mt-2">
186186
<DropdownMenuLabel className="text-[10px] uppercase tracking-widest text-muted-foreground px-3 pt-3">Identity</DropdownMenuLabel>
187-
<DropdownMenuItem asChild><Link to="/profile" className="rounded-xl p-3 cursor-pointer">Profile</Link></DropdownMenuItem>
187+
<DropdownMenuItem asChild><Link to="/settings/account" className="rounded-xl p-3 cursor-pointer">Profile</Link></DropdownMenuItem>
188188
<DropdownMenuItem asChild><Link to="/settings" className="rounded-xl p-3 cursor-pointer">Settings</Link></DropdownMenuItem>
189189
{isAdmin && <DropdownMenuItem asChild><Link to="/admin/dashboard" className="rounded-xl p-3 cursor-pointer text-primary font-bold">Admin Console</Link></DropdownMenuItem>}
190190
<DropdownMenuSeparator className="bg-slate-100 dark:bg-white/5 my-2" />

0 commit comments

Comments
 (0)