+7
web/public/loading.svg
+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
+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
+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
+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);