Convert backends to Firebase and combine projects
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { supabase } from '../supabase';
|
||||
import { SpecialEvent } from '../types';
|
||||
|
||||
interface CreateSpecialEventsViewProps {
|
||||
onShowToast: (msg: string, type: 'success' | 'delete') => void;
|
||||
}
|
||||
|
||||
const CreateSpecialEventsView: React.FC<CreateSpecialEventsViewProps> = ({ onShowToast }) => {
|
||||
const [specialEvents, setSpecialEvents] = useState<SpecialEvent[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [editingEvent, setEditingEvent] = useState<SpecialEvent | null>(null);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
title: '',
|
||||
description: '',
|
||||
link: ''
|
||||
});
|
||||
|
||||
const fetchSpecialEvents = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const { data, error } = await supabase
|
||||
.from('special_events')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (!error && data) {
|
||||
setSpecialEvents(data);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSpecialEvents();
|
||||
}, [fetchSpecialEvents]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error("User not authenticated");
|
||||
|
||||
if (editingEvent) {
|
||||
const { error } = await supabase
|
||||
.from('special_events')
|
||||
.update({
|
||||
title: formData.title,
|
||||
description: formData.description,
|
||||
link: formData.link
|
||||
})
|
||||
.eq('id', editingEvent.id);
|
||||
|
||||
if (error) throw error;
|
||||
onShowToast("Session updated! Awaiting Review.", "success");
|
||||
} else {
|
||||
const { error } = await supabase
|
||||
.from('special_events')
|
||||
.insert({
|
||||
title: formData.title,
|
||||
description: formData.description,
|
||||
link: formData.link,
|
||||
created_by: user.id,
|
||||
verification_status: 'PENDING'
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
onShowToast("Session Broadcasted! Security check initiated.", "success");
|
||||
}
|
||||
|
||||
setFormData({ title: '', description: '', link: '' });
|
||||
setEditingEvent(null);
|
||||
fetchSpecialEvents();
|
||||
} catch (err: any) {
|
||||
console.error("Error saving special event:", err);
|
||||
alert(`Error: ${err.message}`);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (event: SpecialEvent) => {
|
||||
setEditingEvent(event);
|
||||
setFormData({
|
||||
title: event.title,
|
||||
description: event.description || '',
|
||||
link: event.link
|
||||
});
|
||||
// Scroll to form
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm("Are you sure you want to delete this special event?")) return;
|
||||
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('special_events')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
onShowToast("Special event deleted successfully!", "delete");
|
||||
fetchSpecialEvents();
|
||||
} catch (err: any) {
|
||||
console.error("Error deleting special event:", err);
|
||||
alert(`Error: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 md:px-10 py-10 animate-in fade-in duration-700">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||
{/* Creation Form */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="bg-white border border-slate-200 rounded-[2.5rem] p-10 shadow-xl sticky top-32">
|
||||
<h2 className="text-3xl font-black text-slate-900 tracking-tighter uppercase mb-2">
|
||||
{editingEvent ? 'Edit Portal' : 'Create Portal'}
|
||||
</h2>
|
||||
<p className="text-[#004a99] font-bold text-[8px] tracking-[0.4em] uppercase mb-10">
|
||||
External Link Broadcaster
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-[9px] font-black text-slate-500 uppercase tracking-[0.2em] mb-3">Event Title</label>
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
placeholder="e.g. Google Cloud Workshop"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-6 py-4 text-slate-900 focus:ring-1 focus:ring-[#004a99] outline-none font-bold placeholder:text-slate-400 transition-all"
|
||||
value={formData.title}
|
||||
onChange={e => setFormData({ ...formData, title: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[9px] font-black text-slate-500 uppercase tracking-[0.2em] mb-3">External Link</label>
|
||||
<input
|
||||
required
|
||||
type="url"
|
||||
placeholder="https://..."
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-6 py-4 text-slate-900 focus:ring-1 focus:ring-[#004a99] outline-none font-bold placeholder:text-slate-400 transition-all"
|
||||
value={formData.link}
|
||||
onChange={e => setFormData({ ...formData, link: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[9px] font-black text-slate-500 uppercase tracking-[0.2em] mb-3">Description</label>
|
||||
<textarea
|
||||
required
|
||||
placeholder="What is this event about?"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-6 py-4 text-slate-900 focus:ring-1 focus:ring-[#004a99] outline-none font-bold placeholder:text-slate-400 transition-all min-h-[120px] resize-none"
|
||||
value={formData.description}
|
||||
onChange={e => setFormData({ ...formData, description: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 pt-4">
|
||||
{editingEvent && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setEditingEvent(null);
|
||||
setFormData({ title: '', description: '', link: '' });
|
||||
}}
|
||||
className="flex-1 py-4 bg-slate-100 text-slate-600 font-black uppercase tracking-widest text-[10px] rounded-2xl hover:bg-slate-200 transition-all"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="flex-[2] py-4 bg-[#004a99] text-white font-black uppercase tracking-widest text-[10px] rounded-2xl hover:bg-blue-800 transition-all shadow-lg active:scale-95 disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? 'Syncing...' : editingEvent ? 'Update Link' : 'Generate Link'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* List of Special Events */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="flex items-end justify-between mb-12 border-b border-slate-200 pb-8">
|
||||
<div>
|
||||
<h2 className="text-5xl font-black text-slate-900 tracking-tighter uppercase mb-2">Active Portals</h2>
|
||||
<p className="text-[#004a99] font-bold text-xs tracking-[0.4em] uppercase flex items-center gap-3">
|
||||
<span className="w-1.5 h-1.5 bg-[#004a99] rounded-full"></span>
|
||||
External Event Hub
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-[32px] font-black text-slate-900">{specialEvents.length}</span>
|
||||
<p className="text-[9px] font-black text-slate-400 uppercase tracking-widest">Global Links</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{isLoading ? (
|
||||
<div className="py-20 flex flex-col items-center justify-center">
|
||||
<div className="w-10 h-10 border-4 border-slate-200 border-t-[#004a99] rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : specialEvents.length > 0 ? (
|
||||
specialEvents.map((event) => (
|
||||
<div key={event.id} className="bg-white border border-slate-100 rounded-[2.5rem] p-10 shadow-sm hover:shadow-2xl hover:shadow-slate-200 transition-all group flex flex-col md:flex-row md:items-center gap-10 border-l-8 border-l-[#004a99] relative overflow-hidden">
|
||||
<div className="absolute -top-10 -right-10 w-40 h-40 bg-[#004a99]/5 rounded-full blur-3xl group-hover:bg-[#004a99]/10 transition-colors duration-1000"></div>
|
||||
|
||||
<div className="flex-1 relative z-10">
|
||||
<div className="flex flex-wrap items-center gap-4 mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
{event.verification_status === 'APPROVED' ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]"></span>
|
||||
<span className="text-[9px] font-black text-slate-400 uppercase tracking-widest leading-none">Broadcast Active</span>
|
||||
</div>
|
||||
) : event.verification_status === 'REJECTED' ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-rose-500"></span>
|
||||
<span className="text-[9px] font-black text-rose-400 uppercase tracking-widest leading-none">Rejected</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-amber-500 animate-pulse"></span>
|
||||
<span className="text-[9px] font-black text-amber-500 uppercase tracking-widest leading-none">Security Review Queue</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="w-1 h-1 bg-slate-200 rounded-full"></span>
|
||||
<span className="text-[8px] font-black text-slate-300 uppercase tracking-[0.3em] font-inter">Global ID: {event.id.slice(0, 8)}</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-3xl font-black text-slate-900 mb-3 group-hover:text-[#004a99] transition-colors tracking-tight uppercase leading-none">{event.title}</h3>
|
||||
<p className="text-blue-500 font-bold text-[10px] mb-4 truncate max-w-md">{event.link}</p>
|
||||
<p className="text-slate-500 text-sm font-medium line-clamp-2 italic">{event.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={() => handleEdit(event)}
|
||||
className="w-12 h-12 bg-blue-50 text-[#004a99] rounded-2xl flex items-center justify-center hover:bg-[#004a99] hover:text-white transition-all active:scale-90 shadow-sm"
|
||||
title="Edit Portal"
|
||||
>
|
||||
<i className="fas fa-edit text-sm"></i>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(event.id)}
|
||||
className="w-12 h-12 bg-rose-50 text-rose-500 rounded-2xl flex items-center justify-center hover:bg-rose-500 hover:text-white transition-all active:scale-90 shadow-sm"
|
||||
title="Delete Portal"
|
||||
>
|
||||
<i className="fas fa-trash-alt text-sm"></i>
|
||||
</button>
|
||||
<a
|
||||
href={event.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-12 h-12 bg-slate-900 text-white rounded-2xl flex items-center justify-center hover:bg-black transition-all active:scale-90 shadow-lg shadow-slate-900/20"
|
||||
title="Visit Link"
|
||||
>
|
||||
<i className="fas fa-external-link-alt text-sm"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="py-20 text-center bg-slate-50 border border-dashed border-slate-300 rounded-[2rem]">
|
||||
<i className="fas fa-link text-4xl text-slate-300 mb-6"></i>
|
||||
<p className="text-slate-400 font-black uppercase tracking-widest text-xs">No special events found</p>
|
||||
<p className="text-slate-300 font-bold text-[9px] uppercase tracking-widest mt-2">Start by creating your first global portal</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateSpecialEventsView;
|
||||
Reference in New Issue
Block a user