mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
Add onboarding functionality
This commit is contained in:
parent
925f3dff19
commit
a226f3347c
6 changed files with 92 additions and 20 deletions
|
@ -12,7 +12,7 @@ import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
export function OnboardingPage() {
|
export function OnboardingPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const skipModal = useModal("skip");
|
const skipModal = useModal("skip");
|
||||||
const { skipAndRedirect } = useRedirectBack();
|
const { completeAndRedirect } = useRedirectBack();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
|
@ -25,7 +25,7 @@ export function OnboardingPage() {
|
||||||
<Button theme="secondary" onClick={skipModal.hide}>
|
<Button theme="secondary" onClick={skipModal.hide}>
|
||||||
Lorem ipsum
|
Lorem ipsum
|
||||||
</Button>
|
</Button>
|
||||||
<Button theme="danger" onClick={() => skipAndRedirect()}>
|
<Button theme="danger" onClick={() => completeAndRedirect()}>
|
||||||
Lorem ipsum
|
Lorem ipsum
|
||||||
</Button>
|
</Button>
|
||||||
</ModalCard>
|
</ModalCard>
|
||||||
|
|
|
@ -1,14 +1,64 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
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 { Button } from "@/components/buttons/Button";
|
||||||
import { Stepper } from "@/components/layout/Stepper";
|
import { Stepper } from "@/components/layout/Stepper";
|
||||||
import { CenterContainer } from "@/components/layout/ThinContainer";
|
import { CenterContainer } from "@/components/layout/ThinContainer";
|
||||||
import { Heading2, Paragraph } from "@/components/utils/Text";
|
import { Heading2, Paragraph } from "@/components/utils/Text";
|
||||||
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
||||||
|
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
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() {
|
export function OnboardingExtensionPage() {
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
|
@ -17,9 +67,10 @@ export function OnboardingExtensionPage() {
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
|
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
|
||||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
||||||
|
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
|
||||||
<Button onClick={() => navigate("/onboarding")}>Back</Button>
|
<Button onClick={() => navigate("/onboarding")}>Back</Button>
|
||||||
<Button onClick={() => alert("Check extension here or something")}>
|
<Button onClick={() => exec(true)}>
|
||||||
Check extension
|
{value === "success" ? "Continue" : "Check extension"}{" "}
|
||||||
</Button>
|
</Button>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
|
|
|
@ -1,14 +1,33 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useAsyncFn } from "react-use";
|
||||||
|
|
||||||
|
import { singularProxiedFetch } from "@/backend/helpers/fetch";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Stepper } from "@/components/layout/Stepper";
|
import { Stepper } from "@/components/layout/Stepper";
|
||||||
import { CenterContainer } from "@/components/layout/ThinContainer";
|
import { CenterContainer } from "@/components/layout/ThinContainer";
|
||||||
|
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||||
import { Heading2, Paragraph } from "@/components/utils/Text";
|
import { Heading2, Paragraph } from "@/components/utils/Text";
|
||||||
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
||||||
|
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
|
const testUrl = "https://postman-echo.com/get";
|
||||||
|
|
||||||
export function OnboardingProxyPage() {
|
export function OnboardingProxyPage() {
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
|
@ -17,10 +36,12 @@ export function OnboardingProxyPage() {
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
|
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
|
||||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
||||||
<Button onClick={() => navigate("/onboarding")}>Back</Button>
|
<AuthInputBox value={url} onChange={setUrl} placeholder="lorem ipsum" />
|
||||||
<Button onClick={() => alert("Check proxy or smth")}>
|
{error ? <p>url invalid</p> : null}
|
||||||
Check extension
|
<Button onClick={() => navigate("/onboarding")} loading={loading}>
|
||||||
|
Backagd
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={test}>Submit proxy</Button>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,16 +7,16 @@ import { useOnboardingStore } from "@/stores/onboarding";
|
||||||
export function useRedirectBack() {
|
export function useRedirectBack() {
|
||||||
const [url] = useQueryParam("redirect");
|
const [url] = useQueryParam("redirect");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setSkipped = useOnboardingStore((s) => s.setSkipped);
|
const setCompleted = useOnboardingStore((s) => s.setCompleted);
|
||||||
|
|
||||||
const redirectBack = useCallback(() => {
|
const redirectBack = useCallback(() => {
|
||||||
navigate(url ?? "/");
|
navigate(url ?? "/");
|
||||||
}, [navigate, url]);
|
}, [navigate, url]);
|
||||||
|
|
||||||
const skipAndRedirect = useCallback(() => {
|
const completeAndRedirect = useCallback(() => {
|
||||||
setSkipped(true);
|
setCompleted(true);
|
||||||
redirectBack();
|
redirectBack();
|
||||||
}, [redirectBack, setSkipped]);
|
}, [redirectBack, setCompleted]);
|
||||||
|
|
||||||
return { redirectBack, skipAndRedirect };
|
return { completeAndRedirect };
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,17 @@ import { persist } from "zustand/middleware";
|
||||||
import { immer } from "zustand/middleware/immer";
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
export interface OnboardingStore {
|
export interface OnboardingStore {
|
||||||
skipped: boolean;
|
completed: boolean;
|
||||||
setSkipped(v: boolean): void;
|
setCompleted(v: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useOnboardingStore = create(
|
export const useOnboardingStore = create(
|
||||||
persist(
|
persist(
|
||||||
immer<OnboardingStore>((set) => ({
|
immer<OnboardingStore>((set) => ({
|
||||||
skipped: false,
|
completed: false,
|
||||||
setSkipped(v) {
|
setCompleted(v) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.skipped = v;
|
s.completed = v;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -15,9 +15,9 @@ export async function needsOnboarding(): Promise<boolean> {
|
||||||
const proxyUrls = useAuthStore.getState().proxySet;
|
const proxyUrls = useAuthStore.getState().proxySet;
|
||||||
if (proxyUrls) return false;
|
if (proxyUrls) return false;
|
||||||
|
|
||||||
// if onboarding has been skipped, no onboarding needed
|
// if onboarding has been completed, no onboarding needed
|
||||||
const skipped = useOnboardingStore.getState().skipped;
|
const completed = useOnboardingStore.getState().completed;
|
||||||
if (skipped) return false;
|
if (completed) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue