feat: add new onboarding routes and components
This commit is contained in:
parent
cd687d7ce9
commit
b85b12d792
7 changed files with 226 additions and 16 deletions
33
apps/onelauncher/frontend/src/components/base/Show.tsx
Normal file
33
apps/onelauncher/frontend/src/components/base/Show.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import React, { type JSX } from 'react';
|
||||
|
||||
interface ShowProps<T = any> {
|
||||
/**
|
||||
* Condition to evaluate.
|
||||
* If truthy, `children` are rendered.
|
||||
* If T is a value (not just boolean), you might want to use it in children,
|
||||
* but this basic version doesn't automatically pass it like SolidJS does for type narrowing.
|
||||
*/
|
||||
when: T | boolean | undefined | null;
|
||||
/**
|
||||
* Content to render if `when` is falsy.
|
||||
*/
|
||||
fallback?: React.ReactNode;
|
||||
/**
|
||||
* Content to render if `when` is truthy.
|
||||
*/
|
||||
children: React.ReactNode | ((item: NonNullable<T>) => React.ReactNode);
|
||||
}
|
||||
|
||||
const ShowComponent = <T,>({ when, fallback = null, children }: ShowProps<T>): JSX.Element | null => {
|
||||
if (Boolean(when)) {
|
||||
if (typeof children === 'function') {
|
||||
// Ensure 'when' is not null or undefined before calling the function.
|
||||
// This mimics Solid's behavior where children can be a function that receives the truthy 'when' value.
|
||||
return <>{children(when as NonNullable<T>)}</>;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
return <>{fallback}</>;
|
||||
};
|
||||
|
||||
export const Show = React.memo(ShowComponent) as <T>(props: ShowProps<T>) => JSX.Element | null;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/onboarding/complete')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/onboarding/complete"!</div>
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/onboarding/import')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/onboarding/import"!</div>
|
||||
}
|
||||
52
apps/onelauncher/frontend/src/routes/onboarding/language.tsx
Normal file
52
apps/onelauncher/frontend/src/routes/onboarding/language.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { OnboardingStep } from './route'
|
||||
import Illustration from '../../assets/illustrations/onboarding/language.svg';
|
||||
|
||||
export const Route = createFileRoute('/onboarding/language')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
const languages = [
|
||||
{
|
||||
lang: "English",
|
||||
percentage: 100
|
||||
},
|
||||
]
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<>
|
||||
<OnboardingStep
|
||||
// importing and using it as a react component was not working
|
||||
illustration={<img src={Illustration} />}
|
||||
paragraph="Choose your preferred language."
|
||||
title="Language">
|
||||
<div className="h-full w-full flex flex-col gap-y-3">
|
||||
<div className="flex flex-col gap-y-2 rounded-lg bg-page-elevated">
|
||||
<div className="max-h-84 overflow-hidden">
|
||||
<div className="flex flex-col gap-y-1 p-2">
|
||||
<aside>
|
||||
{languages.map(x => (
|
||||
<div
|
||||
className="flex flex-row items-center rounded-lg px-6 py-5"
|
||||
>
|
||||
<div className="flex-1 text-lg font-medium">{x.lang}</div>
|
||||
<div className="flex-1 text-right text-xs">
|
||||
{x.percentage}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-row justify-end">
|
||||
<p className="text-xs">Help translate OneLauncher</p>
|
||||
</div>
|
||||
</div>
|
||||
</OnboardingStep>
|
||||
</>
|
||||
)
|
||||
}
|
||||
43
apps/onelauncher/frontend/src/routes/onboarding/login.tsx
Normal file
43
apps/onelauncher/frontend/src/routes/onboarding/login.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import type { MinecraftCredentials } from '@/bindings.gen'
|
||||
import Button from '@/components/base/Button'
|
||||
import { Show } from '@/components/base/Show'
|
||||
import PlayerHead from '@/components/content/PlayerHead'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const Route = createFileRoute('/onboarding/login')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const [profile, setProfile] = useState<MinecraftCredentials>()
|
||||
const [errorMessage, setErrorMessage] = useState<string>('')
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* trust me i'll fix ui issues later */}
|
||||
<div className="grid grid-cols-2 h-full w-full flex-col items-start justify-center gap-x-16 gap-y-4">
|
||||
<h1 className="text-6xl -mb-2">Login</h1>
|
||||
|
||||
<h3>Before you continue, we require you to own a copy of Minecraft: Java Edition.</h3>
|
||||
|
||||
<Show when={profile}>
|
||||
<div className="w-full flex flex-row items-center justify-start gap-x-3 border border-border/05 rounded-lg bg-component-bg p-3">
|
||||
<PlayerHead className="rounded-md" uuid={profile?.id} />
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<p className="text-lg text-fg-primary">{profile?.username}</p>
|
||||
<p className="text-fg-secondary">{profile?.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
children="Login with Microsoft"
|
||||
/>
|
||||
|
||||
<p className="text-danger">{errorMessage}</p>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,39 +1,66 @@
|
|||
import Button from '@/components/base/Button'
|
||||
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/onboarding')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
const steps = [
|
||||
'/onboarding', // done
|
||||
'/onboarding/language', // done (only ui)
|
||||
'/onboarding/login', // wip
|
||||
'/onboarding/import', // wip
|
||||
|
||||
'/onboarding/summary', // wip
|
||||
'/onboarding/complete', // wip
|
||||
] as const;
|
||||
|
||||
function RouteComponent() {
|
||||
const navigate = useNavigate()
|
||||
const routerState = useRouterState()
|
||||
|
||||
const steps = [
|
||||
'/onboarding/',
|
||||
]
|
||||
|
||||
const currentPath = routerState.location.pathname
|
||||
const currentStepIndex = steps.findIndex(path => currentPath === path || currentPath.startsWith(path))
|
||||
|
||||
const progressPercentage = steps.length > 1
|
||||
? ((currentStepIndex + 1) / steps.length) * 100
|
||||
: (currentStepIndex === 0 ? 100 : 0)
|
||||
const progressPercentage =
|
||||
currentStepIndex >= 0 && steps.length > 1
|
||||
? (currentStepIndex / (steps.length - 1)) * 100
|
||||
: 0;
|
||||
|
||||
const handleBack = () => {
|
||||
console.log("Geri için mevcut yol:", currentPath);
|
||||
console.log("Geri için mevcut adım indeksi:", currentStepIndex);
|
||||
|
||||
if (currentStepIndex > 0) {
|
||||
navigate({ to: steps[currentStepIndex - 1] })
|
||||
const previousStep = steps[currentStepIndex - 1];
|
||||
console.log("Geri gidiliyor:", previousStep);
|
||||
navigate({ to: previousStep as any });
|
||||
} else {
|
||||
console.log("Zaten ilk adımda veya adım bulunamadı, geri gidilemez.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStepIndex < steps.length - 1) {
|
||||
navigate({ to: steps[currentStepIndex + 1] })
|
||||
} else {
|
||||
// if completed navigate to main
|
||||
navigate({ to: '/app' })
|
||||
console.log("İleri için mevcut yol:", currentPath);
|
||||
console.log("İleri için mevcut adım indeksi:", currentStepIndex);
|
||||
|
||||
if (currentStepIndex === -1) {
|
||||
navigate({ to: steps[0] as any });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStepIndex < steps.length - 1) {
|
||||
const nextStep = steps[currentStepIndex + 1];
|
||||
console.log("İleri gidiliyor:", nextStep);
|
||||
navigate({ to: nextStep as any });
|
||||
} else if (currentStepIndex === steps.length - 1) {
|
||||
console.log("Onboarding tamamlandı, /app adresine gidiliyor");
|
||||
navigate({ to: '/app' as any });
|
||||
} else {
|
||||
console.log("Bir sonraki adım belirlenemedi.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// remind me 2 hours! i'll fix this
|
||||
|
|
@ -54,10 +81,38 @@ function RouteComponent() {
|
|||
|
||||
<div className="w-full max-w-280 p-8">
|
||||
<div className="w-1/3 flex flex-row items-stretch gap-x-8 [&>*]:w-full ml-auto">
|
||||
<Button onClick={handleBack} isDisabled={currentStepIndex === 0}>Back</Button>
|
||||
<Button onClick={handleBack}>Back</Button>
|
||||
<Button onClick={handleNext}>{currentStepIndex === steps.length - 1 ? 'Finish' : 'Next'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export interface OnboardingStepProps {
|
||||
title: string;
|
||||
paragraph: string;
|
||||
illustration: JSX.Element;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export function OnboardingStep(props: OnboardingStepProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-2 h-full w-full gap-x-16">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
{props.illustration}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center gap-y-4">
|
||||
<div className="w-full flex flex-col gap-y-2">
|
||||
<h1 className="text-2xl">{props.title}</h1>
|
||||
<p className="text-lg text-fg-secondary line-height-normal">{props.paragraph}</p>
|
||||
</div>
|
||||
|
||||
<div className="max-h-96 w-full flex flex-1 flex-col gap-y-2">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/onboarding/summary')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/onboarding/summary"!</div>
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue