mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-28 08:44:36 -04:00
feat: Improve first time setup ux (#6106)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="d-flex justify-center pb-6 mt-n1">
|
<div class="d-flex pb-6 mt-n1 ml-10">
|
||||||
<div style="flex-basis: 500px">
|
<div style="flex-basis: 500px">
|
||||||
<strong> {{ $t("user.password-strength", { strength: pwStrength.strength.value }) }}</strong>
|
<strong> {{ $t("user.password-strength", { strength: pwStrength.strength.value }) }}</strong>
|
||||||
<v-progress-linear
|
<v-progress-linear
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-card-title>
|
<v-card-title class="pt-0">
|
||||||
<v-icon
|
<v-icon
|
||||||
size="large"
|
size="large"
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<span class="headline"> {{ $t("user-registration.account-details") }}</span>
|
<span class="headline"> {{ $t("user-registration.account-details") }}</span>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider />
|
<v-divider />
|
||||||
<v-card-text>
|
<v-card-text class="mt-2">
|
||||||
<v-form
|
<v-form
|
||||||
ref="domAccountForm"
|
ref="domAccountForm"
|
||||||
@submit.prevent
|
@submit.prevent
|
||||||
|
|||||||
48
frontend/components/global/AppLogo.vue
Normal file
48
frontend/components/global/AppLogo.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div class="icon-container">
|
||||||
|
<v-divider class="icon-divider" />
|
||||||
|
<v-avatar
|
||||||
|
:class="['pa-2', 'icon-avatar']"
|
||||||
|
color="primary"
|
||||||
|
:size="size"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<svg
|
||||||
|
class="icon-white"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
:style="{ width: size + 'px', height: size + 'px' }"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</slot>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { size } = withDefaults(defineProps<{ size?: number }>(), { size: 75 });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-white {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
.icon-divider {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: -2.5rem;
|
||||||
|
}
|
||||||
|
.icon-avatar {
|
||||||
|
border-color: rgba(0, 0, 0, 0.12);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,9 +33,10 @@
|
|||||||
<!-- Check Box -->
|
<!-- Check Box -->
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-if="inputField.type === fieldTypes.BOOLEAN"
|
v-if="inputField.type === fieldTypes.BOOLEAN"
|
||||||
v-model="modelValue[inputField.varName]"
|
v-model="model[inputField.varName]"
|
||||||
:name="inputField.varName"
|
:name="inputField.varName"
|
||||||
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
|
:readonly="fieldState[inputField.varName]?.readonly"
|
||||||
|
:disabled="fieldState[inputField.varName]?.disabled"
|
||||||
:hint="inputField.hint"
|
:hint="inputField.hint"
|
||||||
:hide-details="!inputField.hint"
|
:hide-details="!inputField.hint"
|
||||||
:persistent-hint="!!inputField.hint"
|
:persistent-hint="!!inputField.hint"
|
||||||
@@ -51,9 +52,9 @@
|
|||||||
<!-- Text Field -->
|
<!-- Text Field -->
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-else-if="inputField.type === fieldTypes.TEXT || inputField.type === fieldTypes.PASSWORD"
|
v-else-if="inputField.type === fieldTypes.TEXT || inputField.type === fieldTypes.PASSWORD"
|
||||||
v-model="modelValue[inputField.varName]"
|
v-model="model[inputField.varName]"
|
||||||
:readonly="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (readonlyFields && readonlyFields.includes(inputField.varName))"
|
:readonly="fieldState[inputField.varName]?.readonly"
|
||||||
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
|
:disabled="fieldState[inputField.varName]?.disabled"
|
||||||
:type="inputField.type === fieldTypes.PASSWORD ? 'password' : 'text'"
|
:type="inputField.type === fieldTypes.PASSWORD ? 'password' : 'text'"
|
||||||
variant="solo-filled"
|
variant="solo-filled"
|
||||||
flat
|
flat
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
:label="inputField.label"
|
:label="inputField.label"
|
||||||
:name="inputField.varName"
|
:name="inputField.varName"
|
||||||
:hint="inputField.hint || ''"
|
:hint="inputField.hint || ''"
|
||||||
:rules="!(inputField.disableUpdate && updateMode) ? [...rulesByKey(inputField.rules), ...defaultRules] : []"
|
:rules="!(inputField.disableUpdate && updateMode) ? [...rulesByKey(inputField.rules as any), ...defaultRules] : []"
|
||||||
lazy-validation
|
lazy-validation
|
||||||
@blur="emitBlur"
|
@blur="emitBlur"
|
||||||
/>
|
/>
|
||||||
@@ -70,9 +71,9 @@
|
|||||||
<!-- Text Area -->
|
<!-- Text Area -->
|
||||||
<v-textarea
|
<v-textarea
|
||||||
v-else-if="inputField.type === fieldTypes.TEXT_AREA"
|
v-else-if="inputField.type === fieldTypes.TEXT_AREA"
|
||||||
v-model="modelValue[inputField.varName]"
|
v-model="model[inputField.varName]"
|
||||||
:readonly="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (readonlyFields && readonlyFields.includes(inputField.varName))"
|
:readonly="fieldState[inputField.varName]?.readonly"
|
||||||
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
|
:disabled="fieldState[inputField.varName]?.disabled"
|
||||||
variant="solo-filled"
|
variant="solo-filled"
|
||||||
flat
|
flat
|
||||||
rows="3"
|
rows="3"
|
||||||
@@ -81,7 +82,7 @@
|
|||||||
:label="inputField.label"
|
:label="inputField.label"
|
||||||
:name="inputField.varName"
|
:name="inputField.varName"
|
||||||
:hint="inputField.hint || ''"
|
:hint="inputField.hint || ''"
|
||||||
:rules="[...rulesByKey(inputField.rules), ...defaultRules]"
|
:rules="[...rulesByKey(inputField.rules as any), ...defaultRules]"
|
||||||
lazy-validation
|
lazy-validation
|
||||||
@blur="emitBlur"
|
@blur="emitBlur"
|
||||||
/>
|
/>
|
||||||
@@ -89,12 +90,11 @@
|
|||||||
<!-- Option Select -->
|
<!-- Option Select -->
|
||||||
<v-select
|
<v-select
|
||||||
v-else-if="inputField.type === fieldTypes.SELECT"
|
v-else-if="inputField.type === fieldTypes.SELECT"
|
||||||
v-model="modelValue[inputField.varName]"
|
v-model="model[inputField.varName]"
|
||||||
:readonly="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (readonlyFields && readonlyFields.includes(inputField.varName))"
|
:readonly="fieldState[inputField.varName]?.readonly"
|
||||||
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
|
:disabled="fieldState[inputField.varName]?.disabled"
|
||||||
variant="solo-filled"
|
variant="solo-filled"
|
||||||
flat
|
flat
|
||||||
:prepend-icon="inputField.icons ? modelValue[inputField.varName] : null"
|
|
||||||
:label="inputField.label"
|
:label="inputField.label"
|
||||||
:name="inputField.varName"
|
:name="inputField.varName"
|
||||||
:items="inputField.options"
|
:items="inputField.options"
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
class="my-2 ml-auto"
|
class="my-2 ml-auto"
|
||||||
style="min-width: 200px"
|
style="min-width: 200px"
|
||||||
:color="modelValue[inputField.varName]"
|
:color="model[inputField.varName]"
|
||||||
dark
|
dark
|
||||||
v-bind="templateProps"
|
v-bind="templateProps"
|
||||||
>
|
>
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
v-model="modelValue[inputField.varName]"
|
v-model="model[inputField.varName]"
|
||||||
value="#7417BE"
|
value="#7417BE"
|
||||||
hide-canvas
|
hide-canvas
|
||||||
hide-inputs
|
hide-inputs
|
||||||
@@ -138,11 +138,12 @@
|
|||||||
</v-menu>
|
</v-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Object Type -->
|
||||||
<div v-else-if="inputField.type === fieldTypes.OBJECT">
|
<div v-else-if="inputField.type === fieldTypes.OBJECT">
|
||||||
<auto-form
|
<auto-form
|
||||||
v-model="modelValue[inputField.varName]"
|
v-model="model[inputField.varName]"
|
||||||
:color="color"
|
:color="color"
|
||||||
:items="inputField.items"
|
:items="(inputField as any).items"
|
||||||
@blur="emitBlur"
|
@blur="emitBlur"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +151,7 @@
|
|||||||
<!-- List Type -->
|
<!-- List Type -->
|
||||||
<div v-else-if="inputField.type === fieldTypes.LIST">
|
<div v-else-if="inputField.type === fieldTypes.LIST">
|
||||||
<div
|
<div
|
||||||
v-for="(item, idx) in modelValue[inputField.varName]"
|
v-for="(item, idx) in model[inputField.varName]"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
@@ -160,15 +161,15 @@
|
|||||||
class="ml-5"
|
class="ml-5"
|
||||||
x-small
|
x-small
|
||||||
delete
|
delete
|
||||||
@click="removeByIndex(modelValue[inputField.varName], idx)"
|
@click="removeByIndex(model[inputField.varName], idx)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<v-divider class="mb-5 mx-2" />
|
<v-divider class="mb-5 mx-2" />
|
||||||
<auto-form
|
<auto-form
|
||||||
v-model="modelValue[inputField.varName][idx]"
|
v-model="model[inputField.varName][idx]"
|
||||||
:color="color"
|
:color="color"
|
||||||
:items="inputField.items"
|
:items="(inputField as any).items"
|
||||||
@blur="emitBlur"
|
@blur="emitBlur"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +177,7 @@
|
|||||||
<v-spacer />
|
<v-spacer />
|
||||||
<BaseButton
|
<BaseButton
|
||||||
small
|
small
|
||||||
@click="modelValue[inputField.varName].push(getTemplate(inputField.items))"
|
@click="model[inputField.varName].push(getTemplate((inputField as any).items))"
|
||||||
>
|
>
|
||||||
{{ $t("general.new") }}
|
{{ $t("general.new") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
@@ -197,7 +198,13 @@ const BLUR_EVENT = "blur";
|
|||||||
type ValidatorKey = keyof typeof validators;
|
type ValidatorKey = keyof typeof validators;
|
||||||
|
|
||||||
// Use defineModel for v-model
|
// Use defineModel for v-model
|
||||||
const modelValue = defineModel<[object, Array<any>]>();
|
const modelValue = defineModel<Record<string, any> | any[]>({
|
||||||
|
type: [Object, Array],
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// alias to avoid template TS complaining about possible undefined
|
||||||
|
const model = modelValue as any;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
updateMode: {
|
updateMode: {
|
||||||
@@ -238,26 +245,39 @@ const emit = defineEmits(["blur", "update:modelValue"]);
|
|||||||
|
|
||||||
function rulesByKey(keys?: ValidatorKey[] | null) {
|
function rulesByKey(keys?: ValidatorKey[] | null) {
|
||||||
if (keys === undefined || keys === null) {
|
if (keys === undefined || keys === null) {
|
||||||
return [];
|
return [] as any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = [] as ((v: string) => boolean | string)[];
|
const list: any[] = [];
|
||||||
keys.forEach((key) => {
|
keys.forEach((key) => {
|
||||||
const split = key.split(":");
|
const split = key.split(":");
|
||||||
const validatorKey = split[0] as ValidatorKey;
|
const validatorKey = split[0] as ValidatorKey;
|
||||||
if (validatorKey in validators) {
|
if (validatorKey in validators) {
|
||||||
if (split.length === 1) {
|
if (split.length === 1) {
|
||||||
list.push(validators[validatorKey]);
|
list.push((validators as any)[validatorKey]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
list.push(validators[validatorKey](split[1]));
|
list.push((validators as any)[validatorKey](split[1] as any));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultRules = computed(() => rulesByKey(props.globalRules as ValidatorKey[]));
|
const defaultRules = computed<any[]>(() => rulesByKey(props.globalRules as any));
|
||||||
|
|
||||||
|
// Combined state map for readonly and disabled fields
|
||||||
|
const fieldState = computed<Record<string, { readonly: boolean; disabled: boolean }>>(() => {
|
||||||
|
const map: Record<string, { readonly: boolean; disabled: boolean }> = {};
|
||||||
|
(props.items || []).forEach((field: any) => {
|
||||||
|
const base = (field.disableUpdate && props.updateMode) || (!props.updateMode && field.disableCreate);
|
||||||
|
map[field.varName] = {
|
||||||
|
readonly: base || !!props.readonlyFields?.includes(field.varName),
|
||||||
|
disabled: base || !!props.disabledFields?.includes(field.varName),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
function removeByIndex(list: never[], index: number) {
|
function removeByIndex(list: never[], index: number) {
|
||||||
// Removes the item at the index
|
// Removes the item at the index
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :style="`width: ${width}; height: 100%;`">
|
|
||||||
<LanguageDialog v-model="langDialog" />
|
|
||||||
<v-card>
|
|
||||||
<div>
|
|
||||||
<v-toolbar
|
|
||||||
width="100%"
|
|
||||||
color="primary"
|
|
||||||
class="d-flex justify-center"
|
|
||||||
style="margin-bottom: 4rem"
|
|
||||||
dark
|
|
||||||
>
|
|
||||||
<v-toolbar-title class="headline text-h4 text-center mx-0">
|
|
||||||
Mealie
|
|
||||||
</v-toolbar-title>
|
|
||||||
</v-toolbar>
|
|
||||||
|
|
||||||
<div class="icon-container">
|
|
||||||
<v-divider class="icon-divider" />
|
|
||||||
<v-avatar
|
|
||||||
class="pa-2 icon-avatar"
|
|
||||||
color="primary"
|
|
||||||
size="75"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="icon-white"
|
|
||||||
style="width: 75"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</v-avatar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-center grow items-center my-4">
|
|
||||||
<slot :width="pageWidth" />
|
|
||||||
</div>
|
|
||||||
<div class="mx-2 my-4">
|
|
||||||
<v-progress-linear
|
|
||||||
v-if="wizardPage > 0"
|
|
||||||
:value="Math.ceil((wizardPage / maxPageNumber) * 100)"
|
|
||||||
striped
|
|
||||||
height="10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<v-divider class="ma-2" />
|
|
||||||
<v-card-actions width="100%">
|
|
||||||
<v-btn
|
|
||||||
v-if="prevButtonShow"
|
|
||||||
:disabled="!prevButtonEnable"
|
|
||||||
:color="prevButtonColor"
|
|
||||||
@click="decrementPage"
|
|
||||||
>
|
|
||||||
<v-icon v-if="prevButtonIconRef">
|
|
||||||
{{ prevButtonIconRef }}
|
|
||||||
</v-icon>
|
|
||||||
{{ prevButtonTextRef }}
|
|
||||||
</v-btn>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn
|
|
||||||
v-if="nextButtonShow"
|
|
||||||
variant="elevated"
|
|
||||||
:disabled="!nextButtonEnable"
|
|
||||||
:color="nextButtonColorRef"
|
|
||||||
@click="incrementPage"
|
|
||||||
>
|
|
||||||
<div v-if="isSubmitting">
|
|
||||||
<v-progress-circular
|
|
||||||
indeterminate
|
|
||||||
color="white"
|
|
||||||
size="24"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<v-icon v-if="nextButtonIconRef && !nextButtonIconAfter">
|
|
||||||
{{ nextButtonIconRef }}
|
|
||||||
</v-icon>
|
|
||||||
{{ nextButtonTextRef }}
|
|
||||||
<v-icon v-if="nextButtonIconRef && nextButtonIconAfter">
|
|
||||||
{{ nextButtonIconRef }}
|
|
||||||
</v-icon>
|
|
||||||
</div>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
<v-card-actions class="justify-center flex-column py-8">
|
|
||||||
<BaseButton
|
|
||||||
large
|
|
||||||
color="primary"
|
|
||||||
@click="langDialog = true"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
{{ $globals.icons.translate }}
|
|
||||||
</template>
|
|
||||||
{{ $t("language-dialog.choose-language") }}
|
|
||||||
</BaseButton>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
minPageNumber: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
maxPageNumber: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: "1200px",
|
|
||||||
},
|
|
||||||
pageWidth: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: "600px",
|
|
||||||
},
|
|
||||||
prevButtonText: {
|
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
prevButtonIcon: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
prevButtonColor: {
|
|
||||||
type: String,
|
|
||||||
default: "grey-darken-3",
|
|
||||||
},
|
|
||||||
prevButtonShow: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
prevButtonEnable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
nextButtonText: {
|
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
nextButtonIcon: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
nextButtonIconAfter: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
nextButtonColor: {
|
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
nextButtonShow: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
nextButtonEnable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
nextButtonIsSubmit: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
isSubmitting: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue", "submit"],
|
|
||||||
setup(props, context) {
|
|
||||||
const i18n = useI18n();
|
|
||||||
const { $globals } = useNuxtApp();
|
|
||||||
const ready = ref(false);
|
|
||||||
const langDialog = ref(false);
|
|
||||||
|
|
||||||
const wizardPage = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: value => context.emit("update:modelValue", value),
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevButtonTextRef = computed(() => props.prevButtonText || i18n.t("general.back"));
|
|
||||||
const prevButtonIconRef = computed(() => props.prevButtonIcon || $globals.icons.back);
|
|
||||||
const nextButtonTextRef = computed(
|
|
||||||
() => props.nextButtonText || (
|
|
||||||
props.nextButtonIsSubmit ? i18n.t("general.submit") : i18n.t("general.next")
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const nextButtonIconRef = computed(
|
|
||||||
() => props.nextButtonIcon || (
|
|
||||||
props.nextButtonIsSubmit ? $globals.icons.createAlt : $globals.icons.forward
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const nextButtonColorRef = computed(
|
|
||||||
() => props.nextButtonColor || (props.nextButtonIsSubmit ? "success" : "info"),
|
|
||||||
);
|
|
||||||
|
|
||||||
function goToPage(page: number) {
|
|
||||||
if (page < props.minPageNumber) {
|
|
||||||
goToPage(props.minPageNumber);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (page > props.maxPageNumber) {
|
|
||||||
goToPage(props.maxPageNumber);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wizardPage.value = page;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decrementPage() {
|
|
||||||
goToPage(wizardPage.value - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function incrementPage() {
|
|
||||||
if (props.nextButtonIsSubmit) {
|
|
||||||
context.emit("submit", wizardPage.value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
goToPage(wizardPage.value + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ready.value = true;
|
|
||||||
|
|
||||||
return {
|
|
||||||
wizardPage,
|
|
||||||
ready,
|
|
||||||
langDialog,
|
|
||||||
prevButtonTextRef,
|
|
||||||
prevButtonIconRef,
|
|
||||||
nextButtonTextRef,
|
|
||||||
nextButtonIconRef,
|
|
||||||
nextButtonColorRef,
|
|
||||||
decrementPage,
|
|
||||||
incrementPage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.icon-primary {
|
|
||||||
fill: var(--v-primary-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-white {
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
margin-top: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-divider {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: -2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-avatar {
|
|
||||||
border-color: rgba(0, 0, 0, 0.12);
|
|
||||||
border: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-off-white {
|
|
||||||
background: #f5f8fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferred-width {
|
|
||||||
width: 840px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,32 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container
|
<v-container
|
||||||
fill-height
|
|
||||||
fluid
|
fluid
|
||||||
class="d-flex justify-center align-center"
|
class="d-flex justify-center align-start fill-height"
|
||||||
width="1200px"
|
|
||||||
min-height="700px"
|
|
||||||
:class="{
|
:class="{
|
||||||
'bg-off-white': !$vuetify.theme.current.dark,
|
'bg-off-white': !$vuetify.theme.current.dark && !isDark,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<BaseWizard
|
<!-- Header Toolbar -->
|
||||||
v-model="currentPage"
|
<v-card class="elevation-4" width="1200" :class="{ 'my-10': $vuetify.display.mdAndUp }">
|
||||||
:max-page-number="totalPages"
|
<v-toolbar
|
||||||
:title="$t('admin.setup.first-time-setup')"
|
color="primary"
|
||||||
:prev-button-show="activeConfig.showPrevButton"
|
class="d-flex justify-center"
|
||||||
:next-button-show="activeConfig.showNextButton"
|
dark
|
||||||
:next-button-text="activeConfig.nextButtonText"
|
|
||||||
:next-button-icon="activeConfig.nextButtonIcon"
|
|
||||||
:next-button-color="activeConfig.nextButtonColor"
|
|
||||||
:next-button-is-submit="activeConfig.isSubmit"
|
|
||||||
:is-submitting="isSubmitting"
|
|
||||||
@submit="handleSubmit"
|
|
||||||
>
|
>
|
||||||
<v-container
|
<v-toolbar-title class="headline text-h4 text-center mx-0">
|
||||||
v-if="currentPage === Pages.LANDING"
|
Mealie
|
||||||
class="mb-12"
|
</v-toolbar-title>
|
||||||
>
|
</v-toolbar>
|
||||||
<v-card-title class="text-h4 justify-center text-center">
|
|
||||||
|
<!-- Stepper Wizard -->
|
||||||
|
<v-stepper v-model="currentPage" mobile-breakpoint="sm">
|
||||||
|
<v-stepper-header>
|
||||||
|
<v-stepper-item
|
||||||
|
:value="Pages.LANDING"
|
||||||
|
:complete="currentPage > Pages.LANDING"
|
||||||
|
:title="$t('general.start')"
|
||||||
|
/>
|
||||||
|
<v-divider />
|
||||||
|
<v-stepper-item
|
||||||
|
:value="Pages.USER_INFO"
|
||||||
|
:complete="currentPage > Pages.USER_INFO"
|
||||||
|
:title="$t('user-registration.account-details')"
|
||||||
|
/>
|
||||||
|
<v-divider />
|
||||||
|
<v-stepper-item
|
||||||
|
:value="Pages.PAGE_2"
|
||||||
|
:complete="currentPage > Pages.PAGE_2"
|
||||||
|
:title="$t('settings.site-settings')"
|
||||||
|
/>
|
||||||
|
<v-divider />
|
||||||
|
<v-stepper-item
|
||||||
|
:value="Pages.CONFIRM"
|
||||||
|
:complete="currentPage > Pages.CONFIRM"
|
||||||
|
:title="$t('admin.maintenance.summary-title')"
|
||||||
|
/>
|
||||||
|
<v-divider />
|
||||||
|
<v-stepper-item
|
||||||
|
:value="Pages.END"
|
||||||
|
:complete="currentPage > Pages.END"
|
||||||
|
:title="$t('admin.setup.setup-complete')"
|
||||||
|
/>
|
||||||
|
</v-stepper-header>
|
||||||
|
<v-progress-linear
|
||||||
|
v-if="isSubmitting && currentPage === Pages.CONFIRM"
|
||||||
|
color="primary"
|
||||||
|
indeterminate
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-stepper-window :transition="false" class="stepper-window">
|
||||||
|
<!-- LANDING -->
|
||||||
|
<v-stepper-window-item :value="Pages.LANDING">
|
||||||
|
<v-container class="mb-12">
|
||||||
|
<AppLogo />
|
||||||
|
<v-card-title class="text-h4 justify-center text-center text-break text-pre-wrap">
|
||||||
{{ $t('admin.setup.welcome-to-mealie-get-started') }}
|
{{ $t('admin.setup.welcome-to-mealie-get-started') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -41,12 +78,45 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<v-container v-if="currentPage === Pages.USER_INFO">
|
<v-card-actions class="justify-center flex-column py-8">
|
||||||
|
<BaseButton
|
||||||
|
size="large"
|
||||||
|
color="primary"
|
||||||
|
:icon="$globals.icons.translate"
|
||||||
|
@click="langDialog = true"
|
||||||
|
>
|
||||||
|
{{ $t('language-dialog.choose-language') }}
|
||||||
|
</BaseButton>
|
||||||
|
</v-card-actions>
|
||||||
|
|
||||||
|
<v-stepper-actions
|
||||||
|
class="justify-end"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
next-text="general.next"
|
||||||
|
@click:next="onNext"
|
||||||
|
>
|
||||||
|
<template #prev />
|
||||||
|
</v-stepper-actions>
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
|
<!-- USER INFO -->
|
||||||
|
<v-stepper-window-item :value="Pages.USER_INFO" eager>
|
||||||
|
<v-container max-width="880">
|
||||||
<UserRegistrationForm />
|
<UserRegistrationForm />
|
||||||
</v-container>
|
</v-container>
|
||||||
|
<v-stepper-actions
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
prev-text="general.back"
|
||||||
|
next-text="general.next"
|
||||||
|
@click:prev="onPrev"
|
||||||
|
@click:next="onNext"
|
||||||
|
/>
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
<v-container v-if="currentPage === Pages.PAGE_2">
|
<!-- COMMON SETTINGS -->
|
||||||
<v-card-title class="headline justify-center pa-0">
|
<v-stepper-window-item :value="Pages.PAGE_2">
|
||||||
|
<v-container max-width="880">
|
||||||
|
<v-card-title class="headline pa-0">
|
||||||
{{ $t('admin.setup.common-settings-for-new-sites') }}
|
{{ $t('admin.setup.common-settings-for-new-sites') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<AutoForm
|
<AutoForm
|
||||||
@@ -54,16 +124,27 @@
|
|||||||
:items="commonSettingsForm"
|
:items="commonSettingsForm"
|
||||||
/>
|
/>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
<v-stepper-actions
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
prev-text="general.back"
|
||||||
|
next-text="general.next"
|
||||||
|
@click:prev="onPrev"
|
||||||
|
@click:next="onNext"
|
||||||
|
/>
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
<v-container v-if="currentPage === Pages.CONFIRM">
|
<!-- CONFIRMATION -->
|
||||||
<v-card-title class="headline justify-center">
|
<v-stepper-window-item :value="Pages.CONFIRM">
|
||||||
{{ $t("general.confirm-how-does-everything-look") }}
|
<v-container max-width="880">
|
||||||
|
<v-card-title class="headline pa-0">
|
||||||
|
{{ $t('general.confirm-how-does-everything-look') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-list>
|
<v-list>
|
||||||
<template v-for="(item, idx) in confirmationData">
|
<template v-for="(item, idx) in confirmationData">
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="item.display"
|
v-if="item.display"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
|
class="px-0"
|
||||||
>
|
>
|
||||||
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
||||||
<v-list-item-subtitle>{{ item.value }}</v-list-item-subtitle>
|
<v-list-item-subtitle>{{ item.value }}</v-list-item-subtitle>
|
||||||
@@ -75,8 +156,27 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
<v-stepper-actions
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
prev-text="general.back"
|
||||||
|
@click:prev="onPrev"
|
||||||
|
>
|
||||||
|
<template #next>
|
||||||
|
<BaseButton
|
||||||
|
create flat
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
:loading="isSubmitting"
|
||||||
|
:icon="$globals.icons.check"
|
||||||
|
:text="$t('general.submit')"
|
||||||
|
@click="onNext"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-stepper-actions>
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
<v-container v-if="currentPage === Pages.END">
|
<!-- END -->
|
||||||
|
<v-stepper-window-item :value="Pages.END">
|
||||||
|
<v-container max-width="880">
|
||||||
<v-card-title class="text-h4 justify-center">
|
<v-card-title class="text-h4 justify-center">
|
||||||
{{ $t('admin.setup.setup-complete') }}
|
{{ $t('admin.setup.setup-complete') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
@@ -105,11 +205,35 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</div>
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
</BaseWizard>
|
<v-stepper-actions
|
||||||
</v-container>
|
:disabled="isSubmitting"
|
||||||
|
prev-text="general.back"
|
||||||
|
@click:prev="onPrev"
|
||||||
|
>
|
||||||
|
<template #next>
|
||||||
|
<BaseButton
|
||||||
|
flat
|
||||||
|
color="primary"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
:loading="isSubmitting"
|
||||||
|
:icon="$globals.icons.home"
|
||||||
|
:text="$t('general.home')"
|
||||||
|
@click="onFinish"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-stepper-actions>
|
||||||
|
</v-stepper-window-item>
|
||||||
|
</v-stepper-window>
|
||||||
|
</v-stepper>
|
||||||
|
|
||||||
|
<!-- Dialog Language -->
|
||||||
|
<LanguageDialog v-model="langDialog" />
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useDark } from "@vueuse/core";
|
||||||
import { useAdminApi, useUserApi } from "~/composables/api";
|
import { useAdminApi, useUserApi } from "~/composables/api";
|
||||||
import { useLocales } from "~/composables/use-locales";
|
import { useLocales } from "~/composables/use-locales";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
@@ -117,64 +241,53 @@ import { useUserRegistrationForm } from "~/composables/use-users/user-registrati
|
|||||||
import { useCommonSettingsForm } from "~/composables/use-setup/common-settings-form";
|
import { useCommonSettingsForm } from "~/composables/use-setup/common-settings-form";
|
||||||
import UserRegistrationForm from "~/components/Domain/User/UserRegistrationForm.vue";
|
import UserRegistrationForm from "~/components/Domain/User/UserRegistrationForm.vue";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
definePageMeta({
|
||||||
components: { UserRegistrationForm },
|
|
||||||
setup() {
|
|
||||||
definePageMeta({
|
|
||||||
layout: "blank",
|
layout: "blank",
|
||||||
});
|
});
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Setup
|
// Setup
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const { $globals } = useNuxtApp();
|
const userApi = useUserApi();
|
||||||
const userApi = useUserApi();
|
const adminApi = useAdminApi();
|
||||||
const adminApi = useAdminApi();
|
|
||||||
|
|
||||||
const groupSlug = computed(() => $auth.user.value?.groupSlug);
|
const groupSlug = computed(() => $auth.user.value?.groupSlug);
|
||||||
const { locale } = useLocales();
|
const { locale } = useLocales();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isSubmitting = ref(false);
|
const isSubmitting = ref(false);
|
||||||
useSeoMeta({
|
const langDialog = ref(false);
|
||||||
|
const isDark = useDark();
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
title: i18n.t("admin.setup.first-time-setup"),
|
title: i18n.t("admin.setup.first-time-setup"),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!$auth.loggedIn.value) {
|
if (!$auth.loggedIn.value) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
}
|
}
|
||||||
else if (!$auth.user.value?.admin) {
|
else if (!$auth.user.value?.admin) {
|
||||||
router.push(groupSlug.value ? `/g/${groupSlug.value}` : "/login");
|
router.push(groupSlug.value ? `/g/${groupSlug.value}` : "/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config = {
|
enum Pages {
|
||||||
nextButtonText: string | undefined;
|
|
||||||
nextButtonIcon: string | undefined;
|
|
||||||
nextButtonColor: string | undefined;
|
|
||||||
showPrevButton: boolean;
|
|
||||||
showNextButton: boolean;
|
|
||||||
isSubmit: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const totalPages = 4;
|
|
||||||
enum Pages {
|
|
||||||
LANDING = 0,
|
LANDING = 0,
|
||||||
USER_INFO = 1,
|
USER_INFO = 1,
|
||||||
PAGE_2 = 2,
|
PAGE_2 = 2,
|
||||||
CONFIRM = 3,
|
CONFIRM = 3,
|
||||||
END = 4,
|
END = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Forms
|
// Forms
|
||||||
const { accountDetails, credentials } = useUserRegistrationForm();
|
const { accountDetails, credentials } = useUserRegistrationForm();
|
||||||
const { commonSettingsForm } = useCommonSettingsForm();
|
const { commonSettingsForm } = useCommonSettingsForm();
|
||||||
const commonSettings = ref({
|
const commonSettings = ref({
|
||||||
makeGroupRecipesPublic: false,
|
makeGroupRecipesPublic: false,
|
||||||
useSeedData: true,
|
useSeedData: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const confirmationData = computed(() => {
|
const confirmationData = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
display: true,
|
display: true,
|
||||||
@@ -207,9 +320,9 @@ export default defineNuxtComponent({
|
|||||||
value: commonSettings.value.useSeedData ? i18n.t("general.yes") : i18n.t("general.no"),
|
value: commonSettings.value.useSeedData ? i18n.t("general.yes") : i18n.t("general.no"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
const setupCompleteLinks = ref([
|
const setupCompleteLinks = ref([
|
||||||
{
|
{
|
||||||
section: i18n.t("profile.data-migrations"),
|
section: i18n.t("profile.data-migrations"),
|
||||||
to: "/admin/backups",
|
to: "/admin/backups",
|
||||||
@@ -243,59 +356,23 @@ export default defineNuxtComponent({
|
|||||||
text: i18n.t("profile.manage-user-profile"),
|
text: i18n.t("profile.manage-user-profile"),
|
||||||
description: i18n.t("admin.setup.manage-profile-or-get-invite-link"),
|
description: i18n.t("admin.setup.manage-profile-or-get-invite-link"),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Page Navigation
|
// Page Navigation
|
||||||
const currentPage = ref(0);
|
const currentPage = ref(Pages.LANDING);
|
||||||
const activeConfig = computed<Config>(() => {
|
|
||||||
const config: Config = {
|
|
||||||
nextButtonText: undefined,
|
|
||||||
nextButtonIcon: undefined,
|
|
||||||
nextButtonColor: undefined,
|
|
||||||
showPrevButton: true,
|
|
||||||
showNextButton: true,
|
|
||||||
isSubmit: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (currentPage.value) {
|
// ================================================================
|
||||||
case Pages.LANDING:
|
// Page Submission
|
||||||
config.showPrevButton = false;
|
|
||||||
config.nextButtonText = i18n.t("general.start");
|
|
||||||
config.nextButtonIcon = $globals.icons.forward;
|
|
||||||
break;
|
|
||||||
case Pages.USER_INFO:
|
|
||||||
config.showPrevButton = false;
|
|
||||||
config.nextButtonText = i18n.t("general.next");
|
|
||||||
config.nextButtonIcon = $globals.icons.forward;
|
|
||||||
config.isSubmit = true;
|
|
||||||
break;
|
|
||||||
case Pages.CONFIRM:
|
|
||||||
config.isSubmit = true;
|
|
||||||
break;
|
|
||||||
case Pages.END:
|
|
||||||
config.nextButtonText = i18n.t("general.home");
|
|
||||||
config.nextButtonIcon = $globals.icons.home;
|
|
||||||
config.nextButtonColor = "primary";
|
|
||||||
config.showPrevButton = false;
|
|
||||||
config.isSubmit = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
async function updateUser() {
|
||||||
});
|
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// Page Submission
|
|
||||||
|
|
||||||
async function updateUser() {
|
|
||||||
// Note: $auth.user is now a ref
|
// Note: $auth.user is now a ref
|
||||||
const { response } = await userApi.users.updateOne($auth.user.value!.id, {
|
const { response } = await userApi.users.updateOne($auth.user.value!.id, {
|
||||||
...$auth.user.value,
|
...$auth.user.value,
|
||||||
email: accountDetails.email.value,
|
email: accountDetails.email.value,
|
||||||
username: accountDetails.username.value,
|
username: accountDetails.username.value,
|
||||||
fullName: accountDetails.fullName.value,
|
fullName: accountDetails.fullName.value,
|
||||||
advancedOptions: accountDetails.advancedOptions.value,
|
advanced: accountDetails.advancedOptions.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
@@ -303,16 +380,10 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$auth.refresh();
|
$auth.refresh();
|
||||||
/* $auth.setUser({
|
|
||||||
...$auth.user.value,
|
|
||||||
email: accountDetails.email.value,
|
|
||||||
username: accountDetails.username.value,
|
|
||||||
fullName: accountDetails.fullName.value,
|
|
||||||
}) */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function updatePassword() {
|
async function updatePassword() {
|
||||||
const { response } = await userApi.users.changePassword({
|
const { response } = await userApi.users.changePassword({
|
||||||
currentPassword: "MyPassword",
|
currentPassword: "MyPassword",
|
||||||
newPassword: credentials.password1.value,
|
newPassword: credentials.password1.value,
|
||||||
@@ -321,15 +392,14 @@ export default defineNuxtComponent({
|
|||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
alert.error(i18n.t("events.something-went-wrong"));
|
alert.error(i18n.t("events.something-went-wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitRegistration() {
|
async function submitRegistration() {
|
||||||
// the backend will only update the password without the "currentPassword" field if the user is the default user,
|
// we update the password first, then update the user's details
|
||||||
// so we update the password first, then update the user's details
|
|
||||||
await updatePassword().then(updateUser);
|
await updatePassword().then(updateUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateGroup() {
|
async function updateGroup() {
|
||||||
// Note: $auth.user is now a ref
|
// Note: $auth.user is now a ref
|
||||||
const { data } = await userApi.groups.getOne($auth.user.value!.groupId);
|
const { data } = await userApi.groups.getOne($auth.user.value!.groupId);
|
||||||
if (!data || !data.preferences) {
|
if (!data || !data.preferences) {
|
||||||
@@ -352,9 +422,9 @@ export default defineNuxtComponent({
|
|||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
alert.error(i18n.t("events.something-went-wrong"));
|
alert.error(i18n.t("events.something-went-wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateHousehold() {
|
async function updateHousehold() {
|
||||||
// Note: $auth.user is now a ref
|
// Note: $auth.user is now a ref
|
||||||
const { data } = await adminApi.households.getOne($auth.user.value!.householdId);
|
const { data } = await adminApi.households.getOne($auth.user.value!.householdId);
|
||||||
if (!data || !data.preferences) {
|
if (!data || !data.preferences) {
|
||||||
@@ -378,30 +448,30 @@ export default defineNuxtComponent({
|
|||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
alert.error(i18n.t("events.something-went-wrong"));
|
alert.error(i18n.t("events.something-went-wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedFoods() {
|
async function seedFoods() {
|
||||||
const { response } = await userApi.seeders.foods({ locale: locale.value });
|
const { response } = await userApi.seeders.foods({ locale: locale.value });
|
||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
alert.error(i18n.t("events.something-went-wrong"));
|
alert.error(i18n.t("events.something-went-wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedUnits() {
|
async function seedUnits() {
|
||||||
const { response } = await userApi.seeders.units({ locale: locale.value });
|
const { response } = await userApi.seeders.units({ locale: locale.value });
|
||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
alert.error(i18n.t("events.something-went-wrong"));
|
alert.error(i18n.t("events.something-went-wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedLabels() {
|
async function seedLabels() {
|
||||||
const { response } = await userApi.seeders.labels({ locale: locale.value });
|
const { response } = await userApi.seeders.labels({ locale: locale.value });
|
||||||
if (!response || response.status !== 200) {
|
if (!response || response.status !== 200) {
|
||||||
alert.error(i18n.t("events.something-went-wrong"));
|
alert.error(i18n.t("events.something-went-wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedData() {
|
async function seedData() {
|
||||||
if (!commonSettings.value.useSeedData) {
|
if (!commonSettings.value.useSeedData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -413,9 +483,9 @@ export default defineNuxtComponent({
|
|||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitCommonSettings() {
|
async function submitCommonSettings() {
|
||||||
const tasks = [
|
const tasks = [
|
||||||
updateGroup(),
|
updateGroup(),
|
||||||
updateHousehold(),
|
updateHousehold(),
|
||||||
@@ -423,18 +493,18 @@ export default defineNuxtComponent({
|
|||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitAll() {
|
async function submitAll() {
|
||||||
const tasks = [
|
const tasks = [
|
||||||
submitRegistration(),
|
submitRegistration(),
|
||||||
submitCommonSettings(),
|
submitCommonSettings(),
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(page: number) {
|
async function handleSubmit(page: number) {
|
||||||
if (isSubmitting.value) {
|
if (isSubmitting.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -455,25 +525,59 @@ export default defineNuxtComponent({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
isSubmitting.value = false;
|
isSubmitting.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
// ================================================================
|
||||||
// Setup
|
// Stepper Navigation Handlers
|
||||||
groupSlug,
|
function onPrev() {
|
||||||
// Forms
|
if (isSubmitting.value) return;
|
||||||
commonSettingsForm,
|
if (currentPage.value > Pages.LANDING) currentPage.value -= 1;
|
||||||
commonSettings,
|
}
|
||||||
confirmationData,
|
|
||||||
setupCompleteLinks,
|
async function onNext() {
|
||||||
// Page Navigation
|
if (isSubmitting.value) return;
|
||||||
Pages,
|
if (currentPage.value === Pages.USER_INFO) {
|
||||||
currentPage,
|
await handleSubmit(Pages.USER_INFO);
|
||||||
totalPages,
|
return;
|
||||||
activeConfig,
|
}
|
||||||
// Page Submission
|
if (currentPage.value === Pages.CONFIRM) {
|
||||||
isSubmitting,
|
await handleSubmit(Pages.CONFIRM);
|
||||||
handleSubmit,
|
return;
|
||||||
};
|
}
|
||||||
},
|
currentPage.value += 1;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
async function onFinish() {
|
||||||
|
if (isSubmitting.value) return;
|
||||||
|
await handleSubmit(Pages.END);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-white {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-divider {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: -2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-avatar {
|
||||||
|
border-color: rgba(0, 0, 0, 0.12);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-off-white {
|
||||||
|
background: #f5f8fa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -52,26 +52,7 @@
|
|||||||
Mealie
|
Mealie
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
<AppLogo :size="100" />
|
||||||
<div class="icon-container">
|
|
||||||
<v-divider class="icon-divider" />
|
|
||||||
<v-avatar
|
|
||||||
class="pa-2 icon-avatar"
|
|
||||||
color="primary"
|
|
||||||
size="100"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="icon-white"
|
|
||||||
style="width: 100px; height: 100px"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</v-avatar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-card-title class="text-h5 justify-center pb-3">
|
<v-card-title class="text-h5 justify-center pb-3">
|
||||||
{{ $t('user.sign-in') }}
|
{{ $t('user.sign-in') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|||||||
@@ -23,25 +23,7 @@
|
|||||||
Mealie
|
Mealie
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
<AppLogo />
|
||||||
<div class="icon-container">
|
|
||||||
<v-divider class="icon-divider" />
|
|
||||||
<v-avatar
|
|
||||||
class="pa-2 icon-avatar"
|
|
||||||
color="primary"
|
|
||||||
size="75"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="icon-white"
|
|
||||||
style="width: 75"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</v-avatar>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Container -->
|
<!-- Form Container -->
|
||||||
|
|||||||
4
frontend/types/components.d.ts
vendored
4
frontend/types/components.d.ts
vendored
@@ -15,7 +15,6 @@ import type BaseDivider from "@/components/global/BaseDivider.vue";
|
|||||||
import type BaseOverflowButton from "@/components/global/BaseOverflowButton.vue";
|
import type BaseOverflowButton from "@/components/global/BaseOverflowButton.vue";
|
||||||
import type BasePageTitle from "@/components/global/BasePageTitle.vue";
|
import type BasePageTitle from "@/components/global/BasePageTitle.vue";
|
||||||
import type BaseStatCard from "@/components/global/BaseStatCard.vue";
|
import type BaseStatCard from "@/components/global/BaseStatCard.vue";
|
||||||
import type BaseWizard from "@/components/global/BaseWizard.vue";
|
|
||||||
import type ButtonLink from "@/components/global/ButtonLink.vue";
|
import type ButtonLink from "@/components/global/ButtonLink.vue";
|
||||||
import type ContextMenu from "@/components/global/ContextMenu.vue";
|
import type ContextMenu from "@/components/global/ContextMenu.vue";
|
||||||
import type CrudTable from "@/components/global/CrudTable.vue";
|
import type CrudTable from "@/components/global/CrudTable.vue";
|
||||||
@@ -56,7 +55,6 @@ declare module "vue" {
|
|||||||
BaseOverflowButton: typeof BaseOverflowButton;
|
BaseOverflowButton: typeof BaseOverflowButton;
|
||||||
BasePageTitle: typeof BasePageTitle;
|
BasePageTitle: typeof BasePageTitle;
|
||||||
BaseStatCard: typeof BaseStatCard;
|
BaseStatCard: typeof BaseStatCard;
|
||||||
BaseWizard: typeof BaseWizard;
|
|
||||||
ButtonLink: typeof ButtonLink;
|
ButtonLink: typeof ButtonLink;
|
||||||
ContextMenu: typeof ContextMenu;
|
ContextMenu: typeof ContextMenu;
|
||||||
CrudTable: typeof CrudTable;
|
CrudTable: typeof CrudTable;
|
||||||
@@ -81,4 +79,4 @@ declare module "vue" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export { };
|
||||||
|
|||||||
Reference in New Issue
Block a user