this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

header: show a redirect message while connecting

Clément d619b72b e3394236

+128 -120
+1 -1
app/src/components/Button.tsx
··· 19 19 20 20 return ( 21 21 <button 22 - class={`${styles[customProps.style || 'default']} rounded-xl px-3 py-2 cursor-pointer active:scale-95 active:transition-all border`} 22 + class={`${styles[customProps.style || 'default']} rounded-xl px-3 py-2 border cursor-pointer active:scale-95 active:transition-all disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100`} 23 23 {...restProps} 24 24 > 25 25 {safeChildren()}
+6 -1
app/src/components/Dialog.tsx
··· 6 6 children: JSX.Element; 7 7 title: JSX.Element; 8 8 description?: JSX.Element; 9 + initialFocusEl?: () => HTMLElement | null; 9 10 }; 10 11 11 12 export function Dialog(props: Props) { 12 13 return ( 13 - <ArkDialog.Root lazyMount unmountOnExit> 14 + <ArkDialog.Root 15 + lazyMount 16 + unmountOnExit 17 + initialFocusEl={props.initialFocusEl} 18 + > 14 19 <ArkDialog.Trigger asChild={(p) => <props.trigger {...p} />} /> 15 20 <ArkDialog.Backdrop class="fixed inset-0 bg-black/50" /> 16 21 <ArkDialog.Positioner class="flex items-center justify-center fixed inset-0">
+3 -118
app/src/components/Header.tsx
··· 1 - import { isDid, isHandle } from '@atcute/lexicons/syntax'; 2 - import type { Did, Handle } from '@atcute/lexicons/syntax'; 3 - import { createForm } from '@tanstack/solid-form'; 4 - import { Check } from 'lucide-solid'; 5 - import { Show, createSignal } from 'solid-js'; 1 + import { Show } from 'solid-js'; 6 2 7 - import { useOAuthScopeFlow } from '~/auth/scope-flow'; 8 3 import { useAuth } from '~/contexts/auth'; 9 4 10 5 import { Avatar } from './Avatar'; 11 - import { Button } from './Button'; 12 - import { Dialog } from './Dialog'; 13 - 14 - function LoginDialog() { 15 - const [authHost, setAuthHost] = createSignal(''); 16 - 17 - const scopeFlow = useOAuthScopeFlow({ 18 - onError: (e) => { 19 - form.setErrorMap({ 20 - onSubmit: { fields: { handle: e } }, 21 - }); 22 - }, 23 - beforeRedirect: async (_, url) => { 24 - setAuthHost(url.host); 25 - await new Promise((resolve) => setTimeout(resolve, 1000)); 26 - }, 27 - }); 28 - 29 - const form = createForm(() => ({ 30 - defaultValues: { handle: '' as Handle | Did }, 31 - onSubmit: ({ value }) => { 32 - scopeFlow.connect(value.handle); 33 - }, 34 - })); 35 - 36 - return ( 37 - <Dialog 38 - title="sign in" 39 - description={ 40 - <> 41 - sign in to your account with your{' '} 42 - <a 43 - href="https://internethandle.org" 44 - class="text-blue-400 hover:underline" 45 - target="_blank" 46 - > 47 - internet handle 48 - </a> 49 - </> 50 - } 51 - trigger={(p) => <Button {...p}>sign in</Button>} 52 - > 53 - <form 54 - class="w-[400px]" 55 - onSubmit={(e) => { 56 - e.preventDefault(); 57 - e.stopPropagation(); 58 - form.handleSubmit(); 59 - }} 60 - > 61 - <div class="flex flex-col gap-2"> 62 - <div class="flex flex-col"> 63 - <form.Field 64 - name="handle" 65 - validators={{ 66 - onSubmit: ({ value }) => { 67 - if (isHandle(value) || isDid(value)) return; 68 - return 'invalid handle or DID'; 69 - }, 70 - }} 71 - children={(field) => ( 72 - <> 73 - <label for={field().name} class="select-none"> 74 - handle: 75 - </label> 76 - <input 77 - name={field().name} 78 - value={field().state.value} 79 - onBlur={field().handleBlur} 80 - onInput={(e) => 81 - field().handleChange(e.target.value as Handle | Did) 82 - } 83 - placeholder="drawbu.dev" 84 - class="border border-border-input bg-bg-input rounded-input px-3 py-2" 85 - /> 86 - <p class="text-red-400 font-semibold italic"> 87 - {field().state.meta.errors} 88 - </p> 89 - </> 90 - )} 91 - /> 92 - </div> 93 - <Show when={authHost()}> 94 - <p class="text-green-700 text-sm flex items-center gap-1"> 95 - <Check size={16} /> 96 - identified auth server as {authHost()} 97 - </p> 98 - </Show> 99 - <div class="flex justify-end"> 100 - <form.Subscribe 101 - selector={({ canSubmit, isSubmitting }) => ({ 102 - canSubmit, 103 - isSubmitting, 104 - })} 105 - > 106 - {(p) => ( 107 - <Button 108 - style="contrast" 109 - type="submit" 110 - disabled={!p().canSubmit} 111 - > 112 - {p().isSubmitting ? 'redirecting...' : 'sign in'} 113 - </Button> 114 - )} 115 - </form.Subscribe> 116 - </div> 117 - </div> 118 - </form> 119 - </Dialog> 120 - ); 121 - } 6 + import { SignInDialog } from './SignInDialog'; 122 7 123 8 export default function Header() { 124 9 const auth = useAuth(); ··· 128 13 <header class="p-4 flex items-center shadow-mini h-18 justify-between border-b border-border"> 129 14 <div></div> 130 15 <div> 131 - <Show when={auth.did()} fallback={<LoginDialog />}> 16 + <Show when={auth.did()} fallback={<SignInDialog />}> 132 17 {(did) => <Avatar user={{ did: did() }} />} 133 18 </Show> 134 19 </div>
+118
app/src/components/SignInDialog.tsx
··· 1 + import { isDid, isHandle } from '@atcute/lexicons/syntax'; 2 + import type { Did, Handle } from '@atcute/lexicons/syntax'; 3 + import { createForm } from '@tanstack/solid-form'; 4 + import { Check } from 'lucide-solid'; 5 + import { Show, createSignal } from 'solid-js'; 6 + 7 + import { useOAuthScopeFlow } from '~/auth/scope-flow'; 8 + 9 + import { Button } from './Button'; 10 + import { Dialog } from './Dialog'; 11 + 12 + export function SignInDialog() { 13 + const [authHost, setAuthHost] = createSignal(''); 14 + 15 + const scopeFlow = useOAuthScopeFlow({ 16 + onError: (e) => { 17 + form.setErrorMap({ 18 + onSubmit: { fields: { handle: e } }, 19 + }); 20 + }, 21 + beforeRedirect: async (_, url) => { 22 + setAuthHost(url.host); 23 + await new Promise((resolve) => setTimeout(resolve, 1000)); 24 + }, 25 + }); 26 + 27 + const form = createForm(() => ({ 28 + defaultValues: { handle: '' as Handle | Did }, 29 + onSubmit: async ({ value }) => { 30 + await scopeFlow.connect(value.handle); 31 + }, 32 + })); 33 + 34 + let handleInputRef: HTMLInputElement | undefined; 35 + 36 + return ( 37 + <Dialog 38 + title="sign in" 39 + description={ 40 + <> 41 + sign in to your account with your{' '} 42 + <a 43 + href="https://internethandle.org" 44 + class="text-blue-400 hover:underline" 45 + target="_blank" 46 + > 47 + internet handle 48 + </a> 49 + </> 50 + } 51 + trigger={(p) => <Button {...p}>sign in</Button>} 52 + initialFocusEl={() => handleInputRef!} 53 + > 54 + <form 55 + class="w-[400px]" 56 + onSubmit={(e) => { 57 + e.preventDefault(); 58 + e.stopPropagation(); 59 + form.handleSubmit(); 60 + }} 61 + > 62 + <div class="flex flex-col gap-2"> 63 + <form.Field 64 + name="handle" 65 + validators={{ 66 + onSubmit: ({ value }) => { 67 + if (isHandle(value) || isDid(value)) return; 68 + return 'invalid handle or DID'; 69 + }, 70 + }} 71 + children={(field) => ( 72 + <label class="flex flex-col"> 73 + <span class="select-none">handle:</span> 74 + <input 75 + ref={handleInputRef} 76 + value={field().state.value} 77 + onBlur={field().handleBlur} 78 + onInput={(e) => 79 + field().handleChange(e.target.value as Handle | Did) 80 + } 81 + placeholder="alice.bsky.social or did:plc:..." 82 + class="border border-border-input bg-bg-input rounded-input px-3 py-2" 83 + /> 84 + <p class="text-red-400 font-semibold italic"> 85 + {field().state.meta.errors} 86 + </p> 87 + </label> 88 + )} 89 + /> 90 + <Show when={authHost()}> 91 + <p class="text-green-700 text-sm flex items-center gap-1"> 92 + <Check size={16} /> 93 + identified auth server as {authHost()} 94 + </p> 95 + </Show> 96 + <div class="flex justify-end"> 97 + <form.Subscribe 98 + selector={({ canSubmit, isSubmitting }) => ({ 99 + canSubmit, 100 + isSubmitting, 101 + })} 102 + > 103 + {(p) => ( 104 + <Button 105 + style="contrast" 106 + type="submit" 107 + disabled={!p().canSubmit || p().isSubmitting} 108 + > 109 + {p().isSubmitting ? 'redirecting...' : 'sign in'} 110 + </Button> 111 + )} 112 + </form.Subscribe> 113 + </div> 114 + </div> 115 + </form> 116 + </Dialog> 117 + ); 118 + }