Add admin dashboard data loading & package dependencies

Removed 'admin-header.tsx' and 'admin-sidebar.tsx' components and added 'admin-dashboard.loader.ts' to handle loading data for the admin dashboard. Updated 'admin-dashboard.tsx' and 'page.tsx' to use this loader. Package dependencies were also updated in 'pnpm-lock.yaml' and 'package.json' files of relevant packages. This commit prioritizes loading dashboard data effectively and managing package dependencies.
This commit is contained in:
giancarlo
2024-04-08 14:34:47 +08:00
parent 9a5e614ad5
commit 4655f56143
11 changed files with 175 additions and 98 deletions

View File

@@ -1,11 +1,14 @@
import { AdminDashboard } from '@kit/admin/components/admin-dashboard';
import { PageBody, PageHeader } from '@kit/ui/page';
export default function AdminPage() {
return (
<>
<PageHeader title={'Admin'} />
<PageHeader title={'Admin'} description={`Your SaaS stats at a glance`} />
<PageBody></PageBody>
<PageBody>
<AdminDashboard />
</PageBody>
</>
);
}

View File

@@ -33,6 +33,8 @@
"@kit/supabase": "workspace:^",
"@kit/team-accounts": "workspace:^",
"@kit/ui": "workspace:^",
"@makerkit/data-loader-supabase-core": "0.0.5",
"@makerkit/data-loader-supabase-nextjs": "^0.0.7",
"@marsidev/react-turnstile": "^0.5.4",
"@radix-ui/react-icons": "^1.3.0",
"@supabase/ssr": "^0.1.0",

View File

@@ -14,6 +14,7 @@
"./components": "./src/components/index.ts"
},
"peerDependencies": {
"@hookform/resolvers": "3.3.4",
"@kit/billing": "0.1.0",
"@kit/shared": "^0.1.0",
"@kit/stripe": "0.1.0",
@@ -24,6 +25,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
"@hookform/resolvers": "^3.3.4",
"@kit/billing": "workspace:^",
"@kit/eslint-config": "workspace:*",
"@kit/lemon-squeezy": "workspace:^",

View File

@@ -10,7 +10,9 @@
},
"prettier": "@kit/prettier-config",
"peerDependencies": {
"@kit/ui": "0.1.0"
"@kit/ui": "0.1.0",
"@makerkit/data-loader-supabase-core": "0.0.5",
"@makerkit/data-loader-supabase-nextjs": "^0.0.7"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
@@ -19,6 +21,8 @@
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@makerkit/data-loader-supabase-core": "0.0.5",
"@makerkit/data-loader-supabase-nextjs": "^0.0.7",
"@supabase/supabase-js": "^2.42.0",
"lucide-react": "^0.363.0"
},

View File

@@ -1,17 +1,16 @@
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@kit/ui/card';
interface Data {
usersCount: number;
organizationsCount: number;
activeSubscriptions: number;
trialSubscriptions: number;
}
import { loadAdminDashboard } from '../lib/server/loaders/admin-dashboard.loader';
export async function AdminDashboard() {
const data = await loadAdminDashboard();
export function AdminDashboard({
data,
}: React.PropsWithChildren<{
data: Data;
}>) {
return (
<div
className={
@@ -22,23 +21,31 @@ export function AdminDashboard({
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
<CardDescription>
The number of personal accounts that have been created.
</CardDescription>
</CardHeader>
<CardContent>
<div className={'flex justify-between'}>
<Figure>{data.usersCount}</Figure>
<Figure>{data.accounts}</Figure>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Organizations</CardTitle>
<CardTitle>Team Accounts</CardTitle>
<CardDescription>
The number of team accounts that have been created.
</CardDescription>
</CardHeader>
<CardContent>
<div className={'flex justify-between'}>
<Figure>{data.organizationsCount}</Figure>
<Figure>{data.teamAccounts}</Figure>
</div>
</CardContent>
</Card>
@@ -46,11 +53,14 @@ export function AdminDashboard({
<Card>
<CardHeader>
<CardTitle>Paying Customers</CardTitle>
<CardDescription>
The number of paying customers with active subscriptions.
</CardDescription>
</CardHeader>
<CardContent>
<div className={'flex justify-between'}>
<Figure>{data.activeSubscriptions}</Figure>
<Figure>{data.subscriptions}</Figure>
</div>
</CardContent>
</Card>
@@ -58,11 +68,15 @@ export function AdminDashboard({
<Card>
<CardHeader>
<CardTitle>Trials</CardTitle>
<CardDescription>
Th number of trial subscriptions currently active.
</CardDescription>
</CardHeader>
<CardContent>
<div className={'flex justify-between'}>
<Figure>{data.trialSubscriptions}</Figure>
<Figure>{data.trials}</Figure>
</div>
</CardContent>
</Card>

View File

@@ -1,29 +0,0 @@
import Link from 'next/link';
import { ArrowLeft } from 'lucide-react';
import { Button } from '@kit/ui/button';
import { PageHeader } from '@kit/ui/page';
export function AdminHeader({
children,
paths,
}: React.PropsWithChildren<{
paths: {
appHome: string;
};
}>) {
return (
<PageHeader
title={children}
description={`Manage your app from the admin dashboard.`}
>
<Link href={paths.appHome}>
<Button variant={'link'}>
<ArrowLeft className={'h-4'} />
<span>Back to App</span>
</Button>
</Link>
</PageHeader>
);
}

View File

@@ -1,28 +0,0 @@
import { Home, User, Users } from 'lucide-react';
import { Sidebar, SidebarContent, SidebarItem } from '@kit/ui/sidebar';
export function AdminSidebar(props: { Logo: React.ReactNode }) {
return (
<Sidebar>
<SidebarContent className={'mb-6 mt-4 pt-2'}>{props.Logo}</SidebarContent>
<SidebarContent>
<SidebarItem end path={'/admin'} Icon={<Home className={'h-4'} />}>
Admin
</SidebarItem>
<SidebarItem path={'/admin/users'} Icon={<User className={'h-4'} />}>
Users
</SidebarItem>
<SidebarItem
path={'/admin/organizations'}
Icon={<Users className={'h-4'} />}
>
Organizations
</SidebarItem>
</SidebarContent>
</Sidebar>
);
}

View File

@@ -1 +1 @@
export * from './lib/is-super-admin';
export * from './lib/server/is-super-admin';

View File

@@ -0,0 +1,51 @@
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
export async function loadAdminDashboard(params?: {
count: 'exact' | 'estimated' | 'planned';
}) {
const count = params?.count ?? 'estimated';
const client = getSupabaseServerComponentClient({ admin: true });
const selectParams = {
count,
head: true,
};
const subscriptionsPromise = client
.from('subscriptions')
.select('*', selectParams)
.eq('status', 'active')
.then((response) => response.count);
const trialsPromise = client
.from('subscriptions')
.select('*', selectParams)
.eq('status', 'trialing')
.then((response) => response.count);
const accountsPromise = client
.from('accounts')
.select('*', selectParams)
.eq('is_personal_account', true)
.then((response) => response.count);
const teamAccountsPromise = client
.from('accounts')
.select('*', selectParams)
.eq('is_personal_account', false)
.then((response) => response.count);
const [subscriptions, trials, accounts, teamAccounts] = await Promise.all([
subscriptionsPromise,
trialsPromise,
accountsPromise,
teamAccountsPromise,
]);
return {
subscriptions,
trials,
accounts,
teamAccounts,
};
}

98
pnpm-lock.yaml generated
View File

@@ -86,6 +86,12 @@ importers:
'@kit/ui':
specifier: workspace:^
version: link:../../packages/ui
'@makerkit/data-loader-supabase-core':
specifier: 0.0.5
version: 0.0.5(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)
'@makerkit/data-loader-supabase-nextjs':
specifier: ^0.0.7
version: 0.0.7(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)(next@14.2.0-canary.62)(react@18.2.0)(swr@2.2.5)
'@marsidev/react-turnstile':
specifier: ^0.5.4
version: 0.5.4(react-dom@18.2.0)(react@18.2.0)
@@ -231,6 +237,9 @@ importers:
packages/billing/gateway:
devDependencies:
'@hookform/resolvers':
specifier: ^3.3.4
version: 3.3.4(react-hook-form@7.51.2)
'@kit/billing':
specifier: workspace:^
version: link:../core
@@ -547,6 +556,12 @@ importers:
'@kit/ui':
specifier: workspace:^
version: link:../../ui
'@makerkit/data-loader-supabase-core':
specifier: 0.0.5
version: 0.0.5(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)
'@makerkit/data-loader-supabase-nextjs':
specifier: ^0.0.7
version: 0.0.7(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)(next@14.1.0)(react@18.2.0)(swr@2.2.5)
'@supabase/supabase-js':
specifier: ^2.42.0
version: 2.42.0
@@ -2192,6 +2207,50 @@ packages:
engines: {node: '>=18'}
dev: false
/@makerkit/data-loader-supabase-core@0.0.5(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0):
resolution: {integrity: sha512-J68TcXACZpbBhFPwNX4AP8O37iPHpcqpT8PLTLeMvoCpm2HI2MK+KGV5uj2zJydHKClRi44KkNa0BUHJzg9myw==}
peerDependencies:
'@supabase/postgrest-js': '>1.0.0'
'@supabase/supabase-js': '>=2.0.0'
dependencies:
'@supabase/postgrest-js': 1.15.0
'@supabase/supabase-js': 2.42.0
ts-case-convert: 2.0.7
/@makerkit/data-loader-supabase-nextjs@0.0.7(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)(next@14.1.0)(react@18.2.0)(swr@2.2.5):
resolution: {integrity: sha512-iPi4dWkZnv3awtlJaQIVLo7nca8XEjSMTE1HHZg1PtBAYRyLbq6vJYzhez0XVu/OSncw5QyhTzCzB1dxzOHxhw==}
peerDependencies:
'@supabase/supabase-js': '>=2.0.0'
next: '>=13.4.0'
react: '>=18.0.0'
swr: '>=2.0.0'
dependencies:
'@makerkit/data-loader-supabase-core': 0.0.5(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)
'@supabase/supabase-js': 2.42.0
next: 14.1.0(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
swr: 2.2.5(react@18.2.0)
transitivePeerDependencies:
- '@supabase/postgrest-js'
dev: true
/@makerkit/data-loader-supabase-nextjs@0.0.7(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)(next@14.2.0-canary.62)(react@18.2.0)(swr@2.2.5):
resolution: {integrity: sha512-iPi4dWkZnv3awtlJaQIVLo7nca8XEjSMTE1HHZg1PtBAYRyLbq6vJYzhez0XVu/OSncw5QyhTzCzB1dxzOHxhw==}
peerDependencies:
'@supabase/supabase-js': '>=2.0.0'
next: '>=13.4.0'
react: '>=18.0.0'
swr: '>=2.0.0'
dependencies:
'@makerkit/data-loader-supabase-core': 0.0.5(@supabase/postgrest-js@1.15.0)(@supabase/supabase-js@2.42.0)
'@supabase/supabase-js': 2.42.0
next: 14.2.0-canary.62(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
swr: 2.2.5(react@18.2.0)
transitivePeerDependencies:
- '@supabase/postgrest-js'
dev: false
/@manypkg/cli@0.21.3:
resolution: {integrity: sha512-ro6j5b+44dN2AfId23voWxdlOqUCSbCwUHrUwq0LpoN/oZy6zQFAHDwYHbw50j2nL9EgpwIA03ZjaBceuUcMrw==}
engines: {node: '>=14.18.0'}
@@ -2300,7 +2359,6 @@ packages:
/@next/env@14.1.0:
resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==}
dev: false
/@next/env@14.2.0-canary.62:
resolution: {integrity: sha512-K5lmKK/TalagQELw3W0hKDXmNGGXY3Zxw3yH27y9DOT7evhiJvK2Ywq5uN4lEjYHkeW+QbyC/OjUIcK3RUSHbQ==}
@@ -2327,7 +2385,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@next/swc-darwin-arm64@14.2.0-canary.62:
@@ -2354,7 +2411,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@next/swc-darwin-x64@14.2.0-canary.62:
@@ -2381,7 +2437,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-arm64-gnu@14.2.0-canary.62:
@@ -2408,7 +2463,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-arm64-musl@14.2.0-canary.62:
@@ -2435,7 +2489,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-x64-gnu@14.2.0-canary.62:
@@ -2462,7 +2515,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-x64-musl@14.2.0-canary.62:
@@ -2489,7 +2541,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@next/swc-win32-arm64-msvc@14.2.0-canary.62:
@@ -2516,7 +2567,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@next/swc-win32-ia32-msvc@14.2.0-canary.62:
@@ -2543,7 +2593,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@next/swc-win32-x64-msvc@14.2.0-canary.62:
@@ -2594,7 +2643,6 @@ packages:
/@opentelemetry/api@1.8.0:
resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==}
engines: {node: '>=8.0.0'}
dev: false
/@opentelemetry/context-async-hooks@1.21.0(@opentelemetry/api@1.8.0):
resolution: {integrity: sha512-t0iulGPiMjG/NrSjinPQoIf8ST/o9V0dGOJthfrFporJlNdlKIQPfC7lkrV+5s2dyBThfmSbJlp/4hO1eOcDXA==}
@@ -5013,7 +5061,6 @@ packages:
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
dependencies:
tslib: 2.6.2
dev: false
/@swc/helpers@0.5.5:
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
@@ -6078,7 +6125,6 @@ packages:
engines: {node: '>=10.16.0'}
dependencies:
streamsearch: 1.1.0
dev: false
/cacheable-lookup@7.0.0:
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
@@ -6278,7 +6324,6 @@ packages:
/client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
/clipanion@3.2.1(typanion@3.14.0):
resolution: {integrity: sha512-dYFdjLb7y1ajfxQopN05mylEpK9ZX0sO1/RfMXdfmwjlIsPkbh4p7A682x++zFPLDCo1x3p82dtljHf5cW2LKA==}
@@ -7938,7 +7983,6 @@ packages:
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: false
/gradient-string@2.0.2:
resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==}
@@ -9749,7 +9793,6 @@ packages:
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
dev: false
/next@14.2.0-canary.62(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-SsS+fpJ/anrtLgeCC76V9WOlreZanUYsuKsRMx+FDwOJ3ZnbZkohu3+RRLIQM1vtWcp707iV11+OlF/qgOldCA==}
@@ -10334,7 +10377,6 @@ packages:
nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.2.0
dev: false
/postcss@8.4.33:
resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==}
@@ -11433,7 +11475,6 @@ packages:
/streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
dev: false
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -11574,7 +11615,6 @@ packages:
dependencies:
client-only: 0.0.1
react: 18.2.0
dev: false
/sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
@@ -11634,6 +11674,15 @@ packages:
upper-case: 1.1.3
dev: false
/swr@2.2.5(react@18.2.0):
resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
client-only: 0.0.1
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
/tailwind-merge@2.2.0:
resolution: {integrity: sha512-SqqhhaL0T06SW59+JVNfAqKdqLs0497esifRrZ7jOaefP3o64fdFNDMrAQWZFMxTLJPiHVjRLUywT8uFz1xNWQ==}
dependencies:
@@ -11861,6 +11910,9 @@ packages:
typescript: 5.4.3
dev: false
/ts-case-convert@2.0.7:
resolution: {integrity: sha512-Kqj8wrkuduWsKUOUNRczrkdHCDt4ZNNd6HKjVw42EnMIGHQUABS4pqfy0acETVLwUTppc1fzo/yi11+uMTaqzw==}
/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@@ -11914,7 +11966,6 @@ packages:
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: false
/turbo-darwin-64@1.13.2:
resolution: {integrity: sha512-CCSuD8CfmtncpohCuIgq7eAzUas0IwSbHfI8/Q3vKObTdXyN8vAo01gwqXjDGpzG9bTEVedD0GmLbD23dR0MLA==}
@@ -12243,6 +12294,13 @@ packages:
tslib: 2.6.2
dev: false
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}