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 { Button } from "@/components/ui/button";
|
||||
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">
|
||||
{p.image && (
|
||||
<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>
|
||||
)}
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{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 && (
|
||||
<span className="text-xs text-muted-foreground">{p.timeframe}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{p.timeframe}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -36,7 +51,9 @@ export default function ShowcaseCard({ p }: { p: ShowcaseProject }) {
|
|||
{p.tags && p.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{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>
|
||||
)}
|
||||
|
|
@ -61,7 +78,9 @@ export default function ShowcaseCard({ p }: { p: ShowcaseProject }) {
|
|||
<div className="flex flex-wrap gap-2">
|
||||
{p.links.map((l) => (
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
description:
|
||||
"Marketplace for buying, selling, and trading digital goods and services across the AeThex ecosystem.",
|
||||
tags: ["Platform", "Marketplace", "Commerce"],
|
||||
contributors: [
|
||||
{ name: "AeThex Commerce", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "AeThex Commerce", avatar: "/placeholder.svg" }],
|
||||
},
|
||||
{
|
||||
id: "rodeo-roundup",
|
||||
|
|
@ -58,9 +56,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
description:
|
||||
"App for discovering and tracking rodeos nearby with structured event data, maps, and alerts.",
|
||||
tags: ["Studio", "Mobile", "Events", "Maps"],
|
||||
contributors: [
|
||||
{ name: "Studio Build Team", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "Studio Build Team", avatar: "/placeholder.svg" }],
|
||||
},
|
||||
{
|
||||
id: "hells-highway",
|
||||
|
|
@ -85,9 +81,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
description:
|
||||
"Internal game development toolkit and build pipeline utilities for rapid prototyping and shipping.",
|
||||
tags: ["Labs", "Toolkit", "DevTools"],
|
||||
contributors: [
|
||||
{ name: "Labs Automation", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "Labs Automation", avatar: "/placeholder.svg" }],
|
||||
},
|
||||
{
|
||||
id: "lone-star-bar",
|
||||
|
|
@ -98,9 +92,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
description:
|
||||
"17+ social game on Roblox focusing on immersive spaces and social mechanics.",
|
||||
tags: ["Studio", "Roblox", "Social", "Game"],
|
||||
contributors: [
|
||||
{ name: "Social Experiences", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "Social Experiences", avatar: "/placeholder.svg" }],
|
||||
links: [
|
||||
{
|
||||
label: "Roblox",
|
||||
|
|
@ -116,9 +108,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
timeframe: "Nov 2022 – Present",
|
||||
description: "Narrative‑driven initiative produced by AeThex Studio.",
|
||||
tags: ["Studio", "Narrative", "Production"],
|
||||
contributors: [
|
||||
{ name: "Story Group", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "Story Group", avatar: "/placeholder.svg" }],
|
||||
links: [
|
||||
{ label: "Show project", href: "https://aethex.co/crooked-are-we" },
|
||||
],
|
||||
|
|
@ -132,9 +122,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
description:
|
||||
"Studio directed and shipped a polished prototype game in 86 hours for INSPIRE 2025.",
|
||||
tags: ["Studio", "Game Jam", "Leadership", "Prototype"],
|
||||
contributors: [
|
||||
{ name: "Strike Team", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "Strike Team", avatar: "/placeholder.svg" }],
|
||||
},
|
||||
{
|
||||
id: "the-prototypes-control",
|
||||
|
|
@ -145,9 +133,7 @@ export const SHOWCASE: ShowcaseProject[] = [
|
|||
description:
|
||||
"Roblox DevRel Challenge 2025 entry focused on rapid prototyping and control schemes.",
|
||||
tags: ["Studio", "Roblox", "Prototype", "Challenge"],
|
||||
contributors: [
|
||||
{ name: "Prototype Unit", avatar: "/placeholder.svg" },
|
||||
],
|
||||
contributors: [{ name: "Prototype Unit", avatar: "/placeholder.svg" }],
|
||||
links: [
|
||||
{
|
||||
label: "Roblox",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import Layout from "@/components/Layout";
|
||||
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 ShowcaseCard from "@/components/showcase/ShowcaseCard";
|
||||
import { SHOWCASE, type ShowcaseProject } from "@/data/showcase";
|
||||
|
|
@ -22,7 +28,7 @@ export default function Projects() {
|
|||
supabase
|
||||
.from<any>("showcase_projects" as any)
|
||||
.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 })
|
||||
.then(({ data, error }) => {
|
||||
|
|
@ -70,7 +76,15 @@ export default function Projects() {
|
|||
.finally(() => setLoading(false));
|
||||
}, [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;
|
||||
|
||||
return (
|
||||
|
|
@ -79,13 +93,33 @@ export default function Projects() {
|
|||
<section className="container mx-auto max-w-6xl px-4">
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||
<div>
|
||||
<Badge variant="outline" 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>
|
||||
<Badge
|
||||
variant="outline"
|
||||
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>
|
||||
{isOwner && (
|
||||
<Button 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
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -103,7 +137,8 @@ export default function Projects() {
|
|||
<CardHeader>
|
||||
<CardTitle>No projects yet</CardTitle>
|
||||
<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>
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center gap-2 pt-0 pb-6">
|
||||
|
|
|
|||
|
|
@ -1,22 +1,41 @@
|
|||
import Layout from "@/components/Layout";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
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 { Textarea } from "@/components/ui/textarea";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface Link { label: string; href: string }
|
||||
interface Contributor { name: string; title?: string; avatar?: string }
|
||||
interface Link {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
interface Contributor {
|
||||
name: string;
|
||||
title?: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
export default function ProjectsAdmin() {
|
||||
const { user } = useAuth();
|
||||
const isOwner = user?.email?.toLowerCase() === "mrpiglr@gmail.com";
|
||||
const [list, setList] = useState<any[]>([]);
|
||||
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(() => {
|
||||
if (!isOwner) return;
|
||||
|
|
@ -30,18 +49,32 @@ export default function ProjectsAdmin() {
|
|||
}, [isOwner]);
|
||||
|
||||
const create = async () => {
|
||||
const tags = (draft.tags || "").split(",").map((s: string) => s.trim()).filter(Boolean);
|
||||
const { error } = await supabase.from<any>("showcase_projects" as any).insert({
|
||||
title: draft.title,
|
||||
org_unit: draft.org_unit,
|
||||
role: "AeThex",
|
||||
timeframe: draft.timeframe || null,
|
||||
description: draft.description || null,
|
||||
tags,
|
||||
});
|
||||
const tags = (draft.tags || "")
|
||||
.split(",")
|
||||
.map((s: string) => s.trim())
|
||||
.filter(Boolean);
|
||||
const { error } = await supabase
|
||||
.from<any>("showcase_projects" as any)
|
||||
.insert({
|
||||
title: draft.title,
|
||||
org_unit: draft.org_unit,
|
||||
role: "AeThex",
|
||||
timeframe: draft.timeframe || null,
|
||||
description: draft.description || null,
|
||||
tags,
|
||||
});
|
||||
if (!error) {
|
||||
setDraft({ 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 });
|
||||
setDraft({
|
||||
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 || []);
|
||||
}
|
||||
};
|
||||
|
|
@ -69,11 +102,22 @@ export default function ProjectsAdmin() {
|
|||
<section className="container mx-auto max-w-6xl px-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Badge variant="outline" 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>
|
||||
<Badge
|
||||
variant="outline"
|
||||
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>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
|
|
@ -84,20 +128,48 @@ export default function ProjectsAdmin() {
|
|||
<CardDescription>Title and basics</CardDescription>
|
||||
</CardHeader>
|
||||
<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">
|
||||
<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>Labs</option>
|
||||
<option>Platform</option>
|
||||
<option>Community</option>
|
||||
</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>
|
||||
<Textarea 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 })} />
|
||||
<Textarea
|
||||
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">
|
||||
<Button onClick={create} disabled={!draft.title}>Create</Button>
|
||||
<Button onClick={create} disabled={!draft.title}>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -105,18 +177,39 @@ export default function ProjectsAdmin() {
|
|||
<Card className="bg-card/60 border-border/40 backdrop-blur">
|
||||
<CardHeader>
|
||||
<CardTitle>Existing</CardTitle>
|
||||
<CardDescription>{loading ? "Loading..." : `${list.length} items`}</CardDescription>
|
||||
<CardDescription>
|
||||
{loading ? "Loading..." : `${list.length} items`}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{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="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 className="flex items-center gap-2">
|
||||
<Button asChild size="sm" variant="outline"><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>
|
||||
<Button asChild size="sm" variant="outline">
|
||||
<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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,8 @@ export default function Roadmap() {
|
|||
try {
|
||||
aethexToast.info({
|
||||
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 {}
|
||||
return;
|
||||
|
|
@ -469,7 +470,9 @@ export default function Roadmap() {
|
|||
variant="outline"
|
||||
onClick={() => toggleUnlock(p.id)}
|
||||
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}
|
||||
>
|
||||
{unlocked[p.id] ? (
|
||||
|
|
|
|||
Loading…
Reference in a new issue