Prettier format pending files
This commit is contained in:
parent
16aba004b3
commit
5e3852ecd7
17 changed files with 3112 additions and 2406 deletions
|
|
@ -116,7 +116,10 @@ const App = () => (
|
||||||
<Route path="/docs" element={<DocsLayout />}>
|
<Route path="/docs" element={<DocsLayout />}>
|
||||||
<Route index element={<DocsOverview />} />
|
<Route index element={<DocsOverview />} />
|
||||||
<Route path="tutorials" element={<DocsTutorials />} />
|
<Route path="tutorials" element={<DocsTutorials />} />
|
||||||
<Route path="getting-started" element={<DocsGettingStarted />} />
|
<Route
|
||||||
|
path="getting-started"
|
||||||
|
element={<DocsGettingStarted />}
|
||||||
|
/>
|
||||||
<Route path="platform" element={<DocsPlatform />} />
|
<Route path="platform" element={<DocsPlatform />} />
|
||||||
<Route path="api" element={<DocsApiReference />} />
|
<Route path="api" element={<DocsApiReference />} />
|
||||||
<Route path="cli" element={<DocsCli />} />
|
<Route path="cli" element={<DocsCli />} />
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,11 @@ const AdminAchievementManager = ({
|
||||||
targetUser,
|
targetUser,
|
||||||
}: AdminAchievementManagerProps) => {
|
}: AdminAchievementManagerProps) => {
|
||||||
const [achievements, setAchievements] = useState<AethexAchievement[]>([]);
|
const [achievements, setAchievements] = useState<AethexAchievement[]>([]);
|
||||||
const [userAchievements, setUserAchievements] = useState<AethexAchievement[]>([]);
|
const [userAchievements, setUserAchievements] = useState<AethexAchievement[]>(
|
||||||
const [selectedAchievementId, setSelectedAchievementId] = useState<string>("");
|
[],
|
||||||
|
);
|
||||||
|
const [selectedAchievementId, setSelectedAchievementId] =
|
||||||
|
useState<string>("");
|
||||||
const [loadingList, setLoadingList] = useState(false);
|
const [loadingList, setLoadingList] = useState(false);
|
||||||
const [loadingUserAchievements, setLoadingUserAchievements] = useState(false);
|
const [loadingUserAchievements, setLoadingUserAchievements] = useState(false);
|
||||||
const [awarding, setAwarding] = useState(false);
|
const [awarding, setAwarding] = useState(false);
|
||||||
|
|
@ -53,21 +56,18 @@ const AdminAchievementManager = ({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadUserAchievements = useCallback(
|
const loadUserAchievements = useCallback(async (userId: string) => {
|
||||||
async (userId: string) => {
|
setLoadingUserAchievements(true);
|
||||||
setLoadingUserAchievements(true);
|
try {
|
||||||
try {
|
const list = await aethexAchievementService.getUserAchievements(userId);
|
||||||
const list = await aethexAchievementService.getUserAchievements(userId);
|
setUserAchievements(list);
|
||||||
setUserAchievements(list);
|
} catch (error) {
|
||||||
} catch (error) {
|
console.warn("Failed to load user achievements", error);
|
||||||
console.warn("Failed to load user achievements", error);
|
setUserAchievements([]);
|
||||||
setUserAchievements([]);
|
} finally {
|
||||||
} finally {
|
setLoadingUserAchievements(false);
|
||||||
setLoadingUserAchievements(false);
|
}
|
||||||
}
|
}, []);
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAchievements().catch(() => undefined);
|
loadAchievements().catch(() => undefined);
|
||||||
|
|
@ -82,7 +82,10 @@ const AdminAchievementManager = ({
|
||||||
}, [targetUser?.id, loadUserAchievements]);
|
}, [targetUser?.id, loadUserAchievements]);
|
||||||
|
|
||||||
const selectedAchievement = useMemo(
|
const selectedAchievement = useMemo(
|
||||||
() => achievements.find((achievement) => achievement.id === selectedAchievementId) ?? null,
|
() =>
|
||||||
|
achievements.find(
|
||||||
|
(achievement) => achievement.id === selectedAchievementId,
|
||||||
|
) ?? null,
|
||||||
[achievements, selectedAchievementId],
|
[achievements, selectedAchievementId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -133,7 +136,8 @@ const AdminAchievementManager = ({
|
||||||
if (!result) {
|
if (!result) {
|
||||||
aethexToast.error({
|
aethexToast.error({
|
||||||
title: "Activation failed",
|
title: "Activation failed",
|
||||||
description: "No rewards were activated. Check server logs for details.",
|
description:
|
||||||
|
"No rewards were activated. Check server logs for details.",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const awarded = result.awardedAchievementIds?.length ?? 0;
|
const awarded = result.awardedAchievementIds?.length ?? 0;
|
||||||
|
|
@ -177,7 +181,10 @@ const AdminAchievementManager = ({
|
||||||
{targetUser ? (
|
{targetUser ? (
|
||||||
<div className="rounded border border-border/40 bg-background/40 p-3">
|
<div className="rounded border border-border/40 bg-background/40 p-3">
|
||||||
<p className="font-medium text-foreground">
|
<p className="font-medium text-foreground">
|
||||||
{targetUser.full_name ?? targetUser.username ?? targetUser.email ?? "Unknown"}
|
{targetUser.full_name ??
|
||||||
|
targetUser.username ??
|
||||||
|
targetUser.email ??
|
||||||
|
"Unknown"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{targetUser.email ?? "No email on record"}
|
{targetUser.email ?? "No email on record"}
|
||||||
|
|
@ -197,7 +204,11 @@ const AdminAchievementManager = ({
|
||||||
disabled={!targetUser || loadingList}
|
disabled={!targetUser || loadingList}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="bg-background/60">
|
<SelectTrigger className="bg-background/60">
|
||||||
<SelectValue placeholder={loadingList ? "Loading achievements…" : "Select achievement"} />
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
loadingList ? "Loading achievements…" : "Select achievement"
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{achievements.map((achievement) => (
|
{achievements.map((achievement) => (
|
||||||
|
|
@ -240,7 +251,9 @@ const AdminAchievementManager = ({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<p className="text-sm font-medium text-foreground">Achievement history</p>
|
<p className="text-sm font-medium text-foreground">
|
||||||
|
Achievement history
|
||||||
|
</p>
|
||||||
{loadingUserAchievements ? (
|
{loadingUserAchievements ? (
|
||||||
<span className="flex items-center gap-2 text-xs text-muted-foreground">
|
<span className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" /> Loading…
|
<Loader2 className="h-3.5 w-3.5 animate-spin" /> Loading…
|
||||||
|
|
@ -252,16 +265,24 @@ const AdminAchievementManager = ({
|
||||||
{userAchievements.length ? (
|
{userAchievements.length ? (
|
||||||
<ul className="space-y-3 text-sm">
|
<ul className="space-y-3 text-sm">
|
||||||
{userAchievements.map((achievement) => (
|
{userAchievements.map((achievement) => (
|
||||||
<li key={achievement.id} className="flex items-start justify-between gap-3 rounded border border-border/30 bg-background/40 p-3">
|
<li
|
||||||
|
key={achievement.id}
|
||||||
|
className="flex items-start justify-between gap-3 rounded border border-border/30 bg-background/40 p-3"
|
||||||
|
>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="font-medium text-foreground">{achievement.name}</p>
|
<p className="font-medium text-foreground">
|
||||||
|
{achievement.name}
|
||||||
|
</p>
|
||||||
{achievement.description ? (
|
{achievement.description ? (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{achievement.description}
|
{achievement.description}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline" className="whitespace-nowrap text-xs">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="whitespace-nowrap text-xs"
|
||||||
|
>
|
||||||
{achievement.xp_reward ?? 0} XP
|
{achievement.xp_reward ?? 0} XP
|
||||||
</Badge>
|
</Badge>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -289,12 +310,18 @@ const AdminAchievementManager = ({
|
||||||
<p className="mb-1">{selectedAchievement.description}</p>
|
<p className="mb-1">{selectedAchievement.description}</p>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex flex-wrap gap-2 text-[11px] uppercase tracking-wide">
|
<div className="flex flex-wrap gap-2 text-[11px] uppercase tracking-wide">
|
||||||
<Badge variant="outline">{selectedAchievement.xp_reward ?? 0} XP</Badge>
|
<Badge variant="outline">
|
||||||
|
{selectedAchievement.xp_reward ?? 0} XP
|
||||||
|
</Badge>
|
||||||
<Badge variant="outline">ID: {selectedAchievement.id}</Badge>
|
<Badge variant="outline">ID: {selectedAchievement.id}</Badge>
|
||||||
<Badge variant="outline">
|
<Badge variant="outline">
|
||||||
Created {formatDistanceToNowStrict(new Date(selectedAchievement.created_at), {
|
Created{" "}
|
||||||
addSuffix: true,
|
{formatDistanceToNowStrict(
|
||||||
})}
|
new Date(selectedAchievement.created_at),
|
||||||
|
{
|
||||||
|
addSuffix: true,
|
||||||
|
},
|
||||||
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,11 @@ const buildProfileDraft = (profile: AethexUserProfile): ProfileDraft => ({
|
||||||
: "0",
|
: "0",
|
||||||
});
|
});
|
||||||
|
|
||||||
const ensureOwnerRoles = (roles: string[], profile: AethexUserProfile | null, ownerEmail: string) => {
|
const ensureOwnerRoles = (
|
||||||
|
roles: string[],
|
||||||
|
profile: AethexUserProfile | null,
|
||||||
|
ownerEmail: string,
|
||||||
|
) => {
|
||||||
if (!profile) return roles;
|
if (!profile) return roles;
|
||||||
if ((profile.email ?? "").toLowerCase() !== ownerEmail.toLowerCase()) {
|
if ((profile.email ?? "").toLowerCase() !== ownerEmail.toLowerCase()) {
|
||||||
return roles;
|
return roles;
|
||||||
|
|
@ -138,21 +142,18 @@ const AdminMemberManager = ({
|
||||||
}
|
}
|
||||||
}, [profiles, selectedId, onSelectedIdChange]);
|
}, [profiles, selectedId, onSelectedIdChange]);
|
||||||
|
|
||||||
const loadRoles = useCallback(
|
const loadRoles = useCallback(async (id: string) => {
|
||||||
async (id: string) => {
|
setLoadingRoles(true);
|
||||||
setLoadingRoles(true);
|
try {
|
||||||
try {
|
const fetched = await aethexRoleService.getUserRoles(id);
|
||||||
const fetched = await aethexRoleService.getUserRoles(id);
|
setRoles(normalizeRoles(fetched));
|
||||||
setRoles(normalizeRoles(fetched));
|
} catch (error) {
|
||||||
} catch (error) {
|
console.warn("Failed to load user roles", error);
|
||||||
console.warn("Failed to load user roles", error);
|
setRoles(["member"]);
|
||||||
setRoles(["member"]);
|
} finally {
|
||||||
} finally {
|
setLoadingRoles(false);
|
||||||
setLoadingRoles(false);
|
}
|
||||||
}
|
}, []);
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedProfile) {
|
if (selectedProfile) {
|
||||||
|
|
@ -217,7 +218,8 @@ const AdminMemberManager = ({
|
||||||
console.error("Failed to set user roles", error);
|
console.error("Failed to set user roles", error);
|
||||||
aethexToast.error({
|
aethexToast.error({
|
||||||
title: "Role update failed",
|
title: "Role update failed",
|
||||||
description: error?.message || "Unable to update roles. Check Supabase policies.",
|
description:
|
||||||
|
error?.message || "Unable to update roles. Check Supabase policies.",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setSavingRoles(false);
|
setSavingRoles(false);
|
||||||
|
|
@ -242,7 +244,8 @@ const AdminMemberManager = ({
|
||||||
updates.total_xp = Number(profileDraft.total_xp) || 0;
|
updates.total_xp = Number(profileDraft.total_xp) || 0;
|
||||||
}
|
}
|
||||||
if (profileDraft.loyalty_points.trim().length) {
|
if (profileDraft.loyalty_points.trim().length) {
|
||||||
(updates as any).loyalty_points = Number(profileDraft.loyalty_points) || 0;
|
(updates as any).loyalty_points =
|
||||||
|
Number(profileDraft.loyalty_points) || 0;
|
||||||
}
|
}
|
||||||
await aethexUserService.updateProfile(selectedProfile.id, updates);
|
await aethexUserService.updateProfile(selectedProfile.id, updates);
|
||||||
aethexToast.success({
|
aethexToast.success({
|
||||||
|
|
@ -254,7 +257,9 @@ const AdminMemberManager = ({
|
||||||
console.error("Failed to update profile", error);
|
console.error("Failed to update profile", error);
|
||||||
aethexToast.error({
|
aethexToast.error({
|
||||||
title: "Profile update failed",
|
title: "Profile update failed",
|
||||||
description: error?.message || "Supabase rejected the update. Review payload and RLS policies.",
|
description:
|
||||||
|
error?.message ||
|
||||||
|
"Supabase rejected the update. Review payload and RLS policies.",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setSavingProfile(false);
|
setSavingProfile(false);
|
||||||
|
|
@ -273,7 +278,9 @@ const AdminMemberManager = ({
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Directory</CardTitle>
|
<CardTitle>Directory</CardTitle>
|
||||||
<CardDescription>Search and select members to administer.</CardDescription>
|
<CardDescription>
|
||||||
|
Search and select members to administer.
|
||||||
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -320,7 +327,9 @@ const AdminMemberManager = ({
|
||||||
>
|
>
|
||||||
<TableCell className="font-medium text-foreground/90">
|
<TableCell className="font-medium text-foreground/90">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>{profile.full_name || profile.username || "Unknown"}</span>
|
<span>
|
||||||
|
{profile.full_name || profile.username || "Unknown"}
|
||||||
|
</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{profile.username}
|
{profile.username}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -339,7 +348,10 @@ const AdminMemberManager = ({
|
||||||
})}
|
})}
|
||||||
{!filteredProfiles.length ? (
|
{!filteredProfiles.length ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={3} className="text-center text-muted-foreground">
|
<TableCell
|
||||||
|
colSpan={3}
|
||||||
|
className="text-center text-muted-foreground"
|
||||||
|
>
|
||||||
No members found.
|
No members found.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
@ -410,7 +422,11 @@ const AdminMemberManager = ({
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{experienceOptions.map((option) => (
|
{experienceOptions.map((option) => (
|
||||||
<SelectItem key={option} value={option} className="capitalize">
|
<SelectItem
|
||||||
|
key={option}
|
||||||
|
value={option}
|
||||||
|
className="capitalize"
|
||||||
|
>
|
||||||
{option.replace("_", " ")}
|
{option.replace("_", " ")}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -432,7 +448,11 @@ const AdminMemberManager = ({
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{userTypeOptions.map((option) => (
|
{userTypeOptions.map((option) => (
|
||||||
<SelectItem key={option} value={option} className="capitalize">
|
<SelectItem
|
||||||
|
key={option}
|
||||||
|
value={option}
|
||||||
|
className="capitalize"
|
||||||
|
>
|
||||||
{option.replace("_", " ")}
|
{option.replace("_", " ")}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -463,7 +483,9 @@ const AdminMemberManager = ({
|
||||||
value={profileDraft.level}
|
value={profileDraft.level}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setProfileDraft((draft) =>
|
setProfileDraft((draft) =>
|
||||||
draft ? { ...draft, level: event.target.value } : draft,
|
draft
|
||||||
|
? { ...draft, level: event.target.value }
|
||||||
|
: draft,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
|
|
@ -553,7 +575,10 @@ const AdminMemberManager = ({
|
||||||
variant={active ? "default" : "outline"}
|
variant={active ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleRoleToggle(role)}
|
onClick={() => handleRoleToggle(role)}
|
||||||
className={cn("capitalize", active && "bg-aethex-500/80")}
|
className={cn(
|
||||||
|
"capitalize",
|
||||||
|
active && "bg-aethex-500/80",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{role}
|
{role}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -587,7 +612,11 @@ const AdminMemberManager = ({
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
roles.map((role) => (
|
roles.map((role) => (
|
||||||
<Badge key={role} variant="outline" className="capitalize">
|
<Badge
|
||||||
|
key={role}
|
||||||
|
variant="outline"
|
||||||
|
className="capitalize"
|
||||||
|
>
|
||||||
{role}
|
{role}
|
||||||
</Badge>
|
</Badge>
|
||||||
))
|
))
|
||||||
|
|
@ -610,7 +639,9 @@ const AdminMemberManager = ({
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => selectedProfile && loadRoles(selectedProfile.id)}
|
onClick={() =>
|
||||||
|
selectedProfile && loadRoles(selectedProfile.id)
|
||||||
|
}
|
||||||
disabled={loadingRoles}
|
disabled={loadingRoles}
|
||||||
>
|
>
|
||||||
<RefreshCw className="mr-2 h-4 w-4" /> Reload
|
<RefreshCw className="mr-2 h-4 w-4" /> Reload
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,18 @@ export const AdminStatCard = ({
|
||||||
<Card className="bg-card/60 border-border/40 backdrop-blur">
|
<Card className="bg-card/60 border-border/40 backdrop-blur">
|
||||||
<CardHeader className="space-y-3">
|
<CardHeader className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-base text-foreground/90">{title}</CardTitle>
|
<CardTitle className="text-base text-foreground/90">
|
||||||
<Badge variant="outline" className="border-border/40 text-xs text-muted-foreground">
|
{title}
|
||||||
|
</CardTitle>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-border/40 text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
Admin metric
|
Admin metric
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-3xl font-semibold text-gradient">
|
<div className="text-3xl font-semibold text-gradient">{value}</div>
|
||||||
{value}
|
|
||||||
</div>
|
|
||||||
<Icon className={`h-8 w-8 ${toneConfig[tone]}`} />
|
<Icon className={`h-8 w-8 ${toneConfig[tone]}`} />
|
||||||
</div>
|
</div>
|
||||||
{trend ? (
|
{trend ? (
|
||||||
|
|
@ -51,7 +54,9 @@ export const AdminStatCard = ({
|
||||||
{(description || actions) && (
|
{(description || actions) && (
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">{description}</p>
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
{actions}
|
{actions}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import { Heart, MessageCircle, Share2, Volume2, VolumeX } from "lucide-react";
|
||||||
Heart,
|
|
||||||
MessageCircle,
|
|
||||||
Share2,
|
|
||||||
Volume2,
|
|
||||||
VolumeX,
|
|
||||||
} from "lucide-react";
|
|
||||||
import type { FeedItem } from "@/pages/Feed";
|
import type { FeedItem } from "@/pages/Feed";
|
||||||
|
|
||||||
interface FeedItemCardProps {
|
interface FeedItemCardProps {
|
||||||
|
|
@ -42,7 +36,10 @@ export function FeedItemCard({
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Avatar className="h-12 w-12 ring-2 ring-aethex-500/30">
|
<Avatar className="h-12 w-12 ring-2 ring-aethex-500/30">
|
||||||
<AvatarImage src={item.authorAvatar || undefined} alt={item.authorName} />
|
<AvatarImage
|
||||||
|
src={item.authorAvatar || undefined}
|
||||||
|
alt={item.authorName}
|
||||||
|
/>
|
||||||
<AvatarFallback className="bg-aethex-500/10 text-aethex-300">
|
<AvatarFallback className="bg-aethex-500/10 text-aethex-300">
|
||||||
{item.authorName?.[0]?.toUpperCase() || "U"}
|
{item.authorName?.[0]?.toUpperCase() || "U"}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
|
|
@ -132,7 +129,10 @@ export function FeedItemCard({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="border-border/60 bg-background/60 text-xs uppercase tracking-wide">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-border/60 bg-background/60 text-xs uppercase tracking-wide"
|
||||||
|
>
|
||||||
{item.mediaType === "video"
|
{item.mediaType === "video"
|
||||||
? "Video"
|
? "Video"
|
||||||
: item.mediaType === "image"
|
: item.mediaType === "image"
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,10 @@ let shouldEnableSkipAgent = false;
|
||||||
// Example override (console): window.__AETHEX_SKIP_AGENT_CONFIG = { src: 'https://example.com/agent.js', id: '...' };
|
// Example override (console): window.__AETHEX_SKIP_AGENT_CONFIG = { src: 'https://example.com/agent.js', id: '...' };
|
||||||
const getRuntimeConfig = () => {
|
const getRuntimeConfig = () => {
|
||||||
try {
|
try {
|
||||||
if (typeof window !== "undefined" && (window as any).__AETHEX_SKIP_AGENT_CONFIG) {
|
if (
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
(window as any).__AETHEX_SKIP_AGENT_CONFIG
|
||||||
|
) {
|
||||||
return (window as any).__AETHEX_SKIP_AGENT_CONFIG as {
|
return (window as any).__AETHEX_SKIP_AGENT_CONFIG as {
|
||||||
src?: string;
|
src?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
@ -217,7 +220,10 @@ const createSkipAgentTheme = () => {
|
||||||
|
|
||||||
const isDocsPath = () => {
|
const isDocsPath = () => {
|
||||||
try {
|
try {
|
||||||
return typeof window !== "undefined" && window.location.pathname.startsWith("/docs");
|
return (
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
window.location.pathname.startsWith("/docs")
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -273,9 +279,9 @@ const isSkipAgentReachable = async (): Promise<boolean> => {
|
||||||
throw new Error(`Agent status request failed with ${response.status}`);
|
throw new Error(`Agent status request failed with ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = (await response.json().catch(() => null)) as
|
const payload = (await response.json().catch(() => null)) as {
|
||||||
| { active?: boolean }
|
active?: boolean;
|
||||||
| null;
|
} | null;
|
||||||
|
|
||||||
if (payload && payload.active === false) {
|
if (payload && payload.active === false) {
|
||||||
throw new Error("Agent reported inactive");
|
throw new Error("Agent reported inactive");
|
||||||
|
|
@ -413,7 +419,9 @@ const loadSkipAgent = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const scriptText = await response.text();
|
const scriptText = await response.text();
|
||||||
const blobUrl = URL.createObjectURL(new Blob([scriptText], { type: "application/javascript" }));
|
const blobUrl = URL.createObjectURL(
|
||||||
|
new Blob([scriptText], { type: "application/javascript" }),
|
||||||
|
);
|
||||||
|
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.id = SKIP_AGENT_SCRIPT_ID;
|
script.id = SKIP_AGENT_SCRIPT_ID;
|
||||||
|
|
|
||||||
|
|
@ -171,12 +171,17 @@ export default function Admin() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-red-400">Access denied</CardTitle>
|
<CardTitle className="text-red-400">Access denied</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
This panel is restricted to {ownerEmail}. If you need access, contact the site owner.
|
This panel is restricted to {ownerEmail}. If you need access,
|
||||||
|
contact the site owner.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex gap-2">
|
<CardContent className="flex gap-2">
|
||||||
<Button onClick={() => navigate("/dashboard")}>Go to dashboard</Button>
|
<Button onClick={() => navigate("/dashboard")}>
|
||||||
<Button variant="outline" onClick={() => navigate("/support")}>Contact support</Button>
|
Go to dashboard
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => navigate("/support")}>
|
||||||
|
Contact support
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -191,7 +196,8 @@ export default function Admin() {
|
||||||
|
|
||||||
const selectedMember = useMemo(
|
const selectedMember = useMemo(
|
||||||
() =>
|
() =>
|
||||||
managedProfiles.find((profile) => profile.id === selectedMemberId) ?? null,
|
managedProfiles.find((profile) => profile.id === selectedMemberId) ??
|
||||||
|
null,
|
||||||
[managedProfiles, selectedMemberId],
|
[managedProfiles, selectedMemberId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -200,7 +206,9 @@ export default function Admin() {
|
||||||
const featuredStudios = studios.length;
|
const featuredStudios = studios.length;
|
||||||
const pendingApplications = applications.filter((app) => {
|
const pendingApplications = applications.filter((app) => {
|
||||||
const status = (app.status ?? "").toLowerCase();
|
const status = (app.status ?? "").toLowerCase();
|
||||||
return status !== "approved" && status !== "completed" && status !== "closed";
|
return (
|
||||||
|
status !== "approved" && status !== "completed" && status !== "closed"
|
||||||
|
);
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
const overviewStats = useMemo(
|
const overviewStats = useMemo(
|
||||||
|
|
@ -209,7 +217,9 @@ export default function Admin() {
|
||||||
title: "Total members",
|
title: "Total members",
|
||||||
value: totalMembers ? totalMembers.toString() : "—",
|
value: totalMembers ? totalMembers.toString() : "—",
|
||||||
description: "Profiles synced from AeThex identity service.",
|
description: "Profiles synced from AeThex identity service.",
|
||||||
trend: totalMembers ? `${totalMembers} active profiles` : "Awaiting sync",
|
trend: totalMembers
|
||||||
|
? `${totalMembers} active profiles`
|
||||||
|
: "Awaiting sync",
|
||||||
icon: Users,
|
icon: Users,
|
||||||
tone: "blue" as const,
|
tone: "blue" as const,
|
||||||
},
|
},
|
||||||
|
|
@ -365,23 +375,37 @@ export default function Admin() {
|
||||||
<div className="container mx-auto px-4 max-w-6xl space-y-8">
|
<div className="container mx-auto px-4 max-w-6xl space-y-8">
|
||||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h1 className="text-3xl font-bold text-gradient">Admin Control Center</h1>
|
<h1 className="text-3xl font-bold text-gradient">
|
||||||
|
Admin Control Center
|
||||||
|
</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Unified oversight for AeThex operations, content, and community.
|
Unified oversight for AeThex operations, content, and community.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Badge variant="outline" className="border-green-500/50 text-green-300">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-green-500/50 text-green-300"
|
||||||
|
>
|
||||||
Owner
|
Owner
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="border-blue-500/50 text-blue-300">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-blue-500/50 text-blue-300"
|
||||||
|
>
|
||||||
Admin
|
Admin
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="border-purple-500/50 text-purple-300">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-purple-500/50 text-purple-300"
|
||||||
|
>
|
||||||
Founder
|
Founder
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Signed in as <span className="text-foreground">{normalizedEmail || ownerEmail}</span>
|
Signed in as{" "}
|
||||||
|
<span className="text-foreground">
|
||||||
|
{normalizedEmail || ownerEmail}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
|
@ -391,11 +415,17 @@ export default function Admin() {
|
||||||
<Button variant="outline" onClick={() => navigate("/profile")}>
|
<Button variant="outline" onClick={() => navigate("/profile")}>
|
||||||
Profile
|
Profile
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setActiveTab("content")}>Create update</Button>
|
<Button onClick={() => setActiveTab("content")}>
|
||||||
|
Create update
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={setActiveTab}
|
||||||
|
className="space-y-6"
|
||||||
|
>
|
||||||
<TabsList className="w-full justify-start gap-2 overflow-x-auto border border-border/40 bg-background/40 px-1 py-1 backdrop-blur">
|
<TabsList className="w-full justify-start gap-2 overflow-x-auto border border-border/40 bg-background/40 px-1 py-1 backdrop-blur">
|
||||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||||
<TabsTrigger value="content">Content</TabsTrigger>
|
<TabsTrigger value="content">Content</TabsTrigger>
|
||||||
|
|
@ -425,23 +455,31 @@ export default function Admin() {
|
||||||
<Command className="h-5 w-5 text-aethex-300" />
|
<Command className="h-5 w-5 text-aethex-300" />
|
||||||
<CardTitle>Quick actions</CardTitle>
|
<CardTitle>Quick actions</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Launch frequent administrative workflows.</CardDescription>
|
<CardDescription>
|
||||||
|
Launch frequent administrative workflows.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-3">
|
<CardContent className="grid gap-3">
|
||||||
{quickActions.map(({ label, description, icon: ActionIcon, action }) => (
|
{quickActions.map(
|
||||||
<button
|
({ label, description, icon: ActionIcon, action }) => (
|
||||||
key={label}
|
<button
|
||||||
type="button"
|
key={label}
|
||||||
onClick={action}
|
type="button"
|
||||||
className="group flex items-start gap-3 rounded-lg border border-border/30 bg-background/40 px-4 py-3 text-left transition hover:border-aethex-400/60 hover:bg-background/60"
|
onClick={action}
|
||||||
>
|
className="group flex items-start gap-3 rounded-lg border border-border/30 bg-background/40 px-4 py-3 text-left transition hover:border-aethex-400/60 hover:bg-background/60"
|
||||||
<ActionIcon className="mt-0.5 h-5 w-5 text-aethex-400 transition group-hover:text-aethex-200" />
|
>
|
||||||
<div className="space-y-1">
|
<ActionIcon className="mt-0.5 h-5 w-5 text-aethex-400 transition group-hover:text-aethex-200" />
|
||||||
<p className="font-medium text-foreground">{label}</p>
|
<div className="space-y-1">
|
||||||
<p className="text-sm text-muted-foreground">{description}</p>
|
<p className="font-medium text-foreground">
|
||||||
</div>
|
{label}
|
||||||
</button>
|
</p>
|
||||||
))}
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -451,21 +489,37 @@ export default function Admin() {
|
||||||
<Shield className="h-5 w-5 text-green-400" />
|
<Shield className="h-5 w-5 text-green-400" />
|
||||||
<CardTitle>Access control</CardTitle>
|
<CardTitle>Access control</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Owner-only access enforced via Supabase roles.</CardDescription>
|
<CardDescription>
|
||||||
|
Owner-only access enforced via Supabase roles.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
||||||
<ul className="space-y-2 leading-relaxed">
|
<ul className="space-y-2 leading-relaxed">
|
||||||
<li>
|
<li>
|
||||||
Owner email: <span className="text-foreground">{ownerEmail}</span>
|
Owner email:{" "}
|
||||||
|
<span className="text-foreground">{ownerEmail}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Roles are provisioned automatically on owner sign-in.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Grant additional admins by updating Supabase role
|
||||||
|
assignments.
|
||||||
</li>
|
</li>
|
||||||
<li>Roles are provisioned automatically on owner sign-in.</li>
|
|
||||||
<li>Grant additional admins by updating Supabase role assignments.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" size="sm" onClick={() => setActiveTab("community")}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setActiveTab("community")}
|
||||||
|
>
|
||||||
View members
|
View members
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" size="sm" onClick={() => navigate("/support")}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => navigate("/support")}
|
||||||
|
>
|
||||||
Contact support
|
Contact support
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -482,12 +536,18 @@ export default function Admin() {
|
||||||
<CardTitle>Content overview</CardTitle>
|
<CardTitle>Content overview</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{publishedPosts} published {publishedPosts === 1 ? "post" : "posts"} · {loadingPosts ? "refreshing content…" : "latest Supabase sync"}
|
{publishedPosts} published{" "}
|
||||||
|
{publishedPosts === 1 ? "post" : "posts"} ·{" "}
|
||||||
|
{loadingPosts
|
||||||
|
? "refreshing content…"
|
||||||
|
: "latest Supabase sync"}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-sm text-muted-foreground space-y-2">
|
<CardContent className="text-sm text-muted-foreground space-y-2">
|
||||||
<p>
|
<p>
|
||||||
Drafts and announcements appear instantly on the public blog after saving. Use scheduled releases for major updates and keep thumbnails optimised for 1200×630.
|
Drafts and announcements appear instantly on the public blog
|
||||||
|
after saving. Use scheduled releases for major updates and
|
||||||
|
keep thumbnails optimised for 1200×630.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -498,7 +558,9 @@ export default function Admin() {
|
||||||
<PenTool className="h-5 w-5 text-aethex-400" />
|
<PenTool className="h-5 w-5 text-aethex-400" />
|
||||||
<CardTitle className="text-lg">Blog posts</CardTitle>
|
<CardTitle className="text-lg">Blog posts</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Manage blog content stored in Supabase</CardDescription>
|
<CardDescription>
|
||||||
|
Manage blog content stored in Supabase
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
|
@ -538,7 +600,8 @@ export default function Admin() {
|
||||||
|
|
||||||
{blogPosts.length === 0 && (
|
{blogPosts.length === 0 && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
No posts loaded yet. Use “Refresh” or “Add post” to start managing content.
|
No posts loaded yet. Use “Refresh” or “Add post” to start
|
||||||
|
managing content.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -680,14 +743,28 @@ export default function Admin() {
|
||||||
<UserCog className="h-5 w-5 text-teal-300" />
|
<UserCog className="h-5 w-5 text-teal-300" />
|
||||||
<CardTitle>Community actions</CardTitle>
|
<CardTitle>Community actions</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Grow the network and celebrate contributors.</CardDescription>
|
<CardDescription>
|
||||||
|
Grow the network and celebrate contributors.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-wrap gap-2">
|
<CardContent className="flex flex-wrap gap-2">
|
||||||
<Button size="sm" onClick={() => navigate("/community")}>Open community hub</Button>
|
<Button size="sm" onClick={() => navigate("/community")}>
|
||||||
<Button size="sm" variant="outline" onClick={() => navigate("/mentorship")}>
|
Open community hub
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => navigate("/mentorship")}
|
||||||
|
>
|
||||||
Manage mentorships
|
Manage mentorships
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="outline" onClick={() => navigate("/support")}>Support queue</Button>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => navigate("/support")}
|
||||||
|
>
|
||||||
|
Support queue
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
@ -700,7 +777,9 @@ export default function Admin() {
|
||||||
<Settings className="h-5 w-5 text-yellow-300" />
|
<Settings className="h-5 w-5 text-yellow-300" />
|
||||||
<CardTitle>Featured studios</CardTitle>
|
<CardTitle>Featured studios</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Control studios highlighted across AeThex experiences.</CardDescription>
|
<CardDescription>
|
||||||
|
Control studios highlighted across AeThex experiences.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{studios.map((s, i) => (
|
{studios.map((s, i) => (
|
||||||
|
|
@ -762,7 +841,9 @@ export default function Admin() {
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setStudios(studios.filter((_, idx) => idx !== i))}
|
onClick={() =>
|
||||||
|
setStudios(studios.filter((_, idx) => idx !== i))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -773,7 +854,9 @@ export default function Admin() {
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setStudios([...studios, { name: "New Studio" }])}
|
onClick={() =>
|
||||||
|
setStudios([...studios, { name: "New Studio" }])
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Add studio
|
Add studio
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -788,12 +871,14 @@ export default function Admin() {
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
aethexToast.error({
|
aethexToast.error({
|
||||||
title: "Save failed",
|
title: "Save failed",
|
||||||
description: "Unable to persist featured studios.",
|
description:
|
||||||
|
"Unable to persist featured studios.",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
aethexToast.success({
|
aethexToast.success({
|
||||||
title: "Studios saved",
|
title: "Studios saved",
|
||||||
description: "Featured studios updated successfully.",
|
description:
|
||||||
|
"Featured studios updated successfully.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
@ -810,7 +895,9 @@ export default function Admin() {
|
||||||
<ClipboardList className="h-5 w-5 text-sky-300" />
|
<ClipboardList className="h-5 w-5 text-sky-300" />
|
||||||
<CardTitle>Project applications</CardTitle>
|
<CardTitle>Project applications</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Review collaboration requests and prioritize approvals.</CardDescription>
|
<CardDescription>
|
||||||
|
Review collaboration requests and prioritize approvals.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|
@ -847,7 +934,9 @@ export default function Admin() {
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
<p className="font-medium text-foreground">
|
<p className="font-medium text-foreground">
|
||||||
{app.applicant_name || app.applicant_email || "Unknown applicant"}
|
{app.applicant_name ||
|
||||||
|
app.applicant_email ||
|
||||||
|
"Unknown applicant"}
|
||||||
</p>
|
</p>
|
||||||
<Badge variant="outline" className="capitalize">
|
<Badge variant="outline" className="capitalize">
|
||||||
{(app.status ?? "pending").toLowerCase()}
|
{(app.status ?? "pending").toLowerCase()}
|
||||||
|
|
@ -858,14 +947,18 @@ export default function Admin() {
|
||||||
</p>
|
</p>
|
||||||
{app.created_at ? (
|
{app.created_at ? (
|
||||||
<p className="text-[11px] text-muted-foreground/80">
|
<p className="text-[11px] text-muted-foreground/80">
|
||||||
Submitted {new Date(app.created_at).toLocaleString()}
|
Submitted{" "}
|
||||||
|
{new Date(app.created_at).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p>No applications on file. Encourage partners to apply via briefs.</p>
|
<p>
|
||||||
|
No applications on file. Encourage partners to apply via
|
||||||
|
briefs.
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -876,7 +969,9 @@ export default function Admin() {
|
||||||
<Activity className="h-5 w-5 text-orange-300" />
|
<Activity className="h-5 w-5 text-orange-300" />
|
||||||
<CardTitle>System status</CardTitle>
|
<CardTitle>System status</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Auth, database, and realtime services.</CardDescription>
|
<CardDescription>
|
||||||
|
Auth, database, and realtime services.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-sm text-muted-foreground space-y-1">
|
<CardContent className="text-sm text-muted-foreground space-y-1">
|
||||||
<p>Auth: Operational</p>
|
<p>Auth: Operational</p>
|
||||||
|
|
@ -891,11 +986,16 @@ export default function Admin() {
|
||||||
<UserCog className="h-5 w-5 text-teal-300" />
|
<UserCog className="h-5 w-5 text-teal-300" />
|
||||||
<CardTitle>Your account</CardTitle>
|
<CardTitle>Your account</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Owner privileges are active.</CardDescription>
|
<CardDescription>
|
||||||
|
Owner privileges are active.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-sm text-muted-foreground space-y-2">
|
<CardContent className="text-sm text-muted-foreground space-y-2">
|
||||||
<p>Signed in as {user.email}.</p>
|
<p>Signed in as {user.email}.</p>
|
||||||
<p>You have full administrative access across AeThex services.</p>
|
<p>
|
||||||
|
You have full administrative access across AeThex
|
||||||
|
services.
|
||||||
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -84,9 +84,9 @@ export default function Feed() {
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [following, setFollowing] = useState<string[]>([]);
|
const [following, setFollowing] = useState<string[]>([]);
|
||||||
const [items, setItems] = useState<FeedItem[]>([]);
|
const [items, setItems] = useState<FeedItem[]>([]);
|
||||||
const [activeFilter, setActiveFilter] = useState<"all" | "following" | "trending">(
|
const [activeFilter, setActiveFilter] = useState<
|
||||||
"all",
|
"all" | "following" | "trending"
|
||||||
);
|
>("all");
|
||||||
|
|
||||||
const fetchFeed = useCallback(async () => {
|
const fetchFeed = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
@ -202,11 +202,14 @@ export default function Feed() {
|
||||||
const filteredItems = useMemo(() => {
|
const filteredItems = useMemo(() => {
|
||||||
if (activeFilter === "following") {
|
if (activeFilter === "following") {
|
||||||
return items.filter(
|
return items.filter(
|
||||||
(item) => isFollowingAuthor(item.authorId) || item.authorId === user?.id,
|
(item) =>
|
||||||
|
isFollowingAuthor(item.authorId) || item.authorId === user?.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (activeFilter === "trending") {
|
if (activeFilter === "trending") {
|
||||||
return [...items].sort((a, b) => b.likes + b.comments - (a.likes + a.comments));
|
return [...items].sort(
|
||||||
|
(a, b) => b.likes + b.comments - (a.likes + a.comments),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}, [activeFilter, isFollowingAuthor, items, user?.id]);
|
}, [activeFilter, isFollowingAuthor, items, user?.id]);
|
||||||
|
|
@ -259,7 +262,8 @@ export default function Feed() {
|
||||||
const totalEngagement = useMemo(
|
const totalEngagement = useMemo(
|
||||||
() =>
|
() =>
|
||||||
items.reduce(
|
items.reduce(
|
||||||
(acc, item) => acc + (Number(item.likes) || 0) + (Number(item.comments) || 0),
|
(acc, item) =>
|
||||||
|
acc + (Number(item.likes) || 0) + (Number(item.comments) || 0),
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
[items],
|
[items],
|
||||||
|
|
@ -271,7 +275,10 @@ export default function Feed() {
|
||||||
}, [items.length, totalEngagement]);
|
}, [items.length, totalEngagement]);
|
||||||
|
|
||||||
const handleScrollToComposer = useCallback(() => {
|
const handleScrollToComposer = useCallback(() => {
|
||||||
composerRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
|
composerRef.current?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleManualRefresh = useCallback(() => {
|
const handleManualRefresh = useCallback(() => {
|
||||||
|
|
@ -280,7 +287,11 @@ export default function Feed() {
|
||||||
|
|
||||||
if (loading || (isLoading && items.length === 0)) {
|
if (loading || (isLoading && items.length === 0)) {
|
||||||
return (
|
return (
|
||||||
<LoadingScreen message="Loading your feed..." showProgress duration={1000} />
|
<LoadingScreen
|
||||||
|
message="Loading your feed..."
|
||||||
|
showProgress
|
||||||
|
duration={1000}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,11 +308,15 @@ export default function Feed() {
|
||||||
Community Pulse
|
Community Pulse
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 max-w-2xl text-sm text-muted-foreground sm:text-base">
|
<p className="mt-2 max-w-2xl text-sm text-muted-foreground sm:text-base">
|
||||||
Discover new creations, amplify your voice, and engage with the AeThex community in real time.
|
Discover new creations, amplify your voice, and engage with
|
||||||
|
the AeThex community in real time.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<Badge variant="outline" className="border-aethex-400/60 bg-aethex-400/10 text-aethex-100">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-aethex-400/60 bg-aethex-400/10 text-aethex-100"
|
||||||
|
>
|
||||||
Live updates enabled
|
Live updates enabled
|
||||||
</Badge>
|
</Badge>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -316,28 +331,26 @@ export default function Feed() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{(
|
{[
|
||||||
[
|
{
|
||||||
{
|
key: "all" as const,
|
||||||
key: "all" as const,
|
label: "All stories",
|
||||||
label: "All stories",
|
icon: Sparkles,
|
||||||
icon: Sparkles,
|
description: "Latest community activity",
|
||||||
description: "Latest community activity",
|
},
|
||||||
},
|
{
|
||||||
{
|
key: "following" as const,
|
||||||
key: "following" as const,
|
label: "Following",
|
||||||
label: "Following",
|
icon: Users,
|
||||||
icon: Users,
|
description: "People you follow",
|
||||||
description: "People you follow",
|
},
|
||||||
},
|
{
|
||||||
{
|
key: "trending" as const,
|
||||||
key: "trending" as const,
|
label: "Trending",
|
||||||
label: "Trending",
|
icon: Flame,
|
||||||
icon: Flame,
|
description: "Most engagement",
|
||||||
description: "Most engagement",
|
},
|
||||||
},
|
].map(({ key, label, icon: Icon, description }) => (
|
||||||
]
|
|
||||||
).map(({ key, label, icon: Icon, description }) => (
|
|
||||||
<Button
|
<Button
|
||||||
key={key}
|
key={key}
|
||||||
variant={activeFilter === key ? "default" : "outline"}
|
variant={activeFilter === key ? "default" : "outline"}
|
||||||
|
|
@ -352,7 +365,9 @@ export default function Feed() {
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
<span className="font-medium">{label}</span>
|
<span className="font-medium">{label}</span>
|
||||||
<span className="hidden text-xs sm:inline">{description}</span>
|
<span className="hidden text-xs sm:inline">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -368,9 +383,12 @@ export default function Feed() {
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Share something new</h2>
|
<h2 className="text-lg font-semibold text-foreground">
|
||||||
|
Share something new
|
||||||
|
</h2>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Post updates, showcase progress, or spark a conversation with the community.
|
Post updates, showcase progress, or spark a conversation
|
||||||
|
with the community.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -386,7 +404,8 @@ export default function Feed() {
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-border/30 bg-background/60 p-4 text-sm text-muted-foreground">
|
<div className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-border/30 bg-background/60 p-4 text-sm text-muted-foreground">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Sparkles className="h-4 w-4 text-aethex-300" />
|
<Sparkles className="h-4 w-4 text-aethex-300" />
|
||||||
Your post is shared instantly with followers and the broader community.
|
Your post is shared instantly with followers and the broader
|
||||||
|
community.
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -403,7 +422,8 @@ export default function Feed() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">No stories found</CardTitle>
|
<CardTitle className="text-xl">No stories found</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Try switching filters or follow more creators to personalize your feed.
|
Try switching filters or follow more creators to
|
||||||
|
personalize your feed.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-0">
|
<CardContent className="pt-0">
|
||||||
|
|
@ -434,7 +454,9 @@ export default function Feed() {
|
||||||
<aside className="space-y-6">
|
<aside className="space-y-6">
|
||||||
<Card className="rounded-3xl border-border/40 bg-background/70 shadow-xl backdrop-blur-lg">
|
<Card className="rounded-3xl border-border/40 bg-background/70 shadow-xl backdrop-blur-lg">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">Your community snapshot</CardTitle>
|
<CardTitle className="text-lg">
|
||||||
|
Your community snapshot
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Track how your network is evolving at a glance.
|
Track how your network is evolving at a glance.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|
@ -442,25 +464,33 @@ export default function Feed() {
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Stories today</p>
|
<p className="text-xs uppercase text-muted-foreground">
|
||||||
|
Stories today
|
||||||
|
</p>
|
||||||
<p className="mt-1 text-2xl font-semibold text-foreground">
|
<p className="mt-1 text-2xl font-semibold text-foreground">
|
||||||
{items.length.toLocaleString()}
|
{items.length.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Creators you follow</p>
|
<p className="text-xs uppercase text-muted-foreground">
|
||||||
|
Creators you follow
|
||||||
|
</p>
|
||||||
<p className="mt-1 text-2xl font-semibold text-foreground">
|
<p className="mt-1 text-2xl font-semibold text-foreground">
|
||||||
{following.length.toLocaleString()}
|
{following.length.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Community reactions</p>
|
<p className="text-xs uppercase text-muted-foreground">
|
||||||
|
Community reactions
|
||||||
|
</p>
|
||||||
<p className="mt-1 text-2xl font-semibold text-foreground">
|
<p className="mt-1 text-2xl font-semibold text-foreground">
|
||||||
{totalEngagement.toLocaleString()}
|
{totalEngagement.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
<div className="rounded-2xl border border-border/30 bg-background/60 p-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Avg. engagement</p>
|
<p className="text-xs uppercase text-muted-foreground">
|
||||||
|
Avg. engagement
|
||||||
|
</p>
|
||||||
<p className="mt-1 text-2xl font-semibold text-foreground">
|
<p className="mt-1 text-2xl font-semibold text-foreground">
|
||||||
{averageEngagement.toLocaleString()}
|
{averageEngagement.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -492,7 +522,8 @@ export default function Feed() {
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{trendingTopics.length === 0 ? (
|
{trendingTopics.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Start a conversation by adding hashtags like #gamedev or #design to your next post.
|
Start a conversation by adding hashtags like #gamedev or
|
||||||
|
#design to your next post.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
trendingTopics.map((topic, index) => (
|
trendingTopics.map((topic, index) => (
|
||||||
|
|
@ -505,7 +536,9 @@ export default function Feed() {
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">{topic.topic}</p>
|
<p className="font-medium text-foreground">
|
||||||
|
{topic.topic}
|
||||||
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{topic.count.toLocaleString()} mentions today
|
{topic.count.toLocaleString()} mentions today
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -535,7 +568,8 @@ export default function Feed() {
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{suggestedCreators.length === 0 ? (
|
{suggestedCreators.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
You are up to date with the creators you follow. Engage with new posts to unlock more suggestions.
|
You are up to date with the creators you follow. Engage
|
||||||
|
with new posts to unlock more suggestions.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
suggestedCreators.map((creator) => (
|
suggestedCreators.map((creator) => (
|
||||||
|
|
@ -554,9 +588,12 @@ export default function Feed() {
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">{creator.name}</p>
|
<p className="font-medium text-foreground">
|
||||||
|
{creator.name}
|
||||||
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{creator.posts.toLocaleString()} posts · {creator.likes.toLocaleString()} reactions
|
{creator.posts.toLocaleString()} posts ·{" "}
|
||||||
|
{creator.likes.toLocaleString()} reactions
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,13 @@ const webhookTopics = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "incident.opened",
|
event: "incident.opened",
|
||||||
description: "Raised when monitoring detects outages or SLA breaches in production environments.",
|
description:
|
||||||
|
"Raised when monitoring detects outages or SLA breaches in production environments.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "member.invited",
|
event: "member.invited",
|
||||||
description: "Notify downstream systems when a collaborator invitation is created or accepted.",
|
description:
|
||||||
|
"Notify downstream systems when a collaborator invitation is created or accepted.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -108,11 +110,13 @@ export default function DocsApiReference() {
|
||||||
<ServerCog className="mr-2 h-3 w-3" />
|
<ServerCog className="mr-2 h-3 w-3" />
|
||||||
API Reference
|
API Reference
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="text-3xl font-semibold text-white">Integrate programmatically with the AeThex API</h2>
|
<h2 className="text-3xl font-semibold text-white">
|
||||||
|
Integrate programmatically with the AeThex API
|
||||||
|
</h2>
|
||||||
<p className="text-gray-300 max-w-3xl">
|
<p className="text-gray-300 max-w-3xl">
|
||||||
The REST API exposes every core capability of the AeThex platform. Authenticate with OAuth 2.1 or
|
The REST API exposes every core capability of the AeThex platform.
|
||||||
personal access tokens, call idempotent endpoints, and subscribe to webhooks to react to changes in real
|
Authenticate with OAuth 2.1 or personal access tokens, call idempotent
|
||||||
time.
|
endpoints, and subscribe to webhooks to react to changes in real time.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -125,16 +129,20 @@ export default function DocsApiReference() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 text-gray-300">
|
<CardContent className="space-y-4 text-gray-300">
|
||||||
<p>Use the OAuth client credentials grant for service-to-service integrations:</p>
|
<p>
|
||||||
|
Use the OAuth client credentials grant for service-to-service
|
||||||
|
integrations:
|
||||||
|
</p>
|
||||||
<pre className="rounded-lg border border-slate-700 bg-slate-950/60 p-4 text-sm text-blue-200">
|
<pre className="rounded-lg border border-slate-700 bg-slate-950/60 p-4 text-sm text-blue-200">
|
||||||
{`curl -X POST https://api.aethex.dev/v1/auth/token \
|
{`curl -X POST https://api.aethex.dev/v1/auth/token \
|
||||||
-u CLIENT_ID:CLIENT_SECRET \
|
-u CLIENT_ID:CLIENT_SECRET \
|
||||||
-d "grant_type=client_credentials" \
|
-d "grant_type=client_credentials" \
|
||||||
-d "scope=projects:read deployments:write"`}
|
-d "scope=projects:read deployments:write"`}
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
Prefer user-scoped access? Direct builders through the hosted OAuth consent screen and exchange their
|
Prefer user-scoped access? Direct builders through the hosted
|
||||||
authorization code using the same endpoint.
|
OAuth consent screen and exchange their authorization code using
|
||||||
|
the same endpoint.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -148,10 +156,11 @@ export default function DocsApiReference() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 text-gray-300">
|
<CardContent className="space-y-4 text-gray-300">
|
||||||
<p className="text-gray-300">
|
<p className="text-gray-300">
|
||||||
Call the Projects endpoint with your Bearer token and inspect pagination headers for large result sets.
|
Call the Projects endpoint with your Bearer token and inspect
|
||||||
|
pagination headers for large result sets.
|
||||||
</p>
|
</p>
|
||||||
<pre className="rounded-lg border border-slate-700 bg-slate-950/60 p-4 text-sm text-teal-200">
|
<pre className="rounded-lg border border-slate-700 bg-slate-950/60 p-4 text-sm text-teal-200">
|
||||||
{`fetch("https://api.aethex.dev/v1/projects?page=1&limit=25", {
|
{`fetch("https://api.aethex.dev/v1/projects?page=1&limit=25", {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer ${TOKEN}",
|
Authorization: "Bearer ${TOKEN}",
|
||||||
"AeThex-Environment": "production",
|
"AeThex-Environment": "production",
|
||||||
|
|
@ -162,9 +171,16 @@ export default function DocsApiReference() {
|
||||||
});`}
|
});`}
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
Responses include <code className="rounded bg-black/40 px-2 py-1 text-blue-200">X-RateLimit-Remaining</code>
|
Responses include{" "}
|
||||||
and <code className="rounded bg-black/40 px-2 py-1 text-blue-200">X-Request-ID</code> headers. Share the
|
<code className="rounded bg-black/40 px-2 py-1 text-blue-200">
|
||||||
request ID when contacting support for faster triage.
|
X-RateLimit-Remaining
|
||||||
|
</code>
|
||||||
|
and{" "}
|
||||||
|
<code className="rounded bg-black/40 px-2 py-1 text-blue-200">
|
||||||
|
X-Request-ID
|
||||||
|
</code>{" "}
|
||||||
|
headers. Share the request ID when contacting support for faster
|
||||||
|
triage.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -189,7 +205,9 @@ export default function DocsApiReference() {
|
||||||
{apiEndpoints.map((endpoint) => (
|
{apiEndpoints.map((endpoint) => (
|
||||||
<TableRow key={`${endpoint.method}-${endpoint.path}`}>
|
<TableRow key={`${endpoint.method}-${endpoint.path}`}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge className="bg-blue-600/30 text-blue-100">{endpoint.method}</Badge>
|
<Badge className="bg-blue-600/30 text-blue-100">
|
||||||
|
{endpoint.method}
|
||||||
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-mono text-purple-200">
|
<TableCell className="font-mono text-purple-200">
|
||||||
{endpoint.path}
|
{endpoint.path}
|
||||||
|
|
@ -201,7 +219,8 @@ export default function DocsApiReference() {
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableCaption>
|
<TableCaption>
|
||||||
Need deeper coverage? The JavaScript SDK ships with typed request builders for every endpoint.
|
Need deeper coverage? The JavaScript SDK ships with typed
|
||||||
|
request builders for every endpoint.
|
||||||
</TableCaption>
|
</TableCaption>
|
||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -223,12 +242,23 @@ export default function DocsApiReference() {
|
||||||
className="rounded-lg border border-slate-700 bg-slate-950/40 p-4"
|
className="rounded-lg border border-slate-700 bg-slate-950/40 p-4"
|
||||||
>
|
>
|
||||||
<p className="font-mono text-sm text-blue-300">{topic.event}</p>
|
<p className="font-mono text-sm text-blue-300">{topic.event}</p>
|
||||||
<p className="text-gray-300 text-sm mt-2">{topic.description}</p>
|
<p className="text-gray-300 text-sm mt-2">
|
||||||
|
{topic.description}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<p className="text-gray-400 text-sm">
|
<p className="text-gray-400 text-sm">
|
||||||
Configure webhook destinations and signing secrets from the <Link to="/dashboard" className="text-blue-300 hover:text-blue-200">dashboard</Link>.
|
Configure webhook destinations and signing secrets from the{" "}
|
||||||
Verify requests with the <code className="rounded bg-black/40 px-2 py-1 text-blue-200">AeThex-Signature</code>
|
<Link
|
||||||
|
to="/dashboard"
|
||||||
|
className="text-blue-300 hover:text-blue-200"
|
||||||
|
>
|
||||||
|
dashboard
|
||||||
|
</Link>
|
||||||
|
. Verify requests with the{" "}
|
||||||
|
<code className="rounded bg-black/40 px-2 py-1 text-blue-200">
|
||||||
|
AeThex-Signature
|
||||||
|
</code>
|
||||||
header to guarantee authenticity.
|
header to guarantee authenticity.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -243,13 +273,22 @@ export default function DocsApiReference() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 text-gray-300">
|
<CardContent className="space-y-4 text-gray-300">
|
||||||
<p className="text-gray-300">
|
<p className="text-gray-300">
|
||||||
All endpoints are idempotent where appropriate and support conditional requests via the
|
All endpoints are idempotent where appropriate and support
|
||||||
<code className="rounded bg-black/40 px-2 py-1 text-blue-200">If-Match</code> header.
|
conditional requests via the
|
||||||
|
<code className="rounded bg-black/40 px-2 py-1 text-blue-200">
|
||||||
|
If-Match
|
||||||
|
</code>{" "}
|
||||||
|
header.
|
||||||
</p>
|
</p>
|
||||||
<div className="grid gap-3">
|
<div className="grid gap-3">
|
||||||
{errorExamples.map((error) => (
|
{errorExamples.map((error) => (
|
||||||
<div key={error.code} className="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-950/60 p-3">
|
<div
|
||||||
<Badge className="bg-red-600/30 text-red-200">{error.code}</Badge>
|
key={error.code}
|
||||||
|
className="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-950/60 p-3"
|
||||||
|
>
|
||||||
|
<Badge className="bg-red-600/30 text-red-200">
|
||||||
|
{error.code}
|
||||||
|
</Badge>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-white font-medium">{error.label}</p>
|
<p className="text-white font-medium">{error.label}</p>
|
||||||
<p className="text-sm text-gray-400">{error.hint}</p>
|
<p className="text-sm text-gray-400">{error.hint}</p>
|
||||||
|
|
@ -258,20 +297,28 @@ export default function DocsApiReference() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-300">
|
<p className="text-gray-300">
|
||||||
Monitor rate-limit headers and retry using an exponential backoff strategy. Persistent errors can be
|
Monitor rate-limit headers and retry using an exponential backoff
|
||||||
escalated to the AeThex support team with the failing request ID.
|
strategy. Persistent errors can be escalated to the AeThex support
|
||||||
|
team with the failing request ID.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="resources" className="rounded-2xl border border-blue-500/40 bg-blue-900/20 p-8">
|
<section
|
||||||
|
id="resources"
|
||||||
|
className="rounded-2xl border border-blue-500/40 bg-blue-900/20 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">Ship production-ready integrations</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Ship production-ready integrations
|
||||||
|
</h3>
|
||||||
<p className="text-gray-300 max-w-2xl">
|
<p className="text-gray-300 max-w-2xl">
|
||||||
Combine the REST API with event webhooks for a full-duplex integration pattern. Use the official
|
Combine the REST API with event webhooks for a full-duplex
|
||||||
TypeScript SDK for typed helpers or generate your own client with the published OpenAPI schema.
|
integration pattern. Use the official TypeScript SDK for typed
|
||||||
|
helpers or generate your own client with the published OpenAPI
|
||||||
|
schema.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
|
|
@ -281,7 +328,12 @@ export default function DocsApiReference() {
|
||||||
OpenAPI explorer
|
OpenAPI explorer
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="border-blue-400/60 text-blue-200">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="border-blue-400/60 text-blue-200"
|
||||||
|
>
|
||||||
<Link to="/docs/examples">
|
<Link to="/docs/examples">
|
||||||
<AlertTriangle className="mr-2 h-5 w-5" />
|
<AlertTriangle className="mr-2 h-5 w-5" />
|
||||||
See implementation patterns
|
See implementation patterns
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ const commands = [
|
||||||
{
|
{
|
||||||
command: "aethex deploy",
|
command: "aethex deploy",
|
||||||
description: "Build and deploy the current project",
|
description: "Build and deploy the current project",
|
||||||
usage: "Runs tests, packages artifacts, and promotes to the target environment",
|
usage:
|
||||||
|
"Runs tests, packages artifacts, and promotes to the target environment",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: "aethex env pull",
|
command: "aethex env pull",
|
||||||
|
|
@ -87,11 +88,14 @@ export default function DocsCli() {
|
||||||
<Terminal className="mr-2 h-3 w-3" />
|
<Terminal className="mr-2 h-3 w-3" />
|
||||||
CLI Tools
|
CLI Tools
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="text-3xl font-semibold text-white">Operate AeThex from the command line</h2>
|
<h2 className="text-3xl font-semibold text-white">
|
||||||
|
Operate AeThex from the command line
|
||||||
|
</h2>
|
||||||
<p className="text-gray-300 max-w-3xl">
|
<p className="text-gray-300 max-w-3xl">
|
||||||
The AeThex CLI automates local development, environment management, and production deployments. It is
|
The AeThex CLI automates local development, environment management,
|
||||||
built with stability in mind, featuring transactional deploys, shell-friendly output, and native support for
|
and production deployments. It is built with stability in mind,
|
||||||
Linux, macOS, and Windows.
|
featuring transactional deploys, shell-friendly output, and native
|
||||||
|
support for Linux, macOS, and Windows.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -113,13 +117,22 @@ export default function DocsCli() {
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{commands.map((item) => (
|
{commands.map((item) => (
|
||||||
<TableRow key={item.command}>
|
<TableRow key={item.command}>
|
||||||
<TableCell className="font-mono text-purple-200">{item.command}</TableCell>
|
<TableCell className="font-mono text-purple-200">
|
||||||
<TableCell className="text-gray-300">{item.description}</TableCell>
|
{item.command}
|
||||||
<TableCell className="text-gray-400 text-sm">{item.usage}</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="text-gray-300">
|
||||||
|
{item.description}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-gray-400 text-sm">
|
||||||
|
{item.usage}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableCaption>Run <code className="bg-black/40 px-1">aethex --help</code> for the full command tree.</TableCaption>
|
<TableCaption>
|
||||||
|
Run <code className="bg-black/40 px-1">aethex --help</code> for
|
||||||
|
the full command tree.
|
||||||
|
</TableCaption>
|
||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -135,12 +148,13 @@ export default function DocsCli() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-gray-300 space-y-3 text-sm">
|
<CardContent className="text-gray-300 space-y-3 text-sm">
|
||||||
<p>
|
<p>
|
||||||
Run <code className="bg-black/40 px-1">aethex dev</code> to start local services with hot reloading and the
|
Run <code className="bg-black/40 px-1">aethex dev</code> to start
|
||||||
AeThex mock identity provider. Inspect logs via the integrated tail view.
|
local services with hot reloading and the AeThex mock identity
|
||||||
|
provider. Inspect logs via the integrated tail view.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Use <code className="bg-black/40 px-1">aethex data seed</code> to populate sample datasets for QA or demo
|
Use <code className="bg-black/40 px-1">aethex data seed</code> to
|
||||||
accounts.
|
populate sample datasets for QA or demo accounts.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -154,12 +168,16 @@ export default function DocsCli() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-gray-300 space-y-3 text-sm">
|
<CardContent className="text-gray-300 space-y-3 text-sm">
|
||||||
<p>
|
<p>
|
||||||
Synchronize configuration with <code className="bg-black/40 px-1">aethex env pull</code> or populate remote
|
Synchronize configuration with{" "}
|
||||||
environments from local files using <code className="bg-black/40 px-1">aethex env push</code>.
|
<code className="bg-black/40 px-1">aethex env pull</code> or
|
||||||
|
populate remote environments from local files using{" "}
|
||||||
|
<code className="bg-black/40 px-1">aethex env push</code>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Inspect secrets securely through <code className="bg-black/40 px-1">aethex env inspect</code>. Output is redacted
|
Inspect secrets securely through{" "}
|
||||||
by default, keeping sensitive data safe in terminal logs.
|
<code className="bg-black/40 px-1">aethex env inspect</code>.
|
||||||
|
Output is redacted by default, keeping sensitive data safe in
|
||||||
|
terminal logs.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -173,11 +191,13 @@ export default function DocsCli() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-gray-300 space-y-3 text-sm">
|
<CardContent className="text-gray-300 space-y-3 text-sm">
|
||||||
<p>
|
<p>
|
||||||
Each deployment is transactional: a failure during build or migration automatically halts promotion and
|
Each deployment is transactional: a failure during build or
|
||||||
emits alerts to subscribed channels.
|
migration automatically halts promotion and emits alerts to
|
||||||
|
subscribed channels.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Gates such as quality checks or approvals can be configured in <code className="bg-black/40 px-1">aethex.config.ts</code>
|
Gates such as quality checks or approvals can be configured in{" "}
|
||||||
|
<code className="bg-black/40 px-1">aethex.config.ts</code>
|
||||||
and enforced automatically by the CLI.
|
and enforced automatically by the CLI.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -187,13 +207,17 @@ export default function DocsCli() {
|
||||||
<section id="automation" className="space-y-4">
|
<section id="automation" className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<CloudLightning className="h-6 w-6 text-amber-300" />
|
<CloudLightning className="h-6 w-6 text-amber-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Automate everything</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Automate everything
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
{automationTips.map((tip) => (
|
{automationTips.map((tip) => (
|
||||||
<Card key={tip.title} className="bg-slate-900/60 border-slate-700">
|
<Card key={tip.title} className="bg-slate-900/60 border-slate-700">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white text-lg">{tip.title}</CardTitle>
|
<CardTitle className="text-white text-lg">
|
||||||
|
{tip.title}
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription className="text-sm text-gray-300">
|
<CardDescription className="text-sm text-gray-300">
|
||||||
|
|
@ -205,29 +229,49 @@ export default function DocsCli() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="security" className="rounded-2xl border border-amber-500/40 bg-amber-900/20 p-8">
|
<section
|
||||||
|
id="security"
|
||||||
|
className="rounded-2xl border border-amber-500/40 bg-amber-900/20 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">Stay safe in production</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Stay safe in production
|
||||||
|
</h3>
|
||||||
<p className="text-gray-200 max-w-2xl text-sm">
|
<p className="text-gray-200 max-w-2xl text-sm">
|
||||||
The CLI signs every build artifact and enforces checksums during deployment. Combine this with RBAC token
|
The CLI signs every build artifact and enforces checksums during
|
||||||
policies to guarantee only trusted pipelines can trigger releases.
|
deployment. Combine this with RBAC token policies to guarantee
|
||||||
|
only trusted pipelines can trigger releases.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 sm:flex-row">
|
<div className="flex flex-col gap-3 sm:flex-row">
|
||||||
<Button asChild size="lg" className="bg-amber-500 hover:bg-amber-400 text-black">
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
className="bg-amber-500 hover:bg-amber-400 text-black"
|
||||||
|
>
|
||||||
<Link to="/docs/getting-started">
|
<Link to="/docs/getting-started">
|
||||||
<ArrowRight className="mr-2 h-5 w-5" />
|
<ArrowRight className="mr-2 h-5 w-5" />
|
||||||
Return to setup guide
|
Return to setup guide
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="border-amber-300/60 text-amber-100">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="border-amber-300/60 text-amber-100"
|
||||||
|
>
|
||||||
<Link to="/support">
|
<Link to="/support">
|
||||||
<Shield className="mr-2 h-5 w-5" />
|
<Shield className="mr-2 h-5 w-5" />
|
||||||
Security best practices
|
Security best practices
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="ghost" size="lg" className="text-amber-100 hover:text-amber-50">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="ghost"
|
||||||
|
size="lg"
|
||||||
|
className="text-amber-100 hover:text-amber-50"
|
||||||
|
>
|
||||||
<Link to="/docs/platform">
|
<Link to="/docs/platform">
|
||||||
Discover platform features
|
Discover platform features
|
||||||
<ArrowRight className="ml-2 h-5 w-5" />
|
<ArrowRight className="ml-2 h-5 w-5" />
|
||||||
|
|
|
||||||
|
|
@ -142,19 +142,27 @@ export default function DocsExamples() {
|
||||||
<Blocks className="mr-2 h-3 w-3" />
|
<Blocks className="mr-2 h-3 w-3" />
|
||||||
Examples & Templates
|
Examples & Templates
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="text-3xl font-semibold text-white">Production-ready patterns you can copy</h2>
|
<h2 className="text-3xl font-semibold text-white">
|
||||||
|
Production-ready patterns you can copy
|
||||||
|
</h2>
|
||||||
<p className="text-gray-300 max-w-3xl">
|
<p className="text-gray-300 max-w-3xl">
|
||||||
Explore curated examples covering backend services, realtime overlays, automation scripts, and workflow
|
Explore curated examples covering backend services, realtime overlays,
|
||||||
integrations. Each project includes detailed READMEs, infrastructure diagrams, and deployment runbooks.
|
automation scripts, and workflow integrations. Each project includes
|
||||||
|
detailed READMEs, infrastructure diagrams, and deployment runbooks.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="code-gallery" className="grid gap-6 lg:grid-cols-3">
|
<section id="code-gallery" className="grid gap-6 lg:grid-cols-3">
|
||||||
{exampleSnippets.map((snippet) => (
|
{exampleSnippets.map((snippet) => (
|
||||||
<Card key={snippet.title} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={snippet.title}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader className="space-y-2">
|
<CardHeader className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-white text-lg">{snippet.title}</CardTitle>
|
<CardTitle className="text-white text-lg">
|
||||||
|
{snippet.title}
|
||||||
|
</CardTitle>
|
||||||
<Badge variant="outline">{snippet.language}</Badge>
|
<Badge variant="outline">{snippet.language}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription className="text-gray-300 text-sm">
|
<CardDescription className="text-gray-300 text-sm">
|
||||||
|
|
@ -165,7 +173,10 @@ export default function DocsExamples() {
|
||||||
<pre className="rounded-lg border border-slate-700 bg-slate-950/60 p-4 text-xs text-emerald-200 overflow-x-auto">
|
<pre className="rounded-lg border border-slate-700 bg-slate-950/60 p-4 text-xs text-emerald-200 overflow-x-auto">
|
||||||
<code>{snippet.code}</code>
|
<code>{snippet.code}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<Button asChild className="w-full bg-emerald-500 hover:bg-emerald-400 text-black">
|
<Button
|
||||||
|
asChild
|
||||||
|
className="w-full bg-emerald-500 hover:bg-emerald-400 text-black"
|
||||||
|
>
|
||||||
<Link to={snippet.href} target="_blank" rel="noreferrer">
|
<Link to={snippet.href} target="_blank" rel="noreferrer">
|
||||||
<Github className="mr-2 h-4 w-4" />
|
<Github className="mr-2 h-4 w-4" />
|
||||||
Open repository
|
Open repository
|
||||||
|
|
@ -180,13 +191,17 @@ export default function DocsExamples() {
|
||||||
<section id="templates" className="space-y-4">
|
<section id="templates" className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Flame className="h-6 w-6 text-emerald-300" />
|
<Flame className="h-6 w-6 text-emerald-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Deploy faster with templates</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Deploy faster with templates
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
{integrationIdeas.map((idea) => (
|
{integrationIdeas.map((idea) => (
|
||||||
<Card key={idea.title} className="bg-slate-900/60 border-slate-700">
|
<Card key={idea.title} className="bg-slate-900/60 border-slate-700">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white text-base">{idea.title}</CardTitle>
|
<CardTitle className="text-white text-base">
|
||||||
|
{idea.title}
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription className="text-gray-300 text-sm mb-4">
|
<CardDescription className="text-gray-300 text-sm mb-4">
|
||||||
|
|
@ -208,23 +223,38 @@ export default function DocsExamples() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="share" className="rounded-2xl border border-emerald-500/40 bg-emerald-900/20 p-8">
|
<section
|
||||||
|
id="share"
|
||||||
|
className="rounded-2xl border border-emerald-500/40 bg-emerald-900/20 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">Share what you build</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Share what you build
|
||||||
|
</h3>
|
||||||
<p className="text-gray-200 max-w-2xl text-sm">
|
<p className="text-gray-200 max-w-2xl text-sm">
|
||||||
Publish your own templates or improvements by opening a pull request to the public AeThex examples
|
Publish your own templates or improvements by opening a pull
|
||||||
repository. Every accepted contribution is highlighted in the monthly creator spotlight.
|
request to the public AeThex examples repository. Every accepted
|
||||||
|
contribution is highlighted in the monthly creator spotlight.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button asChild size="lg" className="bg-emerald-500 hover:bg-emerald-400 text-black">
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
className="bg-emerald-500 hover:bg-emerald-400 text-black"
|
||||||
|
>
|
||||||
<Link to="https://github.com/aethex/examples" target="_blank">
|
<Link to="https://github.com/aethex/examples" target="_blank">
|
||||||
<Code2 className="mr-2 h-5 w-5" />
|
<Code2 className="mr-2 h-5 w-5" />
|
||||||
Contribute on GitHub
|
Contribute on GitHub
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="border-emerald-300/60 text-emerald-100">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="border-emerald-300/60 text-emerald-100"
|
||||||
|
>
|
||||||
<Link to="/community">
|
<Link to="/community">
|
||||||
<Share2 className="mr-2 h-5 w-5" />
|
<Share2 className="mr-2 h-5 w-5" />
|
||||||
Showcase to the community
|
Showcase to the community
|
||||||
|
|
@ -234,16 +264,27 @@ export default function DocsExamples() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="services" className="rounded-2xl border border-emerald-500/20 bg-slate-900/80 p-8">
|
<section
|
||||||
|
id="services"
|
||||||
|
className="rounded-2xl border border-emerald-500/20 bg-slate-900/80 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-xl font-semibold text-white">Need a custom integration?</h4>
|
<h4 className="text-xl font-semibold text-white">
|
||||||
|
Need a custom integration?
|
||||||
|
</h4>
|
||||||
<p className="text-gray-300 text-sm">
|
<p className="text-gray-300 text-sm">
|
||||||
Our professional services team partners with studios to build tailored pipelines, analytics dashboards,
|
Our professional services team partners with studios to build
|
||||||
and automation workflows on top of AeThex.
|
tailored pipelines, analytics dashboards, and automation workflows
|
||||||
|
on top of AeThex.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button asChild variant="outline" size="lg" className="border-emerald-300/60 text-emerald-100">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="border-emerald-300/60 text-emerald-100"
|
||||||
|
>
|
||||||
<Link to="/consulting">
|
<Link to="/consulting">
|
||||||
<Globe className="mr-2 h-5 w-5" />
|
<Globe className="mr-2 h-5 w-5" />
|
||||||
Talk to AeThex consultants
|
Talk to AeThex consultants
|
||||||
|
|
|
||||||
|
|
@ -153,27 +153,32 @@ const platformHighlights = [
|
||||||
const explorationLinks = [
|
const explorationLinks = [
|
||||||
{
|
{
|
||||||
title: "Platform Walkthrough",
|
title: "Platform Walkthrough",
|
||||||
description: "Tour the dashboard, notification center, and collaboration features.",
|
description:
|
||||||
|
"Tour the dashboard, notification center, and collaboration features.",
|
||||||
href: "/dashboard",
|
href: "/dashboard",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Platform documentation",
|
title: "Platform documentation",
|
||||||
description: "Share the high-level platform overview with non-technical teammates.",
|
description:
|
||||||
|
"Share the high-level platform overview with non-technical teammates.",
|
||||||
href: "/docs/platform",
|
href: "/docs/platform",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "API Reference",
|
title: "API Reference",
|
||||||
description: "Review authentication flows, REST endpoints, and webhook schemas.",
|
description:
|
||||||
|
"Review authentication flows, REST endpoints, and webhook schemas.",
|
||||||
href: "/docs/api",
|
href: "/docs/api",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Tutorial Library",
|
title: "Tutorial Library",
|
||||||
description: "Follow guided builds for matchmaking services, player analytics, and live events.",
|
description:
|
||||||
|
"Follow guided builds for matchmaking services, player analytics, and live events.",
|
||||||
href: "/docs/tutorials",
|
href: "/docs/tutorials",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Community Support",
|
title: "Community Support",
|
||||||
description: "Ask questions, share templates, and pair up with mentors in the public forums.",
|
description:
|
||||||
|
"Ask questions, share templates, and pair up with mentors in the public forums.",
|
||||||
href: "/community",
|
href: "/community",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -195,31 +200,40 @@ export default function DocsGettingStarted() {
|
||||||
Launch your first AeThex project in under 30 minutes
|
Launch your first AeThex project in under 30 minutes
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-300 max-w-3xl">
|
<p className="text-gray-300 max-w-3xl">
|
||||||
This guide walks through the minimum setup required to ship a production-ready AeThex application.
|
This guide walks through the minimum setup required to ship a
|
||||||
Complete the prerequisites, initialize a workspace with the CLI, and review the deployment checklist
|
production-ready AeThex application. Complete the prerequisites,
|
||||||
before inviting collaborators. Use the platform highlights below to brief product, community, and live-ops
|
initialize a workspace with the CLI, and review the deployment
|
||||||
teams on everything available beyond deployment.
|
checklist before inviting collaborators. Use the platform highlights
|
||||||
|
below to brief product, community, and live-ops teams on everything
|
||||||
|
available beyond deployment.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="categories" className="space-y-6">
|
<section id="categories" className="space-y-6">
|
||||||
<div className="text-center space-y-2">
|
<div className="text-center space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">Documentation categories</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Documentation categories
|
||||||
|
</h3>
|
||||||
<p className="text-gray-300 max-w-2xl mx-auto text-sm">
|
<p className="text-gray-300 max-w-2xl mx-auto text-sm">
|
||||||
Jump into the area you need most. Each category below is mirrored in Builder CMS for collaborative
|
Jump into the area you need most. Each category below is mirrored in
|
||||||
editing.
|
Builder CMS for collaborative editing.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 lg:grid-cols-2">
|
<div className="grid gap-6 lg:grid-cols-2">
|
||||||
{docCategories.map((category) => (
|
{docCategories.map((category) => (
|
||||||
<Card key={category.title} className="border-border/50 hover:border-aethex-400/40 transition-all">
|
<Card
|
||||||
|
key={category.title}
|
||||||
|
className="border-border/50 hover:border-aethex-400/40 transition-all"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div
|
<div
|
||||||
className={`inline-flex rounded-lg bg-gradient-to-r ${category.color} px-3 py-1 text-xs uppercase tracking-wider text-white`}
|
className={`inline-flex rounded-lg bg-gradient-to-r ${category.color} px-3 py-1 text-xs uppercase tracking-wider text-white`}
|
||||||
>
|
>
|
||||||
{category.docs} docs
|
{category.docs} docs
|
||||||
</div>
|
</div>
|
||||||
<CardTitle className="text-xl text-white mt-3">{category.title}</CardTitle>
|
<CardTitle className="text-xl text-white mt-3">
|
||||||
|
{category.title}
|
||||||
|
</CardTitle>
|
||||||
<CardDescription className="text-gray-300">
|
<CardDescription className="text-gray-300">
|
||||||
{category.description}
|
{category.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|
@ -253,7 +267,12 @@ export default function DocsGettingStarted() {
|
||||||
{item.description}
|
{item.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<Button asChild variant="outline" className="justify-start">
|
<Button asChild variant="outline" className="justify-start">
|
||||||
<Link to={item.actionHref} target={item.actionHref.startsWith("http") ? "_blank" : undefined}>
|
<Link
|
||||||
|
to={item.actionHref}
|
||||||
|
target={
|
||||||
|
item.actionHref.startsWith("http") ? "_blank" : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
<ArrowRight className="mr-2 h-4 w-4" />
|
<ArrowRight className="mr-2 h-4 w-4" />
|
||||||
{item.actionLabel}
|
{item.actionLabel}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -266,13 +285,18 @@ export default function DocsGettingStarted() {
|
||||||
<section id="platform-highlights" className="space-y-6">
|
<section id="platform-highlights" className="space-y-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<LayoutDashboard className="h-6 w-6 text-purple-400" />
|
<LayoutDashboard className="h-6 w-6 text-purple-400" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Explore the platform</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Explore the platform
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 lg:grid-cols-2">
|
<div className="grid gap-6 lg:grid-cols-2">
|
||||||
{platformHighlights.map((item) => {
|
{platformHighlights.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
return (
|
return (
|
||||||
<Card key={item.title} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={item.title}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white text-lg flex items-center gap-3">
|
<CardTitle className="text-white text-lg flex items-center gap-3">
|
||||||
<Icon className="h-5 w-5 text-purple-300" />
|
<Icon className="h-5 w-5 text-purple-300" />
|
||||||
|
|
@ -302,7 +326,9 @@ export default function DocsGettingStarted() {
|
||||||
<Badge variant="outline" className="w-fit">
|
<Badge variant="outline" className="w-fit">
|
||||||
Step {index + 1}
|
Step {index + 1}
|
||||||
</Badge>
|
</Badge>
|
||||||
<CardTitle className="text-white text-lg">{step.title}</CardTitle>
|
<CardTitle className="text-white text-lg">
|
||||||
|
{step.title}
|
||||||
|
</CardTitle>
|
||||||
<CardDescription className="text-gray-300">
|
<CardDescription className="text-gray-300">
|
||||||
{step.description}
|
{step.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|
@ -342,7 +368,10 @@ export default function DocsGettingStarted() {
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
{explorationLinks.map((link) => (
|
{explorationLinks.map((link) => (
|
||||||
<Card key={link.title} className="bg-slate-900/60 border-slate-700 hover:border-purple-500/40 transition-colors">
|
<Card
|
||||||
|
key={link.title}
|
||||||
|
className="bg-slate-900/60 border-slate-700 hover:border-purple-500/40 transition-colors"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center justify-between text-white text-base">
|
<CardTitle className="flex items-center justify-between text-white text-base">
|
||||||
{link.title}
|
{link.title}
|
||||||
|
|
@ -369,26 +398,42 @@ export default function DocsGettingStarted() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="deploy" className="rounded-2xl border border-purple-500/40 bg-purple-900/20 p-8">
|
<section
|
||||||
|
id="deploy"
|
||||||
|
className="rounded-2xl border border-purple-500/40 bg-purple-900/20 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
Ready to automate your first deployment?
|
Ready to automate your first deployment?
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-300 max-w-2xl">
|
<p className="text-gray-300 max-w-2xl">
|
||||||
Run <code className="rounded bg-black/40 px-2 py-1 text-purple-200">aethex deploy</code> once you have
|
Run{" "}
|
||||||
verified environment variables, migrations, and smoke tests. Ship changes with confidence knowing
|
<code className="rounded bg-black/40 px-2 py-1 text-purple-200">
|
||||||
guardrails are enabled by default.
|
aethex deploy
|
||||||
|
</code>{" "}
|
||||||
|
once you have verified environment variables, migrations, and
|
||||||
|
smoke tests. Ship changes with confidence knowing guardrails are
|
||||||
|
enabled by default.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button asChild size="lg" className="bg-purple-600 hover:bg-purple-500">
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
className="bg-purple-600 hover:bg-purple-500"
|
||||||
|
>
|
||||||
<Link to="/docs/cli">
|
<Link to="/docs/cli">
|
||||||
<Download className="mr-2 h-5 w-5" />
|
<Download className="mr-2 h-5 w-5" />
|
||||||
Review CLI commands
|
Review CLI commands
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="border-purple-400/60 text-purple-200">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="border-purple-400/60 text-purple-200"
|
||||||
|
>
|
||||||
<Link to="/support">
|
<Link to="/support">
|
||||||
<Code className="mr-2 h-5 w-5" />
|
<Code className="mr-2 h-5 w-5" />
|
||||||
Talk to an engineer
|
Talk to an engineer
|
||||||
|
|
|
||||||
|
|
@ -33,32 +33,38 @@ import {
|
||||||
const connectorFields = [
|
const connectorFields = [
|
||||||
{
|
{
|
||||||
name: "key",
|
name: "key",
|
||||||
description: "Unique identifier referenced across dashboards, APIs, and audit logs.",
|
description:
|
||||||
|
"Unique identifier referenced across dashboards, APIs, and audit logs.",
|
||||||
defaultValue: '"analytics-segment"',
|
defaultValue: '"analytics-segment"',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "category",
|
name: "category",
|
||||||
description: "Integration taxonomy aligned with AeThex surfaces (analytics, identity, commerce, ops).",
|
description:
|
||||||
|
"Integration taxonomy aligned with AeThex surfaces (analytics, identity, commerce, ops).",
|
||||||
defaultValue: '"analytics"',
|
defaultValue: '"analytics"',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "capabilities",
|
name: "capabilities",
|
||||||
description: "Feature flags that unlock widgets, automation hooks, and data pipelines.",
|
description:
|
||||||
|
"Feature flags that unlock widgets, automation hooks, and data pipelines.",
|
||||||
defaultValue: "['metrics', 'webhooks']",
|
defaultValue: "['metrics', 'webhooks']",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "connectionMode",
|
name: "connectionMode",
|
||||||
description: "Determines how credentials are managed (oauth, apiKey, managedVault).",
|
description:
|
||||||
|
"Determines how credentials are managed (oauth, apiKey, managedVault).",
|
||||||
defaultValue: '"oauth"',
|
defaultValue: '"oauth"',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "webhookEndpoint",
|
name: "webhookEndpoint",
|
||||||
description: "Optional callback URL for outbound events delivered by AeThex.",
|
description:
|
||||||
|
"Optional callback URL for outbound events delivered by AeThex.",
|
||||||
defaultValue: '"https://app.example.com/aethex/webhooks"',
|
defaultValue: '"https://app.example.com/aethex/webhooks"',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "uiEmbeds",
|
name: "uiEmbeds",
|
||||||
description: "Declarative config describing dashboard cards, modals, or launchers this integration renders.",
|
description:
|
||||||
|
"Declarative config describing dashboard cards, modals, or launchers this integration renders.",
|
||||||
defaultValue: "[{ surface: 'dashboard', placement: 'sidebar' }]",
|
defaultValue: "[{ surface: 'dashboard', placement: 'sidebar' }]",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -89,11 +95,15 @@ export default function DocsIntegrations() {
|
||||||
<Puzzle className="mr-2 h-3 w-3" />
|
<Puzzle className="mr-2 h-3 w-3" />
|
||||||
Integrations
|
Integrations
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="text-3xl font-semibold text-white">Connecting partner services to AeThex</h2>
|
<h2 className="text-3xl font-semibold text-white">
|
||||||
|
Connecting partner services to AeThex
|
||||||
|
</h2>
|
||||||
<p className="text-gray-300 max-w-3xl">
|
<p className="text-gray-300 max-w-3xl">
|
||||||
AeThex Integrations wrap third-party analytics, identity, payments, and live-ops tooling behind a consistent
|
AeThex Integrations wrap third-party analytics, identity, payments,
|
||||||
runtime, security model, and visual system. Use this guide to register new connectors, surface partner UI in
|
and live-ops tooling behind a consistent runtime, security model, and
|
||||||
product flows, and automate data exchange without hand-rolled plumbing.
|
visual system. Use this guide to register new connectors, surface
|
||||||
|
partner UI in product flows, and automate data exchange without
|
||||||
|
hand-rolled plumbing.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -107,14 +117,18 @@ export default function DocsIntegrations() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 text-gray-300 text-sm leading-relaxed">
|
<CardContent className="space-y-4 text-gray-300 text-sm leading-relaxed">
|
||||||
<p>
|
<p>
|
||||||
Integration manifests are stored in the AeThex Integrations service and synced across the dashboard and
|
Integration manifests are stored in the AeThex Integrations
|
||||||
runtime. Client components resolve connector metadata through the shared API helpers, ensuring credentials
|
service and synced across the dashboard and runtime. Client
|
||||||
and capability flags stay consistent with server state.
|
components resolve connector metadata through the shared API
|
||||||
|
helpers, ensuring credentials and capability flags stay consistent
|
||||||
|
with server state.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
During hydration the runtime mounts partner SDKs behind AeThex loaders, applying sandboxed execution where
|
During hydration the runtime mounts partner SDKs behind AeThex
|
||||||
required. Use lifecycle hooks to emit analytics, hydrate widgets with scoped credentials, and gate access
|
loaders, applying sandboxed execution where required. Use
|
||||||
through the same role-based policies used elsewhere in the platform.
|
lifecycle hooks to emit analytics, hydrate widgets with scoped
|
||||||
|
credentials, and gate access through the same role-based policies
|
||||||
|
used elsewhere in the platform.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -128,13 +142,17 @@ export default function DocsIntegrations() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 text-gray-300 text-sm">
|
<CardContent className="space-y-3 text-gray-300 text-sm">
|
||||||
<p>
|
<p>
|
||||||
Use the integration theming utilities to adapt partner widgets to AeThex gradients, typography, and focus
|
Use the integration theming utilities to adapt partner widgets to
|
||||||
states. Tokens flow through CSS variables defined in <code className="bg-black/40 px-2">global.css</code>, so embeds stay
|
AeThex gradients, typography, and focus states. Tokens flow
|
||||||
visually aligned with dashboards and consumer apps.
|
through CSS variables defined in{" "}
|
||||||
|
<code className="bg-black/40 px-2">global.css</code>, so embeds
|
||||||
|
stay visually aligned with dashboards and consumer apps.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Extend styling with scoped class names or CSS variables exported by the partner SDK. When shipping multiple
|
Extend styling with scoped class names or CSS variables exported
|
||||||
widgets, prefer design tokens over hard-coded overrides to keep dark-mode and accessibility tweaks in sync.
|
by the partner SDK. When shipping multiple widgets, prefer design
|
||||||
|
tokens over hard-coded overrides to keep dark-mode and
|
||||||
|
accessibility tweaks in sync.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -143,7 +161,9 @@ export default function DocsIntegrations() {
|
||||||
<section id="configuration" className="space-y-4">
|
<section id="configuration" className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Palette className="h-6 w-6 text-indigo-300" />
|
<Palette className="h-6 w-6 text-indigo-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Configuration options</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Configuration options
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<Card className="bg-slate-900/60 border-slate-700">
|
<Card className="bg-slate-900/60 border-slate-700">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -158,14 +178,21 @@ export default function DocsIntegrations() {
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{connectorFields.map((field) => (
|
{connectorFields.map((field) => (
|
||||||
<TableRow key={field.name}>
|
<TableRow key={field.name}>
|
||||||
<TableCell className="font-mono text-indigo-200">{field.name}</TableCell>
|
<TableCell className="font-mono text-indigo-200">
|
||||||
<TableCell className="text-gray-300">{field.description}</TableCell>
|
{field.name}
|
||||||
<TableCell className="text-gray-400 text-sm">{field.defaultValue}</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="text-gray-300">
|
||||||
|
{field.description}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-gray-400 text-sm">
|
||||||
|
{field.defaultValue}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableCaption>
|
<TableCaption>
|
||||||
Manage manifests in the Integrations dashboard or via the Admin API to keep environments in sync.
|
Manage manifests in the Integrations dashboard or via the Admin
|
||||||
|
API to keep environments in sync.
|
||||||
</TableCaption>
|
</TableCaption>
|
||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -182,9 +209,18 @@ export default function DocsIntegrations() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 text-gray-300 text-sm">
|
<CardContent className="space-y-3 text-gray-300 text-sm">
|
||||||
<ul className="list-disc space-y-2 pl-5">
|
<ul className="list-disc space-y-2 pl-5">
|
||||||
<li>Store credentials in AeThex-managed vaults and rotate them from the dashboard rather than hard-coding.</li>
|
<li>
|
||||||
<li>Limit embed rendering to audiences that have access to the underlying data to avoid leaking partner UI.</li>
|
Store credentials in AeThex-managed vaults and rotate them from
|
||||||
<li>Log integration events through the shared telemetry helpers so support can trace partner-side failures.</li>
|
the dashboard rather than hard-coding.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Limit embed rendering to audiences that have access to the
|
||||||
|
underlying data to avoid leaking partner UI.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Log integration events through the shared telemetry helpers so
|
||||||
|
support can trace partner-side failures.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -198,12 +234,15 @@ export default function DocsIntegrations() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 text-gray-300 text-sm">
|
<CardContent className="space-y-3 text-gray-300 text-sm">
|
||||||
<p>
|
<p>
|
||||||
Promote integration changes through staging first. AeThex snapshots connector manifests per environment so
|
Promote integration changes through staging first. AeThex
|
||||||
you can test credentials, capability flags, and UI placements without impacting production users.
|
snapshots connector manifests per environment so you can test
|
||||||
|
credentials, capability flags, and UI placements without impacting
|
||||||
|
production users.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
When partners publish SDK updates, pin versions in your manifest, document the change log, and coordinate
|
When partners publish SDK updates, pin versions in your manifest,
|
||||||
rollout windows with stakeholders subscribing to the integration.
|
document the change log, and coordinate rollout windows with
|
||||||
|
stakeholders subscribing to the integration.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -216,9 +255,14 @@ export default function DocsIntegrations() {
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
{troubleshooting.map((issue) => (
|
{troubleshooting.map((issue) => (
|
||||||
<Card key={issue.title} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={issue.title}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white text-base">{issue.title}</CardTitle>
|
<CardTitle className="text-white text-base">
|
||||||
|
{issue.title}
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription className="text-gray-300 text-sm">
|
<CardDescription className="text-gray-300 text-sm">
|
||||||
|
|
@ -230,23 +274,39 @@ export default function DocsIntegrations() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="resources" className="rounded-2xl border border-indigo-500/40 bg-indigo-900/20 p-8">
|
<section
|
||||||
|
id="resources"
|
||||||
|
className="rounded-2xl border border-indigo-500/40 bg-indigo-900/20 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">Further reading</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Further reading
|
||||||
|
</h3>
|
||||||
<p className="text-gray-300 text-sm max-w-2xl">
|
<p className="text-gray-300 text-sm max-w-2xl">
|
||||||
Manage integration documentation centrally in Builder CMS or export static guides for partner teams. Keep
|
Manage integration documentation centrally in Builder CMS or
|
||||||
manifests, onboarding playbooks, and support runbooks together so each connector has a clear owner.
|
export static guides for partner teams. Keep manifests, onboarding
|
||||||
|
playbooks, and support runbooks together so each connector has a
|
||||||
|
clear owner.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button asChild size="lg" className="bg-indigo-600 hover:bg-indigo-500">
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
className="bg-indigo-600 hover:bg-indigo-500"
|
||||||
|
>
|
||||||
<Link to="/docs/api">
|
<Link to="/docs/api">
|
||||||
<LinkIcon className="mr-2 h-5 w-5" />
|
<LinkIcon className="mr-2 h-5 w-5" />
|
||||||
Review API hooks
|
Review API hooks
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="border-indigo-400/60 text-indigo-200">
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="border-indigo-400/60 text-indigo-200"
|
||||||
|
>
|
||||||
<Link to="/docs/examples#code-gallery">
|
<Link to="/docs/examples#code-gallery">
|
||||||
<FileText className="mr-2 h-5 w-5" />
|
<FileText className="mr-2 h-5 w-5" />
|
||||||
Explore sample repos
|
Explore sample repos
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,9 @@ export default function DocsOverview() {
|
||||||
|
|
||||||
{/* Learning Resources */}
|
{/* Learning Resources */}
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<h3 className="text-2xl font-bold text-white mb-6">Learning resources</h3>
|
<h3 className="text-2xl font-bold text-white mb-6">
|
||||||
|
Learning resources
|
||||||
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{learningResources.map((resource, index) => {
|
{learningResources.map((resource, index) => {
|
||||||
const Icon = resource.icon;
|
const Icon = resource.icon;
|
||||||
|
|
@ -372,13 +374,13 @@ export default function DocsOverview() {
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h3 className="text-2xl font-bold text-white">Recent Updates</h3>
|
<h3 className="text-2xl font-bold text-white">Recent Updates</h3>
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="border-slate-600 text-white hover:bg-slate-800"
|
className="border-slate-600 text-white hover:bg-slate-800"
|
||||||
>
|
>
|
||||||
<Link to="/changelog">View All Updates</Link>
|
<Link to="/changelog">View All Updates</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{featuredUpdates.map((update, index) => (
|
{featuredUpdates.map((update, index) => (
|
||||||
|
|
@ -472,10 +474,13 @@ export default function DocsOverview() {
|
||||||
|
|
||||||
{/* Support CTA */}
|
{/* Support CTA */}
|
||||||
<div className="mt-12 rounded-2xl border border-purple-500/40 bg-purple-900/20 p-8 text-center">
|
<div className="mt-12 rounded-2xl border border-purple-500/40 bg-purple-900/20 p-8 text-center">
|
||||||
<h3 className="text-3xl font-semibold text-white mb-4">Need help getting started?</h3>
|
<h3 className="text-3xl font-semibold text-white mb-4">
|
||||||
|
Need help getting started?
|
||||||
|
</h3>
|
||||||
<p className="text-gray-300 max-w-3xl mx-auto mb-6">
|
<p className="text-gray-300 max-w-3xl mx-auto mb-6">
|
||||||
Our documentation team updates these guides weekly. If you're looking for tailored onboarding,
|
Our documentation team updates these guides weekly. If you're
|
||||||
architecture reviews, or migration support, reach out and we'll connect you with the right experts.
|
looking for tailored onboarding, architecture reviews, or migration
|
||||||
|
support, reach out and we'll connect you with the right experts.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -64,19 +64,22 @@ const collaborationWorkflows = [
|
||||||
label: "Onboard & align",
|
label: "Onboard & align",
|
||||||
description:
|
description:
|
||||||
"Welcome teammates through the guided onboarding flow, capture their interests, and assign the right mentorship programs from day one.",
|
"Welcome teammates through the guided onboarding flow, capture their interests, and assign the right mentorship programs from day one.",
|
||||||
highlight: "Onboarding modules cover personal info, interests, and project preferences so teams ramp quickly.",
|
highlight:
|
||||||
|
"Onboarding modules cover personal info, interests, and project preferences so teams ramp quickly.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Build together",
|
label: "Build together",
|
||||||
description:
|
description:
|
||||||
"Kick off projects with shared canvases, synced task boards, and CLI-generated environments. Use the realm switcher to target the correct workspace.",
|
"Kick off projects with shared canvases, synced task boards, and CLI-generated environments. Use the realm switcher to target the correct workspace.",
|
||||||
highlight: "In-app toasts notify collaborators when schema changes, deployments, or reviews need attention.",
|
highlight:
|
||||||
|
"In-app toasts notify collaborators when schema changes, deployments, or reviews need attention.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Launch & iterate",
|
label: "Launch & iterate",
|
||||||
description:
|
description:
|
||||||
"Promote builds through AeThex Deploy, track KPIs in the analytics feed, and publish release notes via the changelog tools.",
|
"Promote builds through AeThex Deploy, track KPIs in the analytics feed, and publish release notes via the changelog tools.",
|
||||||
highlight: "Community announcements and blog posts keep players and stakeholders in the loop automatically.",
|
highlight:
|
||||||
|
"Community announcements and blog posts keep players and stakeholders in the loop automatically.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -166,11 +169,14 @@ export default function DocsPlatform() {
|
||||||
<Sparkles className="mr-2 h-3 w-3" />
|
<Sparkles className="mr-2 h-3 w-3" />
|
||||||
Platform Experience
|
Platform Experience
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="text-3xl font-semibold text-white">Deliver cohesive player and builder journeys on AeThex</h2>
|
<h2 className="text-3xl font-semibold text-white">
|
||||||
|
Deliver cohesive player and builder journeys on AeThex
|
||||||
|
</h2>
|
||||||
<p className="text-gray-300 max-w-3xl">
|
<p className="text-gray-300 max-w-3xl">
|
||||||
Beyond deployment pipelines and CLI tooling, AeThex bundles collaboration, identity, and live-ops systems so
|
Beyond deployment pipelines and CLI tooling, AeThex bundles
|
||||||
teams can craft unforgettable experiences. Use this guide to orient new stakeholders and plan end-to-end
|
collaboration, identity, and live-ops systems so teams can craft
|
||||||
platform rollouts.
|
unforgettable experiences. Use this guide to orient new stakeholders
|
||||||
|
and plan end-to-end platform rollouts.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -183,14 +189,22 @@ export default function DocsPlatform() {
|
||||||
{platformPillars.map((pillar) => {
|
{platformPillars.map((pillar) => {
|
||||||
const Icon = pillar.icon;
|
const Icon = pillar.icon;
|
||||||
return (
|
return (
|
||||||
<Card key={pillar.title} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={pillar.title}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Icon className="h-6 w-6 text-cyan-300" />
|
<Icon className="h-6 w-6 text-cyan-300" />
|
||||||
<CardTitle className="text-white text-lg">{pillar.title}</CardTitle>
|
<CardTitle className="text-white text-lg">
|
||||||
|
{pillar.title}
|
||||||
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline" className="text-xs text-cyan-200 border-cyan-500/40">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs text-cyan-200 border-cyan-500/40"
|
||||||
|
>
|
||||||
Platform
|
Platform
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -219,13 +233,20 @@ export default function DocsPlatform() {
|
||||||
<section id="workflows" className="space-y-6">
|
<section id="workflows" className="space-y-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Workflow className="h-6 w-6 text-cyan-300" />
|
<Workflow className="h-6 w-6 text-cyan-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Collaboration workflows</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Collaboration workflows
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
{collaborationWorkflows.map((stage, index) => (
|
{collaborationWorkflows.map((stage, index) => (
|
||||||
<Card key={stage.label} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={stage.label}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader className="space-y-2">
|
<CardHeader className="space-y-2">
|
||||||
<Badge className="w-fit bg-cyan-600/30 text-cyan-100">Step {index + 1}</Badge>
|
<Badge className="w-fit bg-cyan-600/30 text-cyan-100">
|
||||||
|
Step {index + 1}
|
||||||
|
</Badge>
|
||||||
<CardTitle className="text-white text-lg flex items-center gap-2">
|
<CardTitle className="text-white text-lg flex items-center gap-2">
|
||||||
<Globe className="h-5 w-5 text-cyan-300" />
|
<Globe className="h-5 w-5 text-cyan-300" />
|
||||||
{stage.label}
|
{stage.label}
|
||||||
|
|
@ -247,15 +268,23 @@ export default function DocsPlatform() {
|
||||||
<section id="modules" className="space-y-6">
|
<section id="modules" className="space-y-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Compass className="h-6 w-6 text-cyan-300" />
|
<Compass className="h-6 w-6 text-cyan-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Experience modules</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Experience modules
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
{experienceModules.map((module) => (
|
{experienceModules.map((module) => (
|
||||||
<Card key={module.name} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={module.name}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white text-base flex items-center justify-between">
|
<CardTitle className="text-white text-base flex items-center justify-between">
|
||||||
{module.name}
|
{module.name}
|
||||||
<Badge variant="outline" className="text-xs text-cyan-200 border-cyan-500/40">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs text-cyan-200 border-cyan-500/40"
|
||||||
|
>
|
||||||
Platform
|
Platform
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|
@ -283,17 +312,24 @@ export default function DocsPlatform() {
|
||||||
<section id="analytics" className="space-y-6">
|
<section id="analytics" className="space-y-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<BarChart3 className="h-6 w-6 text-cyan-300" />
|
<BarChart3 className="h-6 w-6 text-cyan-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Insights & analytics</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Insights & analytics
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
{analyticsHighlights.map((item) => {
|
{analyticsHighlights.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
return (
|
return (
|
||||||
<Card key={item.title} className="bg-slate-900/60 border-slate-700">
|
<Card
|
||||||
|
key={item.title}
|
||||||
|
className="bg-slate-900/60 border-slate-700"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Icon className="h-6 w-6 text-cyan-300" />
|
<Icon className="h-6 w-6 text-cyan-300" />
|
||||||
<CardTitle className="text-white text-lg">{item.title}</CardTitle>
|
<CardTitle className="text-white text-lg">
|
||||||
|
{item.title}
|
||||||
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -310,7 +346,9 @@ export default function DocsPlatform() {
|
||||||
<section id="governance" className="space-y-6">
|
<section id="governance" className="space-y-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<ShieldCheck className="h-6 w-6 text-cyan-300" />
|
<ShieldCheck className="h-6 w-6 text-cyan-300" />
|
||||||
<h3 className="text-2xl font-semibold text-white">Governance checklist</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Governance checklist
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<Card className="bg-slate-900/60 border-slate-700">
|
<Card className="bg-slate-900/60 border-slate-700">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -323,13 +361,19 @@ export default function DocsPlatform() {
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="next-steps" className="rounded-2xl border border-cyan-500/40 bg-cyan-900/20 p-8">
|
<section
|
||||||
|
id="next-steps"
|
||||||
|
className="rounded-2xl border border-cyan-500/40 bg-cyan-900/20 p-8"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-2xl font-semibold text-white">Keep exploring the platform</h3>
|
<h3 className="text-2xl font-semibold text-white">
|
||||||
|
Keep exploring the platform
|
||||||
|
</h3>
|
||||||
<p className="text-gray-300 max-w-2xl text-sm">
|
<p className="text-gray-300 max-w-2xl text-sm">
|
||||||
Share this page with non-technical teammates. It links out to every major surface area so marketing,
|
Share this page with non-technical teammates. It links out to
|
||||||
product, and operations groups can navigate confidently.
|
every major surface area so marketing, product, and operations
|
||||||
|
groups can navigate confidently.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-3 md:grid-cols-2">
|
<div className="grid gap-3 md:grid-cols-2">
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,10 @@ export default function DocsTutorials() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button asChild className="bg-purple-600 hover:bg-purple-700">
|
<Button
|
||||||
|
asChild
|
||||||
|
className="bg-purple-600 hover:bg-purple-700"
|
||||||
|
>
|
||||||
<Link to={tutorial.path}>
|
<Link to={tutorial.path}>
|
||||||
Start Tutorial
|
Start Tutorial
|
||||||
<ChevronRight className="h-4 w-4 ml-2" />
|
<ChevronRight className="h-4 w-4 ml-2" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue