Prettier format pending files
This commit is contained in:
parent
1fdc0a40db
commit
15dbb11bcf
6 changed files with 164 additions and 45 deletions
|
|
@ -53,7 +53,10 @@ export default function PostComposer({ onPosted }: { onPosted?: () => void }) {
|
|||
|
||||
const handlePost = async () => {
|
||||
if (!user) {
|
||||
toast({ title: "Sign in required", description: "Please sign in to post" });
|
||||
toast({
|
||||
title: "Sign in required",
|
||||
description: "Please sign in to post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!text.trim() && !mediaFile && !mediaUrlInput.trim()) {
|
||||
|
|
@ -75,15 +78,31 @@ export default function PostComposer({ onPosted }: { onPosted?: () => void }) {
|
|||
}
|
||||
|
||||
if (mediaUrl) {
|
||||
if (/\.(mp4|webm|mov)(\?.*)?$/i.test(mediaUrl) || /video\//.test(mediaFile?.type || "")) {
|
||||
if (
|
||||
/\.(mp4|webm|mov)(\?.*)?$/i.test(mediaUrl) ||
|
||||
/video\//.test(mediaFile?.type || "")
|
||||
) {
|
||||
mediaType = "video";
|
||||
} else if (/\.(png|jpe?g|gif|webp|svg)(\?.*)?$/i.test(mediaUrl) || /image\//.test(mediaFile?.type || "")) {
|
||||
} else if (
|
||||
/\.(png|jpe?g|gif|webp|svg)(\?.*)?$/i.test(mediaUrl) ||
|
||||
/image\//.test(mediaFile?.type || "")
|
||||
) {
|
||||
mediaType = "image";
|
||||
}
|
||||
}
|
||||
|
||||
const content = JSON.stringify({ text: text.trim(), mediaUrl, mediaType });
|
||||
const title = text.trim().slice(0, 80) || (mediaType === "video" ? "New video" : mediaType === "image" ? "New photo" : "Update");
|
||||
const content = JSON.stringify({
|
||||
text: text.trim(),
|
||||
mediaUrl,
|
||||
mediaType,
|
||||
});
|
||||
const title =
|
||||
text.trim().slice(0, 80) ||
|
||||
(mediaType === "video"
|
||||
? "New video"
|
||||
: mediaType === "image"
|
||||
? "New photo"
|
||||
: "Update");
|
||||
|
||||
await communityService.createPost({
|
||||
author_id: user.id,
|
||||
|
|
@ -98,7 +117,11 @@ export default function PostComposer({ onPosted }: { onPosted?: () => void }) {
|
|||
reset();
|
||||
onPosted?.();
|
||||
} catch (e: any) {
|
||||
toast({ variant: "destructive", title: "Could not post", description: e?.message || "Try again later" });
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Could not post",
|
||||
description: e?.message || "Try again later",
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ export const aethexSocialService = {
|
|||
try {
|
||||
const raw = localStorage.getItem("demo_profiles");
|
||||
const profiles = raw ? JSON.parse(raw) : [];
|
||||
return profiles.filter((p:any)=>p.id!==userId).slice(0, limit);
|
||||
} catch { return [] as any[]; }
|
||||
return profiles.filter((p: any) => p.id !== userId).slice(0, limit);
|
||||
} catch {
|
||||
return [] as any[];
|
||||
}
|
||||
},
|
||||
|
||||
async getFollowing(userId: string): Promise<string[]> {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ export function ensureDemoSeed() {
|
|||
username: "TestSubject",
|
||||
full_name: "Test Subject",
|
||||
email: "testsubject@demo.local",
|
||||
avatar_url:
|
||||
"https://i.pravatar.cc/150?img=1",
|
||||
avatar_url: "https://i.pravatar.cc/150?img=1",
|
||||
bio: "Trying features, sharing experiments, and stress-testing the feed.",
|
||||
managed_by: "mrpiglr@gmail.com",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -257,7 +257,9 @@ export const communityService = {
|
|||
const raw = localStorage.getItem("demo_posts");
|
||||
const posts = raw ? JSON.parse(raw) : [];
|
||||
return posts.slice(0, limit);
|
||||
} catch { return []; }
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createPost(
|
||||
|
|
@ -282,12 +284,16 @@ export const communityService = {
|
|||
updated_at: new Date().toISOString(),
|
||||
likes_count: 0,
|
||||
comments_count: 0,
|
||||
user_profiles: (function(){
|
||||
try{
|
||||
const profiles = JSON.parse(localStorage.getItem("demo_profiles")||"[]");
|
||||
return profiles.find((p:any)=>p.id===post.author_id) || null;
|
||||
}catch{ return null; }
|
||||
})()
|
||||
user_profiles: (function () {
|
||||
try {
|
||||
const profiles = JSON.parse(
|
||||
localStorage.getItem("demo_profiles") || "[]",
|
||||
);
|
||||
return profiles.find((p: any) => p.id === post.author_id) || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})(),
|
||||
};
|
||||
const raw = localStorage.getItem("demo_posts");
|
||||
const list = raw ? JSON.parse(raw) : [];
|
||||
|
|
@ -308,8 +314,10 @@ export const communityService = {
|
|||
try {
|
||||
const raw = localStorage.getItem("demo_posts");
|
||||
const posts = raw ? JSON.parse(raw) : [];
|
||||
return posts.filter((p:any)=>p.author_id===userId);
|
||||
} catch { return []; }
|
||||
return posts.filter((p: any) => p.author_id === userId);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,12 @@ export default function Admin() {
|
|||
const { user, loading, roles } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const isOwner = Array.isArray(roles) && roles.includes("owner");
|
||||
const demoProfiles: any[] = (function(){
|
||||
try { return JSON.parse(localStorage.getItem("demo_profiles")||"[]"); } catch { return []; }
|
||||
const demoProfiles: any[] = (function () {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("demo_profiles") || "[]");
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -228,18 +232,28 @@ export default function Admin() {
|
|||
<CardTitle className="text-lg">Demo Accounts</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Managed by <span className="text-foreground">mrpiglr@gmail.com</span>
|
||||
Managed by{" "}
|
||||
<span className="text-foreground">mrpiglr@gmail.com</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{demoProfiles.length === 0 && (
|
||||
<div className="text-sm text-muted-foreground">No demo accounts seeded yet.</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No demo accounts seeded yet.
|
||||
</div>
|
||||
)}
|
||||
{demoProfiles.map((p) => (
|
||||
<div key={p.id} className="flex items-center justify-between p-2 rounded border border-border/40">
|
||||
<div
|
||||
key={p.id}
|
||||
className="flex items-center justify-between p-2 rounded border border-border/40"
|
||||
>
|
||||
<div>
|
||||
<div className="font-medium">{p.full_name || p.username}</div>
|
||||
<div className="text-xs text-muted-foreground">{p.email}</div>
|
||||
<div className="font-medium">
|
||||
{p.full_name || p.username}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{p.email}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline">Managed</Badge>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,13 +33,23 @@ interface FeedItem {
|
|||
comments: number;
|
||||
}
|
||||
|
||||
function parseContent(content: string): { text?: string; mediaUrl?: string | null; mediaType: "video" | "image" | "none" } {
|
||||
function parseContent(content: string): {
|
||||
text?: string;
|
||||
mediaUrl?: string | null;
|
||||
mediaType: "video" | "image" | "none";
|
||||
} {
|
||||
try {
|
||||
const obj = JSON.parse(content || "{}");
|
||||
return {
|
||||
text: obj.text || content,
|
||||
mediaUrl: obj.mediaUrl || null,
|
||||
mediaType: obj.mediaType || (obj.mediaUrl ? (/(mp4|webm|mov)$/i.test(obj.mediaUrl) ? "video" : "image") : "none"),
|
||||
mediaType:
|
||||
obj.mediaType ||
|
||||
(obj.mediaUrl
|
||||
? /(mp4|webm|mov)$/i.test(obj.mediaUrl)
|
||||
? "video"
|
||||
: "image"
|
||||
: "none"),
|
||||
};
|
||||
} catch {
|
||||
return { text: content, mediaUrl: null, mediaType: "none" };
|
||||
|
|
@ -108,7 +118,9 @@ export default function Feed() {
|
|||
|
||||
const sub = realtimeService.subscribeToCommunityPosts(() => load());
|
||||
return () => {
|
||||
try { sub.unsubscribe(); } catch {}
|
||||
try {
|
||||
sub.unsubscribe();
|
||||
} catch {}
|
||||
};
|
||||
}, [user, loading]);
|
||||
|
||||
|
|
@ -128,7 +140,11 @@ export default function Feed() {
|
|||
const url = `${location.origin}/feed#post-${id}`;
|
||||
try {
|
||||
if ((navigator as any).share) {
|
||||
await (navigator as any).share({ title: "AeThex", text: "Check this post", url });
|
||||
await (navigator as any).share({
|
||||
title: "AeThex",
|
||||
text: "Check this post",
|
||||
url,
|
||||
});
|
||||
} else {
|
||||
await navigator.clipboard.writeText(url);
|
||||
toast({ description: "Link copied" });
|
||||
|
|
@ -139,7 +155,11 @@ export default function Feed() {
|
|||
if (!user && !loading) return <Navigate to="/login" replace />;
|
||||
if (loading || isLoading) {
|
||||
return (
|
||||
<LoadingScreen message="Loading your feed..." showProgress duration={1000} />
|
||||
<LoadingScreen
|
||||
message="Loading your feed..."
|
||||
showProgress
|
||||
duration={1000}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -156,13 +176,28 @@ export default function Feed() {
|
|||
</div>
|
||||
)}
|
||||
{items.map((item) => (
|
||||
<section id={`post-${item.id}`} key={item.id} className="snap-start h-[calc(100vh-64px)] relative flex items-center justify-center">
|
||||
<section
|
||||
id={`post-${item.id}`}
|
||||
key={item.id}
|
||||
className="snap-start h-[calc(100vh-64px)] relative flex items-center justify-center"
|
||||
>
|
||||
<Card className="w-full h-full bg-black/60 border-border/30 overflow-hidden">
|
||||
<CardContent className="w-full h-full p-0 relative">
|
||||
{item.mediaType === "video" && item.mediaUrl ? (
|
||||
<video src={item.mediaUrl} className="w-full h-full object-cover" autoPlay loop muted={muted} playsInline />
|
||||
<video
|
||||
src={item.mediaUrl}
|
||||
className="w-full h-full object-cover"
|
||||
autoPlay
|
||||
loop
|
||||
muted={muted}
|
||||
playsInline
|
||||
/>
|
||||
) : item.mediaType === "image" && item.mediaUrl ? (
|
||||
<img src={item.mediaUrl} alt={item.caption || item.authorName} className="w-full h-full object-cover" />
|
||||
<img
|
||||
src={item.mediaUrl}
|
||||
alt={item.caption || item.authorName}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full bg-gradient-to-br from-aethex-500/20 to-neon-blue/20" />
|
||||
)}
|
||||
|
|
@ -170,16 +205,38 @@ export default function Feed() {
|
|||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent" />
|
||||
|
||||
<div className="absolute right-4 bottom-24 flex flex-col items-center gap-4">
|
||||
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30" onClick={() => setMuted((m) => !m)}>
|
||||
{muted ? <VolumeX className="h-5 w-5" /> : <Volume2 className="h-5 w-5" />}
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
className="rounded-full bg-white/20 hover:bg-white/30"
|
||||
onClick={() => setMuted((m) => !m)}
|
||||
>
|
||||
{muted ? (
|
||||
<VolumeX className="h-5 w-5" />
|
||||
) : (
|
||||
<Volume2 className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
className="rounded-full bg-white/20 hover:bg-white/30"
|
||||
>
|
||||
<Heart className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
className="rounded-full bg-white/20 hover:bg-white/30"
|
||||
>
|
||||
<MessageCircle className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30" onClick={() => share(item.id)}>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
className="rounded-full bg-white/20 hover:bg-white/30"
|
||||
onClick={() => share(item.id)}
|
||||
>
|
||||
<Share2 className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -188,20 +245,36 @@ export default function Feed() {
|
|||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src={item.authorAvatar || undefined} />
|
||||
<AvatarFallback>{item.authorName[0] || "U"}</AvatarFallback>
|
||||
<AvatarFallback>
|
||||
{item.authorName[0] || "U"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-semibold text-white">{item.authorName}</div>
|
||||
<div className="font-semibold text-white">
|
||||
{item.authorName}
|
||||
</div>
|
||||
{item.caption && (
|
||||
<div className="text-xs text-white/80 max-w-[60vw] line-clamp-2">{item.caption}</div>
|
||||
<div className="text-xs text-white/80 max-w-[60vw] line-clamp-2">
|
||||
{item.caption}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm" variant={isFollowingAuthor(item.authorId) ? "outline" : "default"} onClick={() => toggleFollow(item.authorId)}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={
|
||||
isFollowingAuthor(item.authorId) ? "outline" : "default"
|
||||
}
|
||||
onClick={() => toggleFollow(item.authorId)}
|
||||
>
|
||||
{isFollowingAuthor(item.authorId) ? (
|
||||
<span className="flex items-center gap-1"><UserCheck className="h-4 w-4" /> Following</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<UserCheck className="h-4 w-4" /> Following
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1"><UserPlus className="h-4 w-4" /> Follow</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<UserPlus className="h-4 w-4" /> Follow
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue