Skip to content

Commit 06218ec

Browse files
committed
Add simple admin page for authorization requests
1 parent 9cfe0ab commit 06218ec

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { FormatDate } from '@/components/Format/FormatDate';
2+
import { Code } from '@/components/Layout/Code';
3+
import { PageLayout } from '@/components/Layout/PageLayout';
4+
import { ColumnSelection } from '@/components/Table/ColumnSelection';
5+
import { db } from '@/lib/db';
6+
import { AuthorizationRequestState } from '@gw2me/database';
7+
import { Headline } from '@gw2treasures/ui/components/Headline/Headline';
8+
import { FlexRow } from '@gw2treasures/ui/components/Layout/FlexRow';
9+
import { createDataTable } from '@gw2treasures/ui/components/Table/DataTable';
10+
import { ensureUserIsAdmin } from '../admin';
11+
import { isExpired } from '@/lib/date';
12+
import { ApplicationImage } from '@/components/Application/ApplicationImage';
13+
import { FC } from 'react';
14+
import { AuthorizationRequestData } from 'app/(authorize)/authorize/types';
15+
import { isDefined, isTruthy } from '@gw2treasures/helper/is';
16+
17+
function getAuthorizationRequests() {
18+
return db.authorizationRequest.findMany({
19+
include: { client: { include: { application: { select: { imageId: true, name: true }}}}},
20+
orderBy: { createdAt: 'desc' },
21+
take: 250
22+
});
23+
}
24+
25+
export default async function AdminAuthorizationRequestsPage() {
26+
await ensureUserIsAdmin();
27+
const requests = await getAuthorizationRequests();
28+
const AuthorizationRequests = createDataTable(requests, (request) => request.id);
29+
30+
return (
31+
<PageLayout>
32+
<Headline id="requests" actions={<ColumnSelection table={AuthorizationRequests}/>}>Authorization Requests ({requests.length})</Headline>
33+
34+
<AuthorizationRequests.Table>
35+
<AuthorizationRequests.Column id="id" title="Id" hidden>{({ id }) => <Code inline borderless>{id}</Code>}</AuthorizationRequests.Column>
36+
<AuthorizationRequests.Column id="type" title="Type" sortBy="type">{({ type }) => type}</AuthorizationRequests.Column>
37+
<AuthorizationRequests.Column id="state" title="Status" sortBy="state">{({ state, expiresAt }) => <State state={state === AuthorizationRequestState.Pending && isExpired(expiresAt) ? 'Expired' : state}/>}</AuthorizationRequests.Column>
38+
<AuthorizationRequests.Column id="app" title="Application" sortBy="clientId">{({ client }) => <FlexRow><ApplicationImage fileId={client.application.imageId}/> {client.application.name}</FlexRow>}</AuthorizationRequests.Column>
39+
<AuthorizationRequests.Column id="features" title="Features">{({ data }) => <Features data={(data as unknown as AuthorizationRequestData)}/>}</AuthorizationRequests.Column>
40+
<AuthorizationRequests.Column id="createdAt" title="Created At" sortBy="createdAt">{({ createdAt }) => <FormatDate date={createdAt}/>}</AuthorizationRequests.Column>
41+
<AuthorizationRequests.Column id="updatedAt" title="Updated At" sortBy="updatedAt" hidden>{({ updatedAt }) => <FormatDate date={updatedAt}/>}</AuthorizationRequests.Column>
42+
<AuthorizationRequests.Column id="expiresAt" title="Expires At" sortBy="expiresAt" hidden>{({ expiresAt }) => expiresAt ? <FormatDate date={expiresAt}/> : 'never'}</AuthorizationRequests.Column>
43+
</AuthorizationRequests.Table>
44+
</PageLayout>
45+
);
46+
}
47+
48+
export const metadata = {
49+
title: 'Authorization Requests'
50+
};
51+
52+
interface StateProps {
53+
state: AuthorizationRequestState | 'Expired'
54+
}
55+
56+
const State: FC<StateProps> = ({ state }) => {
57+
return (
58+
<FlexRow>
59+
<span style={{ width: 12, height: 12, borderRadius: 6, backgroundColor: state === 'Expired' || state === 'Canceled' ? '#f44336' : state === 'Authorized' ? '#4caf50' : '#03a9f4', opacity: .8 }}/>
60+
{state}
61+
</FlexRow>
62+
);
63+
};
64+
65+
66+
interface FeaturesProps {
67+
data: AuthorizationRequestData
68+
}
69+
70+
const Features: FC<FeaturesProps> = ({ data }) => {
71+
const features = [
72+
data.code_challenge_method && 'PKCE',
73+
data.include_granted_scopes && 'Include Granted Scopes',
74+
data.prompt && `Prompt: ${data.prompt}`,
75+
!data.state && 'No State',
76+
data.verified_accounts_only && 'Verified Accounts',
77+
].filter(isTruthy).join(', ');
78+
79+
return (
80+
features
81+
);
82+
};

apps/web/app/admin/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default async function AdminLayout({ children }: LayoutProps) {
1111
<Navigation prefix="/admin/" items={[
1212
{ segment: 'users', icon: 'user', label: 'Users' },
1313
{ segment: 'apps', icon: 'apps', label: 'Apps' },
14+
{ segment: 'authorization-requests', icon: 'gw2me-outline', label: 'Auth Requests' },
1415
{ segment: 'api-keys', icon: 'key', label: 'API Keys' },
1516
{ segment: 'requests', icon: 'api-status', label: 'Requests' },
1617
{ segment: 'email', icon: 'mail', label: 'Email' },

0 commit comments

Comments
 (0)