pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

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

Add onboarding functionality

mrjvs a226f334 925f3dff

+92 -20
+2 -2
src/pages/onboarding/Onboarding.tsx
··· 12 12 export function OnboardingPage() { 13 13 const navigate = useNavigate(); 14 14 const skipModal = useModal("skip"); 15 - const { skipAndRedirect } = useRedirectBack(); 15 + const { completeAndRedirect } = useRedirectBack(); 16 16 17 17 return ( 18 18 <MinimalPageLayout> ··· 25 25 <Button theme="secondary" onClick={skipModal.hide}> 26 26 Lorem ipsum 27 27 </Button> 28 - <Button theme="danger" onClick={() => skipAndRedirect()}> 28 + <Button theme="danger" onClick={() => completeAndRedirect()}> 29 29 Lorem ipsum 30 30 </Button> 31 31 </ModalCard>
+53 -2
src/pages/onboarding/OnboardingExtension.tsx
··· 1 + import { ReactNode } from "react"; 1 2 import { useNavigate } from "react-router-dom"; 3 + import { useAsyncFn, useInterval } from "react-use"; 2 4 5 + import { isAllowedExtensionVersion } from "@/backend/extension/compatibility"; 6 + import { extensionInfo } from "@/backend/extension/messaging"; 3 7 import { Button } from "@/components/buttons/Button"; 4 8 import { Stepper } from "@/components/layout/Stepper"; 5 9 import { CenterContainer } from "@/components/layout/ThinContainer"; 6 10 import { Heading2, Paragraph } from "@/components/utils/Text"; 7 11 import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout"; 12 + import { useRedirectBack } from "@/pages/onboarding/onboardingHooks"; 8 13 import { PageTitle } from "@/pages/parts/util/PageTitle"; 9 14 15 + type ExtensionStatus = 16 + | "unknown" 17 + | "failed" 18 + | "disallowed" 19 + | "noperms" 20 + | "outdated" 21 + | "success"; 22 + 23 + async function getExtensionState(): Promise<ExtensionStatus> { 24 + const info = await extensionInfo(); 25 + if (!info) return "unknown"; // cant talk to extension 26 + if (!info.success) return "failed"; // extension failed to respond 27 + if (!info.allowed) return "disallowed"; // extension is not enabled on this page 28 + if (!info.hasPermission) return "noperms"; // extension has no perms to do it's tasks 29 + if (!isAllowedExtensionVersion(info.version)) return "outdated"; // extension is too old 30 + return "success"; // no problems 31 + } 32 + 33 + export function ExtensionStatus(props: { 34 + status: ExtensionStatus; 35 + loading: boolean; 36 + }) { 37 + let content: ReactNode = null; 38 + if (props.loading || props.status === "unknown") 39 + content = <p>waiting on extension...</p>; 40 + if (props.status === "disallowed") 41 + content = <p>Extension disabled for this page</p>; 42 + else if (props.status === "failed") content = <p>Failed to request status</p>; 43 + else if (props.status === "outdated") content = <p>Extension too old</p>; 44 + else if (props.status === "noperms") content = <p>No permissions to act</p>; 45 + else if (props.status === "success") content = <p>Extension is working!</p>; 46 + return <div>{content}</div>; 47 + } 48 + 10 49 export function OnboardingExtensionPage() { 11 50 const navigate = useNavigate(); 51 + const { completeAndRedirect } = useRedirectBack(); 52 + 53 + const [{ loading, value }, exec] = useAsyncFn( 54 + async (triggeredManually: boolean = false) => { 55 + const status = await getExtensionState(); 56 + if (status === "success" && triggeredManually) completeAndRedirect(); 57 + return status; 58 + }, 59 + [completeAndRedirect], 60 + ); 61 + useInterval(exec, 1000); 12 62 13 63 return ( 14 64 <MinimalPageLayout> ··· 17 67 <Stepper steps={2} current={2} className="mb-12" /> 18 68 <Heading2 className="!mt-0">Lorem ipsum</Heading2> 19 69 <Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph> 70 + <ExtensionStatus status={value ?? "unknown"} loading={loading} /> 20 71 <Button onClick={() => navigate("/onboarding")}>Back</Button> 21 - <Button onClick={() => alert("Check extension here or something")}> 22 - Check extension 72 + <Button onClick={() => exec(true)}> 73 + {value === "success" ? "Continue" : "Check extension"}{" "} 23 74 </Button> 24 75 </CenterContainer> 25 76 </MinimalPageLayout>
+24 -3
src/pages/onboarding/OnboardingProxy.tsx
··· 1 + import { useState } from "react"; 1 2 import { useNavigate } from "react-router-dom"; 3 + import { useAsyncFn } from "react-use"; 2 4 5 + import { singularProxiedFetch } from "@/backend/helpers/fetch"; 3 6 import { Button } from "@/components/buttons/Button"; 4 7 import { Stepper } from "@/components/layout/Stepper"; 5 8 import { CenterContainer } from "@/components/layout/ThinContainer"; 9 + import { AuthInputBox } from "@/components/text-inputs/AuthInputBox"; 6 10 import { Heading2, Paragraph } from "@/components/utils/Text"; 7 11 import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout"; 12 + import { useRedirectBack } from "@/pages/onboarding/onboardingHooks"; 8 13 import { PageTitle } from "@/pages/parts/util/PageTitle"; 14 + import { useAuthStore } from "@/stores/auth"; 15 + 16 + const testUrl = "https://postman-echo.com/get"; 9 17 10 18 export function OnboardingProxyPage() { 11 19 const navigate = useNavigate(); 20 + const { completeAndRedirect } = useRedirectBack(); 21 + const [url, setUrl] = useState(""); 22 + const setProxySet = useAuthStore((s) => s.setProxySet); 23 + 24 + const [{ loading, error }, test] = useAsyncFn(async () => { 25 + if (!url.startsWith("http")) throw new Error("Not a valid URL"); 26 + const res = await singularProxiedFetch(url, testUrl, {}); 27 + if (res.url !== testUrl) throw new Error("Not a proxy"); 28 + setProxySet([url]); 29 + completeAndRedirect(); 30 + }, [url, completeAndRedirect, setProxySet]); 12 31 13 32 return ( 14 33 <MinimalPageLayout> ··· 17 36 <Stepper steps={2} current={2} className="mb-12" /> 18 37 <Heading2 className="!mt-0">Lorem ipsum</Heading2> 19 38 <Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph> 20 - <Button onClick={() => navigate("/onboarding")}>Back</Button> 21 - <Button onClick={() => alert("Check proxy or smth")}> 22 - Check extension 39 + <AuthInputBox value={url} onChange={setUrl} placeholder="lorem ipsum" /> 40 + {error ? <p>url invalid</p> : null} 41 + <Button onClick={() => navigate("/onboarding")} loading={loading}> 42 + Backagd 23 43 </Button> 44 + <Button onClick={test}>Submit proxy</Button> 24 45 </CenterContainer> 25 46 </MinimalPageLayout> 26 47 );
+5 -5
src/pages/onboarding/onboardingHooks.ts
··· 7 7 export function useRedirectBack() { 8 8 const [url] = useQueryParam("redirect"); 9 9 const navigate = useNavigate(); 10 - const setSkipped = useOnboardingStore((s) => s.setSkipped); 10 + const setCompleted = useOnboardingStore((s) => s.setCompleted); 11 11 12 12 const redirectBack = useCallback(() => { 13 13 navigate(url ?? "/"); 14 14 }, [navigate, url]); 15 15 16 - const skipAndRedirect = useCallback(() => { 17 - setSkipped(true); 16 + const completeAndRedirect = useCallback(() => { 17 + setCompleted(true); 18 18 redirectBack(); 19 - }, [redirectBack, setSkipped]); 19 + }, [redirectBack, setCompleted]); 20 20 21 - return { redirectBack, skipAndRedirect }; 21 + return { completeAndRedirect }; 22 22 }
+5 -5
src/stores/onboarding/index.tsx
··· 3 3 import { immer } from "zustand/middleware/immer"; 4 4 5 5 export interface OnboardingStore { 6 - skipped: boolean; 7 - setSkipped(v: boolean): void; 6 + completed: boolean; 7 + setCompleted(v: boolean): void; 8 8 } 9 9 10 10 export const useOnboardingStore = create( 11 11 persist( 12 12 immer<OnboardingStore>((set) => ({ 13 - skipped: false, 14 - setSkipped(v) { 13 + completed: false, 14 + setCompleted(v) { 15 15 set((s) => { 16 - s.skipped = v; 16 + s.completed = v; 17 17 }); 18 18 }, 19 19 })),
+3 -3
src/utils/onboarding.ts
··· 15 15 const proxyUrls = useAuthStore.getState().proxySet; 16 16 if (proxyUrls) return false; 17 17 18 - // if onboarding has been skipped, no onboarding needed 19 - const skipped = useOnboardingStore.getState().skipped; 20 - if (skipped) return false; 18 + // if onboarding has been completed, no onboarding needed 19 + const completed = useOnboardingStore.getState().completed; 20 + if (completed) return false; 21 21 22 22 return true; 23 23 }