Skip to content

Commit e4f0d16

Browse files
committed
feat: Add dynamic footer and legal content
Introduce dynamic display of footer text and legal content (privacy policy, terms of service) by fetching them from the store. This replaces hardcoded values and allows for easier content management. Also, refactors the admin page to include a new "Legal" tab to manage this content and updates the store to include the new fields.
1 parent 596584e commit e4f0d16

5 files changed

Lines changed: 78 additions & 67 deletions

File tree

app/admin.tsx

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React, { useState, useEffect } from 'react';
44
import { useStore, Experience, Education, Service } from '../lib/store';
55
import { useRouter } from '../lib/router';
6-
import { Plus, Trash2, FolderKanban, Mail, LogOut, Settings, FileText, User, Edit, X, Save, CheckCheck, ExternalLink, Briefcase, GraduationCap, ChevronLeft, ChevronRight, Zap } from 'lucide-react';
6+
import { Plus, Trash2, FolderKanban, Mail, LogOut, Settings, FileText, User, Edit, X, Save, CheckCheck, ExternalLink, Briefcase, GraduationCap, ChevronLeft, ChevronRight, Zap, Scale } from 'lucide-react';
77
import { toast } from 'sonner';
88

99
const ADMIN_ITEMS_PER_PAGE = 5;
@@ -20,7 +20,7 @@ export default function AdminPage() {
2020
} = useStore();
2121

2222
const { navigate } = useRouter();
23-
const [activeTab, setActiveTab] = useState<'overview' | 'resume' | 'services' | 'projects' | 'blog' | 'messages'>('overview');
23+
const [activeTab, setActiveTab] = useState<'overview' | 'resume' | 'services' | 'legal' | 'projects' | 'blog' | 'messages'>('overview');
2424

2525
// -- PAGINATION STATES --
2626
const [projectPage, setProjectPage] = useState(1);
@@ -41,7 +41,7 @@ export default function AdminPage() {
4141

4242
// -- PROFILE STATE --
4343
const [profileForm, setProfileForm] = useState<typeof profile>(profile || {
44-
logo_text: '', logo_url: '', name: '', avatar_url: '', title: '', description: '', detailed_bio: '', address: '', resume_url: '', badge: '',
44+
logo_text: '', logo_url: '', name: '', avatar_url: '', title: '', description: '', detailed_bio: '', address: '', resume_url: '', badge: '', footer_text: '', privacy_content: '', terms_content: '',
4545
socials: { github: '', linkedin: '', twitter: '', instagram: '', youtube: '', whatsapp: '', mail: '', steam: '' }
4646
});
4747

@@ -83,7 +83,7 @@ export default function AdminPage() {
8383
const handleUpdateProfile = (e: React.FormEvent) => {
8484
e.preventDefault();
8585
updateProfile(profileForm);
86-
toast.success("Profile settings updated!");
86+
toast.success("Settings updated!");
8787
};
8888

8989
// --- EXPERIENCE HANDLERS ---
@@ -290,6 +290,7 @@ export default function AdminPage() {
290290
{id: 'overview', icon: User, label: 'Overview'},
291291
{id: 'resume', icon: Briefcase, label: 'Resume'},
292292
{id: 'services', icon: Zap, label: 'Services'},
293+
{id: 'legal', icon: Scale, label: 'Legal & Footer'},
293294
{id: 'projects', icon: FolderKanban, label: 'Projects'},
294295
{id: 'blog', icon: FileText, label: 'Blog'},
295296
{id: 'messages', icon: Mail, label: 'Messages'},
@@ -415,6 +416,42 @@ export default function AdminPage() {
415416
</div>
416417
)}
417418

419+
{activeTab === 'legal' && (
420+
<div className="max-w-4xl">
421+
<h1 className="text-2xl font-bold text-slate-900 dark:text-white mb-6">Legal Pages & Footer</h1>
422+
<form onSubmit={handleUpdateProfile} className="space-y-6">
423+
{/* Footer Settings */}
424+
<div className="grid gap-6 p-6 bg-white border border-slate-200 dark:bg-slate-900 dark:border-white/10 rounded-xl shadow-sm">
425+
<h3 className="text-lg font-medium text-slate-900 dark:text-white border-b border-slate-100 dark:border-white/5 pb-2">Footer Settings</h3>
426+
<div>
427+
<label className="block text-sm text-slate-600 dark:text-slate-400 mb-1">Footer Copyright Text</label>
428+
<input value={profileForm?.footer_text || ''} onChange={e => setProfileForm({...profileForm, footer_text: 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" />
429+
</div>
430+
</div>
431+
432+
{/* Privacy Policy */}
433+
<div className="grid gap-6 p-6 bg-white border border-slate-200 dark:bg-slate-900 dark:border-white/10 rounded-xl shadow-sm">
434+
<h3 className="text-lg font-medium text-slate-900 dark:text-white border-b border-slate-100 dark:border-white/5 pb-2">Privacy Policy</h3>
435+
<div>
436+
<label className="block text-sm text-slate-600 dark:text-slate-400 mb-1">Content (Markdown Supported)</label>
437+
<textarea rows={12} value={profileForm?.privacy_content || ''} onChange={e => setProfileForm({...profileForm, privacy_content: 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 font-mono text-sm" />
438+
</div>
439+
</div>
440+
441+
{/* Terms of Service */}
442+
<div className="grid gap-6 p-6 bg-white border border-slate-200 dark:bg-slate-900 dark:border-white/10 rounded-xl shadow-sm">
443+
<h3 className="text-lg font-medium text-slate-900 dark:text-white border-b border-slate-100 dark:border-white/5 pb-2">Terms of Service</h3>
444+
<div>
445+
<label className="block text-sm text-slate-600 dark:text-slate-400 mb-1">Content (Markdown Supported)</label>
446+
<textarea rows={12} value={profileForm?.terms_content || ''} onChange={e => setProfileForm({...profileForm, terms_content: 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 font-mono text-sm" />
447+
</div>
448+
</div>
449+
450+
<button type="submit" className="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium flex items-center gap-2"><Save className="h-4 w-4" /> Save Changes</button>
451+
</form>
452+
</div>
453+
)}
454+
418455
{activeTab === 'resume' && (
419456
<div className="max-w-4xl">
420457
<h1 className="text-2xl font-bold text-slate-900 dark:text-white mb-6">Resume / CV Management</h1>
@@ -560,7 +597,7 @@ export default function AdminPage() {
560597

561598
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
562599
{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">
600+
<div key={srv.id} className="bg-white border border-slate-200 dark:bg-slate-900 dark:border-white/10 p-4 rounded-lg flex justify-between items-start group shadow-sm">
564601
<div className="flex items-start gap-3">
565602
<div className="p-2 bg-slate-100 dark:bg-slate-800 rounded text-indigo-600 dark:text-indigo-400">
566603
<Zap className="h-5 w-5" />

app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const TECH_STACK = [
2323
];
2424

2525
export default function Page() {
26-
const { projects, posts } = useStore();
26+
const { projects, posts, profile } = useStore();
2727
const { navigate } = useRouter();
2828

2929
// Filter only featured projects for homepage
@@ -154,7 +154,7 @@ export default function Page() {
154154
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
155155
<div className="flex flex-col md:flex-row justify-between items-center gap-6">
156156
<span className="text-slate-500 text-sm">
157-
© {new Date().getFullYear()} DevFolio. Built with Next.js & Supabase.
157+
{profile.footer_text}
158158
</span>
159159
<div className="flex gap-6">
160160
<button onClick={() => navigate('/privacy')} className="text-slate-500 hover:text-slate-900 dark:hover:text-white text-sm">Privacy Policy</button>

app/privacy.tsx

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,21 @@
22

33
import React from 'react';
44
import { Navbar } from '../components/Navbar';
5+
import { useStore } from '../lib/store';
56

67
export default function PrivacyPage() {
8+
const { profile } = useStore();
9+
710
return (
811
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-white transition-colors duration-300">
912
<Navbar />
1013
<div className="pt-32 pb-20 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
1114
<h1 className="text-4xl font-bold mb-8">Privacy Policy</h1>
1215
<div className="prose prose-slate dark:prose-invert max-w-none">
13-
<p>Last updated: {new Date().toLocaleDateString()}</p>
14-
<p>
15-
Your privacy is important to us. It is our policy to respect your privacy regarding any information we may collect from you across our website.
16-
</p>
17-
18-
<h3>1. Information We Collect</h3>
19-
<p>
20-
We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.
21-
</p>
22-
23-
<h3>2. Log Data</h3>
24-
<p>
25-
When you visit our website, our servers may automatically log the standard data provided by your web browser. It includes your computer’s Internet Protocol (IP) address, your browser type and version, the pages you visit, the time and date of your visit, the time spent on each page, and other details.
26-
</p>
27-
28-
<h3>3. Use of Information</h3>
29-
<p>
30-
We may use the information we collect from you to:
31-
</p>
32-
<ul>
33-
<li>Provide and operate our services</li>
34-
<li>Respond to your comments, questions, and requests</li>
35-
<li>Send you technical notices, updates, security alerts, and support messages</li>
36-
</ul>
37-
38-
<h3>4. Third-Party Services</h3>
39-
<p>
40-
We do not share any personally identifying information publicly or with third-parties, except when required to by law.
41-
</p>
42-
43-
<h3>5. Contact Us</h3>
44-
<p>
45-
If you have any questions about this privacy policy, please contact us via the contact form on our website.
46-
</p>
16+
<p className="text-sm text-slate-500 mb-8">Last updated: {new Date().toLocaleDateString()}</p>
17+
<div className="whitespace-pre-wrap leading-relaxed text-slate-800 dark:text-slate-300">
18+
{profile.privacy_content}
19+
</div>
4720
</div>
4821
</div>
4922
</div>

app/terms.tsx

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,21 @@
22

33
import React from 'react';
44
import { Navbar } from '../components/Navbar';
5+
import { useStore } from '../lib/store';
56

67
export default function TermsPage() {
8+
const { profile } = useStore();
9+
710
return (
811
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-white transition-colors duration-300">
912
<Navbar />
1013
<div className="pt-32 pb-20 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
1114
<h1 className="text-4xl font-bold mb-8">Terms of Service</h1>
1215
<div className="prose prose-slate dark:prose-invert max-w-none">
13-
<p>Last updated: {new Date().toLocaleDateString()}</p>
14-
15-
<h3>1. Terms</h3>
16-
<p>
17-
By accessing this website, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws.
18-
</p>
19-
20-
<h3>2. Use License</h3>
21-
<p>
22-
Permission is granted to temporarily download one copy of the materials (information or software) on this website for personal, non-commercial transitory viewing only.
23-
</p>
24-
25-
<h3>3. Disclaimer</h3>
26-
<p>
27-
The materials on this website are provided on an 'as is' basis. We make no warranties, expressed or implied, and hereby disclaim and negate all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.
28-
</p>
29-
30-
<h3>4. Limitations</h3>
31-
<p>
32-
In no event shall we or our suppliers be liable for any damages (including, without limitation, damages for loss of data or profit, or due to business interruption) arising out of the use or inability to use the materials on this website.
33-
</p>
34-
35-
<h3>5. Accuracy of Materials</h3>
36-
<p>
37-
The materials appearing on our website could include technical, typographical, or photographic errors. We do not warrant that any of the materials on our website are accurate, complete or current.
38-
</p>
16+
<p className="text-sm text-slate-500 mb-8">Last updated: {new Date().toLocaleDateString()}</p>
17+
<div className="whitespace-pre-wrap leading-relaxed text-slate-800 dark:text-slate-300">
18+
{profile.terms_content}
19+
</div>
3920
</div>
4021
</div>
4122
</div>

lib/store.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ export interface ProfileConfig {
5959
address: string;
6060
resume_url: string;
6161
badge: string;
62+
footer_text: string;
63+
privacy_content: string;
64+
terms_content: string;
6265
socials: {
6366
github: string;
6467
linkedin: string;
@@ -82,6 +85,23 @@ const INITIAL_PROFILE: ProfileConfig = {
8285
address: "Jakarta, Indonesia",
8386
resume_url: "#",
8487
badge: 'Full Stack Software Engineer',
88+
footer_text: ${new Date().getFullYear()} DevFolio. Built with Next.js & Supabase.`,
89+
privacy_content: `## Privacy Policy
90+
91+
Your privacy is important to us. It is our policy to respect your privacy regarding any information we may collect from you across our website.
92+
93+
### 1. Information We Collect
94+
We only ask for personal information when we truly need it to provide a service to you.
95+
96+
### 2. Log Data
97+
When you visit our website, our servers may automatically log the standard data provided by your web browser.`,
98+
terms_content: `## Terms of Service
99+
100+
### 1. Terms
101+
By accessing this website, you are agreeing to be bound by these terms of service.
102+
103+
### 2. Use License
104+
Permission is granted to temporarily download one copy of the materials on this website for personal, non-commercial transitory viewing only.`,
85105
socials: {
86106
github: 'https://github.com',
87107
linkedin: 'https://linkedin.com',

0 commit comments

Comments
 (0)