BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import { useAppSession } from "$/contexts/app-session";
2import type { AccountSummary, ActiveSession } from "$/lib/types";
3import { createMemo, Show } from "solid-js";
4import { Presence } from "solid-motionone";
5import { AvatarBadge } from "./AvatarBadge";
6import { ProfileSkeleton } from "./ProfileSkeleton";
7import { ReauthBanner } from "./ReauthBanner";
8
9export function SessionEmptyState() {
10 return (
11 <div class="grid">
12 <h2 class="m-0 text-[clamp(1.4rem,2vw,1.85rem)] leading-[1.08] tracking-[-0.03em]">No account connected yet.</h2>
13 <p class="m-0 text-xs leading-[1.55] text-on-surface-variant">Connect your Bluesky account to start exploring.</p>
14 </div>
15 );
16}
17
18function SessionExpiredState(props: { account: AccountSummary }) {
19 return (
20 <div class="flex items-center gap-4 [align-content:start] grid-cols-[auto_minmax(0,1fr)]">
21 <AvatarBadge label={props.account.handle || props.account.did} src={props.account.avatar} tone="muted" />
22 <div class="grid">
23 <h2 class="m-0 text-[clamp(1.3rem,2vw,1.7rem)] tracking-[-0.02em]">
24 {props.account.handle || props.account.did}
25 </h2>
26 <p class="m-0 text-xs text-on-surface-variant">Stored account</p>
27 <p class="m-0 text-xs text-on-surface-variant">PDS: {props.account.pdsUrl || "PDS unavailable"}</p>
28 </div>
29 </div>
30 );
31}
32
33function SessionProfile(props: { session: ActiveSession; activeAccount: AccountSummary | null }) {
34 return (
35 <div class="grid items-center gap-4 [align-content:start] grid-cols-[auto_minmax(0,1fr)]">
36 <AvatarBadge label={props.session.handle} src={props.activeAccount?.avatar} tone="primary" />
37 <div class="grid">
38 <h2 class="m-0 text-[clamp(1.3rem,2vw,1.7rem)] tracking-[-0.02em]">{props.session.handle}</h2>
39 <p class="m-0 text-xs text-on-surface-variant">{props.session.did}</p>
40 </div>
41 <Show when={props.activeAccount}>
42 {(account) => <p class="m-0 text-xs text-on-surface-variant">{account().pdsUrl || "PDS unavailable"}</p>}
43 </Show>
44 </div>
45 );
46}
47
48export function SessionSpotlight() {
49 const session = useAppSession();
50 const displayAccount = createMemo(() =>
51 session.activeAccount ?? (session.reauthNeeded ? session.primaryAccount : null)
52 );
53 const label = createMemo(() => {
54 if (session.bootstrapping) {
55 return "Reconnecting";
56 }
57
58 if (session.activeSession) {
59 return "Connected";
60 }
61
62 if (session.reauthNeeded && displayAccount()) {
63 return "Expired";
64 }
65
66 return "Ready";
67 });
68
69 return (
70 <article class="panel-surface grid gap-5 p-5">
71 <div class="flex items-baseline justify-between gap-3">
72 <p class="overline-copy text-[0.75rem] text-on-surface-variant">Your account</p>
73 <p class="overline-copy text-[0.68rem] text-on-surface-variant">{label()}</p>
74 </div>
75
76 <SessionBody
77 activeAccount={displayAccount()}
78 activeSession={session.activeSession}
79 bootstrapping={session.bootstrapping}
80 reauthNeeded={session.reauthNeeded} />
81
82 <Presence>
83 <Show when={session.reauthNeeded}>
84 <ReauthBanner onReauth={() => void session.reauthorizePrimaryAccount()} />
85 </Show>
86 </Presence>
87 </article>
88 );
89}
90
91function SessionBody(
92 props: {
93 activeSession: ActiveSession | null;
94 activeAccount: AccountSummary | null;
95 bootstrapping: boolean;
96 reauthNeeded: boolean;
97 },
98) {
99 return (
100 <Show when={!props.bootstrapping} fallback={<ProfileSkeleton />}>
101 <Show
102 when={props.activeSession}
103 fallback={
104 <Show when={props.reauthNeeded && props.activeAccount} fallback={<SessionEmptyState />}>
105 {(account) => <SessionExpiredState account={account()} />}
106 </Show>
107 }>
108 {(currentSession) => <SessionProfile session={currentSession()} activeAccount={props.activeAccount} />}
109 </Show>
110 </Show>
111 );
112}