From 74de89285986283ef65a7af19eddd76ffdc141c6 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 19 Aug 2023 12:16:00 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Onboarding=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- .gitignore | 3 +- package.json | 1 + public/imgs/app-icons/truenas.svg | 1 + public/imgs/app-icons/unraid-alt.svg | 1 + .../Onboarding/onboarding-steps.tsx | 41 +++++ .../Onboarding/step-create-account.tsx | 79 ++++++++ .../Onboarding/step-docker-import.tsx | 18 ++ .../Onboarding/step-documentation.tsx | 27 +++ .../Onboarding/step-onboarding-finished.tsx | 56 ++++++ .../Onboarding/step-update-path-mappings.tsx | 169 ++++++++++++++++++ src/pages/onboard.tsx | 103 ++++++----- yarn.lock | 25 +++ 13 files changed, 475 insertions(+), 51 deletions(-) create mode 100644 public/imgs/app-icons/truenas.svg create mode 100644 public/imgs/app-icons/unraid-alt.svg create mode 100644 src/components/Onboarding/onboarding-steps.tsx create mode 100644 src/components/Onboarding/step-create-account.tsx create mode 100644 src/components/Onboarding/step-docker-import.tsx create mode 100644 src/components/Onboarding/step-documentation.tsx create mode 100644 src/components/Onboarding/step-onboarding-finished.tsx create mode 100644 src/components/Onboarding/step-update-path-mappings.tsx diff --git a/.env.example b/.env.example index 37c6640d7..444562c2d 100644 --- a/.env.example +++ b/.env.example @@ -11,7 +11,7 @@ # Prisma # https://www.prisma.io/docs/reference/database-reference/connection-urls#env -DATABASE_URL="file:./db.sqlite" +DATABASE_URL="file:../database/db.sqlite" # Next Auth # You can generate a new secret on the command line with: diff --git a/.gitignore b/.gitignore index 4f96eb93c..9472a3696 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,5 @@ public/locales/* !public/locales/en #database -prisma/db.sqlite \ No newline at end of file +prisma/db.sqlite +database/*.sqlite \ No newline at end of file diff --git a/package.json b/package.json index 4db04bb10..089a5013b 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@mantine/modals": "^6.0.0", "@mantine/next": "^6.0.0", "@mantine/notifications": "^6.0.0", + "@mantine/prism": "^6.0.19", "@mantine/tiptap": "^6.0.17", "@next-auth/prisma-adapter": "^1.0.5", "@nivo/core": "^0.83.0", diff --git a/public/imgs/app-icons/truenas.svg b/public/imgs/app-icons/truenas.svg new file mode 100644 index 000000000..c3d96ff70 --- /dev/null +++ b/public/imgs/app-icons/truenas.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/imgs/app-icons/unraid-alt.svg b/public/imgs/app-icons/unraid-alt.svg new file mode 100644 index 000000000..7d695dadc --- /dev/null +++ b/public/imgs/app-icons/unraid-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Onboarding/onboarding-steps.tsx b/src/components/Onboarding/onboarding-steps.tsx new file mode 100644 index 000000000..5b476c934 --- /dev/null +++ b/src/components/Onboarding/onboarding-steps.tsx @@ -0,0 +1,41 @@ +import { Stack, Stepper } from '@mantine/core'; +import { useState } from 'react'; + +import { StepUpdatePathMappings } from './step-update-path-mappings'; +import { StepCreateAccount } from './step-create-account'; +import { StepOnboardingFinished } from './step-onboarding-finished'; +import { StepDockerImport } from './step-docker-import'; +import { StepDocumentation } from './step-documentation'; + +export const OnboardingSteps = ({ isUpdate }: { isUpdate: boolean }) => { + const [currentStep, setCurrentStep] = useState(0); + const nextStep = () => setCurrentStep((current) => (current < 4 ? current + 1 : current)); + const prevStep = () => setCurrentStep((current) => (current > 0 ? current - 1 : current)); + + return ( + + + {isUpdate && ( + + + + )} + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/Onboarding/step-create-account.tsx b/src/components/Onboarding/step-create-account.tsx new file mode 100644 index 000000000..245be24f8 --- /dev/null +++ b/src/components/Onboarding/step-create-account.tsx @@ -0,0 +1,79 @@ +import { Button, Card, PasswordInput, Stack, TextInput, Title } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { signIn } from 'next-auth/react'; +import { useState } from 'react'; +import { z } from 'zod'; +import { api } from '~/utils/api'; +import { useI18nZodResolver } from '~/utils/i18n-zod-resolver'; +import { signUpFormSchema } from '~/validations/user'; + +export const StepCreateAccount = ({ next }: { next: () => void }) => { + const [isSigninIn, setIsSigninIn] = useState(false); + const { mutateAsync } = api.user.createOwnerAccount.useMutation(); + const { i18nZodResolver } = useI18nZodResolver(); + + const form = useForm>({ + validate: i18nZodResolver(signUpFormSchema), + validateInputOnBlur: true, + }); + const handleSubmit = (values: z.infer) => { + setIsSigninIn(true); + void mutateAsync(values, { + onSuccess: () => { + signIn('credentials', { + redirect: false, + name: values.username, + password: values.password, + callbackUrl: '/', + }).then((response) => { + if (!response?.ok) { + setIsSigninIn(false); + return; + } + next(); + }); + }, + }); + }; + + return ( + + + Create your administrator account + +
+ + + + + + + + +
+
+ ); +}; diff --git a/src/components/Onboarding/step-docker-import.tsx b/src/components/Onboarding/step-docker-import.tsx new file mode 100644 index 000000000..b2edd3e29 --- /dev/null +++ b/src/components/Onboarding/step-docker-import.tsx @@ -0,0 +1,18 @@ +import { Button, Card, Stack, Title } from '@mantine/core'; +import { IconArrowRight } from '@tabler/icons-react'; + +export const StepDockerImport = ({ next }: { next: () => void }) => { + return ( + + + Automatic container import + + + + + + + ); +}; diff --git a/src/components/Onboarding/step-documentation.tsx b/src/components/Onboarding/step-documentation.tsx new file mode 100644 index 000000000..1b5117a23 --- /dev/null +++ b/src/components/Onboarding/step-documentation.tsx @@ -0,0 +1,27 @@ +import { Button, Card, Divider, Stack, Text, Title } from '@mantine/core'; +import { IconArrowRight, IconLink } from '@tabler/icons-react'; + +export const StepDocumentation = ({ next }: { next: () => void }) => { + return ( + + + Documentation + + + + We highly encourage you to read the documentation, before you continue. + + + + + + ); +}; diff --git a/src/components/Onboarding/step-onboarding-finished.tsx b/src/components/Onboarding/step-onboarding-finished.tsx new file mode 100644 index 000000000..0d74317e3 --- /dev/null +++ b/src/components/Onboarding/step-onboarding-finished.tsx @@ -0,0 +1,56 @@ +import { Box, Card, NavLink, Stack, Text, Title, createStyles } from '@mantine/core'; +import { + IconChevronRight, + IconDashboard, + IconFileText, + IconManualGearbox, +} from '@tabler/icons-react'; +import Image from 'next/image'; + +export const StepOnboardingFinished = () => { + const { classes } = useStyles(); + return ( + + + + + Congratulations, you've set Homarr up! + + Awesome! What do you want to do next? + + + } + className={classes.link} + icon={} + label="Go to your board" + variant="light" + active + /> + } + className={classes.link} + icon={} + label="Go to the management dashboard" + variant="light" + active + /> + } + className={classes.link} + icon={} + label="Check out the documentation" + variant="light" + active + /> + + + + ); +}; + +const useStyles = createStyles((theme) => ({ + link: { + borderRadius: '0.4rem', + }, +})); diff --git a/src/components/Onboarding/step-update-path-mappings.tsx b/src/components/Onboarding/step-update-path-mappings.tsx new file mode 100644 index 000000000..7a1ef2173 --- /dev/null +++ b/src/components/Onboarding/step-update-path-mappings.tsx @@ -0,0 +1,169 @@ +import { Button, Card, Code, Group, List, Tabs, TabsValue, Text } from '@mantine/core'; +import { Prism } from '@mantine/prism'; +import { IconArrowRight, IconBrandDebian, IconBrandDocker, IconInfoSquareRounded } from '@tabler/icons-react'; +import Image from 'next/image'; +import { useState } from 'react'; + +const dockerRunCommand = `docker run \\ +--name homarr \\ +--restart unless-stopped \\ +-p 7575:7575 \\ +-v your-path/homarr/configs:/app/data/configs \\ +-v your-path/homarr/data:/app/prisma \\ +-v your-path/homarr/icons:/app/public/icons \\ +-d ghcr.io/ajnart/homarr:latest`; + +const dockerComposeCommand = `version: '3' +#---------------------------------------------------------------------# +# Homarr - A simple, yet powerful dashboard for your server. # +#---------------------------------------------------------------------# +services: + homarr: + container_name: homarr + image: ghcr.io/ajnart/homarr:latest + restart: unless-stopped + volumes: + - ./homarr/configs:/app/data/configs + - ./homarr/data:/app/prisma + - ./homarr/icons:/app/public/icons + ports: + - '7575:7575'`; + +const added = { color: 'green', label: '+' }; + +export const StepUpdatePathMappings = ({ next }: { next: () => void }) => { + const [selectedTab, setSelectedTab] = useState(null); + return ( + + + Homarr has updated the location of the saved data. We detected, that your instance might + need an update to function as expected. It is recommended, that you take a backup of your + .json configuration file on the file system and copy it, in case something goes wrong. + + + + What is your installation method? + + setSelectedTab(tab)} mt="xs"> + + }> + Docker + + }> + Docker Compose + + }> + Standalone Linux / Windows + + } + > + Unraid + + }> + Others + + + + + + + + Back up your configuration. In case you didn't mount your configuration + correctly, you could risk loosing your dashboard. To back up, + go on your file system and copy the directory, containing your + default.json to your local machine. + + + + + Before you continue, check that you still have the command, that you set up Homarr + with. Otherwise, your configuration might not be loaded correctly or icons are + missing. + + + + + Run docker rm homarr, where homarr indicates the name of + your container + + + + + Run docker run ... again, that you used to create the Homarr container. + Note, that you need to add a new line: + + + {dockerRunCommand} + + + Refresh this page and click on "continue" + + + + + + + + Back up your configuration. In case you didn't mount your configuration + correctly, you could risk loosing your dashboard. To back up, + go on your file system and copy the directory, containing your + default.json to your local machine. + + + + + Navigate to the directory, where the docker-compose.yml for Homarr is + located. + + + + + Run docker compose down + + + + + Edit docker-compose.yml using text editor. Use Notepad or VSC on GUI + based systems. Use nano or vim on terminal systems. + + + {dockerComposeCommand} + + + + + + + + You're lucky. For installation without Docker on Windows and Linux, there are no + additional steps required. However, be advised that your backups should start to include + the files located at /prisma too, if you run automatic backups. + + + + + + Click on your Homarr application and click "Edit" + Scroll down and click on the link "Add another path, port, variable or device" + After the new modal has opened, make sure that "Path" has been selected at the top + In the container path, enter /app/prisma + In the host path, enter a new path on your host system. Choose a similar path, but the innermost directory should be different, than your existing mounting points (eg. /mnt/user/appdata/homarr/data) + + + + + We are sadly not able to include upgrade guides for all kind of systems. If your system was not listed, you should mount this new mounting point in your container: + /app/prisma + + + + {selectedTab && ( + + + + )} + + ); +}; diff --git a/src/pages/onboard.tsx b/src/pages/onboard.tsx index aa776bc2e..13effaced 100644 --- a/src/pages/onboard.tsx +++ b/src/pages/onboard.tsx @@ -3,12 +3,14 @@ import { Button, Card, Center, + Divider, Flex, Grid, Group, Image, PasswordInput, Stack, + Stepper, Text, TextInput, Title, @@ -17,32 +19,34 @@ import { useMantineTheme, } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { useMediaQuery } from '@mantine/hooks'; +import { useDisclosure, useMediaQuery } from '@mantine/hooks'; import { IconLayoutDashboard, IconUserCog } from '@tabler/icons-react'; import { IconArrowRight, IconBook2, IconUserPlus } from '@tabler/icons-react'; -import { GetServerSideProps } from 'next'; +import fs from 'fs'; +import { GetServerSideProps, GetServerSidePropsResult, InferGetServerSidePropsType } from 'next'; import { signIn } from 'next-auth/react'; import Head from 'next/head'; import Link from 'next/link'; import { ReactNode, useMemo, useState } from 'react'; import { z } from 'zod'; +import { OnboardingSteps } from '~/components/Onboarding/onboarding-steps'; import { prisma } from '~/server/db'; +import { getConfig } from '~/tools/config/getConfig'; import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations'; import { api } from '~/utils/api'; import { useI18nZodResolver } from '~/utils/i18n-zod-resolver'; import { signUpFormSchema } from '~/validations/user'; -const getStepContents = () => [FirstStepContent, SecondStepContent, ThirdStepContent] as const; - -export default function OnboardPage() { - const { fn, colors, breakpoints, colorScheme } = useMantineTheme(); - const [currentStep, setStep] = useState(0); - const next = () => setStep((prev) => prev + 1); - const isSmallerThanMd = useMediaQuery(`(max-width: ${breakpoints.sm})`); - const stepContents = useMemo(() => getStepContents(), []); - const CurrentStepComponent = useMemo(() => stepContents[currentStep], [currentStep]); +export default function OnboardPage({ + configSchemaVersions, +}: InferGetServerSidePropsType) { + const { fn, colors, colorScheme } = useMantineTheme(); const background = colorScheme === 'dark' ? 'dark.6' : 'gray.1'; + const [onboardingSteps, { open: showOnboardingSteps }] = useDisclosure(false); + + const isUpgradeFromSchemaOne = configSchemaVersions.includes(1); + return ( <> @@ -50,50 +54,46 @@ export default function OnboardPage() { -
-
- Homarr Logo +
+
+ Homarr Logo
- - - {stepContents.map((_, index) => ( - - ))} - - - + + {onboardingSteps ? ( + + ) : ( +
+ + + Welcome to Homarr! + + + Your favorite dashboard has received a big upgrade. +
+ We'll help you update within the next few steps +
+ + +
+
+ )} ); } -type StepProps = { - isCurrent: boolean; - isMobile: boolean; - isDark: boolean; -}; -const Step = ({ isCurrent, isMobile, isDark }: StepProps) => { - return ( - - ); -}; - type StepContentComponent = (props: { isMobile: boolean; next: () => void }) => ReactNode; const FirstStepContent: StepContentComponent = ({ isMobile, next }) => { return ( - <> + Hi there! Welcome to Homarr! 👋 @@ -104,7 +104,7 @@ const FirstStepContent: StepContentComponent = ({ isMobile, next }) => { - + ); }; @@ -138,7 +138,7 @@ const SecondStepContent: StepContentComponent = ({ isMobile, next }) => { }; return ( - <> + Configure your credentials
{
- +
); }; @@ -205,7 +205,7 @@ const ThirdStepContent: StepContentComponent = ({ isMobile, next }) => { const { classes } = useStyles(); return ( - <> + Get started! 🚀 {firstActions.map((action) => ( @@ -225,7 +225,7 @@ const ThirdStepContent: StepContentComponent = ({ isMobile, next }) => { ))} - + ); }; @@ -245,11 +245,16 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { }; } + const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json')); + const configs = files.map((file) => getConfig(file)); + const configSchemaVersions = configs.map((config) => config.schemaVersion); + const translations = await getServerSideTranslations([], ctx.locale, ctx.req, ctx.res); return { props: { ...translations, + configSchemaVersions: configSchemaVersions, }, }; }; diff --git a/yarn.lock b/yarn.lock index 7e6e74b8d..d1baa73ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1086,6 +1086,21 @@ __metadata: languageName: node linkType: hard +"@mantine/prism@npm:^6.0.19": + version: 6.0.19 + resolution: "@mantine/prism@npm:6.0.19" + dependencies: + "@mantine/utils": 6.0.19 + prism-react-renderer: ^1.2.1 + peerDependencies: + "@mantine/core": 6.0.19 + "@mantine/hooks": 6.0.19 + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: ae806b6341a0a34831ffd04cd5254e0d37ae3946cb3a0d3fd774b2a86feca09815616a580a581af3338cd7f3574a4861fc79680501b207d8596787e4ddc5b447 + languageName: node + linkType: hard + "@mantine/ssr@npm:6.0.19": version: 6.0.19 resolution: "@mantine/ssr@npm:6.0.19" @@ -6179,6 +6194,7 @@ __metadata: "@mantine/modals": ^6.0.0 "@mantine/next": ^6.0.0 "@mantine/notifications": ^6.0.0 + "@mantine/prism": ^6.0.19 "@mantine/tiptap": ^6.0.17 "@next-auth/prisma-adapter": ^1.0.5 "@next/bundle-analyzer": ^13.0.0 @@ -8460,6 +8476,15 @@ __metadata: languageName: node linkType: hard +"prism-react-renderer@npm:^1.2.1": + version: 1.3.5 + resolution: "prism-react-renderer@npm:1.3.5" + peerDependencies: + react: ">=0.14.9" + checksum: c18806dcbc4c0b4fd6fd15bd06b4f7c0a6da98d93af235c3e970854994eb9b59e23315abb6cfc29e69da26d36709a47e25da85ab27fed81b6812f0a52caf6dfa + languageName: node + linkType: hard + "prisma@npm:^5.0.0": version: 5.1.1 resolution: "prisma@npm:5.1.1"