From d1018f53cfce6e9a13363fb0c20896544366d464 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Wed, 11 Dec 2024 17:50:16 +0100 Subject: [PATCH] feat: add color scheme and language option to onboarding, add previous button --- .../src/app/[locale]/init/_steps/back.tsx | 23 +++++++++ apps/nextjs/src/app/[locale]/init/page.tsx | 38 ++++++++------ .../current-color-scheme-combobox.tsx | 49 +++++++++++++++++++ .../language/current-language-combobox.tsx | 8 ++- .../components/language/language-combobox.tsx | 6 ++- .../src/components/user-avatar-menu.tsx | 2 - apps/nextjs/src/middleware.ts | 2 +- .../api/src/router/onboard/onboard-router.ts | 2 +- packages/translation/src/lang/en.json | 9 +++- .../components/select-with-custom-items.tsx | 3 ++ 10 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 apps/nextjs/src/app/[locale]/init/_steps/back.tsx create mode 100644 apps/nextjs/src/components/color-scheme/current-color-scheme-combobox.tsx diff --git a/apps/nextjs/src/app/[locale]/init/_steps/back.tsx b/apps/nextjs/src/app/[locale]/init/_steps/back.tsx new file mode 100644 index 000000000..7fa98d283 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/init/_steps/back.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { Button } from "@mantine/core"; + +import { clientApi } from "@homarr/api/client"; +import { revalidatePathActionAsync } from "@homarr/common/client"; +import { useI18n } from "@homarr/translation/client"; + +export const BackToStart = () => { + const t = useI18n(); + const { mutateAsync, isPending } = clientApi.onboard.previousStep.useMutation(); + + const handleBackToStartAsync = async () => { + await mutateAsync(); + await revalidatePathActionAsync("/init"); + }; + + return ( + + ); +}; diff --git a/apps/nextjs/src/app/[locale]/init/page.tsx b/apps/nextjs/src/app/[locale]/init/page.tsx index 7ebcd343e..62a4f5f5d 100644 --- a/apps/nextjs/src/app/[locale]/init/page.tsx +++ b/apps/nextjs/src/app/[locale]/init/page.tsx @@ -1,12 +1,15 @@ import type { JSX } from "react"; -import { Center, Stack, Text, Title } from "@mantine/core"; +import { Box, Center, Stack, Text, Title } from "@mantine/core"; import { api } from "@homarr/api/server"; import type { MaybePromise } from "@homarr/common/types"; import type { OnboardingStep } from "@homarr/definitions"; import { getScopedI18n } from "@homarr/translation/server"; +import { CurrentColorSchemeCombobox } from "~/components/color-scheme/current-color-scheme-combobox"; +import { CurrentLanguageCombobox } from "~/components/language/current-language-combobox"; import { HomarrLogoWithTitle } from "~/components/layout/logo/homarr-logo"; +import { BackToStart } from "./_steps/back"; import { InitFinish } from "./_steps/finish/init-finish"; import { InitGroup } from "./_steps/group/init-group"; import { InitImport } from "./_steps/import/init-import"; @@ -27,22 +30,27 @@ export default async function InitPage() { const t = await getScopedI18n("init.step"); const currentStep = await api.onboard.currentStep(); - const CurrentComponent = stepComponents[currentStep]; + const CurrentComponent = stepComponents[currentStep.current]; return ( -
- - - - - {t(`${currentStep}.title`)} - - - {t(`${currentStep}.subtitle`)} - + +
+ + + + + {t(`${currentStep.current}.title`)} + + + {t(`${currentStep.current}.subtitle`)} + + + + + {CurrentComponent && } + {currentStep.previous === "start" && } - {CurrentComponent && } - -
+
+ ); } diff --git a/apps/nextjs/src/components/color-scheme/current-color-scheme-combobox.tsx b/apps/nextjs/src/components/color-scheme/current-color-scheme-combobox.tsx new file mode 100644 index 000000000..b3534c3aa --- /dev/null +++ b/apps/nextjs/src/components/color-scheme/current-color-scheme-combobox.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { Group, Text, useMantineColorScheme } from "@mantine/core"; +import { IconMoon, IconSun } from "@tabler/icons-react"; + +import type { ColorScheme } from "@homarr/definitions"; +import { colorSchemes } from "@homarr/definitions"; +import { useScopedI18n } from "@homarr/translation/client"; +import { SelectWithCustomItems } from "@homarr/ui"; + +interface CurrentColorSchemeComboboxProps { + w?: string; +} + +export const CurrentColorSchemeCombobox = ({ w }: CurrentColorSchemeComboboxProps) => { + const tOptions = useScopedI18n("common.colorScheme.options"); + const { colorScheme, setColorScheme } = useMantineColorScheme(); + + return ( + setColorScheme((value as ColorScheme | null) ?? "light")} + data={colorSchemes.map((scheme) => ({ + value: scheme, + label: tOptions(scheme), + }))} + SelectOption={ColorSchemeCustomOption} + w={w} + /> + ); +}; + +const appearanceIcons = { + light: IconSun, + dark: IconMoon, +}; + +const ColorSchemeCustomOption = ({ value, label }: { value: ColorScheme; label: string }) => { + const Icon = appearanceIcons[value]; + + return ( + + + + {label} + + + ); +}; diff --git a/apps/nextjs/src/components/language/current-language-combobox.tsx b/apps/nextjs/src/components/language/current-language-combobox.tsx index cdb4709d6..edc2d1bc1 100644 --- a/apps/nextjs/src/components/language/current-language-combobox.tsx +++ b/apps/nextjs/src/components/language/current-language-combobox.tsx @@ -4,9 +4,13 @@ import { useChangeLocale, useCurrentLocale } from "@homarr/translation/client"; import { LanguageCombobox } from "./language-combobox"; -export const CurrentLanguageCombobox = () => { +interface CurrentLanguageComboboxProps { + w?: string; +} + +export const CurrentLanguageCombobox = ({ w }: CurrentLanguageComboboxProps) => { const currentLocale = useCurrentLocale(); const { changeLocale, isPending } = useChangeLocale(); - return ; + return ; }; diff --git a/apps/nextjs/src/components/language/language-combobox.tsx b/apps/nextjs/src/components/language/language-combobox.tsx index 91e968686..0d38e5c74 100644 --- a/apps/nextjs/src/components/language/language-combobox.tsx +++ b/apps/nextjs/src/components/language/language-combobox.tsx @@ -9,14 +9,17 @@ import { localeConfigurations, supportedLanguages } from "@homarr/translation"; import classes from "./language-combobox.module.css"; +import "flag-icons/css/flag-icons.min.css"; + interface LanguageComboboxProps { label?: string; value: SupportedLanguage; onChange: (value: SupportedLanguage) => void; isPending?: boolean; + w?: string; } -export const LanguageCombobox = ({ label, value, onChange, isPending }: LanguageComboboxProps) => { +export const LanguageCombobox = ({ label, value, onChange, isPending, w }: LanguageComboboxProps) => { const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption(), }); @@ -49,6 +52,7 @@ export const LanguageCombobox = ({ label, value, onChange, isPending }: Language rightSectionPointerEvents="none" onClick={handleOnClick} variant="filled" + w={w} > diff --git a/apps/nextjs/src/components/user-avatar-menu.tsx b/apps/nextjs/src/components/user-avatar-menu.tsx index 503642482..75274abaa 100644 --- a/apps/nextjs/src/components/user-avatar-menu.tsx +++ b/apps/nextjs/src/components/user-avatar-menu.tsx @@ -21,8 +21,6 @@ import { signOut, useSession } from "@homarr/auth/client"; import { createModal, useModalAction } from "@homarr/modals"; import { useScopedI18n } from "@homarr/translation/client"; -import "flag-icons/css/flag-icons.min.css"; - import { useAuthContext } from "~/app/[locale]/_client-providers/session"; import { CurrentLanguageCombobox } from "./language/current-language-combobox"; diff --git a/apps/nextjs/src/middleware.ts b/apps/nextjs/src/middleware.ts index 1e71a0e53..7d38cfa48 100644 --- a/apps/nextjs/src/middleware.ts +++ b/apps/nextjs/src/middleware.ts @@ -16,7 +16,7 @@ export async function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; if (!pathname.endsWith("/init")) { const currentOnboardingStep = await serverFetchApi.onboard.currentStep.query(); - if (currentOnboardingStep !== "finish") { + if (currentOnboardingStep.current !== "finish") { return NextResponse.redirect(new URL("/init", request.url)); } } diff --git a/packages/api/src/router/onboard/onboard-router.ts b/packages/api/src/router/onboard/onboard-router.ts index 274c2b72d..8b6120655 100644 --- a/packages/api/src/router/onboard/onboard-router.ts +++ b/packages/api/src/router/onboard/onboard-router.ts @@ -7,7 +7,7 @@ import { getOnboardingOrFallbackAsync, nextOnboardingStepAsync } from "./onboard export const onboardRouter = createTRPCRouter({ currentStep: publicProcedure.query(async ({ ctx }) => { - return await getOnboardingOrFallbackAsync(ctx.db).then(({ current }) => current); + return await getOnboardingOrFallbackAsync(ctx.db); }), nextStep: publicProcedure .input( diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 31ee9f2c5..21f5f18b3 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -106,7 +106,8 @@ "docs": "Read the documentation" } } - } + }, + "backToStart": "Back to start" }, "user": { "title": "Users", @@ -799,6 +800,12 @@ "label": "Icon URL", "header": "Type name or objects to filter for icons... Homarr will search through {countIcons} icons for you." }, + "colorScheme": { + "options": { + "light": "Light", + "dark": "Dark" + } + }, "information": { "min": "Min", "max": "Max", diff --git a/packages/ui/src/components/select-with-custom-items.tsx b/packages/ui/src/components/select-with-custom-items.tsx index 410fe3429..bbed3cd1f 100644 --- a/packages/ui/src/components/select-with-custom-items.tsx +++ b/packages/ui/src/components/select-with-custom-items.tsx @@ -17,6 +17,7 @@ export interface SelectWithCustomItemsProps withAsterisk?: boolean; onBlur?: (event: React.FocusEvent) => void; onFocus?: (event: React.FocusEvent) => void; + w?: string; } type Props = SelectWithCustomItemsProps & { @@ -30,6 +31,7 @@ export const SelectWithCustomItems = ({ defaultValue, placeholder, SelectOption, + w, ...props }: Props) => { const combobox = useCombobox({ @@ -75,6 +77,7 @@ export const SelectWithCustomItems = ({ onClick={toggle} rightSectionPointerEvents="none" multiline + w={w} > {selectedOption ? : {placeholder}}