Add onboarding functionality

This commit is contained in:
mrjvs 2024-01-16 22:07:21 +01:00
parent 925f3dff19
commit a226f3347c
6 changed files with 92 additions and 20 deletions

View File

@ -12,7 +12,7 @@ import { PageTitle } from "@/pages/parts/util/PageTitle";
export function OnboardingPage() {
const navigate = useNavigate();
const skipModal = useModal("skip");
const { skipAndRedirect } = useRedirectBack();
const { completeAndRedirect } = useRedirectBack();
return (
<MinimalPageLayout>
@ -25,7 +25,7 @@ export function OnboardingPage() {
<Button theme="secondary" onClick={skipModal.hide}>
Lorem ipsum
</Button>
<Button theme="danger" onClick={() => skipAndRedirect()}>
<Button theme="danger" onClick={() => completeAndRedirect()}>
Lorem ipsum
</Button>
</ModalCard>

View File

@ -1,14 +1,64 @@
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import { useAsyncFn, useInterval } from "react-use";
import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
import { extensionInfo } from "@/backend/extension/messaging";
import { Button } from "@/components/buttons/Button";
import { Stepper } from "@/components/layout/Stepper";
import { CenterContainer } from "@/components/layout/ThinContainer";
import { Heading2, Paragraph } from "@/components/utils/Text";
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
import { PageTitle } from "@/pages/parts/util/PageTitle";
type ExtensionStatus =
| "unknown"
| "failed"
| "disallowed"
| "noperms"
| "outdated"
| "success";
async function getExtensionState(): Promise<ExtensionStatus> {
const info = await extensionInfo();
if (!info) return "unknown"; // cant talk to extension
if (!info.success) return "failed"; // extension failed to respond
if (!info.allowed) return "disallowed"; // extension is not enabled on this page
if (!info.hasPermission) return "noperms"; // extension has no perms to do it's tasks
if (!isAllowedExtensionVersion(info.version)) return "outdated"; // extension is too old
return "success"; // no problems
}
export function ExtensionStatus(props: {
status: ExtensionStatus;
loading: boolean;
}) {
let content: ReactNode = null;
if (props.loading || props.status === "unknown")
content = <p>waiting on extension...</p>;
if (props.status === "disallowed")
content = <p>Extension disabled for this page</p>;
else if (props.status === "failed") content = <p>Failed to request status</p>;
else if (props.status === "outdated") content = <p>Extension too old</p>;
else if (props.status === "noperms") content = <p>No permissions to act</p>;
else if (props.status === "success") content = <p>Extension is working!</p>;
return <div>{content}</div>;
}
export function OnboardingExtensionPage() {
const navigate = useNavigate();
const { completeAndRedirect } = useRedirectBack();
const [{ loading, value }, exec] = useAsyncFn(
async (triggeredManually: boolean = false) => {
const status = await getExtensionState();
if (status === "success" && triggeredManually) completeAndRedirect();
return status;
},
[completeAndRedirect],
);
useInterval(exec, 1000);
return (
<MinimalPageLayout>
@ -17,9 +67,10 @@ export function OnboardingExtensionPage() {
<Stepper steps={2} current={2} className="mb-12" />
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
<Button onClick={() => navigate("/onboarding")}>Back</Button>
<Button onClick={() => alert("Check extension here or something")}>
Check extension
<Button onClick={() => exec(true)}>
{value === "success" ? "Continue" : "Check extension"}{" "}
</Button>
</CenterContainer>
</MinimalPageLayout>

View File

@ -1,14 +1,33 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAsyncFn } from "react-use";
import { singularProxiedFetch } from "@/backend/helpers/fetch";
import { Button } from "@/components/buttons/Button";
import { Stepper } from "@/components/layout/Stepper";
import { CenterContainer } from "@/components/layout/ThinContainer";
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
import { Heading2, Paragraph } from "@/components/utils/Text";
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
import { PageTitle } from "@/pages/parts/util/PageTitle";
import { useAuthStore } from "@/stores/auth";
const testUrl = "https://postman-echo.com/get";
export function OnboardingProxyPage() {
const navigate = useNavigate();
const { completeAndRedirect } = useRedirectBack();
const [url, setUrl] = useState("");
const setProxySet = useAuthStore((s) => s.setProxySet);
const [{ loading, error }, test] = useAsyncFn(async () => {
if (!url.startsWith("http")) throw new Error("Not a valid URL");
const res = await singularProxiedFetch(url, testUrl, {});
if (res.url !== testUrl) throw new Error("Not a proxy");
setProxySet([url]);
completeAndRedirect();
}, [url, completeAndRedirect, setProxySet]);
return (
<MinimalPageLayout>
@ -17,10 +36,12 @@ export function OnboardingProxyPage() {
<Stepper steps={2} current={2} className="mb-12" />
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
<Button onClick={() => navigate("/onboarding")}>Back</Button>
<Button onClick={() => alert("Check proxy or smth")}>
Check extension
<AuthInputBox value={url} onChange={setUrl} placeholder="lorem ipsum" />
{error ? <p>url invalid</p> : null}
<Button onClick={() => navigate("/onboarding")} loading={loading}>
Backagd
</Button>
<Button onClick={test}>Submit proxy</Button>
</CenterContainer>
</MinimalPageLayout>
);

View File

@ -7,16 +7,16 @@ import { useOnboardingStore } from "@/stores/onboarding";
export function useRedirectBack() {
const [url] = useQueryParam("redirect");
const navigate = useNavigate();
const setSkipped = useOnboardingStore((s) => s.setSkipped);
const setCompleted = useOnboardingStore((s) => s.setCompleted);
const redirectBack = useCallback(() => {
navigate(url ?? "/");
}, [navigate, url]);
const skipAndRedirect = useCallback(() => {
setSkipped(true);
const completeAndRedirect = useCallback(() => {
setCompleted(true);
redirectBack();
}, [redirectBack, setSkipped]);
}, [redirectBack, setCompleted]);
return { redirectBack, skipAndRedirect };
return { completeAndRedirect };
}

View File

@ -3,17 +3,17 @@ import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
export interface OnboardingStore {
skipped: boolean;
setSkipped(v: boolean): void;
completed: boolean;
setCompleted(v: boolean): void;
}
export const useOnboardingStore = create(
persist(
immer<OnboardingStore>((set) => ({
skipped: false,
setSkipped(v) {
completed: false,
setCompleted(v) {
set((s) => {
s.skipped = v;
s.completed = v;
});
},
})),

View File

@ -15,9 +15,9 @@ export async function needsOnboarding(): Promise<boolean> {
const proxyUrls = useAuthStore.getState().proxySet;
if (proxyUrls) return false;
// if onboarding has been skipped, no onboarding needed
const skipped = useOnboardingStore.getState().skipped;
if (skipped) return false;
// if onboarding has been completed, no onboarding needed
const completed = useOnboardingStore.getState().completed;
if (completed) return false;
return true;
}