Skip to content

Commit b3ca96a

Browse files
blogs v1
1 parent e7b7690 commit b3ca96a

14 files changed

Lines changed: 1566 additions & 6 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Navigate, Outlet } from 'react-router-dom';
2+
import { usePublicAuth } from '../../context/PublicAuthContext';
3+
import Loader from '../ui/Loader';
4+
5+
export default function ProtectedRoute() {
6+
const { isAuthenticated, isLoading } = usePublicAuth();
7+
8+
if (isLoading) {
9+
return <div className="h-screen flex items-center justify-center"><Loader /></div>;
10+
}
11+
12+
// If not logged in, redirect to Home (since login is a modal)
13+
if (!isAuthenticated) {
14+
return <Navigate to="/" replace />;
15+
}
16+
17+
return <Outlet />;
18+
}
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { motion } from 'framer-motion';
3+
import { FiMessageSquare, FiHeart, FiClock, FiExternalLink, FiArrowRight } from 'react-icons/fi';
4+
import { getUserActivity } from '../../services/publicUserService';
5+
import Spinner from '../ui/Spinner';
6+
import { Link } from 'react-router-dom';
7+
8+
export default function ActivitySettings() {
9+
const { data, isLoading } = useQuery({
10+
queryKey: ['userActivity'],
11+
queryFn: () => getUserActivity(undefined, 1, 10), // No type filter for overview, get defaults
12+
});
13+
14+
if (isLoading) {
15+
return (
16+
<div className="py-20 flex justify-center">
17+
<Spinner />
18+
</div>
19+
);
20+
}
21+
22+
const comments = data?.comments?.data || [];
23+
const commentTotal = data?.comments?.total || 0;
24+
const likes = data?.likes?.data || [];
25+
const likesTotal = data?.likes?.total || 0;
26+
27+
// Show only recent 4 for overview
28+
const recentComments = comments.slice(0, 4);
29+
const recentLikes = likes.slice(0, 4);
30+
31+
const containerVariants = {
32+
hidden: { opacity: 0 },
33+
visible: {
34+
opacity: 1,
35+
transition: {
36+
staggerChildren: 0.1,
37+
},
38+
},
39+
};
40+
41+
const itemVariants = {
42+
hidden: { opacity: 0, y: 20 },
43+
visible: { opacity: 1, y: 0 },
44+
};
45+
46+
return (
47+
<motion.div
48+
initial={{ opacity: 0, y: 20 }}
49+
animate={{ opacity: 1, y: 0 }}
50+
transition={{ duration: 0.3 }}
51+
>
52+
<div className="border-b border-slate-200 pb-5 mb-6">
53+
<h2 className="text-xl sm:text-2xl font-bold text-slate-900">My Activity</h2>
54+
<p className="text-xs sm:text-sm text-slate-600 mt-1">
55+
View your comments and liked articles
56+
</p>
57+
</div>
58+
59+
<div className="space-y-8">
60+
{/* Activity Stats */}
61+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
62+
<motion.div
63+
initial={{ opacity: 0, scale: 0.95 }}
64+
animate={{ opacity: 1, scale: 1 }}
65+
transition={{ delay: 0.1 }}
66+
className="p-6 bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl border border-blue-200"
67+
>
68+
<div className="flex items-center justify-between">
69+
<div>
70+
<p className="text-sm font-medium text-blue-800">Total Comments</p>
71+
<p className="text-3xl font-bold text-blue-900 mt-1">{commentTotal}</p>
72+
</div>
73+
<div className="w-12 h-12 bg-blue-200 rounded-full flex items-center justify-center">
74+
<FiMessageSquare className="text-blue-700 text-xl" />
75+
</div>
76+
</div>
77+
</motion.div>
78+
79+
<motion.div
80+
initial={{ opacity: 0, scale: 0.95 }}
81+
animate={{ opacity: 1, scale: 1 }}
82+
transition={{ delay: 0.2 }}
83+
className="p-6 bg-gradient-to-br from-red-50 to-pink-100 rounded-xl border border-pink-200"
84+
>
85+
<div className="flex items-center justify-between">
86+
<div>
87+
<p className="text-sm font-medium text-red-800">Liked Articles</p>
88+
<p className="text-3xl font-bold text-red-900 mt-1">{likesTotal}</p>
89+
</div>
90+
<div className="w-12 h-12 bg-pink-200 rounded-full flex items-center justify-center">
91+
<FiHeart className="text-red-700 text-xl" />
92+
</div>
93+
</div>
94+
</motion.div>
95+
</div>
96+
97+
{/* Recent Comments */}
98+
<div>
99+
<div className="flex items-center justify-between gap-2 mb-4">
100+
<div className="flex items-center gap-2">
101+
<FiMessageSquare className="text-blue-600 text-xl" />
102+
<h3 className="text-lg font-bold text-slate-800">Recent Comments</h3>
103+
</div>
104+
{commentTotal > 4 && (
105+
<Link
106+
to="/my-comments"
107+
className="text-xs sm:text-sm text-blue-600 hover:text-blue-700 font-medium flex items-center gap-1 hover:gap-2 transition-all"
108+
>
109+
View all ({commentTotal})
110+
<FiArrowRight className="text-xs" />
111+
</Link>
112+
)}
113+
</div>
114+
115+
{recentComments.length === 0 ? (
116+
<EmptyState
117+
icon={<FiMessageSquare />}
118+
title="No comments yet"
119+
description="Start engaging with articles by leaving your thoughts!"
120+
/>
121+
) : (
122+
<motion.div
123+
variants={containerVariants}
124+
initial="hidden"
125+
animate="visible"
126+
className="space-y-3"
127+
>
128+
{recentComments.map((c: any) => (
129+
<motion.div
130+
key={c.id}
131+
variants={itemVariants}
132+
className="group p-5 bg-white border border-slate-200 rounded-xl hover:shadow-md hover:border-blue-300 transition-all duration-200"
133+
>
134+
<div className="flex items-start gap-3">
135+
<div className="mt-1 w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0">
136+
<FiMessageSquare className="text-blue-600 text-sm" />
137+
</div>
138+
<div className="flex-1 min-w-0">
139+
<p className="text-slate-800 leading-relaxed mb-3 italic">
140+
"{c.content}"
141+
</p>
142+
<div className="flex flex-wrap items-center gap-2 text-xs text-slate-500">
143+
<span className="flex items-center gap-1">
144+
<FiClock className="text-slate-400" />
145+
{new Date(c.createdAt).toLocaleDateString('en-US', {
146+
month: 'short',
147+
day: 'numeric',
148+
year: 'numeric',
149+
})}
150+
</span>
151+
<span className="text-slate-300"></span>
152+
<span>on article:</span>
153+
<a
154+
href={`/#/blogs/${c.post.slug}`}
155+
className="inline-flex items-center gap-1 text-blue-600 hover:text-blue-700 font-medium hover:underline"
156+
>
157+
{c.post.title}
158+
<FiExternalLink className="text-xs" />
159+
</a>
160+
</div>
161+
</div>
162+
</div>
163+
</motion.div>
164+
))}
165+
</motion.div>
166+
)}
167+
</div>
168+
169+
{/* Liked Posts */}
170+
<div>
171+
<div className="flex items-center justify-between gap-2 mb-4">
172+
<div className="flex items-center gap-2">
173+
<FiHeart className="text-red-600 text-xl" />
174+
<h3 className="text-lg font-bold text-slate-800">Liked Articles</h3>
175+
</div>
176+
{likesTotal > 4 && (
177+
<Link
178+
to="/my-likes"
179+
className="text-xs sm:text-sm text-red-600 hover:text-red-700 font-medium flex items-center gap-1 hover:gap-2 transition-all"
180+
>
181+
View all ({likesTotal})
182+
<FiArrowRight className="text-xs" />
183+
</Link>
184+
)}
185+
</div>
186+
187+
{recentLikes.length === 0 ? (
188+
<EmptyState
189+
icon={<FiHeart />}
190+
title="No liked articles yet"
191+
description="Show some love by liking articles you enjoy!"
192+
/>
193+
) : (
194+
<motion.div
195+
variants={containerVariants}
196+
initial="hidden"
197+
animate="visible"
198+
className="grid grid-cols-1 sm:grid-cols-2 gap-4"
199+
>
200+
{recentLikes.map((l: any) => (
201+
<motion.a
202+
key={l.id}
203+
variants={itemVariants}
204+
href={`/#/blogs/${l.post.slug}`}
205+
className="group block bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-lg hover:border-red-300 transition-all duration-200"
206+
>
207+
<div className="relative h-40 bg-gradient-to-br from-slate-200 to-slate-300 overflow-hidden">
208+
{l.post.coverImage ? (
209+
<img
210+
src={l.post.coverImage}
211+
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"
212+
alt={l.post.title}
213+
/>
214+
) : (
215+
<div className="w-full h-full flex items-center justify-center">
216+
<FiHeart className="text-slate-400 text-4xl" />
217+
</div>
218+
)}
219+
<div className="absolute top-3 right-3 w-8 h-8 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center">
220+
<FiHeart className="text-red-500 fill-red-500 text-sm" />
221+
</div>
222+
</div>
223+
<div className="p-4">
224+
<h4 className="font-semibold text-slate-900 group-hover:text-blue-600 transition-colors line-clamp-2 mb-2">
225+
{l.post.title}
226+
</h4>
227+
<div className="flex items-center gap-2 text-xs text-slate-500">
228+
<FiClock className="text-slate-400" />
229+
<span>
230+
Liked on{' '}
231+
{new Date(l.createdAt).toLocaleDateString('en-US', {
232+
month: 'short',
233+
day: 'numeric',
234+
year: 'numeric',
235+
})}
236+
</span>
237+
</div>
238+
</div>
239+
</motion.a>
240+
))}
241+
</motion.div>
242+
)}
243+
</div>
244+
</div>
245+
</motion.div>
246+
);
247+
}
248+
249+
// Empty State Component
250+
interface EmptyStateProps {
251+
icon: React.ReactNode;
252+
title: string;
253+
description: string;
254+
}
255+
256+
function EmptyState({ icon, title, description }: EmptyStateProps) {
257+
return (
258+
<motion.div
259+
initial={{ opacity: 0, scale: 0.95 }}
260+
animate={{ opacity: 1, scale: 1 }}
261+
className="py-12 px-6 text-center bg-slate-50 border-2 border-dashed border-slate-200 rounded-xl"
262+
>
263+
<div className="w-16 h-16 mx-auto bg-slate-100 rounded-full flex items-center justify-center text-slate-400 text-2xl mb-4">
264+
{icon}
265+
</div>
266+
<h3 className="text-lg font-semibold text-slate-700 mb-2">{title}</h3>
267+
<p className="text-sm text-slate-500">{description}</p>
268+
</motion.div>
269+
);
270+
}

0 commit comments

Comments
 (0)