13 changed files with 281 additions and 3 deletions
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
export interface StepperProps { |
||||
current: number; |
||||
steps: number; |
||||
className?: string; |
||||
} |
||||
|
||||
export function Stepper(props: StepperProps) { |
||||
const percentage = (props.current / (props.steps + 1)) * 100; |
||||
|
||||
return ( |
||||
<div className={props.className}> |
||||
<p className="mb-2"> |
||||
{props.current}/{props.steps} |
||||
</p> |
||||
<div className="max-w-full h-1 w-32 bg-white rounded-full overflow-hidden"> |
||||
<div |
||||
className="h-full bg-blue-500 transition-[width] rounded-full" |
||||
style={{ |
||||
width: `${percentage.toFixed(0)}%`, |
||||
}} |
||||
/> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
import { Link } from "react-router-dom"; |
||||
|
||||
import { BrandPill } from "@/components/layout/BrandPill"; |
||||
import { BlurEllipsis } from "@/pages/layouts/SubPageLayout"; |
||||
|
||||
export function MinimalPageLayout(props: { children: React.ReactNode }) { |
||||
return ( |
||||
<div |
||||
className="bg-background-main min-h-screen" |
||||
style={{ |
||||
backgroundImage: |
||||
"linear-gradient(to bottom, var(--tw-gradient-from), var(--tw-gradient-to) 800px)", |
||||
}} |
||||
> |
||||
<BlurEllipsis /> |
||||
{/* Main page */} |
||||
<div className="fixed px-7 py-5 left-0 top-0"> |
||||
<Link |
||||
className="block tabbable rounded-full text-xs ssm:text-base" |
||||
to="/" |
||||
> |
||||
<BrandPill clickable /> |
||||
</Link> |
||||
</div> |
||||
<div className="min-h-screen">{props.children}</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
import { useNavigate } from "react-router-dom"; |
||||
|
||||
import { Button } from "@/components/buttons/Button"; |
||||
import { Stepper } from "@/components/layout/Stepper"; |
||||
import { CenterContainer } from "@/components/layout/ThinContainer"; |
||||
import { Modal, ModalCard, useModal } from "@/components/overlays/Modal"; |
||||
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"; |
||||
|
||||
export function OnboardingPage() { |
||||
const navigate = useNavigate(); |
||||
const skipModal = useModal("skip"); |
||||
const { skipAndRedirect } = useRedirectBack(); |
||||
|
||||
return ( |
||||
<MinimalPageLayout> |
||||
<PageTitle subpage k="global.pages.about" /> |
||||
<Modal id={skipModal.id}> |
||||
<ModalCard> |
||||
<ModalCard> |
||||
<Heading2 className="!mt-0">Lorem ipsum</Heading2> |
||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph> |
||||
<Button theme="secondary" onClick={skipModal.hide}> |
||||
Lorem ipsum |
||||
</Button> |
||||
<Button theme="danger" onClick={() => skipAndRedirect()}> |
||||
Lorem ipsum |
||||
</Button> |
||||
</ModalCard> |
||||
</ModalCard> |
||||
</Modal> |
||||
<CenterContainer> |
||||
<Stepper steps={2} current={1} className="mb-12" /> |
||||
<Heading2 className="!mt-0">Lorem ipsum</Heading2> |
||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph> |
||||
<Button onClick={() => navigate("/onboarding/proxy")}> |
||||
Custom proxy |
||||
</Button> |
||||
<Button onClick={() => navigate("/onboarding/extension")}> |
||||
Extension |
||||
</Button> |
||||
<Button onClick={skipModal.show}>Default</Button> |
||||
</CenterContainer> |
||||
</MinimalPageLayout> |
||||
); |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
import { useNavigate } from "react-router-dom"; |
||||
|
||||
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 { PageTitle } from "@/pages/parts/util/PageTitle"; |
||||
|
||||
export function OnboardingExtensionPage() { |
||||
const navigate = useNavigate(); |
||||
|
||||
return ( |
||||
<MinimalPageLayout> |
||||
<PageTitle subpage k="global.pages.about" /> |
||||
<CenterContainer> |
||||
<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 extension here or something")}> |
||||
Check extension |
||||
</Button> |
||||
</CenterContainer> |
||||
</MinimalPageLayout> |
||||
); |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
import { useNavigate } from "react-router-dom"; |
||||
|
||||
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 { PageTitle } from "@/pages/parts/util/PageTitle"; |
||||
|
||||
export function OnboardingProxyPage() { |
||||
const navigate = useNavigate(); |
||||
|
||||
return ( |
||||
<MinimalPageLayout> |
||||
<PageTitle subpage k="global.pages.about" /> |
||||
<CenterContainer> |
||||
<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 |
||||
</Button> |
||||
</CenterContainer> |
||||
</MinimalPageLayout> |
||||
); |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
import { useCallback } from "react"; |
||||
import { useNavigate } from "react-router-dom"; |
||||
|
||||
import { useQueryParam } from "@/hooks/useQueryParams"; |
||||
import { useOnboardingStore } from "@/stores/onboarding"; |
||||
|
||||
export function useRedirectBack() { |
||||
const [url] = useQueryParam("redirect"); |
||||
const navigate = useNavigate(); |
||||
const setSkipped = useOnboardingStore((s) => s.setSkipped); |
||||
|
||||
const redirectBack = useCallback(() => { |
||||
navigate(url ?? "/"); |
||||
}, [navigate, url]); |
||||
|
||||
const skipAndRedirect = useCallback(() => { |
||||
setSkipped(true); |
||||
redirectBack(); |
||||
}, [redirectBack, setSkipped]); |
||||
|
||||
return { redirectBack, skipAndRedirect }; |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
import { create } from "zustand"; |
||||
import { persist } from "zustand/middleware"; |
||||
import { immer } from "zustand/middleware/immer"; |
||||
|
||||
export interface OnboardingStore { |
||||
skipped: boolean; |
||||
setSkipped(v: boolean): void; |
||||
} |
||||
|
||||
export const useOnboardingStore = create( |
||||
persist( |
||||
immer<OnboardingStore>((set) => ({ |
||||
skipped: false, |
||||
setSkipped(v) { |
||||
set((s) => { |
||||
s.skipped = v; |
||||
}); |
||||
}, |
||||
})), |
||||
{ name: "__MW::onboarding" }, |
||||
), |
||||
); |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
import { isExtensionActive } from "@/backend/extension/messaging"; |
||||
import { conf } from "@/setup/config"; |
||||
import { useAuthStore } from "@/stores/auth"; |
||||
import { useOnboardingStore } from "@/stores/onboarding"; |
||||
|
||||
export async function needsOnboarding(): Promise<boolean> { |
||||
// if onboarding is dislabed, no onboarding needed
|
||||
if (!conf().HAS_ONBOARDING) return false; |
||||
|
||||
// if extension is active and working, no onboarding needed
|
||||
const extensionActive = await isExtensionActive(); |
||||
if (extensionActive) return false; |
||||
|
||||
// if there is any custom proxy urls, no onboarding needed
|
||||
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; |
||||
|
||||
return true; |
||||
} |
Loading…
Reference in new issue