feat: add new onboarding routes and components

This commit is contained in:
emirsassan 2025-05-16 17:44:16 +03:00
parent cd687d7ce9
commit b85b12d792
7 changed files with 226 additions and 16 deletions

View 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;

View file

@ -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>
}

View file

@ -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>
}

View 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>
</>
)
}

View 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>
</>
)
}

View file

@ -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>
);
}

View file

@ -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>
}