Skip to content

Commit 542d444

Browse files
authored
Apps frontend (#945)
1 parent 1751ea4 commit 542d444

59 files changed

Lines changed: 3332 additions & 1682 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
]
3838
}
3939
],
40-
"Stop": [
40+
"no-longer-used-Stop": [
4141
{
4242
"hooks": [
4343
{
327 KB
Loading
411 KB
Loading
26 KB
Loading
21.8 KB
Loading

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
'use client';
22

3+
import { AppSquare, appSquarePaddingExpression, appSquareWidthExpression } from "@/components/app-square";
4+
import { Link } from "@/components/link";
35
import { useRouter } from "@/components/router";
46
import { ErrorBoundary } from '@sentry/nextjs';
57
import { UserAvatar } from '@stackframe/stack';
8+
import { ALL_APPS, AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
69
import { fromNow } from '@stackframe/stack-shared/dist/utils/dates';
7-
import { Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableRow, Typography } from '@stackframe/stack-ui';
10+
import { typedEntries } from "@stackframe/stack-shared/dist/utils/objects";
11+
import { urlString } from "@stackframe/stack-shared/dist/utils/urls";
12+
import { Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableRow, Typography, cn } from '@stackframe/stack-ui';
13+
import { ArrowRight } from "lucide-react";
814
import { useState } from 'react';
915
import { PageLayout } from "../page-layout";
1016
import { useAdminApp } from '../use-admin-app';
@@ -38,16 +44,77 @@ const dauConfig = {
3844

3945
export default function MetricsPage(props: { toSetup: () => void }) {
4046
const adminApp = useAdminApp();
47+
const project = adminApp.useProject();
48+
const config = project.useConfig();
4149
const router = useRouter();
4250
const [includeAnonymous, setIncludeAnonymous] = useState(false);
4351

4452
const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous);
4553

54+
const installedApps = Object.entries(config.apps.installed)
55+
.filter(([_, appConfig]) => appConfig.enabled)
56+
.map(([appId]) => appId as AppId);
57+
58+
const suggestedApps = typedEntries(ALL_APPS)
59+
.filter(([_, app]) => app.stage === "stable")
60+
.map(([appId]) => appId)
61+
.filter((appId) => !config.apps.installed[appId].enabled);
62+
4663
return (
47-
<PageLayout fillWidth>
64+
<PageLayout>
4865
<ErrorBoundary fallback={<div className='text-center text-sm text-red-500'>Error initializing globe visualization. Please try updating your browser or enabling WebGL.</div>}>
4966
<GlobeSection countryData={data.users_by_country} totalUsers={data.total_users} />
5067
</ErrorBoundary>
68+
69+
70+
{/* Apps */}
71+
<section className="mb-8">
72+
<div
73+
className="grid gap-y-1 gap-x-1.5 justify-items-between"
74+
style={{
75+
gridTemplateColumns: `repeat(auto-fit,minmax(${appSquareWidthExpression},1fr))`,
76+
}}
77+
>
78+
<h2 className="text-xl font-bold col-span-full mb-4">Installed Apps</h2>
79+
{installedApps.length > 0 ? (
80+
installedApps.map(appId => (
81+
<AppSquare key={appId} appId={appId} />
82+
))
83+
) : (
84+
<p className="text-gray-500 dark:text-gray-400 col-span-full">No apps installed yet.</p>
85+
)}
86+
87+
<h2 className="text-xl font-bold col-span-full mb-4 mt-4">Suggested Apps</h2>
88+
{suggestedApps.length > 0 ? (<>
89+
{suggestedApps.map(appId => (
90+
<AppSquare key={appId} appId={appId} />
91+
))}
92+
<div className="flex flex-col items-center">
93+
<Link
94+
href={urlString`/projects/${adminApp.projectId}/apps`}
95+
className={cn(
96+
"flex-grow flex flex-col items-center gap-1 sm:gap-2 transition-all duration-200 cursor-pointer group select-none",
97+
"p-2 rounded-lg",
98+
"hover:bg-foreground/15 hover:duration-0",
99+
)}
100+
style={{
101+
padding: appSquarePaddingExpression,
102+
width: appSquareWidthExpression,
103+
}}
104+
>
105+
<div className="flex-grow" />
106+
<ArrowRight className="w-16 h-16 text-gray-400" strokeWidth={1.5} />
107+
<div className="flex-grow" />
108+
<span className="text-xs lg:text-sm text-gray-600 dark:text-gray-400">Explore more</span>
109+
</Link>
110+
</div>
111+
</>
112+
) : (
113+
<p className="text-gray-500 dark:text-gray-400 col-span-full">No app suggestions.</p>
114+
)}
115+
</div>
116+
</section>
117+
51118
<div className='grid gap-4 lg:grid-cols-2'>
52119
<LineChartDisplay
53120
config={dailySignUpsConfig}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { Metadata } from "next";
12
import PageClient from "./page-client";
23

3-
export const metadata = {
4+
export const metadata: Metadata = {
45
title: "Dashboard",
56
};
67

78
export default function Page() {
89
return (
9-
<PageClient />
10+
<>
11+
<PageClient />
12+
</>
1013
);
1114
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use client';
2+
3+
import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app";
4+
import { AppStoreEntry } from "@/components/app-store-entry";
5+
import { useRouter } from "@/components/router";
6+
import { AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
7+
import { wait } from "@stackframe/stack-shared/dist/utils/promises";
8+
import { Dialog, DialogContent, DialogTitle } from "@stackframe/stack-ui";
9+
10+
export default function AppDetailsModalPageClient({ appId }: { appId: AppId }) {
11+
const router = useRouter();
12+
13+
const adminApp = useAdminApp();
14+
const project = adminApp.useProject();
15+
16+
const handleEnable = async () => {
17+
await wait(1000);
18+
await project.updateConfig({
19+
[`apps.installed.${appId}.enabled`]: true,
20+
});
21+
router.back();
22+
};
23+
24+
return (
25+
<Dialog open={true} onOpenChange={(open) => !open && router.back()} modal>
26+
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden p-0" noCloseButton>
27+
<AppStoreEntry
28+
appId={appId}
29+
onEnable={handleEnable}
30+
titleComponent={DialogTitle}
31+
/>
32+
</DialogContent>
33+
</Dialog>
34+
);
35+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
2+
import AppDetailsModalPageClient from "./page-client";
3+
4+
export default async function AppDetailsModalPage({ params }: { params: Promise<{ appId: AppId }> }) {
5+
const appId = (await params).appId;
6+
7+
return (
8+
<AppDetailsModalPageClient appId={appId} />
9+
);
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Default() {
2+
return null;
3+
}

0 commit comments

Comments
 (0)