Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart

tasks:
- init: yarn install && yarn run build
command: yarn run start
- init: yarn
command: yarn dev


3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"easymde": "^2.20.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^12.4.7",
"input-otp": "^1.2.4",
Expand All @@ -59,9 +60,11 @@
"react-hook-form": "^7.54.2",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^2.1.3",
"react-simplemde-editor": "^5.2.0",
"react-syntax-highlighter": "^15.6.1",
"recharts": "^2.12.7",
"remark-gfm": "^4.0.1",
"simplemde": "^1.11.2",
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
Expand Down
160 changes: 160 additions & 0 deletions src/components/Admin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useState } from "react";
import { motion } from "framer-motion";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { useToast } from "@/hooks/use-toast";
import Link from "next/link";
import { Home, FileText, Calendar, Users, ExternalLink } from "lucide-react";
import HeroEditor from "@/components/admin/HeroEditor";
import ProjectsEditor from "@/components/admin/ProjectsEditor";
import ActivitiesEditor from "@/components/admin/ActivitiesEditor";
import EventsEditor from "@/components/admin/EventsEditor";
import TeamEditor from "@/components/admin/TeamEditor";
import { handleLogout } from "@/lib/utils";
import PageLayout from "./layout/PageLayout";

const Admin = () => {
const { toast } = useToast();
const [activeTab, setActiveTab] = useState("hero");

return (
<PageLayout>
<div className="min-h-screen bg-white p-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="max-w-6xl mx-auto"
>
<div className="flex flex-row justify-between items-center mb-8">
<div>
<h1 className="text-4xl font-bold">Admin Dashboard</h1>
<p className="text-gray-600 mt-2">Manage website content</p>
</div>
<Button asChild variant="outline">
<Link href="/">
<ExternalLink className="h-4 w-4 mr-2" />
View Website
</Link>
</Button>
<Button asChild variant="destructive" onClick={handleLogout}>
<Link href="/">
<ExternalLink className="h-4 w-4 mr-2" />
Logout
</Link>
</Button>
</div>

<Tabs defaultValue="hero" value={activeTab} onValueChange={setActiveTab} className="w-full mb-12">
<div className="overflow-x-auto pb-2">
<TabsList className="mb-8">
<TabsTrigger value="hero" className="flex items-center gap-2">
<Home className="h-4 w-4" /> Hero Section
</TabsTrigger>
<TabsTrigger value="projects" className="flex items-center gap-2">
<FileText className="h-4 w-4" /> Projects
</TabsTrigger>
<TabsTrigger value="activities" className="flex items-center gap-2">
<FileText className="h-4 w-4" /> Activities
</TabsTrigger>
<TabsTrigger value="events" className="flex items-center gap-2">
<Calendar className="h-4 w-4" /> Events
</TabsTrigger>
<TabsTrigger value="team" className="flex items-center gap-2">
<Users className="h-4 w-4" /> Team
</TabsTrigger>
</TabsList>
</div>

<Card className="shadow-md">
<CardHeader>
<CardTitle>
{activeTab === "hero" && "Hero Section Editor"}
{activeTab === "projects" && "Projects Editor"}
{activeTab === "activities" && "Activities Editor"}
{activeTab === "events" && "Events Editor"}
{activeTab === "team" && "Team Members Editor"}
</CardTitle>
<CardDescription>
{activeTab === "hero" && "Edit the main heading, text and background images"}
{activeTab === "projects" && "Add, edit or remove projects"}
{activeTab === "activities" && "Manage robotics activities and workshops"}
{activeTab === "events" && "Add or edit upcoming events and competitions"}
{activeTab === "team" && "Manage team members and their information"}
</CardDescription>
</CardHeader>
<CardContent>
<TabsContent value="hero" className="mt-0">
<HeroEditor />
</TabsContent>

<TabsContent value="projects" className="mt-0">
<ProjectsEditor />
</TabsContent>

<TabsContent value="activities" className="mt-0">
<ActivitiesEditor />
</TabsContent>

<TabsContent value="events" className="mt-0">
<EventsEditor />
</TabsContent>

<TabsContent value="team" className="mt-0">
<TeamEditor />
</TabsContent>
</CardContent>
</Card>
</Tabs>

<div className="mt-8">
<h2 className="text-xl font-semibold mb-4">Preview Pages</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<h3 className="text-lg font-medium mb-2">Activities Page</h3>
<p className="text-gray-600 text-sm mb-4">View all robotics activities and workshops.</p>
<Button asChild variant="outline" className="w-full">
<Link href="/activities" target="_blank">
<ExternalLink className="h-4 w-4 mr-2" />
View Page
</Link>
</Button>
</CardContent>
</Card>

<Card className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<h3 className="text-lg font-medium mb-2">Events Page</h3>
<p className="text-gray-600 text-sm mb-4">View upcoming events and competitions.</p>
<Button asChild variant="outline" className="w-full">
<Link href="/events" target="_blank">
<ExternalLink className="h-4 w-4 mr-2" />
View Page
</Link>
</Button>
</CardContent>
</Card>

<Card className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<h3 className="text-lg font-medium mb-2">Team Page</h3>
<p className="text-gray-600 text-sm mb-4">View all team members and their profiles.</p>
<Button asChild variant="outline" className="w-full">
<Link href="/team" target="_blank">
<ExternalLink className="h-4 w-4 mr-2" />
View Page
</Link>
</Button>
</CardContent>
</Card>
</div>
</div>
</motion.div>
</div>
</PageLayout >
);
};

export default Admin;
76 changes: 76 additions & 0 deletions src/components/Blob.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { SetStateAction, useRef, useState } from 'react'
import { useToast } from './ui/use-toast';
import { Button } from './ui/button';
import { Project } from '@/types';

const Blob = ({ id, onChange, setFileName }: { id: string, onChange: (value: SetStateAction<Project>) => void, setFileName: (value: SetStateAction<string>) => void }) => {
const [isLoading, setIsLoading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { toast } = useToast();

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;

// Check if the file is an image
if (!file.type.match('image.*')) {
toast({
title: "Invalid file type",
description: "Please select an image file",
variant: "destructive",
});
return;
}

setIsLoading(true);
const reader = new FileReader();

reader.onload = (e) => {
const result = e.target?.result as string;
onChange(prev => ({ ...prev, [id]: result }));
setFileName(file.name);
setIsLoading(false);
toast({
title: "Success!",
description: "Image successfully converted to base64",
});
};

reader.onerror = () => {
setIsLoading(false);
toast({
title: "Error",
description: "Failed to read the image file",
variant: "destructive",
});
};

reader.readAsDataURL(file);
};

const handleButtonClick = () => {
fileInputRef.current?.click();
};

return (
<div>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept="image/*"
className="hidden"
/>

<Button
onClick={handleButtonClick}
className="w-full mb-4"
disabled={isLoading}
>
{isLoading ? "Converting..." : "Select Image"}
</Button>
</div>
)
}

export default Blob;
Loading
Loading