Prettier format pending files
This commit is contained in:
parent
97f60000d1
commit
9865e97baf
5 changed files with 204 additions and 68 deletions
|
|
@ -1,4 +1,10 @@
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
|
@ -15,17 +21,26 @@ export default function ShowcaseCard({ p }: { p: ShowcaseProject }) {
|
||||||
<Card className="bg-card/60 border-border/40 backdrop-blur overflow-hidden group">
|
<Card className="bg-card/60 border-border/40 backdrop-blur overflow-hidden group">
|
||||||
{p.image && (
|
{p.image && (
|
||||||
<div className="relative h-44 w-full">
|
<div className="relative h-44 w-full">
|
||||||
<img src={p.image} alt={p.title} className="h-full w-full object-cover" loading="lazy" />
|
<img
|
||||||
|
src={p.image}
|
||||||
|
alt={p.title}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{p.orgUnit && (
|
{p.orgUnit && (
|
||||||
<Badge className="bg-gradient-to-r from-aethex-500 to-neon-blue">{p.orgUnit}</Badge>
|
<Badge className="bg-gradient-to-r from-aethex-500 to-neon-blue">
|
||||||
|
{p.orgUnit}
|
||||||
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{p.timeframe && (
|
{p.timeframe && (
|
||||||
<span className="text-xs text-muted-foreground">{p.timeframe}</span>
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{p.timeframe}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -36,7 +51,9 @@ export default function ShowcaseCard({ p }: { p: ShowcaseProject }) {
|
||||||
{p.tags && p.tags.length > 0 && (
|
{p.tags && p.tags.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{p.tags.map((t) => (
|
{p.tags.map((t) => (
|
||||||
<Badge key={t} variant="outline" className="text-xs">{t}</Badge>
|
<Badge key={t} variant="outline" className="text-xs">
|
||||||
|
{t}
|
||||||
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -61,7 +78,9 @@ export default function ShowcaseCard({ p }: { p: ShowcaseProject }) {
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{p.links.map((l) => (
|
{p.links.map((l) => (
|
||||||
<Button key={l.href} asChild size="sm" variant="outline">
|
<Button key={l.href} asChild size="sm" variant="outline">
|
||||||
<a href={l.href} target="_blank" rel="noreferrer noopener">{l.label}</a>
|
<a href={l.href} target="_blank" rel="noreferrer noopener">
|
||||||
|
{l.label}
|
||||||
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
description:
|
description:
|
||||||
"Marketplace for buying, selling, and trading digital goods and services across the AeThex ecosystem.",
|
"Marketplace for buying, selling, and trading digital goods and services across the AeThex ecosystem.",
|
||||||
tags: ["Platform", "Marketplace", "Commerce"],
|
tags: ["Platform", "Marketplace", "Commerce"],
|
||||||
contributors: [
|
contributors: [{ name: "AeThex Commerce", avatar: "/placeholder.svg" }],
|
||||||
{ name: "AeThex Commerce", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "rodeo-roundup",
|
id: "rodeo-roundup",
|
||||||
|
|
@ -58,9 +56,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
description:
|
description:
|
||||||
"App for discovering and tracking rodeos nearby with structured event data, maps, and alerts.",
|
"App for discovering and tracking rodeos nearby with structured event data, maps, and alerts.",
|
||||||
tags: ["Studio", "Mobile", "Events", "Maps"],
|
tags: ["Studio", "Mobile", "Events", "Maps"],
|
||||||
contributors: [
|
contributors: [{ name: "Studio Build Team", avatar: "/placeholder.svg" }],
|
||||||
{ name: "Studio Build Team", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "hells-highway",
|
id: "hells-highway",
|
||||||
|
|
@ -85,9 +81,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
description:
|
description:
|
||||||
"Internal game development toolkit and build pipeline utilities for rapid prototyping and shipping.",
|
"Internal game development toolkit and build pipeline utilities for rapid prototyping and shipping.",
|
||||||
tags: ["Labs", "Toolkit", "DevTools"],
|
tags: ["Labs", "Toolkit", "DevTools"],
|
||||||
contributors: [
|
contributors: [{ name: "Labs Automation", avatar: "/placeholder.svg" }],
|
||||||
{ name: "Labs Automation", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "lone-star-bar",
|
id: "lone-star-bar",
|
||||||
|
|
@ -98,9 +92,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
description:
|
description:
|
||||||
"17+ social game on Roblox focusing on immersive spaces and social mechanics.",
|
"17+ social game on Roblox focusing on immersive spaces and social mechanics.",
|
||||||
tags: ["Studio", "Roblox", "Social", "Game"],
|
tags: ["Studio", "Roblox", "Social", "Game"],
|
||||||
contributors: [
|
contributors: [{ name: "Social Experiences", avatar: "/placeholder.svg" }],
|
||||||
{ name: "Social Experiences", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
label: "Roblox",
|
label: "Roblox",
|
||||||
|
|
@ -116,9 +108,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
timeframe: "Nov 2022 – Present",
|
timeframe: "Nov 2022 – Present",
|
||||||
description: "Narrative‑driven initiative produced by AeThex Studio.",
|
description: "Narrative‑driven initiative produced by AeThex Studio.",
|
||||||
tags: ["Studio", "Narrative", "Production"],
|
tags: ["Studio", "Narrative", "Production"],
|
||||||
contributors: [
|
contributors: [{ name: "Story Group", avatar: "/placeholder.svg" }],
|
||||||
{ name: "Story Group", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
links: [
|
links: [
|
||||||
{ label: "Show project", href: "https://aethex.co/crooked-are-we" },
|
{ label: "Show project", href: "https://aethex.co/crooked-are-we" },
|
||||||
],
|
],
|
||||||
|
|
@ -132,9 +122,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
description:
|
description:
|
||||||
"Studio directed and shipped a polished prototype game in 86 hours for INSPIRE 2025.",
|
"Studio directed and shipped a polished prototype game in 86 hours for INSPIRE 2025.",
|
||||||
tags: ["Studio", "Game Jam", "Leadership", "Prototype"],
|
tags: ["Studio", "Game Jam", "Leadership", "Prototype"],
|
||||||
contributors: [
|
contributors: [{ name: "Strike Team", avatar: "/placeholder.svg" }],
|
||||||
{ name: "Strike Team", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "the-prototypes-control",
|
id: "the-prototypes-control",
|
||||||
|
|
@ -145,9 +133,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
||||||
description:
|
description:
|
||||||
"Roblox DevRel Challenge 2025 entry focused on rapid prototyping and control schemes.",
|
"Roblox DevRel Challenge 2025 entry focused on rapid prototyping and control schemes.",
|
||||||
tags: ["Studio", "Roblox", "Prototype", "Challenge"],
|
tags: ["Studio", "Roblox", "Prototype", "Challenge"],
|
||||||
contributors: [
|
contributors: [{ name: "Prototype Unit", avatar: "/placeholder.svg" }],
|
||||||
{ name: "Prototype Unit", avatar: "/placeholder.svg" },
|
|
||||||
],
|
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
label: "Roblox",
|
label: "Roblox",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import Layout from "@/components/Layout";
|
import Layout from "@/components/Layout";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import ShowcaseCard from "@/components/showcase/ShowcaseCard";
|
import ShowcaseCard from "@/components/showcase/ShowcaseCard";
|
||||||
import { SHOWCASE, type ShowcaseProject } from "@/data/showcase";
|
import { SHOWCASE, type ShowcaseProject } from "@/data/showcase";
|
||||||
|
|
@ -22,7 +28,7 @@ export default function Projects() {
|
||||||
supabase
|
supabase
|
||||||
.from<any>("showcase_projects" as any)
|
.from<any>("showcase_projects" as any)
|
||||||
.select(
|
.select(
|
||||||
"id,title,org_unit,role,timeframe,description,tags,image, links:showcase_project_links(label,href), contributors:showcase_contributors(name,title,avatar)"
|
"id,title,org_unit,role,timeframe,description,tags,image, links:showcase_project_links(label,href), contributors:showcase_contributors(name,title,avatar)",
|
||||||
)
|
)
|
||||||
.order("created_at", { ascending: false })
|
.order("created_at", { ascending: false })
|
||||||
.then(({ data, error }) => {
|
.then(({ data, error }) => {
|
||||||
|
|
@ -70,7 +76,15 @@ export default function Projects() {
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, [dbItems]);
|
}, [dbItems]);
|
||||||
|
|
||||||
const items = useMemo(() => (dbItems && dbItems.length ? dbItems : cmsItems && cmsItems.length ? cmsItems : SHOWCASE), [dbItems, cmsItems]);
|
const items = useMemo(
|
||||||
|
() =>
|
||||||
|
dbItems && dbItems.length
|
||||||
|
? dbItems
|
||||||
|
: cmsItems && cmsItems.length
|
||||||
|
? cmsItems
|
||||||
|
: SHOWCASE,
|
||||||
|
[dbItems, cmsItems],
|
||||||
|
);
|
||||||
const hasProjects = Array.isArray(items) && items.length > 0;
|
const hasProjects = Array.isArray(items) && items.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -79,13 +93,33 @@ export default function Projects() {
|
||||||
<section className="container mx-auto max-w-6xl px-4">
|
<section className="container mx-auto max-w-6xl px-4">
|
||||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<Badge variant="outline" className="border-aethex-400/50 text-aethex-300">Showcase</Badge>
|
<Badge
|
||||||
<h1 className="mt-2 text-4xl font-extrabold text-gradient">Projects & Testimonials</h1>
|
variant="outline"
|
||||||
<p className="text-muted-foreground max-w-2xl mt-1">Studio initiatives across AeThex Platform, Labs, and Studio.</p>
|
className="border-aethex-400/50 text-aethex-300"
|
||||||
|
>
|
||||||
|
Showcase
|
||||||
|
</Badge>
|
||||||
|
<h1 className="mt-2 text-4xl font-extrabold text-gradient">
|
||||||
|
Projects & Testimonials
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground max-w-2xl mt-1">
|
||||||
|
Studio initiatives across AeThex Platform, Labs, and Studio.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<Button asChild variant="outline" size="sm" title="Edit in Builder CMS">
|
<Button
|
||||||
<a href="https://builder.io/content" target="_blank" rel="noreferrer noopener">Open CMS</a>
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
title="Edit in Builder CMS"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://builder.io/content"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
Open CMS
|
||||||
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -103,7 +137,8 @@ export default function Projects() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>No projects yet</CardTitle>
|
<CardTitle>No projects yet</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Add entries in <code>code/client/data/showcase.ts</code> or manage them via CMS.
|
Add entries in <code>code/client/data/showcase.ts</code> or
|
||||||
|
manage them via CMS.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex items-center gap-2 pt-0 pb-6">
|
<CardContent className="flex items-center gap-2 pt-0 pb-6">
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,41 @@
|
||||||
import Layout from "@/components/Layout";
|
import Layout from "@/components/Layout";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { supabase } from "@/lib/supabase";
|
import { supabase } from "@/lib/supabase";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Link { label: string; href: string }
|
interface Link {
|
||||||
interface Contributor { name: string; title?: string; avatar?: string }
|
label: string;
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
interface Contributor {
|
||||||
|
name: string;
|
||||||
|
title?: string;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ProjectsAdmin() {
|
export default function ProjectsAdmin() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const isOwner = user?.email?.toLowerCase() === "mrpiglr@gmail.com";
|
const isOwner = user?.email?.toLowerCase() === "mrpiglr@gmail.com";
|
||||||
const [list, setList] = useState<any[]>([]);
|
const [list, setList] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [draft, setDraft] = useState<any>({ title: "", org_unit: "Studio", timeframe: "", description: "", tags: "" });
|
const [draft, setDraft] = useState<any>({
|
||||||
|
title: "",
|
||||||
|
org_unit: "Studio",
|
||||||
|
timeframe: "",
|
||||||
|
description: "",
|
||||||
|
tags: "",
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOwner) return;
|
if (!isOwner) return;
|
||||||
|
|
@ -30,18 +49,32 @@ export default function ProjectsAdmin() {
|
||||||
}, [isOwner]);
|
}, [isOwner]);
|
||||||
|
|
||||||
const create = async () => {
|
const create = async () => {
|
||||||
const tags = (draft.tags || "").split(",").map((s: string) => s.trim()).filter(Boolean);
|
const tags = (draft.tags || "")
|
||||||
const { error } = await supabase.from<any>("showcase_projects" as any).insert({
|
.split(",")
|
||||||
title: draft.title,
|
.map((s: string) => s.trim())
|
||||||
org_unit: draft.org_unit,
|
.filter(Boolean);
|
||||||
role: "AeThex",
|
const { error } = await supabase
|
||||||
timeframe: draft.timeframe || null,
|
.from<any>("showcase_projects" as any)
|
||||||
description: draft.description || null,
|
.insert({
|
||||||
tags,
|
title: draft.title,
|
||||||
});
|
org_unit: draft.org_unit,
|
||||||
|
role: "AeThex",
|
||||||
|
timeframe: draft.timeframe || null,
|
||||||
|
description: draft.description || null,
|
||||||
|
tags,
|
||||||
|
});
|
||||||
if (!error) {
|
if (!error) {
|
||||||
setDraft({ title: "", org_unit: "Studio", timeframe: "", description: "", tags: "" });
|
setDraft({
|
||||||
const { data } = await supabase.from<any>("showcase_projects" as any).select("id,title,org_unit,role,timeframe,description,tags").order("created_at", { ascending: false });
|
title: "",
|
||||||
|
org_unit: "Studio",
|
||||||
|
timeframe: "",
|
||||||
|
description: "",
|
||||||
|
tags: "",
|
||||||
|
});
|
||||||
|
const { data } = await supabase
|
||||||
|
.from<any>("showcase_projects" as any)
|
||||||
|
.select("id,title,org_unit,role,timeframe,description,tags")
|
||||||
|
.order("created_at", { ascending: false });
|
||||||
setList(data || []);
|
setList(data || []);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -69,11 +102,22 @@ export default function ProjectsAdmin() {
|
||||||
<section className="container mx-auto max-w-6xl px-4">
|
<section className="container mx-auto max-w-6xl px-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<Badge variant="outline" className="border-aethex-400/50 text-aethex-300">Admin</Badge>
|
<Badge
|
||||||
<h1 className="mt-2 text-3xl font-extrabold text-gradient">Projects Admin</h1>
|
variant="outline"
|
||||||
<p className="text-muted-foreground">Create and manage showcase entries (Supabase)</p>
|
className="border-aethex-400/50 text-aethex-300"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</Badge>
|
||||||
|
<h1 className="mt-2 text-3xl font-extrabold text-gradient">
|
||||||
|
Projects Admin
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Create and manage showcase entries (Supabase)
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button asChild variant="outline" size="sm"><a href="/projects">View page</a></Button>
|
<Button asChild variant="outline" size="sm">
|
||||||
|
<a href="/projects">View page</a>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -84,20 +128,48 @@ export default function ProjectsAdmin() {
|
||||||
<CardDescription>Title and basics</CardDescription>
|
<CardDescription>Title and basics</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<Input placeholder="Title" value={draft.title} onChange={(e) => setDraft({ ...draft, title: e.target.value })} />
|
<Input
|
||||||
|
placeholder="Title"
|
||||||
|
value={draft.title}
|
||||||
|
onChange={(e) => setDraft({ ...draft, title: e.target.value })}
|
||||||
|
/>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<select className="rounded border border-border/40 bg-background/70 px-3 py-2" value={draft.org_unit} onChange={(e) => setDraft({ ...draft, org_unit: e.target.value })}>
|
<select
|
||||||
|
className="rounded border border-border/40 bg-background/70 px-3 py-2"
|
||||||
|
value={draft.org_unit}
|
||||||
|
onChange={(e) =>
|
||||||
|
setDraft({ ...draft, org_unit: e.target.value })
|
||||||
|
}
|
||||||
|
>
|
||||||
<option>Studio</option>
|
<option>Studio</option>
|
||||||
<option>Labs</option>
|
<option>Labs</option>
|
||||||
<option>Platform</option>
|
<option>Platform</option>
|
||||||
<option>Community</option>
|
<option>Community</option>
|
||||||
</select>
|
</select>
|
||||||
<Input placeholder="Timeframe (e.g., Jan 2025 – Present)" value={draft.timeframe} onChange={(e) => setDraft({ ...draft, timeframe: e.target.value })} />
|
<Input
|
||||||
|
placeholder="Timeframe (e.g., Jan 2025 – Present)"
|
||||||
|
value={draft.timeframe}
|
||||||
|
onChange={(e) =>
|
||||||
|
setDraft({ ...draft, timeframe: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Textarea placeholder="Description" value={draft.description} onChange={(e) => setDraft({ ...draft, description: e.target.value })} />
|
<Textarea
|
||||||
<Input placeholder="Tags (comma separated)" value={draft.tags} onChange={(e) => setDraft({ ...draft, tags: e.target.value })} />
|
placeholder="Description"
|
||||||
|
value={draft.description}
|
||||||
|
onChange={(e) =>
|
||||||
|
setDraft({ ...draft, description: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="Tags (comma separated)"
|
||||||
|
value={draft.tags}
|
||||||
|
onChange={(e) => setDraft({ ...draft, tags: e.target.value })}
|
||||||
|
/>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button onClick={create} disabled={!draft.title}>Create</Button>
|
<Button onClick={create} disabled={!draft.title}>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -105,18 +177,39 @@ export default function ProjectsAdmin() {
|
||||||
<Card className="bg-card/60 border-border/40 backdrop-blur">
|
<Card className="bg-card/60 border-border/40 backdrop-blur">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Existing</CardTitle>
|
<CardTitle>Existing</CardTitle>
|
||||||
<CardDescription>{loading ? "Loading..." : `${list.length} items`}</CardDescription>
|
<CardDescription>
|
||||||
|
{loading ? "Loading..." : `${list.length} items`}
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2">
|
<CardContent className="space-y-2">
|
||||||
{list.map((p) => (
|
{list.map((p) => (
|
||||||
<div key={p.id} className="flex items-center justify-between rounded border border-border/40 p-2 text-sm">
|
<div
|
||||||
|
key={p.id}
|
||||||
|
className="flex items-center justify-between rounded border border-border/40 p-2 text-sm"
|
||||||
|
>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="font-medium truncate">{p.title}</div>
|
<div className="font-medium truncate">{p.title}</div>
|
||||||
<div className="text-xs text-muted-foreground">{p.org_unit} • {p.timeframe || ""}</div>
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{p.org_unit} • {p.timeframe || ""}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button asChild size="sm" variant="outline"><a href={`/projects/${p.id}`}>View</a></Button>
|
<Button asChild size="sm" variant="outline">
|
||||||
<Button size="sm" variant="destructive" onClick={async () => { await supabase.from<any>("showcase_projects" as any).delete().eq("id", p.id); setList(list.filter((x) => x.id !== p.id)); }}>Delete</Button>
|
<a href={`/projects/${p.id}`}>View</a>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={async () => {
|
||||||
|
await supabase
|
||||||
|
.from<any>("showcase_projects" as any)
|
||||||
|
.delete()
|
||||||
|
.eq("id", p.id);
|
||||||
|
setList(list.filter((x) => x.id !== p.id));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,8 @@ export default function Roadmap() {
|
||||||
try {
|
try {
|
||||||
aethexToast.info({
|
aethexToast.info({
|
||||||
title: "Sign in required",
|
title: "Sign in required",
|
||||||
description: "Create an account to unlock Dev Drops and save progress.",
|
description:
|
||||||
|
"Create an account to unlock Dev Drops and save progress.",
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
return;
|
return;
|
||||||
|
|
@ -469,7 +470,9 @@ export default function Roadmap() {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => toggleUnlock(p.id)}
|
onClick={() => toggleUnlock(p.id)}
|
||||||
disabled={!user}
|
disabled={!user}
|
||||||
className={!user ? "cursor-not-allowed opacity-60" : undefined}
|
className={
|
||||||
|
!user ? "cursor-not-allowed opacity-60" : undefined
|
||||||
|
}
|
||||||
title={!user ? "Sign in to unlock" : undefined}
|
title={!user ? "Sign in to unlock" : undefined}
|
||||||
>
|
>
|
||||||
{unlocked[p.id] ? (
|
{unlocked[p.id] ? (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue