learn and share notes on atproto (wip) 🦉 malfestio.stormlightlabs.org/
readability solid axum atproto srs

feat: app loading screen

* fix tutorial

Changed files
+62 -16
web
public
src
+7
web/public/loading.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"> 2 + <g fill="none" fill-rule="evenodd"> 3 + <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" /> 4 + <path fill="currentColor" d="M12 4.5a7.5 7.5 0 1 0 0 15a7.5 7.5 0 0 0 0-15M1.5 12C1.5 6.201 6.201 1.5 12 1.5S22.5 6.201 22.5 12S17.799 22.5 12 22.5S1.5 17.799 1.5 12" opacity="0.1" /> 5 + <path fill="currentColor" d="M12 4.5a7.46 7.46 0 0 0-5.187 2.083a1.5 1.5 0 0 1-2.075-2.166A10.46 10.46 0 0 1 12 1.5a1.5 1.5 0 0 1 0 3" /> 6 + </g> 7 + </svg>
+34 -4
web/src/App.tsx
··· 26 26 import { Route, Router } from "@solidjs/router"; 27 27 import type { Component, ParentComponent } from "solid-js"; 28 28 import { createEffect, createSignal, onMount, Show } from "solid-js"; 29 + import { Motion, Presence } from "solid-motionone"; 30 + 31 + const LoadingScreen: Component = () => ( 32 + <Motion.div 33 + exit={{ opacity: 0 }} 34 + transition={{ duration: 0.5 }} 35 + class="fixed inset-0 bg-[#161616] flex items-center justify-center z-50"> 36 + <div class="text-[#0F62FE] animate-spin text-4xl w-12 h-12"> 37 + <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> 38 + <g fill="none" fill-rule="evenodd"> 39 + <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" /> 40 + <path 41 + fill="currentColor" 42 + d="M12 4.5a7.5 7.5 0 1 0 0 15a7.5 7.5 0 0 0 0-15M1.5 12C1.5 6.201 6.201 1.5 12 1.5S22.5 6.201 22.5 12S17.799 22.5 12 22.5S1.5 17.799 1.5 12" 43 + opacity="0.1" /> 44 + <path 45 + fill="currentColor" 46 + d="M12 4.5a7.46 7.46 0 0 0-5.187 2.083a1.5 1.5 0 0 1-2.075-2.166A10.46 10.46 0 0 1 12 1.5a1.5 1.5 0 0 1 0 3" /> 47 + </g> 48 + </svg> 49 + </div> 50 + </Motion.div> 51 + ); 29 52 30 53 const ProtectedLayout: ParentComponent = (props) => { 31 54 const [showOnboarding, setShowOnboarding] = createSignal(false); ··· 48 71 }; 49 72 50 73 return ( 51 - <Show when={authStore.isAuthenticated()} fallback={<Landing />}> 52 - <AppLayout>{props.children}</AppLayout> 53 - <OnboardingDialog open={showOnboarding()} onComplete={handleOnboardingComplete} /> 54 - </Show> 74 + <Presence> 75 + <Show when={authStore.loading()}> 76 + <LoadingScreen /> 77 + </Show> 78 + <Show when={!authStore.loading()}> 79 + <Show when={authStore.isAuthenticated()} fallback={<Landing />}> 80 + <AppLayout>{props.children}</AppLayout> 81 + <OnboardingDialog open={showOnboarding()} onComplete={handleOnboardingComplete} /> 82 + </Show> 83 + </Show> 84 + </Presence> 55 85 ); 56 86 }; 57 87
+9 -9
web/src/components/TutorialOverlay.tsx
··· 75 75 }); 76 76 77 77 createEffect(() => { 78 - if (!tutorial.active()) return; 79 - const element = currentTarget(); 80 - if (element && typeof element.scrollIntoView === "function") { 81 - element.scrollIntoView({ behavior: "smooth", block: "center" }); 78 + if (tutorial.active()) { 79 + document.body.style.overflow = "hidden"; 80 + } else { 81 + document.body.style.overflow = ""; 82 82 } 83 + onCleanup(() => { 84 + document.body.style.overflow = ""; 85 + }); 83 86 }); 84 87 85 88 return ( ··· 127 130 "box-shadow": "0 0 0 4px rgba(15, 98, 254, 0.3)", 128 131 }} /> 129 132 130 - <Motion.div 131 - initial={{ opacity: 0, scale: 0.95 }} 132 - animate={{ opacity: 1, scale: 1 }} 133 - transition={{ duration: 0.2 }} 133 + <div 134 134 class="absolute w-80 bg-[#262626] border border-[#393939] rounded-lg shadow-xl p-4 pointer-events-auto" 135 135 style={{ 136 136 top: `${getTooltipPosition(pos(), tutorial.currentStep()!.placement).top}px`, ··· 178 178 </Index> 179 179 </div> 180 180 <p class="text-xs text-[#525252] text-center mt-3">Use ← → arrow keys or Esc to skip</p> 181 - </Motion.div> 181 + </div> 182 182 </> 183 183 )} 184 184 </Show>
+12 -3
web/src/lib/store.ts
··· 15 15 ? { did: localStorage.getItem("did")!, handle: localStorage.getItem("handle") || "" } 16 16 : null, 17 17 ); 18 - const [accessJwt, setAccessJwt] = createSignal<string | null>(localStorage.getItem("accessJwt")); 19 - const [_refreshJwt, setRefreshJwt] = createSignal<string | null>(localStorage.getItem("refreshJwt")); 18 + const [accessJwt, setAccessJwt] = createSignal(localStorage.getItem("accessJwt")); 19 + const [_, setRefreshJwt] = createSignal(localStorage.getItem("refreshJwt")); 20 20 21 21 const login = (data: { accessJwt: string; refreshJwt: string; did: string; handle: string }) => { 22 22 setAccessJwt(data.accessJwt); ··· 36 36 localStorage.clear(); 37 37 }; 38 38 39 - return { user, accessJwt, isAuthenticated: () => !!accessJwt(), login, logout }; 39 + const [loading, setLoading] = createSignal(true); 40 + 41 + const init = async () => { 42 + await new Promise((resolve) => setTimeout(resolve, 1500)); 43 + setLoading(false); 44 + }; 45 + 46 + init(); 47 + 48 + return { user, accessJwt, isAuthenticated: () => !!accessJwt(), login, logout, loading }; 40 49 } 41 50 42 51 export const authStore = createRoot(createAuthStore);