feat: Improve startup workflow UI (#6342)

Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
miah
2025-12-05 16:18:32 -06:00
committed by GitHub
parent 22a0e6d608
commit f857ca18da
3 changed files with 201 additions and 78 deletions

View File

@@ -0,0 +1,121 @@
<template>
<v-container max-width="880" class="end-page-content">
<div class="d-flex flex-column ga-6">
<div>
<v-card-title class="text-h4 justify-center">
{{ $t('admin.setup.setup-complete') }}
</v-card-title>
<v-card-subtitle class="justify-center">
{{ $t('admin.setup.here-are-a-few-things-to-help-you-get-started') }}
</v-card-subtitle>
</div>
<div
v-for="section, idx in sections"
:key="idx"
class="d-flex flex-column ga-3"
>
<v-card-title class="text-h6 pl-0">
{{ section.title }}
</v-card-title>
<div class="sections d-flex flex-column ga-2">
<v-card
v-for="link, linkIdx in section.links"
:key="linkIdx"
clas="link-card"
:href="link.to"
:title="link.text"
:subtitle="link.description"
:append-icon="$globals.icons.chevronRight"
>
<template #prepend>
<v-avatar :icon="link.icon || undefined" variant="tonal" :color="section.color" />
</template>
</v-card>
</div>
</div>
</div>
</v-container>
</template>
<script lang="ts">
export default defineNuxtComponent({
setup() {
const i18n = useI18n();
const $auth = useMealieAuth();
const groupSlug = computed(() => $auth.user.value?.groupSlug);
const { $globals } = useNuxtApp();
const sections = ref([
{
title: i18n.t("profile.data-migrations"),
color: "info",
links: [
{
icon: $globals.icons.backupRestore,
to: "/admin/backups",
text: i18n.t("settings.backup.backup-restore"),
description: i18n.t("admin.setup.restore-from-v1-backup"),
},
{
icon: $globals.icons.import,
to: "/group/migrations",
text: i18n.t("migration.recipe-migration"),
description: i18n.t("migration.coming-from-another-application-or-an-even-older-version-of-mealie"),
},
],
},
{
title: i18n.t("recipe.create-recipes"),
color: "success",
links: [
{
icon: $globals.icons.createAlt,
to: computed(() => `/g/${groupSlug.value || ""}/r/create/new`),
text: i18n.t("recipe.create-recipe"),
description: i18n.t("recipe.create-recipe-description"),
},
{
icon: $globals.icons.link,
to: computed(() => `/g/${groupSlug.value || ""}/r/create/url`),
text: i18n.t("recipe.import-with-url"),
description: i18n.t("recipe.scrape-recipe-description"),
},
],
},
{
title: i18n.t("user.manage-users"),
color: "primary",
links: [
{
icon: $globals.icons.group,
to: "/admin/manage/users",
text: i18n.t("user.manage-users"),
description: i18n.t("user.manage-users-description"),
},
{
icon: $globals.icons.user,
to: "/user/profile",
text: i18n.t("profile.manage-user-profile"),
description: i18n.t("admin.setup.manage-profile-or-get-invite-link"),
},
],
},
]);
return { sections };
},
});
</script>
<style>
.v-container {
.v-card-title,
.v-card-subtitle {
padding: 0;
white-space: unset;
}
.v-card-item {
gap: 0.5rem;
}
}
</style>

View File

@@ -154,6 +154,7 @@ import {
mdiWebhook, mdiWebhook,
mdiWindowClose, mdiWindowClose,
mdiWrench, mdiWrench,
mdiHandWaveOutline,
} from "@mdi/js"; } from "@mdi/js";
export const icons = { export const icons = {
@@ -287,6 +288,7 @@ export const icons = {
undo: mdiUndo, undo: mdiUndo,
bread: mdiCookie, bread: mdiCookie,
fileSign: mdiFileSign, fileSign: mdiFileSign,
wave: mdiHandWaveOutline,
// Crud // Crud
backArrow: mdiArrowLeftBoldOutline, backArrow: mdiArrowLeftBoldOutline,

View File

@@ -19,35 +19,45 @@
</v-toolbar> </v-toolbar>
<!-- Stepper Wizard --> <!-- Stepper Wizard -->
<v-stepper v-model="currentPage" mobile-breakpoint="sm"> <v-stepper v-model="currentPage" mobile-breakpoint="sm" alt-labels>
<v-stepper-header> <v-stepper-header>
<v-stepper-item <v-stepper-item
:value="Pages.LANDING" :value="Pages.LANDING"
:icon="$globals.icons.wave"
:complete="currentPage > Pages.LANDING" :complete="currentPage > Pages.LANDING"
:color="getStepperColor(currentPage, Pages.LANDING)"
:title="$t('general.start')" :title="$t('general.start')"
/> />
<v-divider /> <v-divider />
<v-stepper-item <v-stepper-item
:value="Pages.USER_INFO" :value="Pages.USER_INFO"
:icon="$globals.icons.user"
:complete="currentPage > Pages.USER_INFO" :complete="currentPage > Pages.USER_INFO"
:color="getStepperColor(currentPage, Pages.USER_INFO)"
:title="$t('user-registration.account-details')" :title="$t('user-registration.account-details')"
/> />
<v-divider /> <v-divider />
<v-stepper-item <v-stepper-item
:value="Pages.PAGE_2" :value="Pages.PAGE_2"
:icon="$globals.icons.cog"
:complete="currentPage > Pages.PAGE_2" :complete="currentPage > Pages.PAGE_2"
:color="getStepperColor(currentPage, Pages.PAGE_2)"
:title="$t('settings.site-settings')" :title="$t('settings.site-settings')"
/> />
<v-divider /> <v-divider />
<v-stepper-item <v-stepper-item
:value="Pages.CONFIRM" :value="Pages.CONFIRM"
:icon="$globals.icons.chefHat"
:complete="currentPage > Pages.CONFIRM" :complete="currentPage > Pages.CONFIRM"
:color="getStepperColor(currentPage, Pages.CONFIRM)"
:title="$t('admin.maintenance.summary-title')" :title="$t('admin.maintenance.summary-title')"
/> />
<v-divider /> <v-divider />
<v-stepper-item <v-stepper-item
:value="Pages.END" :value="Pages.END"
:icon="$globals.icons.check"
:complete="currentPage > Pages.END" :complete="currentPage > Pages.END"
:color="getStepperColor(currentPage, Pages.END)"
:title="$t('admin.setup.setup-complete')" :title="$t('admin.setup.setup-complete')"
/> />
</v-stepper-header> </v-stepper-header>
@@ -82,6 +92,8 @@
<BaseButton <BaseButton
size="large" size="large"
color="primary" color="primary"
class="px-10"
rounded
:icon="$globals.icons.translate" :icon="$globals.icons.translate"
@click="langDialog = true" @click="langDialog = true"
> >
@@ -95,6 +107,16 @@
next-text="general.next" next-text="general.next"
@click:next="onNext" @click:next="onNext"
> >
<template #next>
<v-btn
variant="flat"
color="success"
:disabled="isSubmitting"
:loading="isSubmitting"
:text="$t('general.next')"
@click="onNext"
/>
</template>
<template #prev /> <template #prev />
</v-stepper-actions> </v-stepper-actions>
</v-stepper-window-item> </v-stepper-window-item>
@@ -107,10 +129,19 @@
<v-stepper-actions <v-stepper-actions
:disabled="isSubmitting" :disabled="isSubmitting"
prev-text="general.back" prev-text="general.back"
next-text="general.next"
@click:prev="onPrev" @click:prev="onPrev"
@click:next="onNext" >
/> <template #next>
<v-btn
variant="flat"
color="success"
:disabled="isSubmitting"
:loading="isSubmitting"
:text="$t('general.next')"
@click="onNext"
/>
</template>
</v-stepper-actions>
</v-stepper-window-item> </v-stepper-window-item>
<!-- COMMON SETTINGS --> <!-- COMMON SETTINGS -->
@@ -127,10 +158,19 @@
<v-stepper-actions <v-stepper-actions
:disabled="isSubmitting" :disabled="isSubmitting"
prev-text="general.back" prev-text="general.back"
next-text="general.next"
@click:prev="onPrev" @click:prev="onPrev"
@click:next="onNext" >
/> <template #next>
<v-btn
variant="flat"
color="success"
:disabled="isSubmitting"
:loading="isSubmitting"
:text="$t('general.next')"
@click="onNext"
/>
</template>
</v-stepper-actions>
</v-stepper-window-item> </v-stepper-window-item>
<!-- CONFIRMATION --> <!-- CONFIRMATION -->
@@ -177,35 +217,7 @@
<!-- END --> <!-- END -->
<v-stepper-window-item :value="Pages.END"> <v-stepper-window-item :value="Pages.END">
<v-container max-width="880"> <EndPageContent />
<v-card-title class="text-h4 justify-center">
{{ $t('admin.setup.setup-complete') }}
</v-card-title>
<v-card-title class="text-h6 justify-center">
{{ $t('admin.setup.here-are-a-few-things-to-help-you-get-started') }}
</v-card-title>
<div
v-for="link, idx in setupCompleteLinks"
:key="idx"
class="px-4 pt-4"
>
<div v-if="link.section">
<v-divider v-if="idx" />
<v-card-text class="headline pl-0">
{{ link.section }}
</v-card-text>
</div>
<v-btn
:to="link.to"
color="info"
>
{{ link.text }}
</v-btn>
<v-card-text class="subtitle px-0 py-2">
{{ link.description }}
</v-card-text>
</div>
</v-container>
<v-stepper-actions <v-stepper-actions
:disabled="isSubmitting" :disabled="isSubmitting"
prev-text="general.back" prev-text="general.back"
@@ -266,11 +278,21 @@ useSeoMeta({
}); });
enum Pages { enum Pages {
LANDING = 0, LANDING = 1,
USER_INFO = 1, USER_INFO = 2,
PAGE_2 = 2, PAGE_2 = 3,
CONFIRM = 3, CONFIRM = 4,
END = 4, END = 5,
}
function getStepperColor(currentPage: Pages, page: Pages) {
if (currentPage == page) {
return "info";
}
if (currentPage > page) {
return "success";
}
return "";
} }
// ================================================================ // ================================================================
@@ -317,42 +339,6 @@ const confirmationData = computed(() => {
]; ];
}); });
const setupCompleteLinks = ref([
{
section: i18n.t("profile.data-migrations"),
to: "/admin/backups",
text: i18n.t("settings.backup.backup-restore"),
description: i18n.t("admin.setup.restore-from-v1-backup"),
},
{
to: "/group/migrations",
text: i18n.t("migration.recipe-migration"),
description: i18n.t("migration.coming-from-another-application-or-an-even-older-version-of-mealie"),
},
{
section: i18n.t("recipe.create-recipes"),
to: computed(() => `/g/${groupSlug.value || ""}/r/create/new`),
text: i18n.t("recipe.create-recipe"),
description: i18n.t("recipe.create-recipe-description"),
},
{
to: computed(() => `/g/${groupSlug.value || ""}/r/create/url`),
text: i18n.t("recipe.import-with-url"),
description: i18n.t("recipe.scrape-recipe-description"),
},
{
section: i18n.t("user.manage-users"),
to: "/admin/manage/users",
text: i18n.t("user.manage-users"),
description: i18n.t("user.manage-users-description"),
},
{
to: "/user/profile",
text: i18n.t("profile.manage-user-profile"),
description: i18n.t("admin.setup.manage-profile-or-get-invite-link"),
},
]);
// ================================================================ // ================================================================
// Page Navigation // Page Navigation
const currentPage = ref(Pages.LANDING); const currentPage = ref(Pages.LANDING);
@@ -548,7 +534,7 @@ async function onFinish() {
} }
</script> </script>
<style scoped> <style>
.icon-white { .icon-white {
fill: white; fill: white;
} }
@@ -575,4 +561,18 @@ async function onFinish() {
.bg-off-white { .bg-off-white {
background: #f5f8fa; background: #f5f8fa;
} }
.v-stepper-item__avatar.v-avatar.v-stepper-item__avatar.v-avatar {
width: 3rem !important; /** Override inline style :( */
height: 3rem !important; /** Override inline style :( */
margin-inline-end: 0; /** reset weird margin */
.v-icon {
font-size: 1.4rem;
}
}
.v-stepper--alt-labels .v-stepper-header .v-divider {
margin: 48px -42px 0 !important;
}
</style> </style>