Fix admin dashboard build: remove date-fns, replace Card with native divs, fix Button props

Made-with: Cursor
This commit is contained in:
Usman Baig
2026-02-25 22:02:53 +01:00
parent 3fe20a4b1b
commit 30b450cdb6
3 changed files with 53 additions and 54 deletions

View File

@@ -3,8 +3,24 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation' import { useParams, useRouter } from 'next/navigation'
import { getAdminOrg, grantPlan, type AdminOrgDetail } from '@/lib/api/admin' import { getAdminOrg, grantPlan, type AdminOrgDetail } from '@/lib/api/admin'
import { Card, CardHeader, CardTitle, CardContent, Button, LoadingOverlay, Select, toast } from '@ciphera-net/ui' import { Button, LoadingOverlay, Select, toast } from '@ciphera-net/ui'
import { format, addMonths, addYears } from 'date-fns'
function formatDate(d: Date) {
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
}
function formatDateTime(d: Date) {
return d.toLocaleDateString('en-US', { dateStyle: 'long' }) + ' ' + d.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' })
}
function addMonths(d: Date, months: number) {
const out = new Date(d)
out.setMonth(out.getMonth() + months)
return out
}
function addYears(d: Date, years: number) {
const out = new Date(d)
out.setFullYear(out.getFullYear() + years)
return out
}
const PLAN_OPTIONS = [ const PLAN_OPTIONS = [
{ value: 'free', label: 'Free' }, { value: 'free', label: 'Free' },
@@ -105,12 +121,9 @@ export default function AdminOrgDetailPage() {
<div className="grid gap-6 md:grid-cols-2"> <div className="grid gap-6 md:grid-cols-2">
{/* Current Status */} {/* Current Status */}
<Card> <div className="rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm">
<CardHeader> <h3 className="text-lg font-semibold text-neutral-900 dark:text-white mb-4">Current Status</h3>
<CardTitle>Current Status</CardTitle> <div className="grid grid-cols-2 gap-2 text-sm">
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-2 text-sm">
<span className="text-neutral-500">Plan:</span> <span className="text-neutral-500">Plan:</span>
<span className="font-medium">{org.plan_id}</span> <span className="font-medium">{org.plan_id}</span>
@@ -125,7 +138,7 @@ export default function AdminOrgDetailPage() {
<span className="text-neutral-500">Period End:</span> <span className="text-neutral-500">Period End:</span>
<span className="font-medium"> <span className="font-medium">
{org.current_period_end ? format(new Date(org.current_period_end), 'PPP p') : '-'} {org.current_period_end ? formatDateTime(new Date(org.current_period_end)) : '-'}
</span> </span>
<span className="text-neutral-500">Stripe Cust:</span> <span className="text-neutral-500">Stripe Cust:</span>
@@ -133,35 +146,27 @@ export default function AdminOrgDetailPage() {
<span className="text-neutral-500">Stripe Sub:</span> <span className="text-neutral-500">Stripe Sub:</span>
<span className="font-mono text-xs">{org.stripe_subscription_id || '-'}</span> <span className="font-mono text-xs">{org.stripe_subscription_id || '-'}</span>
</div> </div>
</CardContent> </div>
</Card>
{/* Sites */} {/* Sites */}
<Card> <div className="rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm">
<CardHeader> <h3 className="text-lg font-semibold text-neutral-900 dark:text-white mb-4">Sites ({org.sites.length})</h3>
<CardTitle>Sites ({org.sites.length})</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2 max-h-60 overflow-y-auto"> <ul className="space-y-2 max-h-60 overflow-y-auto">
{org.sites.map((site) => ( {org.sites.map((site) => (
<li key={site.id} className="flex justify-between items-center text-sm p-2 bg-neutral-50 dark:bg-neutral-900 rounded"> <li key={site.id} className="flex justify-between items-center text-sm p-2 bg-neutral-50 dark:bg-neutral-900 rounded">
<span className="font-medium">{site.domain}</span> <span className="font-medium">{site.domain}</span>
<span className="text-neutral-500 text-xs">{format(new Date(site.created_at), 'MMM d, yyyy')}</span> <span className="text-neutral-500 text-xs">{formatDate(new Date(site.created_at))}</span>
</li> </li>
))} ))}
{org.sites.length === 0 && <li className="text-neutral-500 text-sm">No sites found</li>} {org.sites.length === 0 && <li className="text-neutral-500 text-sm">No sites found</li>}
</ul> </ul>
</CardContent> </div>
</Card>
</div> </div>
{/* Grant Plan Form */} {/* Grant Plan Form */}
<Card> <div className="rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm">
<CardHeader> <h3 className="text-lg font-semibold text-neutral-900 dark:text-white mb-4">Grant Plan (Manual Override)</h3>
<CardTitle>Grant Plan (Manual Override)</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleGrantPlan} className="space-y-4"> <form onSubmit={handleGrantPlan} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
@@ -232,8 +237,7 @@ export default function AdminOrgDetailPage() {
</Button> </Button>
</div> </div>
</form> </form>
</CardContent> </div>
</Card>
</div> </div>
) )
} }

View File

@@ -3,8 +3,11 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { listAdminOrgs, type AdminOrgSummary } from '@/lib/api/admin' import { listAdminOrgs, type AdminOrgSummary } from '@/lib/api/admin'
import { Card, CardHeader, CardTitle, CardContent, Button, LoadingOverlay } from '@ciphera-net/ui' import { Button, LoadingOverlay } from '@ciphera-net/ui'
import { format } from 'date-fns'
function formatDate(d: Date) {
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
}
export default function AdminOrgsPage() { export default function AdminOrgsPage() {
const [orgs, setOrgs] = useState<AdminOrgSummary[]>([]) const [orgs, setOrgs] = useState<AdminOrgSummary[]>([])
@@ -26,12 +29,9 @@ export default function AdminOrgsPage() {
<h2 className="text-xl font-semibold text-neutral-900 dark:text-white">Organizations</h2> <h2 className="text-xl font-semibold text-neutral-900 dark:text-white">Organizations</h2>
</div> </div>
<Card> <div className="rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm">
<CardHeader> <h3 className="text-lg font-semibold text-neutral-900 dark:text-white mb-4">All Organizations</h3>
<CardTitle>All Organizations</CardTitle> <div className="overflow-x-auto">
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="border-b border-neutral-200 dark:border-neutral-800"> <thead className="border-b border-neutral-200 dark:border-neutral-800">
<tr> <tr>
@@ -70,20 +70,19 @@ export default function AdminOrgsPage() {
{new Intl.NumberFormat().format(org.pageview_limit)} {new Intl.NumberFormat().format(org.pageview_limit)}
</td> </td>
<td className="px-4 py-3 text-neutral-500 text-xs"> <td className="px-4 py-3 text-neutral-500 text-xs">
{format(new Date(org.updated_at), 'MMM d, yyyy')} {formatDate(new Date(org.updated_at))}
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<Link href={`/admin/orgs/${org.organization_id}`}> <Link href={`/admin/orgs/${org.organization_id}`}>
<Button variant="ghost" size="sm">Manage</Button> <Button variant="ghost">Manage</Button>
</Link> </Link>
</td> </td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
</CardContent> </div>
</Card>
</div> </div>
) )
} }

View File

@@ -1,23 +1,19 @@
'use client' 'use client'
import Link from 'next/link' import Link from 'next/link'
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@ciphera-net/ui'
export default function AdminDashboard() { export default function AdminDashboard() {
return ( return (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Link href="/admin/orgs" className="block transition-transform hover:scale-[1.02]"> <Link
<Card> href="/admin/orgs"
<CardHeader> className="block transition-transform hover:scale-[1.02] rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm"
<CardTitle>Organizations</CardTitle> >
<CardDescription>Manage organization plans and limits</CardDescription> <h3 className="text-lg font-semibold text-neutral-900 dark:text-white">Organizations</h3>
</CardHeader> <p className="text-sm text-neutral-500 dark:text-neutral-400 mt-1">Manage organization plans and limits</p>
<CardContent> <p className="text-sm text-neutral-500 dark:text-neutral-400 mt-4">
<p className="text-sm text-neutral-500 dark:text-neutral-400"> View all organizations, check billing status, and manually grant plans.
View all organizations, check billing status, and manually grant plans. </p>
</p>
</CardContent>
</Card>
</Link> </Link>
</div> </div>
) )