Skip to content

g1t/apps/web/app/routes/settings/applications.tsx

51 lines1,989 bytesCodeBlame
1import { identity } from "../../lib/services.server";
2
3import type { Route } from "./+types/applications";
4import { page } from "../../lib/meta";
5import { TimeAgo } from "../../components/ui";
6import { DeleteButton } from "../../components/account-settings";
7import { assertSameOrigin, requireUser } from "../../lib/session.server";
8
9export function meta(args: Route.MetaArgs) {
10 return page(args, { title: "Connected applications · Settings · g1t" });
11}
12
13export async function loader({ request, context }: Route.LoaderArgs) {
14 const user = requireUser(context, request);
15 return { applications: await identity.listOAuthGrants(user) };
16}
17
18export async function action({ request, context }: Route.ActionArgs) {
19 assertSameOrigin(request);
20 const user = requireUser(context, request);
21 const form = await request.formData();
22 if (form.get("intent") === "sign-out-application") {
23 await identity.revokeOAuthGrant(user, String(form.get("id") ?? ""));
24 }
25 return null;
26}
27
28export default function ApplicationSettings({ loaderData }: Route.ComponentProps) {
29 const { applications } = loaderData;
30 return (
31 <section id="applications" className="scroll-mt-20">
32 {applications.length === 0 ? (
33 <p className="text-sm text-faint">None yet.</p>
34 ) : (
35 <ul className="divide-y divide-line rounded-md border border-line">
36 {applications.map((application) => (
37 <li key={application.id} className="flex items-center gap-4 px-4 py-3">
38 <div className="min-w-0">
39 <p className="truncate text-sm">{application.clientName}</p>
40 <p className="text-xs text-faint">
41 Connected <TimeAgo at={application.createdAt} /> · last used <TimeAgo at={application.lastUsedAt} />
42 </p>
43 </div>
44 <DeleteButton intent="sign-out-application" id={application.id} label="Sign out" />
45 </li>
46 ))}
47 </ul>
48 )}
49 </section>
50 );
51}