|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | 3 | import React, { useState, useEffect } from 'react'; |
4 | | -import { useStore, Experience, Education } from '../lib/store'; |
| 4 | +import { useStore, Experience, Education, Service } from '../lib/store'; |
5 | 5 | import { useRouter } from '../lib/router'; |
6 | | -import { Plus, Trash2, FolderKanban, Mail, LogOut, Settings, FileText, User, Edit, X, Save, CheckCheck, ExternalLink, Briefcase, GraduationCap, ChevronLeft, ChevronRight } from 'lucide-react'; |
| 6 | +import { Plus, Trash2, FolderKanban, Mail, LogOut, Settings, FileText, User, Edit, X, Save, CheckCheck, ExternalLink, Briefcase, GraduationCap, ChevronLeft, ChevronRight, Zap } from 'lucide-react'; |
7 | 7 | import { toast } from 'sonner'; |
8 | 8 |
|
9 | 9 | const ADMIN_ITEMS_PER_PAGE = 5; |
10 | 10 |
|
11 | 11 | export default function AdminPage() { |
12 | 12 | const { |
13 | | - projects, posts, messages, profile, experience, education, |
| 13 | + projects, posts, messages, profile, experience, education, services, |
14 | 14 | deleteProject, addProject, updateProject, |
15 | 15 | deletePost, addPost, updatePost, |
16 | 16 | updateProfile, setExperience, updateExperience, setEducation, updateEducation, |
| 17 | + setServices, updateService, deleteService, |
17 | 18 | markMessageRead, markAllMessagesRead, deleteMessage, |
18 | 19 | logout |
19 | 20 | } = useStore(); |
20 | 21 |
|
21 | 22 | const { navigate } = useRouter(); |
22 | | - const [activeTab, setActiveTab] = useState<'overview' | 'resume' | 'projects' | 'blog' | 'messages'>('overview'); |
| 23 | + const [activeTab, setActiveTab] = useState<'overview' | 'resume' | 'services' | 'projects' | 'blog' | 'messages'>('overview'); |
23 | 24 |
|
24 | 25 | // -- PAGINATION STATES -- |
25 | 26 | const [projectPage, setProjectPage] = useState(1); |
@@ -57,6 +58,11 @@ export default function AdminPage() { |
57 | 58 | const [showExpForm, setShowExpForm] = useState(false); |
58 | 59 | const [showEduForm, setShowEduForm] = useState(false); |
59 | 60 |
|
| 61 | + // -- SERVICES STATES -- |
| 62 | + const [serviceForm, setServiceForm] = useState<{title: string, description: string, icon: Service['icon']}>({ title: '', description: '', icon: 'code' }); |
| 63 | + const [editingServiceId, setEditingServiceId] = useState<string | null>(null); |
| 64 | + const [showServiceForm, setShowServiceForm] = useState(false); |
| 65 | + |
60 | 66 | // Unread Messages Count |
61 | 67 | const unreadCount = messages ? messages.filter(m => !m.is_read).length : 0; |
62 | 68 |
|
@@ -147,6 +153,33 @@ export default function AdminPage() { |
147 | 153 | }; |
148 | 154 | const handleDeleteEdu = (id: string) => setEducation(education.filter(e => e.id !== id)); |
149 | 155 |
|
| 156 | + // --- SERVICES HANDLERS --- |
| 157 | + const handleEditService = (srv?: Service) => { |
| 158 | + if (srv) { |
| 159 | + setEditingServiceId(srv.id); |
| 160 | + setServiceForm({ |
| 161 | + title: srv.title, |
| 162 | + description: srv.description, |
| 163 | + icon: srv.icon |
| 164 | + }); |
| 165 | + } else { |
| 166 | + setEditingServiceId(null); |
| 167 | + setServiceForm({ title: '', description: '', icon: 'code' }); |
| 168 | + } |
| 169 | + setShowServiceForm(true); |
| 170 | + } |
| 171 | + |
| 172 | + const saveService = (e: React.FormEvent) => { |
| 173 | + e.preventDefault(); |
| 174 | + if (editingServiceId) { |
| 175 | + updateService(editingServiceId, serviceForm); |
| 176 | + toast.success("Service updated!"); |
| 177 | + } else { |
| 178 | + setServices([...services, { ...serviceForm, id: Math.random().toString(36).substr(2, 9) }]); |
| 179 | + toast.success("Service added!"); |
| 180 | + } |
| 181 | + setShowServiceForm(false); |
| 182 | + } |
150 | 183 |
|
151 | 184 | // --- PROJECT HANDLERS --- |
152 | 185 | const openProjectForm = (project?: any) => { |
@@ -256,6 +289,7 @@ export default function AdminPage() { |
256 | 289 | {[ |
257 | 290 | {id: 'overview', icon: User, label: 'Overview'}, |
258 | 291 | {id: 'resume', icon: Briefcase, label: 'Resume'}, |
| 292 | + {id: 'services', icon: Zap, label: 'Services'}, |
259 | 293 | {id: 'projects', icon: FolderKanban, label: 'Projects'}, |
260 | 294 | {id: 'blog', icon: FileText, label: 'Blog'}, |
261 | 295 | {id: 'messages', icon: Mail, label: 'Messages'}, |
@@ -485,6 +519,69 @@ export default function AdminPage() { |
485 | 519 | </div> |
486 | 520 | )} |
487 | 521 |
|
| 522 | + {activeTab === 'services' && ( |
| 523 | + <div className="max-w-4xl"> |
| 524 | + <h1 className="text-2xl font-bold text-slate-900 dark:text-white mb-6">Services Management</h1> |
| 525 | + |
| 526 | + <div className="mb-12"> |
| 527 | + <div className="flex justify-between items-center mb-4"> |
| 528 | + <h2 className="text-xl font-semibold text-slate-900 dark:text-white">Offered Services</h2> |
| 529 | + <button onClick={() => handleEditService()} className="text-sm bg-indigo-600 text-white px-3 py-1.5 rounded-lg flex items-center gap-1 hover:bg-indigo-700"><Plus className="h-4 w-4" /> Add</button> |
| 530 | + </div> |
| 531 | + |
| 532 | + {showServiceForm && ( |
| 533 | + <form onSubmit={saveService} className="bg-white border border-slate-200 dark:bg-slate-900 p-6 rounded-xl dark:border-white/10 mb-6 space-y-4 shadow-sm"> |
| 534 | + <h3 className="text-slate-900 dark:text-white font-medium mb-2">{editingServiceId ? 'Edit Service' : 'New Service'}</h3> |
| 535 | + <input placeholder="Service Title (e.g., Web Development)" required value={serviceForm.title} onChange={e => setServiceForm({...serviceForm, title: e.target.value})} className="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-white/10 rounded px-3 py-2 text-slate-900 dark:text-white" /> |
| 536 | + <textarea placeholder="Description" required value={serviceForm.description} onChange={e => setServiceForm({...serviceForm, description: e.target.value})} className="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-white/10 rounded px-3 py-2 text-slate-900 dark:text-white" rows={3} /> |
| 537 | + |
| 538 | + <div> |
| 539 | + <label className="block text-sm text-slate-500 dark:text-slate-400 mb-2">Icon</label> |
| 540 | + <div className="flex flex-wrap gap-2"> |
| 541 | + {(['code', 'smartphone', 'cloud', 'terminal', 'layout', 'database'] as const).map(icon => ( |
| 542 | + <button |
| 543 | + key={icon} |
| 544 | + type="button" |
| 545 | + onClick={() => setServiceForm({...serviceForm, icon})} |
| 546 | + className={`p-2 rounded border transition-colors capitalize text-xs ${serviceForm.icon === icon ? 'bg-indigo-600 text-white border-indigo-600' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-white/10'}`} |
| 547 | + > |
| 548 | + {icon} |
| 549 | + </button> |
| 550 | + ))} |
| 551 | + </div> |
| 552 | + </div> |
| 553 | + |
| 554 | + <div className="flex justify-end gap-2"> |
| 555 | + <button type="button" onClick={() => setShowServiceForm(false)} className="text-slate-500 dark:text-slate-400 text-sm">Cancel</button> |
| 556 | + <button type="submit" className="bg-indigo-600 text-white px-4 py-1.5 rounded-lg text-sm">{editingServiceId ? 'Update' : 'Save'}</button> |
| 557 | + </div> |
| 558 | + </form> |
| 559 | + )} |
| 560 | + |
| 561 | + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| 562 | + {services && services.map(srv => ( |
| 563 | + <div key={srv.id} className="bg-white border border-slate-200 dark:bg-slate-900 dark:border-white/5 p-4 rounded-lg flex justify-between items-start group shadow-sm"> |
| 564 | + <div className="flex items-start gap-3"> |
| 565 | + <div className="p-2 bg-slate-100 dark:bg-slate-800 rounded text-indigo-600 dark:text-indigo-400"> |
| 566 | + <Zap className="h-5 w-5" /> |
| 567 | + </div> |
| 568 | + <div> |
| 569 | + <h3 className="font-bold text-slate-900 dark:text-white text-sm">{srv.title}</h3> |
| 570 | + <p className="text-slate-600 dark:text-slate-400 text-xs mt-1 leading-relaxed">{srv.description}</p> |
| 571 | + <div className="mt-2 inline-block px-2 py-0.5 rounded bg-slate-100 dark:bg-slate-800 text-xs text-slate-500 dark:text-slate-400 capitalize">{srv.icon}</div> |
| 572 | + </div> |
| 573 | + </div> |
| 574 | + <div className="flex flex-col gap-2 opacity-0 group-hover:opacity-100 transition-opacity"> |
| 575 | + <button onClick={() => handleEditService(srv)} className="text-blue-500 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-500/10 p-1 rounded"><Edit className="h-4 w-4" /></button> |
| 576 | + <button onClick={() => deleteService(srv.id)} className="text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10 p-1 rounded"><Trash2 className="h-4 w-4" /></button> |
| 577 | + </div> |
| 578 | + </div> |
| 579 | + ))} |
| 580 | + </div> |
| 581 | + </div> |
| 582 | + </div> |
| 583 | + )} |
| 584 | + |
488 | 585 | {activeTab === 'projects' && ( |
489 | 586 | <div> |
490 | 587 | <div className="flex justify-between items-center mb-6"> |
|
0 commit comments