Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/app/(app)/app/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Account } from "@/components/app/account";

export const metadata = { title: "Account" };

export default function Page() {
return <Account />;
}
2 changes: 1 addition & 1 deletion app/app/signin/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function safeNext(raw: string | null) {
}

const QUICK_FAILED = (method: "Google" | "passkey") =>
`Couldn't sign in with ${method === "Google" ? "Google" : "a passkey"}. If you haven't turned on ${method} sign-in for your Setlo account yet, sign in with an email code first, then turn it on from your bookings page.`;
`Couldn't sign in with ${method === "Google" ? "Google" : "a passkey"}. If you haven't turned on ${method} sign-in for your Setlo account yet, sign in with an email code first, then turn it on from your Account page.`;

export function SignIn() {
const { ready, authenticated } = usePrivy();
Expand Down
89 changes: 89 additions & 0 deletions app/components/app/account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use client";

import { useCallback, useEffect, useState } from "react";
import { QuickSignIn } from "@/components/app/quick-sign-in";
import { useSession } from "@/components/app/session";
import { AddressLink, Amount, CopyButton } from "@/components/booking/parts";
import { Button } from "@/components/ui/button";
import { Card, Notice, Skeleton } from "@/components/ui/status";
import { CHAINS } from "@/lib/chains";
import { account } from "@/lib/client/actions";
import { friendly } from "@/lib/client/errors";
import type { AccountState } from "@/lib/types";

type Row = { chainId: number; state?: AccountState; error?: string };
const chainIds = Object.keys(CHAINS).map(Number);

export function Account() {
const { email, wallet, logout } = useSession();
const [rows, setRows] = useState<Row[] | null>(null);
const [loading, setLoading] = useState(false);

const load = useCallback(async () => {
setLoading(true);
const res = await Promise.allSettled(chainIds.map((id) => account(id, wallet)));
setRows(res.map((r, i) => (r.status === "fulfilled" ? { chainId: chainIds[i], state: r.value } : { chainId: chainIds[i], error: friendly(r.reason) })));
setLoading(false);
}, [wallet]);
useEffect(() => void load(), [load]);

return (
<div className="flex flex-col gap-8">
<div>
<h1 className="font-display text-4xl sm:text-5xl">Account</h1>
<p className="mt-2 text-[15px] text-text-muted">Signed in as {email}</p>
</div>

<Card>
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 className="text-lg font-semibold">Payout account</h2>
<p className="mt-1 text-[15px] text-text-muted">Setlo created this wallet for you. Payments from your bookings arrive here.</p>
</div>
<Button intent="secondary" size="sm" pending={loading} onPress={() => void load()}>
Refresh
</Button>
</div>
<div className="mt-4 flex flex-wrap items-center gap-3">
<code className="break-all rounded-md bg-surface-muted px-3 py-2 font-mono text-[13px]">{wallet}</code>
<CopyButton text={wallet} label="Copy address" />
</div>
<ul className="mt-5 grid gap-3 sm:grid-cols-2">
{chainIds.map((id) => {
const row = rows?.find((r) => r.chainId === id);
return (
<li key={id} className="rounded-lg border border-border p-4">
<p className="text-sm text-text-muted">{CHAINS[id].chain.name}</p>
{!row ? (
<Skeleton className="mt-2 h-7 w-32" />
) : row.error ? (
<Notice tone="bad" className="mt-2">Couldn&apos;t load the balance. {row.error}</Notice>
) : (
<>
<Amount value={row.state!.usdg} strong className="mt-1 block text-2xl" />
{BigInt(row.state!.claimable) > BigInt(0) && (
<p className="mt-1 text-[13px] text-text-muted">
Plus <Amount value={row.state!.claimable} /> on its way
</p>
)}
</>
)}
<div className="mt-2">
<AddressLink chainId={id} address={wallet} />
</div>
</li>
);
})}
</ul>
</Card>

<QuickSignIn />

<div>
<Button intent="secondary" onPress={() => void logout()}>
Sign out
</Button>
</div>
</div>
);
}
6 changes: 3 additions & 3 deletions app/components/app/app-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export function AppHeader() {
<Link href="/app/new" className="inline-flex min-h-11 items-center rounded-md px-3 text-[15px] hover:bg-surface-muted">
New package
</Link>
<span className="hidden max-w-48 truncate text-sm text-text-muted md:inline" title={email}>
{email}
</span>
<Link href="/app/account" className="inline-flex min-h-11 items-center rounded-md px-3 text-[15px] hover:bg-surface-muted" title={email}>
Account
</Link>
<Button intent="ghost" size="sm" onPress={() => void logout()}>
Sign out
</Button>
Expand Down
7 changes: 3 additions & 4 deletions app/components/app/quick-sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export function QuickSignIn() {
if (!user) return null;
const google = user.google?.email;
const passkeys = user.linkedAccounts.filter((a) => a.type === "passkey").length;
if (google && passkeys > 0 && !done) return null;

async function addPasskey() {
setBusy(true);
Expand All @@ -40,9 +39,9 @@ export function QuickSignIn() {

return (
<Card>
<h2 className="text-lg font-semibold">Sign in faster next time</h2>
<h2 className="text-lg font-semibold">Sign-in methods</h2>
<p className="mt-1 text-[15px] text-text-muted">
Skip the email code when you come back. These sign in to this same account, so your bookings and payout account stay the same.
Email code always works. Add Google or a passkey to skip the code next time. They sign in to this same account, so your bookings and payout account stay the same.
</p>
{done && <Notice tone="ok" className="mt-4">{done}</Notice>}
{error && <Notice tone="bad" className="mt-4">{error}</Notice>}
Expand All @@ -55,7 +54,7 @@ export function QuickSignIn() {
</Button>
)}
{passkeys > 0 ? (
<p className="text-sm text-text-muted">Passkey sign-in is on.</p>
<p className="text-sm text-text-muted">Passkey sign-in is on ({passkeys === 1 ? "1 passkey" : `${passkeys} passkeys`}).</p>
) : (
<Button intent="secondary" pending={busy} onPress={() => void addPasskey()}>
Add a passkey
Expand Down
2 changes: 0 additions & 2 deletions app/components/booking/bookings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { QuickSignIn } from "@/components/app/quick-sign-in";
import { useSession } from "@/components/app/session";
import { NetworkBadge } from "@/components/app/shell";
import { Button, LinkButton } from "@/components/ui/button";
Expand Down Expand Up @@ -92,7 +91,6 @@ export function Bookings() {
</ul>
)}
</section>
<QuickSignIn />
</div>
);
}
Loading