mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-06-13 12:30:14 -04:00
chore: migrate remaining pages to script setup (#7310)
This commit is contained in:
@@ -100,6 +100,7 @@ const {
|
||||
usernameErrorMessages,
|
||||
validateUsername,
|
||||
validateEmail,
|
||||
domAccountForm,
|
||||
} = useUserRegistrationForm();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -21,5 +21,6 @@ export default withNuxt({
|
||||
],
|
||||
"vue/no-mutating-props": "error",
|
||||
"vue/no-v-html": "error",
|
||||
"vue/component-api-style": ["error", ["script-setup"]],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,30 +48,29 @@
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useGlobalI18n } from "~/composables/use-global-i18n";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
props: {
|
||||
const props = defineProps({
|
||||
error: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
definePageMeta({
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
layout: "basic",
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useGlobalI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $globals } = useNuxtApp();
|
||||
const ready = ref(false);
|
||||
const i18n = useGlobalI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $globals } = useNuxtApp();
|
||||
const ready = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
async function insertGroupSlugIntoRoute() {
|
||||
async function insertGroupSlugIntoRoute() {
|
||||
const groupSlug = ref(auth.user.value?.groupSlug);
|
||||
if (!groupSlug.value) {
|
||||
return;
|
||||
@@ -99,9 +98,9 @@ export default defineNuxtComponent({
|
||||
if (replaceRoute) {
|
||||
await router.replace(routeVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handle404() {
|
||||
async function handle404() {
|
||||
const normalizedRoute = route.fullPath.replace(/\/$/, "");
|
||||
const newRoute = normalizedRoute.replace(/^\/group\/(mealplan|members|notifiers|webhooks)(\/.*)?$/, "/household/$1$2");
|
||||
|
||||
@@ -113,32 +112,25 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
ready.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.error.statusCode === 404) {
|
||||
if (props.error.statusCode === 404) {
|
||||
handle404();
|
||||
}
|
||||
else {
|
||||
}
|
||||
else {
|
||||
ready.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title:
|
||||
props.error.statusCode === 404
|
||||
? (i18n.t("page.404-not-found") as string)
|
||||
: (i18n.t("page.an-error-occurred") as string),
|
||||
});
|
||||
|
||||
const buttons = [
|
||||
{ icon: $globals.icons.home, to: "/", text: i18n.t("general.home") },
|
||||
];
|
||||
|
||||
return {
|
||||
buttons,
|
||||
ready,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const buttons = [
|
||||
{ icon: $globals.icons.home, to: "/", text: i18n.t("general.home") },
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: ["admin-only"],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section>
|
||||
<!-- Delete Dialog -->
|
||||
<BaseDialog
|
||||
v-model="deleteDialog"
|
||||
v-model="state.deleteDialog"
|
||||
:title="$t('settings.backup.delete-backup')"
|
||||
color="error"
|
||||
:icon="$globals.icons.alertCircle"
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- Import Dialog -->
|
||||
<BaseDialog
|
||||
v-model="importDialog"
|
||||
v-model="state.importDialog"
|
||||
color="error"
|
||||
:title="$t('settings.backup.backup-restore')"
|
||||
:icon="$globals.icons.database"
|
||||
@@ -40,7 +40,7 @@
|
||||
</p>
|
||||
|
||||
<v-checkbox
|
||||
v-model="confirmImport"
|
||||
v-model="state.confirmImport"
|
||||
class="checkbox-top"
|
||||
color="error"
|
||||
hide-details
|
||||
@@ -50,7 +50,7 @@
|
||||
<v-card-actions class="justify-center pt-0">
|
||||
<BaseButton
|
||||
delete
|
||||
:disabled="!confirmImport || runningRestore"
|
||||
:disabled="!state.confirmImport || state.runningRestore"
|
||||
@click="restoreBackup(selected)"
|
||||
>
|
||||
<template #icon>
|
||||
@@ -63,7 +63,7 @@
|
||||
{{ selected }}
|
||||
</p>
|
||||
<v-progress-linear
|
||||
v-if="runningRestore"
|
||||
v-if="state.runningRestore"
|
||||
indeterminate
|
||||
/>
|
||||
</BaseDialog>
|
||||
@@ -81,7 +81,7 @@
|
||||
>
|
||||
<BaseButton
|
||||
class="mr-2"
|
||||
:loading="runningBackup"
|
||||
:loading="state.runningBackup"
|
||||
@click="createBackup"
|
||||
>
|
||||
{{ $t("settings.backup.create-heading") }}
|
||||
@@ -96,13 +96,13 @@
|
||||
</v-toolbar>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:headers="state.headers"
|
||||
:items="backups.imports || []"
|
||||
class="elevation-0"
|
||||
:items-per-page="-1"
|
||||
hide-default-footer
|
||||
disable-pagination
|
||||
:search="search"
|
||||
:search="state.search"
|
||||
@click:row="setSelected"
|
||||
>
|
||||
<template #[`item.date`]="{ item }">
|
||||
@@ -115,7 +115,7 @@
|
||||
color="error"
|
||||
variant="text"
|
||||
@click.stop="
|
||||
deleteDialog = true;
|
||||
state.deleteDialog = true;
|
||||
deleteTarget = item.name;
|
||||
"
|
||||
>
|
||||
@@ -130,7 +130,7 @@
|
||||
/>
|
||||
<BaseButton
|
||||
small
|
||||
@click.stop="setSelected(item); importDialog = true"
|
||||
@click.stop="setSelected(item); state.importDialog = true"
|
||||
>
|
||||
<template #icon>
|
||||
{{ $globals.icons.backupRestore }}
|
||||
@@ -151,38 +151,33 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useAdminApi } from "~/composables/api";
|
||||
import type { AllBackups } from "~/lib/api/types/admin";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug || auth.user.value?.groupSlug || "");
|
||||
const i18n = useI18n();
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
const selected = ref("");
|
||||
const adminApi = useAdminApi();
|
||||
const selected = ref("");
|
||||
|
||||
const backups = ref<AllBackups>({
|
||||
const backups = ref<AllBackups>({
|
||||
imports: [],
|
||||
templates: [],
|
||||
});
|
||||
});
|
||||
|
||||
async function refreshBackups() {
|
||||
async function refreshBackups() {
|
||||
const { data } = await adminApi.backups.getAll();
|
||||
if (data) {
|
||||
backups.value = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createBackup() {
|
||||
async function createBackup() {
|
||||
state.runningBackup = true;
|
||||
const { data } = await adminApi.backups.create();
|
||||
|
||||
@@ -194,9 +189,9 @@ export default defineNuxtComponent({
|
||||
alert.error(i18n.t("settings.backup.error-creating-backup-see-log-file"));
|
||||
}
|
||||
state.runningBackup = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreBackup(fileName: string) {
|
||||
async function restoreBackup(fileName: string) {
|
||||
state.runningRestore = true;
|
||||
const { error } = await adminApi.backups.restore(fileName);
|
||||
|
||||
@@ -212,20 +207,20 @@ export default defineNuxtComponent({
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteTarget = ref("");
|
||||
const deleteTarget = ref("");
|
||||
|
||||
async function deleteBackup() {
|
||||
async function deleteBackup() {
|
||||
const { data } = await adminApi.backups.delete(deleteTarget.value);
|
||||
|
||||
if (!data?.error) {
|
||||
alert.success(i18n.t("settings.backup.backup-deleted"));
|
||||
refreshBackups();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
confirmImport: false,
|
||||
deleteDialog: false,
|
||||
createDialog: false,
|
||||
@@ -239,42 +234,25 @@ export default defineNuxtComponent({
|
||||
{ title: i18n.t("export.size"), value: "size" },
|
||||
{ title: "", value: "actions", align: "right" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
function setSelected(data: { name: string; date: string }) {
|
||||
function setSelected(data: { name: string; date: string }) {
|
||||
if (!data.name) {
|
||||
return;
|
||||
}
|
||||
selected.value = data.name;
|
||||
}
|
||||
}
|
||||
|
||||
const backupsFileNameDownload = (fileName: string) => `api/admin/backups/${fileName}`;
|
||||
const backupsFileNameDownload = (fileName: string) => `api/admin/backups/${fileName}`;
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("sidebar.backups"),
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(refreshBackups);
|
||||
onMounted(refreshBackups);
|
||||
|
||||
return {
|
||||
groupSlug,
|
||||
restoreBackup,
|
||||
selected,
|
||||
...toRefs(state),
|
||||
backups,
|
||||
createBackup,
|
||||
deleteBackup,
|
||||
deleteTarget,
|
||||
setSelected,
|
||||
refreshBackups,
|
||||
backupsFileNameDownload,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: useI18n().t("sidebar.backups"),
|
||||
};
|
||||
},
|
||||
useHead({
|
||||
title: i18n.t("sidebar.backups"),
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -83,45 +83,42 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useAdminApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const api = useAdminApi();
|
||||
const i18n = useI18n();
|
||||
const api = useAdminApi();
|
||||
const i18n = useI18n();
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("admin.debug-openai-services"),
|
||||
});
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const response = ref("");
|
||||
const loading = ref(false);
|
||||
const response = ref("");
|
||||
|
||||
const uploadForm = ref<VForm | null>(null);
|
||||
const uploadedImage = ref<Blob | File>();
|
||||
const uploadedImageName = ref<string>("");
|
||||
const uploadedImagePreviewUrl = ref<string>();
|
||||
const uploadedImage = ref<Blob | File>();
|
||||
const uploadedImageName = ref<string>("");
|
||||
const uploadedImagePreviewUrl = ref<string>();
|
||||
|
||||
function uploadImage(fileObject: File) {
|
||||
function uploadImage(fileObject: File) {
|
||||
uploadedImage.value = fileObject;
|
||||
uploadedImageName.value = fileObject.name;
|
||||
uploadedImagePreviewUrl.value = URL.createObjectURL(fileObject);
|
||||
}
|
||||
}
|
||||
|
||||
function clearImage() {
|
||||
function clearImage() {
|
||||
uploadedImage.value = undefined;
|
||||
uploadedImageName.value = "";
|
||||
uploadedImagePreviewUrl.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function testOpenAI() {
|
||||
async function testOpenAI() {
|
||||
response.value = "";
|
||||
|
||||
loading.value = true;
|
||||
@@ -134,18 +131,5 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
response.value = data.response || (data.success ? "Test Successful" : "Test Failed");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
response,
|
||||
uploadForm,
|
||||
uploadedImage,
|
||||
uploadedImagePreviewUrl,
|
||||
uploadImage,
|
||||
clearImage,
|
||||
testOpenAI,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="d-flex align-center justify-center justify-md-start flex-wrap">
|
||||
<v-btn-toggle
|
||||
v-model="parser"
|
||||
v-model="state.parser"
|
||||
density="compact"
|
||||
mandatory="force"
|
||||
@change="processIngredient"
|
||||
@@ -38,7 +38,7 @@
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="ingredient"
|
||||
v-model="state.ingredient"
|
||||
:label="$t('admin.ingredient-text')"
|
||||
/>
|
||||
</v-card-text>
|
||||
@@ -55,9 +55,9 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-container>
|
||||
<v-container v-if="results">
|
||||
<v-container v-if="state.results">
|
||||
<div
|
||||
v-if="parser !== 'brute' && getConfidence('average')"
|
||||
v-if="state.parser !== 'brute' && getConfidence('average')"
|
||||
class="d-flex"
|
||||
>
|
||||
<v-chip
|
||||
@@ -111,7 +111,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import type { IngredientConfidence } from "~/lib/api/types/recipe";
|
||||
@@ -119,31 +119,29 @@ import type { Parser } from "~/lib/api/user/recipes/recipe";
|
||||
|
||||
type ConfidenceAttribute = "average" | "comment" | "name" | "unit" | "quantity" | "food";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
const api = useUserApi();
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
ingredient: "",
|
||||
results: false,
|
||||
parser: "nlp" as Parser,
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const i18n = useI18n();
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("admin.parser"),
|
||||
});
|
||||
});
|
||||
|
||||
const confidence = ref<IngredientConfidence>({});
|
||||
const confidence = ref<IngredientConfidence>({});
|
||||
|
||||
function getColor(attribute: ConfidenceAttribute) {
|
||||
function getColor(attribute: ConfidenceAttribute) {
|
||||
const percentage = getConfidence(attribute);
|
||||
if (percentage === undefined) return;
|
||||
|
||||
@@ -159,9 +157,9 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getConfidence(attribute: ConfidenceAttribute) {
|
||||
function getConfidence(attribute: ConfidenceAttribute) {
|
||||
if (!confidence.value) {
|
||||
return;
|
||||
}
|
||||
@@ -171,22 +169,22 @@ export default defineNuxtComponent({
|
||||
return `${(+property * 100).toFixed(0)}%`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const tryText = [
|
||||
const tryText = [
|
||||
"2 tbsp minced cilantro, leaves and stems",
|
||||
"1 large yellow onion, coarsely chopped",
|
||||
"1 1/2 tsp garam masala",
|
||||
"1 inch piece fresh ginger, (peeled and minced)",
|
||||
"2 cups mango chunks, (2 large mangoes) (fresh or frozen)",
|
||||
];
|
||||
];
|
||||
|
||||
function processTryText(str: string) {
|
||||
function processTryText(str: string) {
|
||||
state.ingredient = str;
|
||||
processIngredient();
|
||||
}
|
||||
}
|
||||
|
||||
async function processIngredient() {
|
||||
async function processIngredient() {
|
||||
if (state.ingredient === "") {
|
||||
return;
|
||||
}
|
||||
@@ -224,9 +222,9 @@ export default defineNuxtComponent({
|
||||
state.results = false;
|
||||
}
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
const properties = reactive({
|
||||
const properties = reactive({
|
||||
quantity: {
|
||||
subtitle: i18n.t("recipe.quantity"),
|
||||
value: "" as string | number,
|
||||
@@ -251,23 +249,9 @@ export default defineNuxtComponent({
|
||||
color: null,
|
||||
confidence: null,
|
||||
},
|
||||
});
|
||||
|
||||
const showConfidence = ref(false);
|
||||
|
||||
return {
|
||||
showConfidence,
|
||||
getColor,
|
||||
confidence,
|
||||
getConfidence,
|
||||
...toRefs(state),
|
||||
tryText,
|
||||
properties,
|
||||
processTryText,
|
||||
processIngredient,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const showConfidence = ref(false);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -96,41 +96,39 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useAdminApi } from "~/composables/api";
|
||||
import type { MaintenanceStorageDetails, MaintenanceSummary } from "~/lib/api/types/admin";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
storageDetails: false,
|
||||
storageDetailsLoading: false,
|
||||
fetchingInfo: false,
|
||||
actionLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
const i18n = useI18n();
|
||||
const adminApi = useAdminApi();
|
||||
const i18n = useI18n();
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("admin.maintenance.page-title"),
|
||||
});
|
||||
});
|
||||
|
||||
// ==========================================================================
|
||||
// General Info
|
||||
// ==========================================================================
|
||||
// General Info
|
||||
|
||||
const infoResults = ref<MaintenanceSummary>({
|
||||
const infoResults = ref<MaintenanceSummary>({
|
||||
dataDirSize: i18n.t("about.unknown-version"),
|
||||
cleanableDirs: 0,
|
||||
cleanableImages: 0,
|
||||
});
|
||||
});
|
||||
|
||||
async function getSummary() {
|
||||
async function getSummary() {
|
||||
state.fetchingInfo = true;
|
||||
const { data } = await adminApi.maintenance.getInfo();
|
||||
|
||||
@@ -141,9 +139,9 @@ export default defineNuxtComponent({
|
||||
};
|
||||
|
||||
state.fetchingInfo = false;
|
||||
}
|
||||
}
|
||||
|
||||
const info = computed(() => {
|
||||
const info = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: i18n.t("admin.maintenance.info-description-data-dir-size"),
|
||||
@@ -158,26 +156,26 @@ export default defineNuxtComponent({
|
||||
value: infoResults.value.cleanableImages,
|
||||
},
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
// ==========================================================================
|
||||
// Storage Details
|
||||
// ==========================================================================
|
||||
// Storage Details
|
||||
|
||||
const storageTitles: { [key: string]: string } = {
|
||||
const storageTitles: { [key: string]: string } = {
|
||||
tempDirSize: i18n.t("admin.maintenance.storage.title-temporary-directory") as string,
|
||||
backupsDirSize: i18n.t("admin.maintenance.storage.title-backups-directory") as string,
|
||||
groupsDirSize: i18n.t("admin.maintenance.storage.title-groups-directory") as string,
|
||||
recipesDirSize: i18n.t("admin.maintenance.storage.title-recipes-directory") as string,
|
||||
userDirSize: i18n.t("admin.maintenance.storage.title-user-directory") as string,
|
||||
};
|
||||
};
|
||||
|
||||
function storageDetailsText(key: string) {
|
||||
function storageDetailsText(key: string) {
|
||||
return storageTitles[key] ?? i18n.t("about.unknown-version");
|
||||
}
|
||||
}
|
||||
|
||||
const storageDetails = ref<MaintenanceStorageDetails | null>(null);
|
||||
const storageDetails = ref<MaintenanceStorageDetails | null>(null);
|
||||
|
||||
async function openDetails() {
|
||||
async function openDetails() {
|
||||
state.storageDetailsLoading = true;
|
||||
state.storageDetails = true;
|
||||
|
||||
@@ -188,30 +186,30 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
state.storageDetailsLoading = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Actions
|
||||
// ==========================================================================
|
||||
// Actions
|
||||
|
||||
async function handleCleanDirectories() {
|
||||
async function handleCleanDirectories() {
|
||||
state.actionLoading = true;
|
||||
await adminApi.maintenance.cleanRecipeFolders();
|
||||
state.actionLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCleanImages() {
|
||||
async function handleCleanImages() {
|
||||
state.actionLoading = true;
|
||||
await adminApi.maintenance.cleanImages();
|
||||
state.actionLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCleanTemp() {
|
||||
async function handleCleanTemp() {
|
||||
state.actionLoading = true;
|
||||
await adminApi.maintenance.cleanTemp();
|
||||
state.actionLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
const actions = [
|
||||
const actions = [
|
||||
{
|
||||
name: i18n.t("admin.maintenance.action-clean-directories-name"),
|
||||
handler: handleCleanDirectories,
|
||||
@@ -227,19 +225,7 @@ export default defineNuxtComponent({
|
||||
handler: handleCleanImages,
|
||||
subtitle: i18n.t("admin.maintenance.action-clean-images-description"),
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
storageDetailsText,
|
||||
openDetails,
|
||||
storageDetails,
|
||||
state,
|
||||
info,
|
||||
getSummary,
|
||||
actions,
|
||||
};
|
||||
},
|
||||
});
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -49,36 +49,31 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import GroupPreferencesEditor from "~/components/Domain/Group/GroupPreferencesEditor.vue";
|
||||
import { useAdminApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import type { VForm } from "vuetify/components";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
GroupPreferencesEditor,
|
||||
},
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
const route = useRoute();
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
|
||||
const i18n = useI18n();
|
||||
const groupId = computed(() => route.params.id as string);
|
||||
|
||||
const groupId = computed(() => route.params.id as string);
|
||||
// ==============================================
|
||||
// New User Form
|
||||
|
||||
// ==============================================
|
||||
// New User Form
|
||||
const refGroupEditForm = ref<VForm | null>(null);
|
||||
|
||||
const refGroupEditForm = ref<VForm | null>(null);
|
||||
const adminApi = useAdminApi();
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
const userError = ref(false);
|
||||
|
||||
const userError = ref(false);
|
||||
|
||||
const { data: group } = useLazyAsyncData(`get-household-${groupId.value}`, async () => {
|
||||
const { data: group } = useLazyAsyncData(`get-household-${groupId.value}`, async () => {
|
||||
if (!groupId.value) {
|
||||
return null;
|
||||
}
|
||||
@@ -89,9 +84,9 @@ export default defineNuxtComponent({
|
||||
userError.value = true;
|
||||
}
|
||||
return data;
|
||||
}, { watch: [groupId] });
|
||||
}, { watch: [groupId] });
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit() {
|
||||
if (!refGroupEditForm.value?.validate() || group.value === null) {
|
||||
return;
|
||||
}
|
||||
@@ -107,14 +102,5 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("settings.settings-update-failed"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
group,
|
||||
userError,
|
||||
refGroupEditForm,
|
||||
handleSubmit,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<BaseDialog
|
||||
v-model="createDialog"
|
||||
v-model="state.createDialog"
|
||||
:title="$t('group.create-group')"
|
||||
:icon="$globals.icons.group"
|
||||
can-submit
|
||||
@submit="createGroup(createGroupForm.data)"
|
||||
@submit="createGroup(state.createGroupForm.data)"
|
||||
>
|
||||
<template #activator />
|
||||
<v-card-text>
|
||||
<AutoForm
|
||||
v-model="createGroupForm.data"
|
||||
:update-mode="updateMode"
|
||||
:items="createGroupForm.items"
|
||||
v-model="state.createGroupForm.data"
|
||||
:update-mode="state.updateMode"
|
||||
:items="state.createGroupForm.items"
|
||||
/>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<BaseDialog
|
||||
v-model="confirmDialog"
|
||||
v-model="state.confirmDialog"
|
||||
:title="$t('general.confirm')"
|
||||
color="error"
|
||||
can-confirm
|
||||
@confirm="deleteGroup(deleteTarget)"
|
||||
@confirm="deleteGroup(state.deleteTarget)"
|
||||
>
|
||||
<template #activator />
|
||||
<v-card-text>
|
||||
@@ -43,14 +43,14 @@
|
||||
</v-toolbar>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:headers="state.headers"
|
||||
:items="groups || []"
|
||||
item-key="id"
|
||||
class="elevation-0"
|
||||
:items-per-page="-1"
|
||||
hide-default-footer
|
||||
disable-pagination
|
||||
:search="search"
|
||||
:search="state.search"
|
||||
@click:row="($event, { item }) => handleRowClick(item)"
|
||||
>
|
||||
<template #[`item.households`]="{ item }">
|
||||
@@ -73,8 +73,8 @@
|
||||
color="error"
|
||||
variant="text"
|
||||
@click.stop="
|
||||
confirmDialog = true;
|
||||
deleteTarget = item.id;
|
||||
state.confirmDialog = true;
|
||||
state.deleteTarget = item.id;
|
||||
"
|
||||
>
|
||||
<v-icon>
|
||||
@@ -92,28 +92,30 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { fieldTypes } from "~/composables/forms";
|
||||
import { useGroups } from "~/composables/use-groups";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { GroupInDB } from "~/lib/api/types/user";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const i18n = useI18n();
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
useHead({
|
||||
title: i18n.t("group.manage-groups"),
|
||||
});
|
||||
});
|
||||
|
||||
const { groups, refreshAllGroups, deleteGroup, createGroup } = useGroups();
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("group.manage-groups"),
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
const { groups, deleteGroup, createGroup } = useGroups();
|
||||
|
||||
const state = reactive({
|
||||
createDialog: false,
|
||||
confirmDialog: false,
|
||||
deleteTarget: "",
|
||||
@@ -144,23 +146,14 @@ export default defineNuxtComponent({
|
||||
name: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
function openDialog() {
|
||||
function openDialog() {
|
||||
state.createDialog = true;
|
||||
state.createGroupForm.data.name = "";
|
||||
}
|
||||
}
|
||||
|
||||
function handleRowClick(item: GroupInDB) {
|
||||
function handleRowClick(item: GroupInDB) {
|
||||
navigateTo(`/admin/manage/groups/${item.id}`);
|
||||
}
|
||||
|
||||
return { ...toRefs(state), groups, refreshAllGroups, deleteGroup, createGroup, openDialog, handleRowClick };
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: useI18n().t("group.manage-groups"),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -67,38 +67,34 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue";
|
||||
import { useGroups } from "~/composables/use-groups";
|
||||
import { useAdminApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { VForm } from "vuetify/components";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
HouseholdPreferencesEditor,
|
||||
},
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
|
||||
const { groups } = useGroups();
|
||||
const householdId = computed(() => route.params.id as string);
|
||||
const { groups } = useGroups();
|
||||
const householdId = computed(() => route.params.id as string);
|
||||
|
||||
// ==============================================
|
||||
// New User Form
|
||||
// ==============================================
|
||||
// New User Form
|
||||
|
||||
const refHouseholdEditForm = ref<VForm | null>(null);
|
||||
const refHouseholdEditForm = ref<VForm | null>(null);
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
const adminApi = useAdminApi();
|
||||
|
||||
const userError = ref(false);
|
||||
const userError = ref(false);
|
||||
|
||||
const { data: household } = useAsyncData(`get-household-${householdId.value}`, async () => {
|
||||
const { data: household } = useAsyncData(`get-household-${householdId.value}`, async () => {
|
||||
if (!householdId.value) {
|
||||
return null;
|
||||
}
|
||||
@@ -109,9 +105,9 @@ export default defineNuxtComponent({
|
||||
userError.value = true;
|
||||
}
|
||||
return data;
|
||||
}, { watch: [householdId] });
|
||||
}, { watch: [householdId] });
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit() {
|
||||
if (!refHouseholdEditForm.value?.validate() || household.value === null) {
|
||||
return;
|
||||
}
|
||||
@@ -124,16 +120,5 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("settings.settings-update-failed"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
groups,
|
||||
household,
|
||||
validators,
|
||||
userError,
|
||||
refHouseholdEditForm,
|
||||
handleSubmit,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useAdminApi, useUserApi } from "~/composables/api";
|
||||
import { useGroups } from "~/composables/use-groups";
|
||||
import { useAdminHouseholds } from "~/composables/use-households";
|
||||
@@ -146,40 +146,38 @@ import { useUserForm } from "~/composables/use-users";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
const { userForm } = useUserForm();
|
||||
const { groups } = useGroups();
|
||||
const { useHouseholdsInGroup } = useAdminHouseholds();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const { userForm } = useUserForm();
|
||||
const { groups } = useGroups();
|
||||
const { useHouseholdsInGroup } = useAdminHouseholds();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const userId = route.params.id as string;
|
||||
const userId = route.params.id as string;
|
||||
|
||||
// ==============================================
|
||||
// New User Form
|
||||
// ==============================================
|
||||
// New User Form
|
||||
|
||||
const refNewUserForm = ref<VForm | null>(null);
|
||||
const refNewUserForm = ref<VForm | null>(null);
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
const adminApi = useAdminApi();
|
||||
|
||||
const user = ref<UserOut | null>(null);
|
||||
const households = useHouseholdsInGroup(computed(() => user.value?.groupId || ""));
|
||||
const user = ref<UserOut | null>(null);
|
||||
const households = useHouseholdsInGroup(computed(() => user.value?.groupId || ""));
|
||||
|
||||
const disabledFields = computed(() => {
|
||||
const disabledFields = computed(() => {
|
||||
return user.value?.authMethod !== "Mealie" ? ["admin"] : [];
|
||||
});
|
||||
});
|
||||
|
||||
const userError = ref(false);
|
||||
const userError = ref(false);
|
||||
|
||||
const resetUrl = ref<string | null>(null);
|
||||
const generatingToken = ref(false);
|
||||
const resetUrl = ref<string | null>(null);
|
||||
const generatingToken = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
const { data, error } = await adminApi.users.getOne(userId);
|
||||
|
||||
if (error?.response?.status === 404) {
|
||||
@@ -190,9 +188,9 @@ export default defineNuxtComponent({
|
||||
if (data) {
|
||||
user.value = data;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit() {
|
||||
if (!refNewUserForm.value?.validate() || user.value === null) return;
|
||||
|
||||
const { response, data } = await adminApi.users.updateOne(user.value.id, user.value);
|
||||
@@ -200,9 +198,9 @@ export default defineNuxtComponent({
|
||||
if (response?.status === 200 && data) {
|
||||
user.value = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePasswordReset() {
|
||||
async function handlePasswordReset() {
|
||||
if (user.value === null) return;
|
||||
generatingToken.value = true;
|
||||
|
||||
@@ -214,10 +212,10 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
generatingToken.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const userApi = useUserApi();
|
||||
async function sendResetEmail() {
|
||||
const userApi = useUserApi();
|
||||
async function sendResetEmail() {
|
||||
if (!user.value?.email) return;
|
||||
const { response } = await userApi.email.sendForgotPassword({ email: user.value.email });
|
||||
if (response && response.status === 200) {
|
||||
@@ -226,23 +224,5 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("profile.error-sending-email"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
disabledFields,
|
||||
userError,
|
||||
userForm,
|
||||
refNewUserForm,
|
||||
handleSubmit,
|
||||
groups,
|
||||
households,
|
||||
validators,
|
||||
handlePasswordReset,
|
||||
resetUrl,
|
||||
generatingToken,
|
||||
sendResetEmail,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<v-container fluid>
|
||||
<UserInviteDialog v-model="inviteDialog" />
|
||||
<BaseDialog
|
||||
v-model="deleteDialog"
|
||||
v-model="state.deleteDialog"
|
||||
:title="$t('general.confirm')"
|
||||
color="error"
|
||||
can-confirm
|
||||
@confirm="deleteUser(deleteTargetId)"
|
||||
@confirm="deleteUser(state.deleteTargetId)"
|
||||
>
|
||||
<template #activator />
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
:items-per-page="-1"
|
||||
hide-default-footer
|
||||
disable-pagination
|
||||
:search="search"
|
||||
:search="state.search"
|
||||
@click:row="($event, { item }) => handleRowClick(item)"
|
||||
>
|
||||
<template #[`item.admin`]="{ item }">
|
||||
@@ -78,8 +78,8 @@
|
||||
color="error"
|
||||
variant="text"
|
||||
@click.stop="
|
||||
deleteDialog = true;
|
||||
deleteTargetId = item.id;
|
||||
state.deleteDialog = true;
|
||||
state.deleteTargetId = item.id;
|
||||
"
|
||||
>
|
||||
<v-icon>
|
||||
@@ -93,74 +93,72 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useAdminApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { useUser, useAllUsers } from "~/composables/use-user";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
import UserInviteDialog from "~/components/Domain/User/UserInviteDialog.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
UserInviteDialog,
|
||||
},
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
const i18n = useI18n();
|
||||
|
||||
const api = useAdminApi();
|
||||
const refUserDialog = ref();
|
||||
const inviteDialog = ref();
|
||||
const auth = useMealieAuth();
|
||||
useHead({
|
||||
title: i18n.t("sidebar.manage-users"),
|
||||
});
|
||||
|
||||
const user = computed(() => auth.user.value);
|
||||
const api = useAdminApi();
|
||||
const inviteDialog = ref();
|
||||
const auth = useMealieAuth();
|
||||
|
||||
const i18n = useI18n();
|
||||
const { $globals } = useNuxtApp();
|
||||
const user = computed(() => auth.user.value);
|
||||
|
||||
const router = useRouter();
|
||||
const { $globals } = useNuxtApp();
|
||||
|
||||
const isUserOwnAccount = computed(() => {
|
||||
const router = useRouter();
|
||||
|
||||
const isUserOwnAccount = computed(() => {
|
||||
return state.deleteTargetId === user.value?.id;
|
||||
});
|
||||
});
|
||||
|
||||
const ACTIONS_OPTIONS = [
|
||||
const ACTIONS_OPTIONS = [
|
||||
{
|
||||
text: i18n.t("user.reset-locked-users"),
|
||||
icon: $globals.icons.lock,
|
||||
event: "unlock-all-users",
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
deleteDialog: false,
|
||||
deleteTargetId: "",
|
||||
search: "",
|
||||
groups: [],
|
||||
households: [],
|
||||
sendTo: "",
|
||||
});
|
||||
});
|
||||
|
||||
const { users, refreshAllUsers } = useAllUsers();
|
||||
const { loading, deleteUser: deleteUserMixin } = useUser(refreshAllUsers);
|
||||
const { users, refreshAllUsers } = useAllUsers();
|
||||
const { deleteUser: deleteUserMixin } = useUser(refreshAllUsers);
|
||||
|
||||
function deleteUser(id: string) {
|
||||
function deleteUser(id: string) {
|
||||
deleteUserMixin(id);
|
||||
|
||||
if (isUserOwnAccount.value) {
|
||||
auth.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRowClick(item: UserOut) {
|
||||
function handleRowClick(item: UserOut) {
|
||||
router.push(`/admin/manage/users/${item.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Constants / Non-reactive
|
||||
// ==========================================================
|
||||
// Constants / Non-reactive
|
||||
|
||||
const headers = [
|
||||
const headers = [
|
||||
{
|
||||
title: i18n.t("user.user-id"),
|
||||
align: "start",
|
||||
@@ -174,9 +172,9 @@ export default defineNuxtComponent({
|
||||
{ title: i18n.t("user.auth-method"), value: "authMethod" },
|
||||
{ title: i18n.t("user.admin"), value: "admin" },
|
||||
{ title: i18n.t("general.delete"), value: "actions", sortable: false, align: "center" },
|
||||
];
|
||||
];
|
||||
|
||||
async function unlockAllUsers(): Promise<void> {
|
||||
async function unlockAllUsers(): Promise<void> {
|
||||
const { data } = await api.users.unlockAllUsers(true);
|
||||
|
||||
if (data) {
|
||||
@@ -185,31 +183,9 @@ export default defineNuxtComponent({
|
||||
alert.success(`${unlocked} user(s) unlocked`);
|
||||
refreshAllUsers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("sidebar.manage-users"),
|
||||
});
|
||||
|
||||
return {
|
||||
isUserOwnAccount,
|
||||
unlockAllUsers,
|
||||
...toRefs(state),
|
||||
headers,
|
||||
deleteUser,
|
||||
loading,
|
||||
refUserDialog,
|
||||
inviteDialog,
|
||||
users,
|
||||
user,
|
||||
handleRowClick,
|
||||
ACTIONS_OPTIONS,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: useI18n().t("sidebar.manage-users"),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<v-text-field
|
||||
v-model="address"
|
||||
v-model="state.address"
|
||||
class="mr-4"
|
||||
:label="$t('user.email')"
|
||||
:rules="[validators.email]"
|
||||
@@ -135,7 +135,7 @@
|
||||
color="info"
|
||||
variant="elevated"
|
||||
:disabled="!appConfig.emailReady || !validEmail"
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
class="opacity-100"
|
||||
@click="testEmail"
|
||||
>
|
||||
@@ -144,12 +144,12 @@
|
||||
</template>
|
||||
{{ $t("general.test") }}
|
||||
</BaseButton>
|
||||
<template v-if="tested">
|
||||
<template v-if="state.tested">
|
||||
<v-divider class="my-x mt-6" />
|
||||
<v-card-text class="px-0">
|
||||
<h4> {{ $t("settings.email-test-results") }}</h4>
|
||||
<span class="pl-4">
|
||||
{{ success ? $t('settings.succeeded') : $t('settings.failed') }}
|
||||
{{ state.success ? $t('settings.succeeded') : $t('settings.failed') }}
|
||||
</span>
|
||||
</v-card-text>
|
||||
</template>
|
||||
@@ -226,7 +226,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { TranslateResult } from "vue-i18n";
|
||||
import { useAdminApi, useUserApi } from "~/composables/api";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
@@ -234,12 +234,6 @@ import { useAsyncKey } from "~/composables/use-utils";
|
||||
import type { CheckAppConfig } from "~/lib/api/types/admin";
|
||||
import AppLoader from "~/components/global/AppLoader.vue";
|
||||
|
||||
enum DockerVolumeState {
|
||||
Unknown = "unknown",
|
||||
Success = "success",
|
||||
Error = "error",
|
||||
}
|
||||
|
||||
interface SimpleCheck {
|
||||
id: string;
|
||||
text: TranslateResult;
|
||||
@@ -254,36 +248,33 @@ interface CheckApp extends CheckAppConfig {
|
||||
isSiteSecure?: boolean;
|
||||
}
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { AppLoader },
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
// For some reason the layout is not set automatically, so we set it here,
|
||||
// even though it's defined above in the page meta.
|
||||
onMounted(() => {
|
||||
// For some reason the layout is not set automatically, so we set it here,
|
||||
// even though it's defined above in the page meta.
|
||||
onMounted(() => {
|
||||
setPageLayout("admin");
|
||||
});
|
||||
});
|
||||
|
||||
const { $globals } = useNuxtApp();
|
||||
const i18n = useI18n();
|
||||
const { $globals } = useNuxtApp();
|
||||
const i18n = useI18n();
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
address: "",
|
||||
success: false,
|
||||
error: "",
|
||||
tested: false,
|
||||
});
|
||||
});
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("settings.site-settings"),
|
||||
});
|
||||
});
|
||||
|
||||
const appConfig = ref<CheckApp>({
|
||||
const appConfig = ref<CheckApp>({
|
||||
emailReady: true,
|
||||
baseUrlSet: true,
|
||||
isSiteSecure: true,
|
||||
@@ -291,20 +282,20 @@ export default defineNuxtComponent({
|
||||
ldapReady: false,
|
||||
oidcReady: false,
|
||||
enableOpenai: false,
|
||||
});
|
||||
function isLocalHostOrHttps() {
|
||||
});
|
||||
function isLocalHostOrHttps() {
|
||||
return window.location.hostname === "localhost" || window.location.protocol === "https:";
|
||||
}
|
||||
const api = useUserApi();
|
||||
const adminApi = useAdminApi();
|
||||
onMounted(async () => {
|
||||
}
|
||||
const api = useUserApi();
|
||||
const adminApi = useAdminApi();
|
||||
onMounted(async () => {
|
||||
const { data } = await adminApi.about.checkApp();
|
||||
if (data) {
|
||||
appConfig.value = { ...data, isSiteSecure: false };
|
||||
}
|
||||
appConfig.value.isSiteSecure = isLocalHostOrHttps();
|
||||
});
|
||||
const simpleChecks = computed<SimpleCheck[]>(() => {
|
||||
});
|
||||
const simpleChecks = computed<SimpleCheck[]>(() => {
|
||||
const goodIcon = $globals.icons.checkboxMarkedCircle;
|
||||
const badIcon = $globals.icons.alert;
|
||||
const warningIcon = $globals.icons.alertCircle;
|
||||
@@ -368,8 +359,8 @@ export default defineNuxtComponent({
|
||||
},
|
||||
];
|
||||
return data;
|
||||
});
|
||||
async function testEmail() {
|
||||
});
|
||||
async function testEmail() {
|
||||
state.loading = true;
|
||||
state.tested = false;
|
||||
const { data } = await api.email.test({ email: state.address });
|
||||
@@ -384,8 +375,8 @@ export default defineNuxtComponent({
|
||||
}
|
||||
state.loading = false;
|
||||
state.tested = true;
|
||||
}
|
||||
const validEmail = computed(() => {
|
||||
}
|
||||
const validEmail = computed(() => {
|
||||
if (state.address === "") {
|
||||
return false;
|
||||
}
|
||||
@@ -395,14 +386,14 @@ export default defineNuxtComponent({
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// ============================================================
|
||||
// General About Info
|
||||
const rawAppInfo = ref({
|
||||
});
|
||||
// ============================================================
|
||||
// General About Info
|
||||
const rawAppInfo = ref({
|
||||
version: "null",
|
||||
versionLatest: "null",
|
||||
});
|
||||
function getAppInfo() {
|
||||
});
|
||||
function getAppInfo() {
|
||||
const { data: statistics } = useAsyncData(useAsyncKey(), async () => {
|
||||
const { data } = await adminApi.about.about();
|
||||
if (data) {
|
||||
@@ -473,10 +464,10 @@ export default defineNuxtComponent({
|
||||
return data;
|
||||
});
|
||||
return statistics;
|
||||
}
|
||||
const appInfo = getAppInfo();
|
||||
const bugReportDialog = ref(false);
|
||||
const bugReportText = computed(() => {
|
||||
}
|
||||
const appInfo = getAppInfo();
|
||||
const bugReportDialog = ref(false);
|
||||
const bugReportText = computed(() => {
|
||||
const ignore = {
|
||||
[i18n.t("about.database-url")]: true,
|
||||
[i18n.t("about.default-group")]: true,
|
||||
@@ -503,20 +494,6 @@ export default defineNuxtComponent({
|
||||
});
|
||||
text += `${i18n.t("settings.email-configured")}: ${appConfig.value.emailReady ? i18n.t("general.yes") : i18n.t("general.no")}\n`;
|
||||
return text;
|
||||
});
|
||||
return {
|
||||
bugReportDialog,
|
||||
bugReportText,
|
||||
DockerVolumeState,
|
||||
simpleChecks,
|
||||
appConfig,
|
||||
validEmail,
|
||||
validators,
|
||||
...toRefs(state),
|
||||
testEmail,
|
||||
appInfo,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="requestLink()">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
v-model="state.email"
|
||||
:prepend-inner-icon="$globals.icons.email"
|
||||
variant="solo-filled"
|
||||
flat
|
||||
@@ -31,7 +31,7 @@
|
||||
<v-card-actions class="justify-center">
|
||||
<div class="max-button">
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
color="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
@@ -60,32 +60,30 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "basic",
|
||||
});
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
email: "",
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const i18n = useI18n();
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("user.login"),
|
||||
});
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
const api = useUserApi();
|
||||
|
||||
async function requestLink() {
|
||||
async function requestLink() {
|
||||
state.loading = true;
|
||||
// TODO: Fix Response to send meaningful error
|
||||
const { response } = await api.email.sendForgotPassword({ email: state.email });
|
||||
@@ -100,14 +98,7 @@ export default defineNuxtComponent({
|
||||
state.error = true;
|
||||
alert.error(i18n.t("profile.error-sending-email"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
requestLink,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
<CookbookPage />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import CookbookPage from "@/components/Domain/Cookbook/CookbookPage.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { CookbookPage },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import { useCookbookStore } from "~/composables/store/use-cookbook-store";
|
||||
import { useHouseholdSelf } from "@/composables/use-households";
|
||||
@@ -143,28 +143,28 @@ import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
||||
import type { CreateCookBook, ReadCookBook } from "~/lib/api/types/cookbook";
|
||||
import { useCookbookPreferences } from "~/composables/use-users/preferences";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { CookbookEditor, VueDraggable },
|
||||
definePageMeta({
|
||||
middleware: ["group-only"],
|
||||
setup() {
|
||||
const dialogStates = reactive({
|
||||
});
|
||||
|
||||
const dialogStates = reactive({
|
||||
create: false,
|
||||
delete: false,
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const i18n = useI18n();
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("cookbook.cookbooks"),
|
||||
});
|
||||
});
|
||||
|
||||
const auth = useMealieAuth();
|
||||
const { store: allCookbooks, actions, updateAll } = useCookbookStore();
|
||||
const auth = useMealieAuth();
|
||||
const { store: allCookbooks, actions, updateAll } = useCookbookStore();
|
||||
|
||||
// Make a local reactive copy of myCookbooks
|
||||
const myCookbooks = ref<ReadCookBook[]>([]);
|
||||
watch(
|
||||
// Make a local reactive copy of myCookbooks
|
||||
const myCookbooks = ref<ReadCookBook[]>([]);
|
||||
watch(
|
||||
allCookbooks,
|
||||
(cookbooks) => {
|
||||
myCookbooks.value
|
||||
@@ -173,15 +173,15 @@ export default defineNuxtComponent({
|
||||
).sort((a, b) => a.position > b.position) ?? [];
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
);
|
||||
|
||||
const { household } = useHouseholdSelf();
|
||||
const cookbookPreferences = useCookbookPreferences();
|
||||
const { household } = useHouseholdSelf();
|
||||
const cookbookPreferences = useCookbookPreferences();
|
||||
|
||||
// create
|
||||
const createTargetKey = ref(0);
|
||||
const createTarget = ref<ReadCookBook | null>(null);
|
||||
async function createCookbook() {
|
||||
// create
|
||||
const createTargetKey = ref(0);
|
||||
const createTarget = ref<ReadCookBook | null>(null);
|
||||
async function createCookbook() {
|
||||
const name = i18n.t("cookbook.household-cookbook-name", [
|
||||
household.value?.name || "",
|
||||
String((myCookbooks.value?.length ?? 0) + 1),
|
||||
@@ -198,15 +198,15 @@ export default defineNuxtComponent({
|
||||
createTargetKey.value++;
|
||||
});
|
||||
dialogStates.create = true;
|
||||
}
|
||||
}
|
||||
|
||||
// delete
|
||||
const deleteTarget = ref<ReadCookBook | null>(null);
|
||||
function deleteEventHandler(item: ReadCookBook) {
|
||||
// delete
|
||||
const deleteTarget = ref<ReadCookBook | null>(null);
|
||||
function deleteEventHandler(item: ReadCookBook) {
|
||||
deleteTarget.value = item;
|
||||
dialogStates.delete = true;
|
||||
}
|
||||
async function deleteCookbook() {
|
||||
}
|
||||
async function deleteCookbook() {
|
||||
if (!deleteTarget.value) {
|
||||
return;
|
||||
}
|
||||
@@ -214,9 +214,9 @@ export default defineNuxtComponent({
|
||||
myCookbooks.value = myCookbooks.value.filter(c => c.id !== deleteTarget.value?.id);
|
||||
dialogStates.delete = false;
|
||||
deleteTarget.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCreateTarget() {
|
||||
async function deleteCreateTarget() {
|
||||
if (!createTarget.value?.id) {
|
||||
return;
|
||||
}
|
||||
@@ -224,40 +224,18 @@ export default defineNuxtComponent({
|
||||
myCookbooks.value = myCookbooks.value.filter(c => c.id !== createTarget.value?.id);
|
||||
dialogStates.create = false;
|
||||
createTarget.value = null;
|
||||
}
|
||||
function handleUnmount() {
|
||||
}
|
||||
function handleUnmount() {
|
||||
if (!createTarget.value?.id || createTarget.value.queryFilterString) {
|
||||
return;
|
||||
}
|
||||
deleteCreateTarget();
|
||||
}
|
||||
onMounted(() => {
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener("beforeunload", handleUnmount);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
handleUnmount();
|
||||
window.removeEventListener("beforeunload", handleUnmount);
|
||||
});
|
||||
|
||||
return {
|
||||
myCookbooks,
|
||||
cookbookPreferences,
|
||||
actions,
|
||||
dialogStates,
|
||||
// create
|
||||
createTargetKey,
|
||||
createTarget,
|
||||
createCookbook,
|
||||
|
||||
// update
|
||||
updateAll,
|
||||
|
||||
// delete
|
||||
deleteTarget,
|
||||
deleteEventHandler,
|
||||
deleteCookbook,
|
||||
deleteCreateTarget,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import RecipeExplorerPage from "~/components/Domain/Recipe/RecipeExplorerPage/RecipeExplorerPage.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { RecipeExplorerPage },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -42,23 +42,23 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { MenuItem } from "~/components/global/BaseOverflowButton.vue";
|
||||
import AdvancedOnly from "~/components/global/AdvancedOnly.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { AdvancedOnly },
|
||||
definePageMeta({
|
||||
middleware: ["group-only"],
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $appInfo, $globals } = useNuxtApp();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $appInfo, $globals } = useNuxtApp();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("general.create"),
|
||||
});
|
||||
});
|
||||
|
||||
const subpages = computed<MenuItem[]>(() => [
|
||||
const subpages = computed<MenuItem[]>(() => [
|
||||
{
|
||||
icon: $globals.icons.link,
|
||||
text: i18n.t("recipe.import-with-url"),
|
||||
@@ -95,26 +95,18 @@ export default defineNuxtComponent({
|
||||
text: i18n.t("recipe.debug-scraper"),
|
||||
value: "debug",
|
||||
},
|
||||
]);
|
||||
]);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const groupSlug = computed(() => route.params.groupSlug || auth.user.value?.groupSlug || "");
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const groupSlug = computed(() => route.params.groupSlug || auth.user.value?.groupSlug || "");
|
||||
|
||||
const subpage = computed({
|
||||
const subpage = computed({
|
||||
set(subpage: string) {
|
||||
router.push({ path: `/g/${groupSlug.value}/r/create/${subpage}`, query: route.query });
|
||||
},
|
||||
get() {
|
||||
return route.path.split("/").pop() ?? "url";
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
groupSlug,
|
||||
subpages,
|
||||
subpage,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<template v-if="showCatTags">
|
||||
<template v-if="state.showCatTags">
|
||||
<v-col
|
||||
cols="12"
|
||||
xs="12"
|
||||
@@ -115,14 +115,14 @@
|
||||
{{ $t('general.new') }}
|
||||
</BaseButton>
|
||||
<RecipeDialogBulkAdd
|
||||
v-model="bulkDialog"
|
||||
v-model="state.bulkDialog"
|
||||
class="mr-1 mr-sm-0 mb-1"
|
||||
@bulk-data="assignUrls"
|
||||
/>
|
||||
</v-card-actions>
|
||||
<div class="px-0">
|
||||
<v-checkbox
|
||||
v-model="showCatTags"
|
||||
v-model="state.showCatTags"
|
||||
hide-details
|
||||
:label="$t('recipe.set-categories-and-tags')"
|
||||
/>
|
||||
@@ -154,7 +154,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { whenever } from "@vueuse/shared";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
@@ -162,30 +162,25 @@ import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerS
|
||||
import type { ReportSummary } from "~/lib/api/types/reports";
|
||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { RecipeOrganizerSelector, RecipeDialogBulkAdd },
|
||||
setup() {
|
||||
const state = reactive({
|
||||
error: false,
|
||||
loading: false,
|
||||
const state = reactive({
|
||||
showCatTags: false,
|
||||
bulkDialog: false,
|
||||
});
|
||||
});
|
||||
|
||||
whenever(
|
||||
whenever(
|
||||
() => !state.showCatTags,
|
||||
() => {
|
||||
console.log("showCatTags changed");
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
|
||||
const bulkUrls = ref([{ url: "", categories: [], tags: [] }]);
|
||||
const lockBulkImport = ref(false);
|
||||
const bulkUrls = ref([{ url: "", categories: [], tags: [] }]);
|
||||
const lockBulkImport = ref(false);
|
||||
|
||||
async function bulkCreate() {
|
||||
async function bulkCreate() {
|
||||
if (bulkUrls.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -201,19 +196,19 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
fetchReports();
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Reports
|
||||
// =========================================================
|
||||
// Reports
|
||||
|
||||
const reports = ref<ReportSummary[]>([]);
|
||||
const reports = ref<ReportSummary[]>([]);
|
||||
|
||||
async function fetchReports() {
|
||||
async function fetchReports() {
|
||||
const { data } = await api.groupReports.getAll("bulk_import");
|
||||
reports.value = data ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteReport(id: string) {
|
||||
async function deleteReport(id: string) {
|
||||
console.log(id);
|
||||
const { response } = await api.groupReports.deleteOne(id);
|
||||
|
||||
@@ -223,23 +218,11 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("recipe.report-deletion-failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchReports();
|
||||
fetchReports();
|
||||
|
||||
function assignUrls(urls: string[]) {
|
||||
function assignUrls(urls: string[]) {
|
||||
bulkUrls.value = urls.map(url => ({ url, categories: [], tags: [] }));
|
||||
}
|
||||
|
||||
return {
|
||||
assignUrls,
|
||||
reports,
|
||||
deleteReport,
|
||||
bulkCreate,
|
||||
bulkUrls,
|
||||
lockBulkImport,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<v-card-text v-if="$appInfo.enableOpenai">
|
||||
{{ $t('recipe.recipe-debugger-use-openai-description') }}
|
||||
<v-checkbox
|
||||
v-model="useOpenAI"
|
||||
v-model="state.useOpenAI"
|
||||
:label="$t('recipe.use-openai')"
|
||||
/>
|
||||
</v-card-text>
|
||||
@@ -40,7 +40,7 @@
|
||||
block
|
||||
type="submit"
|
||||
color="info"
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
>
|
||||
<template #icon>
|
||||
{{ $globals.icons.robot }}
|
||||
@@ -67,24 +67,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { Recipe } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
error: false,
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
useOpenAI: false,
|
||||
});
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const recipeUrl = computed({
|
||||
const recipeUrl = computed({
|
||||
set(recipe_import_url: string | null) {
|
||||
if (recipe_import_url !== null) {
|
||||
recipe_import_url = recipe_import_url.trim();
|
||||
@@ -94,13 +91,13 @@ export default defineNuxtComponent({
|
||||
get() {
|
||||
return route.query.recipe_import_url as string | null;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const debugTreeView = ref(false);
|
||||
const debugTreeView = ref(false);
|
||||
|
||||
const debugData = ref<Recipe | null>(null);
|
||||
const debugData = ref<Recipe | null>(null);
|
||||
|
||||
async function debugUrl(url: string | null) {
|
||||
async function debugUrl(url: string | null) {
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
@@ -111,16 +108,5 @@ export default defineNuxtComponent({
|
||||
|
||||
state.loading = false;
|
||||
debugData.value = data;
|
||||
}
|
||||
|
||||
return {
|
||||
recipeUrl,
|
||||
debugTreeView,
|
||||
debugUrl,
|
||||
debugData,
|
||||
...toRefs(state),
|
||||
validators,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
rounded
|
||||
block
|
||||
type="submit"
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
/>
|
||||
</div>
|
||||
<v-card-text class="py-2">
|
||||
@@ -103,7 +103,7 @@
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { useTagStore } from "~/composables/store/use-tag-store";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
@@ -111,30 +111,28 @@ import { useNewRecipeOptions } from "~/composables/use-new-recipe-options";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
error: false,
|
||||
loading: false,
|
||||
isEditJSON: false,
|
||||
});
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
const domUrlForm = ref<VForm | null>(null);
|
||||
});
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
const domUrlForm = ref<VForm | null>(null);
|
||||
|
||||
const api = useUserApi();
|
||||
const tags = useTagStore();
|
||||
const api = useUserApi();
|
||||
const tags = useTagStore();
|
||||
|
||||
const {
|
||||
const {
|
||||
importKeywordsAsTags,
|
||||
importCategories,
|
||||
stayInEditMode,
|
||||
parseRecipe,
|
||||
navigateToRecipe,
|
||||
} = useNewRecipeOptions();
|
||||
} = useNewRecipeOptions();
|
||||
|
||||
function handleResponse(response: AxiosResponse<string> | null, refreshTags = false) {
|
||||
function handleResponse(response: AxiosResponse<string> | null, refreshTags = false) {
|
||||
if (response?.status !== 201) {
|
||||
state.error = true;
|
||||
state.loading = false;
|
||||
@@ -145,12 +143,12 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
navigateToRecipe(response.data, groupSlug.value, `/g/${groupSlug.value}/r/create/html`);
|
||||
}
|
||||
}
|
||||
|
||||
const newRecipeData = ref<string | object | null>(null);
|
||||
const newRecipeUrl = ref<string | null>(null);
|
||||
const newRecipeData = ref<string | object | null>(null);
|
||||
const newRecipeUrl = ref<string | null>(null);
|
||||
|
||||
function handleIsEditJson() {
|
||||
function handleIsEditJson() {
|
||||
if (state.isEditJSON) {
|
||||
if (newRecipeData.value) {
|
||||
try {
|
||||
@@ -170,11 +168,11 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
newRecipeData.value = null;
|
||||
}
|
||||
}
|
||||
handleIsEditJson();
|
||||
}
|
||||
handleIsEditJson();
|
||||
|
||||
const createStatus = ref<string | null>(null);
|
||||
async function createFromHtmlOrJson(htmlOrJsonData: string | object | null, importKeywordsAsTags: boolean, importCategories: boolean, url: string | null = null) {
|
||||
const createStatus = ref<string | null>(null);
|
||||
async function createFromHtmlOrJson(htmlOrJsonData: string | object | null, importKeywordsAsTags: boolean, importCategories: boolean, url: string | null = null) {
|
||||
if (!htmlOrJsonData) {
|
||||
return;
|
||||
}
|
||||
@@ -202,22 +200,5 @@ export default defineNuxtComponent({
|
||||
);
|
||||
createStatus.value = null;
|
||||
handleResponse(response, importKeywordsAsTags);
|
||||
}
|
||||
|
||||
return {
|
||||
domUrlForm,
|
||||
importKeywordsAsTags,
|
||||
stayInEditMode,
|
||||
parseRecipe,
|
||||
importCategories,
|
||||
newRecipeData,
|
||||
newRecipeUrl,
|
||||
handleIsEditJson,
|
||||
createStatus,
|
||||
createFromHtmlOrJson,
|
||||
...toRefs(state),
|
||||
validators,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
:img="imageUrl"
|
||||
cropper-height="100%"
|
||||
cropper-width="100%"
|
||||
:submitted="loading"
|
||||
:submitted="state.loading"
|
||||
class="mt-4 mb-2"
|
||||
@save="(croppedImage) => updateUploadedImage(index, croppedImage)"
|
||||
@delete="clearImage(index)"
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<v-btn
|
||||
v-if="uploadedImages.length > 1"
|
||||
:disabled="loading || index === 0"
|
||||
:disabled="state.loading || index === 0"
|
||||
color="primary"
|
||||
@click="() => setCoverImage(index)"
|
||||
>
|
||||
@@ -66,7 +66,7 @@
|
||||
color="primary"
|
||||
hide-details
|
||||
:label="$t('recipe.should-translate-description')"
|
||||
:disabled="loading"
|
||||
:disabled="state.loading"
|
||||
/>
|
||||
<v-checkbox
|
||||
v-if="uploadedImages.length"
|
||||
@@ -74,15 +74,15 @@
|
||||
color="primary"
|
||||
hide-details
|
||||
:label="$t('recipe.parse-recipe-ingredients-after-import')"
|
||||
:disabled="loading"
|
||||
:disabled="state.loading"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions v-if="uploadedImages.length">
|
||||
<div class="w-100 d-flex flex-column align-center">
|
||||
<p style="width: 250px">
|
||||
<BaseButton rounded block type="submit" :loading="loading" />
|
||||
<BaseButton rounded block type="submit" :loading="state.loading" />
|
||||
</p>
|
||||
<p v-if="loading" class="mb-0">
|
||||
<p v-if="state.loading" class="mb-0">
|
||||
{{
|
||||
uploadedImages.length > 1
|
||||
? $t("recipe.please-wait-images-processing")
|
||||
@@ -96,50 +96,48 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { useNewRecipeOptions } from "~/composables/use-new-recipe-options";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug || "");
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug || "");
|
||||
|
||||
const domUrlForm = ref<VForm | null>(null);
|
||||
const uploadedImages = ref<(Blob | File)[]>([]);
|
||||
const uploadedImageNames = ref<string[]>([]);
|
||||
const uploadedImagesPreviewUrls = ref<string[]>([]);
|
||||
const shouldTranslate = ref(true);
|
||||
const domUrlForm = ref<VForm | null>(null);
|
||||
const uploadedImages = ref<(Blob | File)[]>([]);
|
||||
const uploadedImageNames = ref<string[]>([]);
|
||||
const uploadedImagesPreviewUrls = ref<string[]>([]);
|
||||
const shouldTranslate = ref(true);
|
||||
|
||||
const { parseRecipe, navigateToRecipe } = useNewRecipeOptions();
|
||||
const { parseRecipe, navigateToRecipe } = useNewRecipeOptions();
|
||||
|
||||
function uploadImages(files: File[]) {
|
||||
function uploadImages(files: File[]) {
|
||||
uploadedImages.value = [...uploadedImages.value, ...files];
|
||||
uploadedImageNames.value = [...uploadedImageNames.value, ...files.map(file => file.name)];
|
||||
uploadedImagesPreviewUrls.value = [
|
||||
...uploadedImagesPreviewUrls.value,
|
||||
...files.map(file => URL.createObjectURL(file)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function clearImage(index: number) {
|
||||
function clearImage(index: number) {
|
||||
// Revoke _before_ splicing
|
||||
URL.revokeObjectURL(uploadedImagesPreviewUrls.value[index]);
|
||||
|
||||
uploadedImages.value.splice(index, 1);
|
||||
uploadedImageNames.value.splice(index, 1);
|
||||
uploadedImagesPreviewUrls.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
async function createRecipe() {
|
||||
async function createRecipe() {
|
||||
if (uploadedImages.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -155,14 +153,14 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
navigateToRecipe(data, groupSlug.value, `/g/${groupSlug.value}/r/create/image`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateUploadedImage(index: number, croppedImage: Blob) {
|
||||
function updateUploadedImage(index: number, croppedImage: Blob) {
|
||||
uploadedImages.value[index] = croppedImage;
|
||||
uploadedImagesPreviewUrls.value[index] = URL.createObjectURL(croppedImage);
|
||||
}
|
||||
}
|
||||
|
||||
function swapItem(array: any[], i: number, j: number) {
|
||||
function swapItem(array: any[], i: number, j: number) {
|
||||
if (i < 0 || j < 0 || i >= array.length || j >= array.length) {
|
||||
return;
|
||||
}
|
||||
@@ -170,37 +168,21 @@ export default defineNuxtComponent({
|
||||
const temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
function swapImages(i: number, j: number) {
|
||||
function swapImages(i: number, j: number) {
|
||||
swapItem(uploadedImages.value, i, j);
|
||||
swapItem(uploadedImageNames.value, i, j);
|
||||
swapItem(uploadedImagesPreviewUrls.value, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
// Put the intended cover image at the start of the array
|
||||
// The backend currently sets the first image as the cover image
|
||||
function setCoverImage(index: number) {
|
||||
// Put the intended cover image at the start of the array
|
||||
// The backend currently sets the first image as the cover image
|
||||
function setCoverImage(index: number) {
|
||||
if (index < 0 || index >= uploadedImages.value.length || index === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
swapImages(0, index);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
domUrlForm,
|
||||
uploadedImages,
|
||||
uploadedImagesPreviewUrls,
|
||||
shouldTranslate,
|
||||
parseRecipe,
|
||||
uploadImages,
|
||||
clearImage,
|
||||
createRecipe,
|
||||
updateUploadedImage,
|
||||
setCoverImage,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
onMounted(() => {
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
onMounted(() => {
|
||||
// Force redirect to first valid page
|
||||
router.replace("/r/create/url");
|
||||
});
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:disabled="newRecipeName.trim() === ''"
|
||||
rounded
|
||||
block
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
@click="createByName(newRecipeName)"
|
||||
/>
|
||||
</div>
|
||||
@@ -41,51 +41,40 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
error: false,
|
||||
loading: false,
|
||||
});
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
});
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
|
||||
const api = useUserApi();
|
||||
const router = useRouter();
|
||||
const api = useUserApi();
|
||||
const router = useRouter();
|
||||
|
||||
function handleResponse(response: AxiosResponse<string> | null, edit = false) {
|
||||
function handleResponse(response: AxiosResponse<string> | null, edit = false) {
|
||||
if (response?.status !== 201) {
|
||||
state.error = true;
|
||||
state.loading = false;
|
||||
return;
|
||||
}
|
||||
router.push(`/g/${groupSlug.value}/r/${response.data}?edit=${edit.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
const newRecipeName = ref("");
|
||||
const domCreateByName = ref<VForm | null>(null);
|
||||
const newRecipeName = ref("");
|
||||
const domCreateByName = ref<VForm | null>(null);
|
||||
|
||||
async function createByName(name: string) {
|
||||
async function createByName(name: string) {
|
||||
if (!domCreateByName.value?.validate() || name === "") {
|
||||
return;
|
||||
}
|
||||
const { response } = await api.recipes.createOne({ name });
|
||||
handleResponse(response as any, true);
|
||||
}
|
||||
return {
|
||||
domCreateByName,
|
||||
newRecipeName,
|
||||
createByName,
|
||||
...toRefs(state),
|
||||
validators,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
rounded
|
||||
block
|
||||
type="submit"
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
/>
|
||||
</div>
|
||||
<v-card-text class="py-2">
|
||||
@@ -85,7 +85,7 @@
|
||||
</v-form>
|
||||
<v-expand-transition>
|
||||
<v-alert
|
||||
v-if="error"
|
||||
v-if="state.error"
|
||||
color="error"
|
||||
class="mt-6 white--text"
|
||||
>
|
||||
@@ -140,7 +140,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useTagStore } from "~/composables/store/use-tag-store";
|
||||
@@ -148,36 +148,34 @@ import { useNewRecipeOptions } from "~/composables/use-new-recipe-options";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
key: route => route.path,
|
||||
});
|
||||
const state = reactive({
|
||||
});
|
||||
const state = reactive({
|
||||
error: false,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
|
||||
const auth = useMealieAuth();
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
const auth = useMealieAuth();
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
|
||||
const router = useRouter();
|
||||
const tags = useTagStore();
|
||||
const router = useRouter();
|
||||
const tags = useTagStore();
|
||||
|
||||
const {
|
||||
const {
|
||||
importKeywordsAsTags,
|
||||
importCategories,
|
||||
stayInEditMode,
|
||||
parseRecipe,
|
||||
navigateToRecipe,
|
||||
} = useNewRecipeOptions();
|
||||
} = useNewRecipeOptions();
|
||||
|
||||
const bulkImporterTarget = computed(() => `/g/${groupSlug.value}/r/create/bulk`);
|
||||
const htmlOrJsonImporterTarget = computed(() => `/g/${groupSlug.value}/r/create/html`);
|
||||
const bulkImporterTarget = computed(() => `/g/${groupSlug.value}/r/create/bulk`);
|
||||
const htmlOrJsonImporterTarget = computed(() => `/g/${groupSlug.value}/r/create/html`);
|
||||
|
||||
function handleResponse(response: AxiosResponse<string> | null, refreshTags = false) {
|
||||
function handleResponse(response: AxiosResponse<string> | null, refreshTags = false) {
|
||||
if (response?.status !== 201) {
|
||||
state.error = true;
|
||||
state.loading = false;
|
||||
@@ -188,9 +186,9 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
navigateToRecipe(response.data, groupSlug.value, `/g/${groupSlug.value}/r/create/url`);
|
||||
}
|
||||
}
|
||||
|
||||
const recipeUrl = computed({
|
||||
const recipeUrl = computed({
|
||||
set(recipe_import_url: string | null) {
|
||||
if (recipe_import_url !== null) {
|
||||
recipe_import_url = recipe_import_url.trim();
|
||||
@@ -200,9 +198,9 @@ export default defineNuxtComponent({
|
||||
get() {
|
||||
return route.query.recipe_import_url as string | null;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
if (recipeUrl.value && recipeUrl.value.includes("https")) {
|
||||
// Check if we have a query params for using keywords as tags or staying in edit mode.
|
||||
// We don't use these in the app anymore, but older automations such as Bookmarklet might still use them,
|
||||
@@ -226,22 +224,22 @@ export default defineNuxtComponent({
|
||||
createByUrl(recipeUrl.value, importKeywordsAsTags.value, false);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const domUrlForm = ref<VForm | null>(null);
|
||||
const domUrlForm = ref<VForm | null>(null);
|
||||
|
||||
// Remove import URL from query params when leaving the page
|
||||
const isLeaving = ref(false);
|
||||
onBeforeRouteLeave((to) => {
|
||||
// Remove import URL from query params when leaving the page
|
||||
const isLeaving = ref(false);
|
||||
onBeforeRouteLeave((to) => {
|
||||
if (isLeaving.value) {
|
||||
return;
|
||||
}
|
||||
isLeaving.value = true;
|
||||
router.replace({ query: undefined }).then(() => router.push(to));
|
||||
});
|
||||
});
|
||||
|
||||
const createStatus = ref<string | null>(null);
|
||||
async function createByUrl(url: string | null, importKeywordsAsTags: boolean, importCategories: boolean) {
|
||||
const createStatus = ref<string | null>(null);
|
||||
async function createByUrl(url: string | null, importKeywordsAsTags: boolean, importCategories: boolean) {
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
@@ -259,24 +257,7 @@ export default defineNuxtComponent({
|
||||
);
|
||||
createStatus.value = null;
|
||||
handleResponse(response, importKeywordsAsTags);
|
||||
}
|
||||
|
||||
return {
|
||||
bulkImporterTarget,
|
||||
htmlOrJsonImporterTarget,
|
||||
recipeUrl,
|
||||
importKeywordsAsTags,
|
||||
importCategories: importCategories,
|
||||
stayInEditMode,
|
||||
parseRecipe,
|
||||
domUrlForm,
|
||||
createStatus,
|
||||
createByUrl,
|
||||
...toRefs(state),
|
||||
validators,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
:disabled="newRecipeZip === null"
|
||||
rounded
|
||||
block
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
@click="createByZip"
|
||||
/>
|
||||
</div>
|
||||
@@ -36,28 +36,25 @@
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useGlobalI18n } from "~/composables/use-global-i18n";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
});
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
});
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
|
||||
const api = useUserApi();
|
||||
const router = useRouter();
|
||||
const api = useUserApi();
|
||||
const router = useRouter();
|
||||
|
||||
const newRecipeZip = ref<File | null>(null);
|
||||
const newRecipeZipFileName = "archive";
|
||||
const newRecipeZip = ref<File | null>(null);
|
||||
const newRecipeZipFileName = "archive";
|
||||
|
||||
async function createByZip() {
|
||||
async function createByZip() {
|
||||
if (!newRecipeZip.value) {
|
||||
return;
|
||||
}
|
||||
@@ -79,14 +76,5 @@ export default defineNuxtComponent({
|
||||
finally {
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
newRecipeZip,
|
||||
createByZip,
|
||||
...toRefs(state),
|
||||
validators,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,27 +15,18 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import RecipeOrganizerPage from "~/components/Domain/Recipe/RecipeOrganizerPage.vue";
|
||||
import { useCategoryStore } from "~/composables/store";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
RecipeOrganizerPage,
|
||||
},
|
||||
definePageMeta({
|
||||
middleware: ["group-only"],
|
||||
setup() {
|
||||
const { store, actions } = useCategoryStore();
|
||||
const i18n = useI18n();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const { store, actions } = useCategoryStore();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("category.categories"),
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
actions,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ $t('recipe-finder.recipe-finder-description') }}
|
||||
</template>
|
||||
</BasePageTitle>
|
||||
<v-container v-if="ready">
|
||||
<v-container v-if="state.ready">
|
||||
<v-row>
|
||||
<v-col :cols="useMobile ? 12 : 3">
|
||||
<v-container class="ma-0 pa-0">
|
||||
@@ -51,38 +51,38 @@
|
||||
</SearchFilter>
|
||||
<div :class="attrs.searchFilter.filterClass">
|
||||
<v-badge
|
||||
:model-value="!!queryFilterJSON.parts && queryFilterJSON.parts.length > 0"
|
||||
:model-value="!!state.queryFilterJSON.parts && state.queryFilterJSON.parts.length > 0"
|
||||
size="small"
|
||||
color="primary"
|
||||
:content="(queryFilterJSON.parts || []).length"
|
||||
:content="(state.queryFilterJSON.parts || []).length"
|
||||
>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="accent"
|
||||
dark
|
||||
@click="queryFilterMenu = !queryFilterMenu"
|
||||
@click="state.queryFilterMenu = !state.queryFilterMenu"
|
||||
>
|
||||
<v-icon start>
|
||||
{{ $globals.icons.filter }}
|
||||
</v-icon>
|
||||
{{ $t("recipe-finder.other-filters") }}
|
||||
<BaseDialog
|
||||
v-model="queryFilterMenu"
|
||||
v-model="state.queryFilterMenu"
|
||||
:title="$t('recipe-finder.other-filters')"
|
||||
:icon="$globals.icons.filter"
|
||||
width="100%"
|
||||
max-width="1100px"
|
||||
:submit-disabled="!queryFilterEditorValue"
|
||||
:submit-disabled="!state.queryFilterEditorValue"
|
||||
can-confirm
|
||||
@confirm="saveQueryFilter"
|
||||
>
|
||||
<v-card-text>
|
||||
<QueryFilterBuilder
|
||||
:key="queryFilterMenuKey"
|
||||
:initial-query-filter="queryFilterJSON"
|
||||
:key="state.queryFilterMenuKey"
|
||||
:initial-query-filter="state.queryFilterJSON"
|
||||
:field-defs="queryFilterBuilderFields"
|
||||
@input="(value) => queryFilterEditorValue = value"
|
||||
@input-j-s-o-n="(value) => queryFilterEditorValueJSON = value"
|
||||
@input="(value) => state.queryFilterEditorValue = value"
|
||||
@input-j-s-o-n="(value) => state.queryFilterEditorValueJSON = value"
|
||||
/>
|
||||
</v-card-text>
|
||||
<template #custom-card-action>
|
||||
@@ -113,7 +113,7 @@
|
||||
:class="attrs.settings.colClass"
|
||||
>
|
||||
<v-menu
|
||||
v-model="settingsMenu"
|
||||
v-model="state.settingsMenu"
|
||||
offset-y
|
||||
nudge-bottom="3"
|
||||
:close-on-content-click="false"
|
||||
@@ -135,7 +135,7 @@
|
||||
<v-card-text>
|
||||
<div>
|
||||
<v-number-input
|
||||
v-model="settings.maxMissingFoods"
|
||||
v-model="state.settings.maxMissingFoods"
|
||||
:precision="null"
|
||||
:min="0"
|
||||
control-variant="stacked"
|
||||
@@ -144,7 +144,7 @@
|
||||
:label="$t('recipe-finder.max-missing-ingredients')"
|
||||
/>
|
||||
<v-number-input
|
||||
v-model="settings.maxMissingTools"
|
||||
v-model="state.settings.maxMissingTools"
|
||||
:precision="null"
|
||||
:min="0"
|
||||
control-variant="stacked"
|
||||
@@ -157,7 +157,7 @@
|
||||
<div class="mt-1">
|
||||
<v-checkbox
|
||||
v-if="isOwnGroup"
|
||||
v-model="settings.includeFoodsOnHand"
|
||||
v-model="state.settings.includeFoodsOnHand"
|
||||
density="compact"
|
||||
size="small"
|
||||
hide-details
|
||||
@@ -166,7 +166,7 @@
|
||||
/>
|
||||
<v-checkbox
|
||||
v-if="isOwnGroup"
|
||||
v-model="settings.includeToolsOnHand"
|
||||
v-model="state.settings.includeToolsOnHand"
|
||||
density="compact"
|
||||
size="small"
|
||||
hide-details
|
||||
@@ -328,7 +328,7 @@
|
||||
:recipe="item.recipe"
|
||||
:missing-foods="item.missingFoods"
|
||||
:missing-tools="item.missingTools"
|
||||
:disable-checkbox="loading"
|
||||
:disable-checkbox="state.loading"
|
||||
@add-food="addFood"
|
||||
@remove-food="removeFood"
|
||||
@add-tool="addTool"
|
||||
@@ -356,7 +356,7 @@
|
||||
:recipe="item.recipe"
|
||||
:missing-foods="item.missingFoods"
|
||||
:missing-tools="item.missingTools"
|
||||
:disable-checkbox="loading"
|
||||
:disable-checkbox="state.loading"
|
||||
@add-food="addFood"
|
||||
@remove-food="removeFood"
|
||||
@add-tool="addTool"
|
||||
@@ -366,7 +366,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<v-container v-else-if="!recipesReady">
|
||||
<v-container v-else-if="!state.recipesReady">
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
@@ -411,7 +411,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { watchDebounced } from "@vueuse/core";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { usePublicExploreApi } from "~/composables/api/api-client";
|
||||
@@ -431,26 +431,23 @@ interface RecipeSuggestions {
|
||||
missingItems: RecipeSuggestionResponseItem[];
|
||||
}
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { QueryFilterBuilder, RecipeSuggestion, SearchFilter },
|
||||
setup() {
|
||||
const display = useDisplay();
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const display = useDisplay();
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("recipe-finder.recipe-finder"),
|
||||
});
|
||||
});
|
||||
|
||||
const useMobile = computed(() => display.smAndDown.value);
|
||||
const useMobile = computed(() => display.smAndDown.value);
|
||||
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
const api = isOwnGroup.value ? useUserApi() : usePublicExploreApi(groupSlug.value).explore;
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
const api = isOwnGroup.value ? useUserApi() : usePublicExploreApi(groupSlug.value).explore;
|
||||
|
||||
const preferences = useRecipeFinderPreferences();
|
||||
const state = reactive({
|
||||
const preferences = useRecipeFinderPreferences();
|
||||
const state = reactive({
|
||||
ready: false,
|
||||
loading: false,
|
||||
recipesReady: false,
|
||||
@@ -468,16 +465,16 @@ export default defineNuxtComponent({
|
||||
queryFilter: preferences.value.queryFilter,
|
||||
limit: 20,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
if (!isOwnGroup.value) {
|
||||
state.settings.includeFoodsOnHand = false;
|
||||
state.settings.includeToolsOnHand = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => state,
|
||||
(newState) => {
|
||||
preferences.value.queryFilter = newState.settings.queryFilter;
|
||||
@@ -490,9 +487,9 @@ export default defineNuxtComponent({
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
const attrs = computed(() => {
|
||||
const attrs = computed(() => {
|
||||
return {
|
||||
title: {
|
||||
class: {
|
||||
@@ -508,51 +505,53 @@ export default defineNuxtComponent({
|
||||
colClass: useMobile.value ? "d-flex flex-wrap justify-end" : "d-flex flex-wrap justify-start",
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const foodStore = isOwnGroup.value ? useFoodStore() : usePublicFoodStore(groupSlug.value);
|
||||
const selectedFoods = ref<IngredientFood[]>([]);
|
||||
function addFood(food: IngredientFood) {
|
||||
const foodStore = isOwnGroup.value ? useFoodStore() : usePublicFoodStore(groupSlug.value);
|
||||
const foods = foodStore.store.value;
|
||||
const selectedFoods = ref<IngredientFood[]>([]);
|
||||
function addFood(food: IngredientFood) {
|
||||
selectedFoods.value = [...selectedFoods.value, food];
|
||||
handleFoodUpdates();
|
||||
}
|
||||
function removeFood(food: IngredientFood) {
|
||||
}
|
||||
function removeFood(food: IngredientFood) {
|
||||
selectedFoods.value = selectedFoods.value.filter(f => f.id !== food.id);
|
||||
handleFoodUpdates();
|
||||
}
|
||||
function handleFoodUpdates() {
|
||||
}
|
||||
function handleFoodUpdates() {
|
||||
selectedFoods.value.sort((a, b) => (a.pluralName || a.name).localeCompare(b.pluralName || b.name));
|
||||
preferences.value.foodIds = selectedFoods.value.map(food => food.id);
|
||||
}
|
||||
watch(
|
||||
}
|
||||
watch(
|
||||
() => selectedFoods.value,
|
||||
() => {
|
||||
handleFoodUpdates();
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
const toolStore = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
|
||||
const selectedTools = ref<RecipeTool[]>([]);
|
||||
function addTool(tool: RecipeTool) {
|
||||
const toolStore = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
|
||||
const tools = toolStore.store.value;
|
||||
const selectedTools = ref<RecipeTool[]>([]);
|
||||
function addTool(tool: RecipeTool) {
|
||||
selectedTools.value = [...selectedTools.value, tool];
|
||||
handleToolUpdates();
|
||||
}
|
||||
function removeTool(tool: RecipeTool) {
|
||||
}
|
||||
function removeTool(tool: RecipeTool) {
|
||||
selectedTools.value = selectedTools.value.filter(t => t.id !== tool.id);
|
||||
handleToolUpdates();
|
||||
}
|
||||
function handleToolUpdates() {
|
||||
}
|
||||
function handleToolUpdates() {
|
||||
selectedTools.value.sort((a, b) => a.name.localeCompare(b.name));
|
||||
preferences.value.toolIds = selectedTools.value.map(tool => tool.id);
|
||||
}
|
||||
watch(
|
||||
}
|
||||
watch(
|
||||
() => selectedTools.value,
|
||||
() => {
|
||||
handleToolUpdates();
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
async function hydrateFoods() {
|
||||
async function hydrateFoods() {
|
||||
if (!preferences.value.foodIds.length) {
|
||||
return;
|
||||
}
|
||||
@@ -565,9 +564,9 @@ export default defineNuxtComponent({
|
||||
.filter(food => !!food);
|
||||
|
||||
selectedFoods.value = foods;
|
||||
}
|
||||
}
|
||||
|
||||
async function hydrateTools() {
|
||||
async function hydrateTools() {
|
||||
if (!preferences.value.toolIds.length) {
|
||||
return;
|
||||
}
|
||||
@@ -580,18 +579,18 @@ export default defineNuxtComponent({
|
||||
.filter(tool => !!tool);
|
||||
|
||||
selectedTools.value = tools;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await Promise.all([hydrateFoods(), hydrateTools()]);
|
||||
state.ready = true;
|
||||
if (!selectedFoods.value.length) {
|
||||
state.recipesReady = true;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const recipeResponseItems = ref<RecipeSuggestionResponseItem[]>([]);
|
||||
const recipeSuggestions = computed<RecipeSuggestions>(() => {
|
||||
const recipeResponseItems = ref<RecipeSuggestionResponseItem[]>([]);
|
||||
const recipeSuggestions = computed<RecipeSuggestions>(() => {
|
||||
const readyToMake: RecipeSuggestionResponseItem[] = [];
|
||||
const missingItems: RecipeSuggestionResponseItem[] = [];
|
||||
recipeResponseItems.value.forEach((responseItem) => {
|
||||
@@ -607,9 +606,9 @@ export default defineNuxtComponent({
|
||||
readyToMake,
|
||||
missingItems,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
watchDebounced(
|
||||
watchDebounced(
|
||||
[selectedFoods, selectedTools, state.settings], async () => {
|
||||
// don't search for suggestions if no foods are selected
|
||||
if (!selectedFoods.value.length) {
|
||||
@@ -641,9 +640,9 @@ export default defineNuxtComponent({
|
||||
{
|
||||
debounce: 500,
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
const queryFilterBuilderFields: FieldDefinition[] = [
|
||||
const queryFilterBuilderFields: FieldDefinition[] = [
|
||||
{
|
||||
name: "recipe_category.id",
|
||||
label: i18n.t("category.categories"),
|
||||
@@ -669,41 +668,20 @@ export default defineNuxtComponent({
|
||||
label: i18n.t("general.last-made"),
|
||||
type: "relativeDate",
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
function clearQueryFilter() {
|
||||
function clearQueryFilter() {
|
||||
state.queryFilterEditorValue = "";
|
||||
state.queryFilterEditorValueJSON = { parts: [] } as QueryFilterJSON;
|
||||
state.settings.queryFilter = "";
|
||||
state.queryFilterJSON = { parts: [] } as QueryFilterJSON;
|
||||
state.queryFilterMenu = false;
|
||||
state.queryFilterMenuKey += 1;
|
||||
}
|
||||
}
|
||||
|
||||
function saveQueryFilter() {
|
||||
function saveQueryFilter() {
|
||||
state.settings.queryFilter = state.queryFilterEditorValue || "";
|
||||
state.queryFilterJSON = state.queryFilterEditorValueJSON || { parts: [] } as QueryFilterJSON;
|
||||
state.queryFilterMenu = false;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
useMobile,
|
||||
attrs,
|
||||
isOwnGroup,
|
||||
foods: foodStore.store,
|
||||
selectedFoods,
|
||||
addFood,
|
||||
removeFood,
|
||||
tools: toolStore.store,
|
||||
selectedTools,
|
||||
addTool,
|
||||
removeTool,
|
||||
recipeSuggestions,
|
||||
queryFilterBuilderFields,
|
||||
clearQueryFilter,
|
||||
saveQueryFilter,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,27 +15,18 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import RecipeOrganizerPage from "~/components/Domain/Recipe/RecipeOrganizerPage.vue";
|
||||
import { useTagStore } from "~/composables/store";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
RecipeOrganizerPage,
|
||||
},
|
||||
definePageMeta({
|
||||
middleware: ["group-only"],
|
||||
setup() {
|
||||
const { store, actions } = useTagStore();
|
||||
const i18n = useI18n();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const { store, actions } = useTagStore();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("tag.tags"),
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
actions,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -30,25 +30,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import RecipeTimeline from "~/components/Domain/Recipe/RecipeTimeline.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { RecipeTimeline },
|
||||
definePageMeta({
|
||||
middleware: ["group-only"],
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const ready = ref<boolean>(false);
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const ready = ref<boolean>(false);
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("recipe.timeline"),
|
||||
});
|
||||
});
|
||||
|
||||
const groupName = ref<string>("");
|
||||
const queryFilter = ref<string>("");
|
||||
async function fetchHousehold() {
|
||||
const groupName = ref<string>("");
|
||||
const queryFilter = ref<string>("");
|
||||
async function fetchHousehold() {
|
||||
const { data } = await api.households.getCurrentUserHousehold();
|
||||
if (data) {
|
||||
queryFilter.value = `recipe.group_id="${data.groupId}"`;
|
||||
@@ -56,15 +56,7 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
ready.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
useAsyncData("house-hold", fetchHousehold);
|
||||
|
||||
return {
|
||||
groupName,
|
||||
queryFilter,
|
||||
ready,
|
||||
};
|
||||
},
|
||||
});
|
||||
useAsyncData("house-hold", fetchHousehold);
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import RecipeOrganizerPage from "~/components/Domain/Recipe/RecipeOrganizerPage.vue";
|
||||
import { useToolStore } from "~/composables/store";
|
||||
import type { RecipeTool } from "~/lib/api/types/recipe";
|
||||
@@ -24,34 +24,31 @@ interface RecipeToolWithOnHand extends RecipeTool {
|
||||
onHand: boolean;
|
||||
}
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
RecipeOrganizerPage,
|
||||
},
|
||||
definePageMeta({
|
||||
middleware: ["group-only"],
|
||||
setup() {
|
||||
const auth = useMealieAuth();
|
||||
const toolStore = useToolStore();
|
||||
const dialog = ref(false);
|
||||
const i18n = useI18n();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const auth = useMealieAuth();
|
||||
const toolStore = useToolStore();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("tool.tools"),
|
||||
});
|
||||
});
|
||||
|
||||
const userHousehold = computed(() => auth.user.value?.householdSlug || "");
|
||||
const tools = computed(() => toolStore.store.value.map(tool => (
|
||||
const userHousehold = computed(() => auth.user.value?.householdSlug || "");
|
||||
const tools = computed(() => toolStore.store.value.map(tool => (
|
||||
{
|
||||
...tool,
|
||||
onHand: tool.householdsWithTool?.includes(userHousehold.value) || false,
|
||||
} as RecipeToolWithOnHand
|
||||
)));
|
||||
)));
|
||||
|
||||
async function deleteOne(id: string | number) {
|
||||
async function deleteOne(id: string | number) {
|
||||
await toolStore.actions.deleteOne(id);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateOne(tool: RecipeToolWithOnHand) {
|
||||
async function updateOne(tool: RecipeToolWithOnHand) {
|
||||
if (userHousehold.value) {
|
||||
if (tool.onHand && !tool.householdsWithTool?.includes(userHousehold.value)) {
|
||||
if (!tool.householdsWithTool) {
|
||||
@@ -66,14 +63,5 @@ export default defineNuxtComponent({
|
||||
}
|
||||
}
|
||||
await toolStore.actions.updateOne(tool);
|
||||
}
|
||||
|
||||
return {
|
||||
dialog,
|
||||
tools,
|
||||
deleteOne,
|
||||
updateOne,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -34,12 +34,13 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineNuxtComponent({
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: ["can-organize-only"],
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const buttonLookup: { [key: string]: string } = {
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const buttonLookup: { [key: string]: string } = {
|
||||
recipes: i18n.t("general.recipes"),
|
||||
recipeActions: i18n.t("recipe.recipe-actions"),
|
||||
foods: i18n.t("general.foods"),
|
||||
@@ -48,11 +49,11 @@ export default defineNuxtComponent({
|
||||
categories: i18n.t("category.categories"),
|
||||
tags: i18n.t("tag.tags"),
|
||||
tools: i18n.t("tool.tools"),
|
||||
};
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const route = useRoute();
|
||||
|
||||
const DATA_TYPE_OPTIONS = computed(() => [
|
||||
const DATA_TYPE_OPTIONS = computed(() => [
|
||||
{
|
||||
text: i18n.t("general.recipes"),
|
||||
value: "new",
|
||||
@@ -95,9 +96,9 @@ export default defineNuxtComponent({
|
||||
value: "new",
|
||||
to: "/group/data/tools",
|
||||
},
|
||||
]);
|
||||
]);
|
||||
|
||||
const buttonText = computed(() => {
|
||||
const buttonText = computed(() => {
|
||||
const last = route.path
|
||||
.split("/")
|
||||
.pop()
|
||||
@@ -111,16 +112,9 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
return i18n.t("data-pages.select-data");
|
||||
});
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("data-pages.data-management"),
|
||||
});
|
||||
|
||||
return {
|
||||
buttonText,
|
||||
DATA_TYPE_OPTIONS,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
onMounted(() => {
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
onMounted(() => {
|
||||
// Force redirect to first valid page
|
||||
router.push("/group/data/foods");
|
||||
});
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import RecipeDataTable from "~/components/Domain/Recipe/RecipeDataTable.vue";
|
||||
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
@@ -249,29 +249,29 @@ enum MODES {
|
||||
changeOwner = "changeOwner",
|
||||
}
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { RecipeDataTable, RecipeOrganizerSelector, GroupExportData, RecipeSettingsSwitches, UserAvatar },
|
||||
definePageMeta({
|
||||
scrollToTop: true,
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $globals } = useNuxtApp();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $globals } = useNuxtApp();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("data-pages.recipes.recipe-data"),
|
||||
});
|
||||
});
|
||||
|
||||
const { getAllRecipes, refreshRecipes } = useRecipes(true, true, false, `householdId=${auth.user.value?.householdId || ""}`);
|
||||
const selected = ref<Recipe[]>([]);
|
||||
const { refreshRecipes } = useRecipes(true, true, false, `householdId=${auth.user.value?.householdId || ""}`);
|
||||
const selected = ref<Recipe[]>([]);
|
||||
|
||||
function resetAll() {
|
||||
function resetAll() {
|
||||
selected.value = [];
|
||||
toSetTags.value = [];
|
||||
toSetCategories.value = [];
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const headers = reactive({
|
||||
const headers = reactive({
|
||||
id: false,
|
||||
owner: false,
|
||||
tags: true,
|
||||
@@ -281,9 +281,9 @@ export default defineNuxtComponent({
|
||||
recipeYieldQuantity: false,
|
||||
recipeYield: false,
|
||||
dateAdded: false,
|
||||
});
|
||||
});
|
||||
|
||||
const headerLabels = {
|
||||
const headerLabels = {
|
||||
id: i18n.t("general.id"),
|
||||
owner: i18n.t("general.owner"),
|
||||
tags: i18n.t("tag.tags"),
|
||||
@@ -293,9 +293,9 @@ export default defineNuxtComponent({
|
||||
recipeYieldQuantity: i18n.t("recipe.recipe-yield"),
|
||||
recipeYield: i18n.t("recipe.recipe-yield-text"),
|
||||
dateAdded: i18n.t("general.date-added"),
|
||||
};
|
||||
};
|
||||
|
||||
const actions: MenuItem[] = [
|
||||
const actions: MenuItem[] = [
|
||||
{
|
||||
icon: $globals.icons.database,
|
||||
text: i18n.t("export.export"),
|
||||
@@ -326,42 +326,42 @@ export default defineNuxtComponent({
|
||||
text: i18n.t("general.delete"),
|
||||
event: "delete-selected",
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
const api = useUserApi();
|
||||
const loading = ref(false);
|
||||
const api = useUserApi();
|
||||
const loading = ref(false);
|
||||
|
||||
// ===============================================================
|
||||
// Group Exports
|
||||
// ===============================================================
|
||||
// Group Exports
|
||||
|
||||
const purgeExportsDialog = ref(false);
|
||||
const purgeExportsDialog = ref(false);
|
||||
|
||||
async function purgeExports() {
|
||||
async function purgeExports() {
|
||||
await api.bulk.purgeExports();
|
||||
refreshExports();
|
||||
}
|
||||
}
|
||||
|
||||
const groupExports = ref<GroupDataExport[]>([]);
|
||||
const groupExports = ref<GroupDataExport[]>([]);
|
||||
|
||||
async function refreshExports() {
|
||||
async function refreshExports() {
|
||||
const { data } = await api.bulk.fetchExports();
|
||||
|
||||
if (data) {
|
||||
groupExports.value = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await refreshExports();
|
||||
});
|
||||
// ===============================================================
|
||||
// All Recipes
|
||||
});
|
||||
// ===============================================================
|
||||
// All Recipes
|
||||
|
||||
function selectAll() {
|
||||
function selectAll() {
|
||||
selected.value = allRecipes.value;
|
||||
}
|
||||
}
|
||||
|
||||
async function exportSelected() {
|
||||
async function exportSelected() {
|
||||
loading.value = true;
|
||||
const { data } = await api.bulk.bulkExport({
|
||||
recipes: selected.value.map((x: Recipe) => x.slug ?? ""),
|
||||
@@ -374,31 +374,31 @@ export default defineNuxtComponent({
|
||||
|
||||
resetAll();
|
||||
refreshExports();
|
||||
}
|
||||
}
|
||||
|
||||
const toSetTags = ref([]);
|
||||
const toSetTags = ref([]);
|
||||
|
||||
async function tagSelected() {
|
||||
async function tagSelected() {
|
||||
loading.value = true;
|
||||
|
||||
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||
await api.bulk.bulkTag({ recipes, tags: toSetTags.value });
|
||||
await refreshRecipes();
|
||||
resetAll();
|
||||
}
|
||||
}
|
||||
|
||||
const toSetCategories = ref([]);
|
||||
const toSetCategories = ref([]);
|
||||
|
||||
async function categorizeSelected() {
|
||||
async function categorizeSelected() {
|
||||
loading.value = true;
|
||||
|
||||
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||
await api.bulk.bulkCategorize({ recipes, categories: toSetCategories.value });
|
||||
await refreshRecipes();
|
||||
resetAll();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSelected() {
|
||||
async function deleteSelected() {
|
||||
loading.value = true;
|
||||
|
||||
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||
@@ -407,18 +407,18 @@ export default defineNuxtComponent({
|
||||
|
||||
await refreshRecipes();
|
||||
resetAll();
|
||||
}
|
||||
}
|
||||
|
||||
const recipeSettings = reactive<RecipeSettings>({
|
||||
const recipeSettings = reactive<RecipeSettings>({
|
||||
public: false,
|
||||
showNutrition: false,
|
||||
showAssets: false,
|
||||
landscapeView: false,
|
||||
disableComments: false,
|
||||
locked: false,
|
||||
});
|
||||
});
|
||||
|
||||
async function updateSettings() {
|
||||
async function updateSettings() {
|
||||
loading.value = true;
|
||||
|
||||
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||
@@ -427,9 +427,9 @@ export default defineNuxtComponent({
|
||||
|
||||
await refreshRecipes();
|
||||
resetAll();
|
||||
}
|
||||
}
|
||||
|
||||
async function changeOwner() {
|
||||
async function changeOwner() {
|
||||
if (!selected.value.length || !selectedOwner.value) {
|
||||
return;
|
||||
}
|
||||
@@ -443,12 +443,12 @@ export default defineNuxtComponent({
|
||||
|
||||
await refreshRecipes();
|
||||
resetAll();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Dialog Management
|
||||
// ============================================================
|
||||
// Dialog Management
|
||||
|
||||
const dialog = reactive({
|
||||
const dialog = reactive({
|
||||
state: false,
|
||||
title: i18n.t("data-pages.recipes.tag-recipes"),
|
||||
mode: MODES.tag,
|
||||
@@ -458,9 +458,9 @@ export default defineNuxtComponent({
|
||||
return Promise.resolve();
|
||||
},
|
||||
icon: $globals.icons.tags,
|
||||
});
|
||||
});
|
||||
|
||||
function openDialog(mode: MODES) {
|
||||
function openDialog(mode: MODES) {
|
||||
const titles: Record<MODES, string> = {
|
||||
[MODES.tag]: i18n.t("data-pages.recipes.tag-recipes"),
|
||||
[MODES.category]: i18n.t("data-pages.recipes.categorize-recipes"),
|
||||
@@ -493,12 +493,12 @@ export default defineNuxtComponent({
|
||||
dialog.callback = callbacks[mode];
|
||||
dialog.icon = icons[mode];
|
||||
dialog.state = true;
|
||||
}
|
||||
}
|
||||
|
||||
const { store: allUsers } = useUserStore();
|
||||
const { store: households } = useHouseholdStore();
|
||||
const selectedOwner = ref("");
|
||||
const selectedOwnerHousehold = computed(() => {
|
||||
const { store: allUsers } = useUserStore();
|
||||
const { store: households } = useHouseholdStore();
|
||||
const selectedOwner = ref("");
|
||||
const selectedOwnerHousehold = computed(() => {
|
||||
if (!selectedOwner.value) {
|
||||
return null;
|
||||
}
|
||||
@@ -509,35 +509,6 @@ export default defineNuxtComponent({
|
||||
};
|
||||
|
||||
return households.value.find(h => h.id === owner.householdId);
|
||||
});
|
||||
|
||||
return {
|
||||
recipeSettings,
|
||||
selectAll,
|
||||
loading,
|
||||
actions,
|
||||
allRecipes,
|
||||
categorizeSelected,
|
||||
deleteSelected,
|
||||
dialog,
|
||||
exportSelected,
|
||||
getAllRecipes,
|
||||
headerLabels,
|
||||
headers,
|
||||
MODES,
|
||||
openDialog,
|
||||
selected,
|
||||
tagSelected,
|
||||
toSetCategories,
|
||||
toSetTags,
|
||||
groupExports,
|
||||
purgeExportsDialog,
|
||||
purgeExports,
|
||||
allUsers,
|
||||
selectedOwner,
|
||||
selectedOwnerHousehold,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -43,24 +43,18 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useGroupSelf } from "~/composables/use-groups";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
definePageMeta({
|
||||
middleware: ["can-manage-only"],
|
||||
setup() {
|
||||
const { group, actions: groupActions } = useGroupSelf();
|
||||
const i18n = useI18n();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const { group, actions: groupActions } = useGroupSelf();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("group.group"),
|
||||
});
|
||||
|
||||
return {
|
||||
group,
|
||||
groupActions,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<BaseCardSectionTitle :title="$t('migration.new-migration')" />
|
||||
<v-card
|
||||
variant="outlined"
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
style="border-color: lightgrey;"
|
||||
>
|
||||
<v-card-title> {{ $t('migration.choose-migration-type') }} </v-card-title>
|
||||
@@ -29,7 +29,7 @@
|
||||
>
|
||||
<div class="mb-2">
|
||||
<BaseOverflowButton
|
||||
v-model="migrationType"
|
||||
v-model="state.migrationType"
|
||||
mode="model"
|
||||
:items="items"
|
||||
/>
|
||||
@@ -37,7 +37,7 @@
|
||||
{{ content.text }}
|
||||
<v-treeview
|
||||
v-if="content.tree && Array.isArray(content.tree)"
|
||||
:key="migrationType"
|
||||
:key="state.migrationType"
|
||||
density="compact"
|
||||
:items="content.tree"
|
||||
>
|
||||
@@ -59,15 +59,15 @@
|
||||
:text-btn="false"
|
||||
@uploaded="setFileObject"
|
||||
/>
|
||||
{{ fileObject.name || $t('migration.no-file-selected') }}
|
||||
{{ state.fileObject.name || $t('migration.no-file-selected') }}
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
<v-checkbox v-model="addMigrationTag">
|
||||
<v-checkbox v-model="state.addMigrationTag">
|
||||
<template #label>
|
||||
<i18n-t keypath="migration.tag-all-recipes">
|
||||
<template #tag-name>
|
||||
<b class="mx-1"> {{ migrationType }} </b>
|
||||
<b class="mx-1"> {{ state.migrationType }} </b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</template>
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
<v-card-actions class="justify-end">
|
||||
<BaseButton
|
||||
:disabled="!fileObject.name"
|
||||
:disabled="!state.fileObject.name"
|
||||
submit
|
||||
@click="startMigration"
|
||||
>
|
||||
@@ -88,14 +88,14 @@
|
||||
<v-container class="$vuetify.display.smAndDown ? 'px-0': ''">
|
||||
<BaseCardSectionTitle :title="$t('migration.previous-migrations')" />
|
||||
<ReportTable
|
||||
:items="reports"
|
||||
:items="state.reports"
|
||||
@delete="deleteReport"
|
||||
/>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { ReportSummary } from "~/lib/api/types/reports";
|
||||
import type { MenuItem } from "~/components/global/BaseOverflowButton.vue";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
@@ -127,28 +127,29 @@ const MIGRATIONS = {
|
||||
cookn: "cookn",
|
||||
};
|
||||
|
||||
export default defineNuxtComponent({
|
||||
definePageMeta({
|
||||
middleware: ["advanced-only"],
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const { $globals } = useNuxtApp();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const { $globals } = useNuxtApp();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("settings.migrations"),
|
||||
});
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
const api = useUserApi();
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
addMigrationTag: false,
|
||||
loading: false,
|
||||
treeState: true,
|
||||
migrationType: MIGRATIONS.mealie as SupportedMigrations,
|
||||
fileObject: {} as File,
|
||||
reports: [] as ReportSummary[],
|
||||
});
|
||||
});
|
||||
|
||||
const items: MenuItem[] = [
|
||||
const items: MenuItem[] = [
|
||||
{
|
||||
text: i18n.t("migration.mealie-pre-v1.title"),
|
||||
value: MIGRATIONS.mealie,
|
||||
@@ -190,8 +191,8 @@ export default defineNuxtComponent({
|
||||
text: i18n.t("migration.cookn.title"),
|
||||
value: MIGRATIONS.cookn,
|
||||
},
|
||||
];
|
||||
const _content: Record<string, MigrationContent> = {
|
||||
];
|
||||
const _content: Record<string, MigrationContent> = {
|
||||
[MIGRATIONS.mealie]: {
|
||||
text: i18n.t("migration.mealie-pre-v1.description-long"),
|
||||
acceptedFileType: ".zip",
|
||||
@@ -431,9 +432,9 @@ export default defineNuxtComponent({
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
function addIdToNode(counter: number, node: TreeNode): number {
|
||||
function addIdToNode(counter: number, node: TreeNode): number {
|
||||
node.id = counter;
|
||||
counter += 1;
|
||||
if (node.children) {
|
||||
@@ -442,9 +443,9 @@ export default defineNuxtComponent({
|
||||
});
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in _content) {
|
||||
for (const key in _content) {
|
||||
const migration = _content[key];
|
||||
if (migration.tree && Array.isArray(migration.tree)) {
|
||||
let counter = 1;
|
||||
@@ -452,15 +453,15 @@ export default defineNuxtComponent({
|
||||
counter = addIdToNode(counter, node);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(_content);
|
||||
console.log(_content);
|
||||
|
||||
function setFileObject(fileObject: File) {
|
||||
function setFileObject(fileObject: File) {
|
||||
state.fileObject = fileObject;
|
||||
}
|
||||
}
|
||||
|
||||
async function startMigration() {
|
||||
async function startMigration() {
|
||||
state.loading = true;
|
||||
const payload = {
|
||||
addMigrationTag: state.addMigrationTag,
|
||||
@@ -475,26 +476,26 @@ export default defineNuxtComponent({
|
||||
if (data) {
|
||||
state.reports.unshift(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getMigrationReports() {
|
||||
async function getMigrationReports() {
|
||||
const { data } = await api.groupReports.getAll("migration");
|
||||
|
||||
if (data) {
|
||||
state.reports = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteReport(id: string) {
|
||||
async function deleteReport(id: string) {
|
||||
await api.groupReports.deleteOne(id);
|
||||
getMigrationReports();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
getMigrationReports();
|
||||
});
|
||||
});
|
||||
|
||||
const content = computed(() => {
|
||||
const content = computed(() => {
|
||||
const data = _content[state.migrationType];
|
||||
|
||||
if (data) {
|
||||
@@ -507,18 +508,6 @@ export default defineNuxtComponent({
|
||||
tree: false,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
items,
|
||||
content,
|
||||
setFileObject,
|
||||
deleteReport,
|
||||
startMigration,
|
||||
getMigrationReports,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -47,41 +47,31 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import type { ReportOut } from "~/lib/api/types/reports";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const id = route.params.id as string;
|
||||
const route = useRoute();
|
||||
const id = route.params.id as string;
|
||||
|
||||
const api = useUserApi();
|
||||
const api = useUserApi();
|
||||
|
||||
const report = ref<ReportOut | null>(null);
|
||||
const report = ref<ReportOut | null>(null);
|
||||
|
||||
async function getReport() {
|
||||
async function getReport() {
|
||||
const { data } = await api.groupReports.getOne(id);
|
||||
report.value = data ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await getReport();
|
||||
});
|
||||
});
|
||||
|
||||
const itemHeaders = [
|
||||
const itemHeaders = [
|
||||
{ title: "Success", value: "success" },
|
||||
{ title: "Message", value: "message" },
|
||||
{ title: "Timestamp", value: "timestamp" },
|
||||
];
|
||||
|
||||
return {
|
||||
report,
|
||||
id,
|
||||
itemHeaders,
|
||||
};
|
||||
},
|
||||
});
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -32,105 +32,26 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue";
|
||||
import { useHouseholdSelf } from "~/composables/use-households";
|
||||
import type { ReadHouseholdPreferences } from "~/lib/api/types/household";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
HouseholdPreferencesEditor,
|
||||
},
|
||||
definePageMeta({
|
||||
middleware: ["can-manage-household-only"],
|
||||
setup() {
|
||||
const { household, actions: householdActions } = useHouseholdSelf();
|
||||
const i18n = useI18n();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const { household, actions: householdActions } = useHouseholdSelf();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("household.household"),
|
||||
});
|
||||
});
|
||||
|
||||
const refHouseholdEditForm = ref<VForm | null>(null);
|
||||
const refHouseholdEditForm = ref<VForm | null>(null);
|
||||
|
||||
type Preference = {
|
||||
key: keyof ReadHouseholdPreferences;
|
||||
value: boolean;
|
||||
label: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const preferencesEditor = computed<Preference[]>(() => {
|
||||
if (!household.value || !household.value.preferences) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: "recipePublic",
|
||||
value: household.value.preferences.recipePublic || false,
|
||||
label: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes"),
|
||||
description: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes-description"),
|
||||
} as Preference,
|
||||
{
|
||||
key: "recipeShowNutrition",
|
||||
value: household.value.preferences.recipeShowNutrition || false,
|
||||
label: i18n.t("group.show-nutrition-information"),
|
||||
description: i18n.t("group.show-nutrition-information-description"),
|
||||
} as Preference,
|
||||
{
|
||||
key: "recipeShowAssets",
|
||||
value: household.value.preferences.recipeShowAssets || false,
|
||||
label: i18n.t("group.show-recipe-assets"),
|
||||
description: i18n.t("group.show-recipe-assets-description"),
|
||||
} as Preference,
|
||||
{
|
||||
key: "recipeLandscapeView",
|
||||
value: household.value.preferences.recipeLandscapeView || false,
|
||||
label: i18n.t("group.default-to-landscape-view"),
|
||||
description: i18n.t("group.default-to-landscape-view-description"),
|
||||
} as Preference,
|
||||
{
|
||||
key: "recipeDisableComments",
|
||||
value: household.value.preferences.recipeDisableComments || false,
|
||||
label: i18n.t("group.disable-users-from-commenting-on-recipes"),
|
||||
description: i18n.t("group.disable-users-from-commenting-on-recipes-description"),
|
||||
} as Preference,
|
||||
];
|
||||
});
|
||||
|
||||
const allDays = [
|
||||
{
|
||||
name: i18n.t("general.sunday"),
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.monday"),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.tuesday"),
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.wednesday"),
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.thursday"),
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.friday"),
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.saturday"),
|
||||
value: 6,
|
||||
},
|
||||
];
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit() {
|
||||
if (!refHouseholdEditForm.value?.validate() || !household.value?.preferences) {
|
||||
console.log(refHouseholdEditForm.value?.validate());
|
||||
return;
|
||||
@@ -143,18 +64,7 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("settings.settings-update-failed"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
household,
|
||||
householdActions,
|
||||
allDays,
|
||||
preferencesEditor,
|
||||
refHouseholdEditForm,
|
||||
handleSubmit,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { isSameDay, addDays, parseISO, format, isValid } from "date-fns";
|
||||
import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue";
|
||||
import { useHouseholdSelf } from "~/composables/use-households";
|
||||
@@ -95,41 +95,36 @@ import { useUserMealPlanPreferences } from "~/composables/use-users/preferences"
|
||||
import type { ShoppingListSummary } from "~/lib/api/types/household";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
RecipeDialogAddToShoppingList,
|
||||
},
|
||||
setup() {
|
||||
const TABS = {
|
||||
const TABS = {
|
||||
view: "household-mealplan-planner-view",
|
||||
edit: "household-mealplan-planner-edit",
|
||||
};
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const { household } = useHouseholdSelf();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const { household } = useHouseholdSelf();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("meal-plan.dinner-this-week"),
|
||||
});
|
||||
});
|
||||
|
||||
const mealPlanPreferences = useUserMealPlanPreferences();
|
||||
const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7);
|
||||
watch(numberOfDays, (val) => {
|
||||
const mealPlanPreferences = useUserMealPlanPreferences();
|
||||
const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7);
|
||||
watch(numberOfDays, (val) => {
|
||||
mealPlanPreferences.value.numberOfDays = Number(val);
|
||||
});
|
||||
});
|
||||
|
||||
// Force to /view if current route is /planner
|
||||
if (route.path === "/household/mealplan/planner") {
|
||||
// Force to /view if current route is /planner
|
||||
if (route.path === "/household/mealplan/planner") {
|
||||
router.push({
|
||||
name: TABS.view,
|
||||
query: route.query,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function safeParseISO(date: string, fallback: Date | undefined = undefined) {
|
||||
function safeParseISO(date: string, fallback: Date | undefined = undefined) {
|
||||
try {
|
||||
const parsed = parseISO(date);
|
||||
return isValid(parsed) ? parsed : fallback;
|
||||
@@ -137,28 +132,28 @@ export default defineNuxtComponent({
|
||||
catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize dates from query parameters or defaults
|
||||
const initialStartDate = safeParseISO(route.query.start as string, new Date());
|
||||
const initialEndDate = safeParseISO(route.query.end as string, addDays(new Date(), adjustForToday(numberOfDays.value)));
|
||||
// Initialize dates from query parameters or defaults
|
||||
const initialStartDate = safeParseISO(route.query.start as string, new Date());
|
||||
const initialEndDate = safeParseISO(route.query.end as string, addDays(new Date(), adjustForToday(numberOfDays.value)));
|
||||
|
||||
const state = ref({
|
||||
const state = ref({
|
||||
range: [initialStartDate, initialEndDate] as [Date, Date],
|
||||
start: initialStartDate,
|
||||
picker: false,
|
||||
end: initialEndDate,
|
||||
shoppingListDialog: false,
|
||||
addAllLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
const shoppingLists = ref<ShoppingListSummary[]>();
|
||||
const shoppingLists = ref<ShoppingListSummary[]>();
|
||||
|
||||
const firstDayOfWeek = computed(() => {
|
||||
const firstDayOfWeek = computed(() => {
|
||||
return household.value?.preferences?.firstDayOfWeek || 0;
|
||||
});
|
||||
});
|
||||
|
||||
const weekRange = computed(() => {
|
||||
const weekRange = computed(() => {
|
||||
const sorted = [...state.value.range].sort((a, b) => a.getTime() - b.getTime());
|
||||
|
||||
const start = sorted[0];
|
||||
@@ -171,10 +166,10 @@ export default defineNuxtComponent({
|
||||
start: new Date(),
|
||||
end: addDays(new Date(), adjustForToday(numberOfDays.value)),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Update query parameters when date range changes
|
||||
watch(weekRange, (newRange) => {
|
||||
// Update query parameters when date range changes
|
||||
watch(weekRange, (newRange) => {
|
||||
// Keep current route name and params, just update the query
|
||||
router.replace({
|
||||
name: route.name || TABS.view,
|
||||
@@ -185,25 +180,25 @@ export default defineNuxtComponent({
|
||||
end: format(newRange.end, "yyyy-MM-dd"),
|
||||
},
|
||||
});
|
||||
}, { immediate: true });
|
||||
}, { immediate: true });
|
||||
|
||||
const { mealplans, actions } = useMealplans(weekRange);
|
||||
const { mealplans, actions } = useMealplans(weekRange);
|
||||
|
||||
function filterMealByDate(date: Date) {
|
||||
function filterMealByDate(date: Date) {
|
||||
if (!mealplans.value) return [];
|
||||
return mealplans.value.filter((meal) => {
|
||||
const mealDate = parseISO(meal.date);
|
||||
return isSameDay(mealDate, date);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustForToday(days: number) {
|
||||
function adjustForToday(days: number) {
|
||||
// The use case for this function is "how many days are we adding to 'today'?"
|
||||
// e.g. If the user wants 7 days, we substract one to do "today + 6"
|
||||
return days > 0 ? days - 1 : days + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const days = computed(() => {
|
||||
const days = computed(() => {
|
||||
const numDays
|
||||
= Math.floor((weekRange.value.end.getTime() - weekRange.value.start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
|
||||
|
||||
@@ -217,19 +212,19 @@ export default defineNuxtComponent({
|
||||
return date;
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const mealsByDate = computed(() => {
|
||||
const mealsByDate = computed(() => {
|
||||
return days.value.map((day) => {
|
||||
return { date: day, meals: filterMealByDate(day) };
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const hasRecipes = computed(() => {
|
||||
const hasRecipes = computed(() => {
|
||||
return mealsByDate.value.some(day => day.meals.some(meal => meal.recipe));
|
||||
});
|
||||
});
|
||||
|
||||
const weekRecipesWithScales = computed(() => {
|
||||
const weekRecipesWithScales = computed(() => {
|
||||
const allRecipes: any[] = [];
|
||||
for (const day of mealsByDate.value) {
|
||||
for (const meal of day.meals) {
|
||||
@@ -242,38 +237,21 @@ export default defineNuxtComponent({
|
||||
scale: 1,
|
||||
...recipe,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
async function getShoppingLists() {
|
||||
async function getShoppingLists() {
|
||||
const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
|
||||
if (data) {
|
||||
shoppingLists.value = data.items as ShoppingListSummary[] ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addAllToList() {
|
||||
async function addAllToList() {
|
||||
state.value.addAllLoading = true;
|
||||
await getShoppingLists();
|
||||
state.value.shoppingListDialog = true;
|
||||
state.value.addAllLoading = false;
|
||||
}
|
||||
|
||||
return {
|
||||
TABS,
|
||||
route,
|
||||
state,
|
||||
actions,
|
||||
mealsByDate,
|
||||
weekRange,
|
||||
firstDayOfWeek,
|
||||
numberOfDays,
|
||||
hasRecipes,
|
||||
shoppingLists,
|
||||
weekRecipesWithScales,
|
||||
addAllToList,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
@@ -129,9 +129,9 @@
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props }">
|
||||
<template #activator="{ props: menuProps }">
|
||||
<v-chip
|
||||
v-bind="props"
|
||||
v-bind="menuProps"
|
||||
label
|
||||
variant="elevated"
|
||||
size="small"
|
||||
@@ -232,7 +232,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from "date-fns";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
@@ -246,38 +246,27 @@ import { useHouseholdSelf } from "~/composables/use-households";
|
||||
import { normalizeFilter } from "~/composables/use-utils";
|
||||
import { useRecipeSearch } from "~/composables/recipes/use-recipe-search";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
VueDraggable,
|
||||
RecipeCardImage,
|
||||
},
|
||||
props: {
|
||||
mealplans: {
|
||||
type: Array as () => MealsByDate[],
|
||||
required: true,
|
||||
},
|
||||
actions: {
|
||||
type: Object as () => ReturnType<typeof useMealplans>["actions"],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const api = useUserApi();
|
||||
const auth = useMealieAuth();
|
||||
const { household } = useHouseholdSelf();
|
||||
const requiredRule = (value: any) => !!value || "Required.";
|
||||
const props = defineProps<{
|
||||
mealplans: MealsByDate[];
|
||||
actions: ReturnType<typeof useMealplans>["actions"];
|
||||
}>();
|
||||
|
||||
const state = ref({
|
||||
const api = useUserApi();
|
||||
const auth = useMealieAuth();
|
||||
const { household } = useHouseholdSelf();
|
||||
const requiredRule = (value: any) => !!value || "Required.";
|
||||
|
||||
const state = ref({
|
||||
dialog: false,
|
||||
});
|
||||
});
|
||||
|
||||
const firstDayOfWeek = computed(() => {
|
||||
const firstDayOfWeek = computed(() => {
|
||||
return household.value?.preferences?.firstDayOfWeek || 0;
|
||||
});
|
||||
});
|
||||
|
||||
// Local mutable meals object
|
||||
const mealplansByDate = reactive<{ [date: string]: UpdatePlanEntry[] }>({});
|
||||
watch(
|
||||
// Local mutable meals object
|
||||
const mealplansByDate = reactive<{ [date: string]: UpdatePlanEntry[] }>({});
|
||||
watch(
|
||||
() => props.mealplans,
|
||||
(plans) => {
|
||||
for (const plan of plans) {
|
||||
@@ -291,9 +280,9 @@ export default defineNuxtComponent({
|
||||
});
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
);
|
||||
|
||||
function onMoveCallback(evt: SortableEvent) {
|
||||
function onMoveCallback(evt: SortableEvent) {
|
||||
const supportedEvents = ["drop", "touchend"];
|
||||
|
||||
// Adapted From https://github.com/SortableJS/Vue.Draggable/issues/1029
|
||||
@@ -317,24 +306,24 @@ export default defineNuxtComponent({
|
||||
props.actions.updateOne(mealData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// New Meal Dialog
|
||||
// =====================================================
|
||||
// New Meal Dialog
|
||||
|
||||
const dialog = reactive({
|
||||
const dialog = reactive({
|
||||
loading: false,
|
||||
error: false,
|
||||
note: false,
|
||||
});
|
||||
});
|
||||
|
||||
watch(dialog, () => {
|
||||
watch(dialog, () => {
|
||||
if (dialog.note) {
|
||||
newMeal.recipeId = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const newMeal = reactive({
|
||||
const newMeal = reactive({
|
||||
date: new Date(Date.now() - new Date().getTimezoneOffset() * 60000),
|
||||
title: "",
|
||||
text: "",
|
||||
@@ -344,25 +333,25 @@ export default defineNuxtComponent({
|
||||
id: 0,
|
||||
groupId: "",
|
||||
userId: auth.user.value?.id || "",
|
||||
});
|
||||
});
|
||||
|
||||
const newMealDateString = computed(() => {
|
||||
const newMealDateString = computed(() => {
|
||||
return format(newMeal.date, "yyyy-MM-dd");
|
||||
});
|
||||
});
|
||||
|
||||
const isCreateDisabled = computed(() => {
|
||||
const isCreateDisabled = computed(() => {
|
||||
if (dialog.note) {
|
||||
return !newMeal.title.trim();
|
||||
}
|
||||
return !newMeal.recipeId;
|
||||
});
|
||||
});
|
||||
|
||||
function openDialog(date: Date) {
|
||||
function openDialog(date: Date) {
|
||||
newMeal.date = date;
|
||||
state.value.dialog = true;
|
||||
}
|
||||
}
|
||||
|
||||
function editMeal(mealplan: UpdatePlanEntry) {
|
||||
function editMeal(mealplan: UpdatePlanEntry) {
|
||||
const { date, title, text, entryType, recipeId, id, groupId, userId } = mealplan;
|
||||
if (!entryType) return;
|
||||
|
||||
@@ -379,18 +368,18 @@ export default defineNuxtComponent({
|
||||
|
||||
state.value.dialog = true;
|
||||
dialog.note = !recipeId;
|
||||
}
|
||||
}
|
||||
|
||||
function resetDialog() {
|
||||
function resetDialog() {
|
||||
newMeal.date = new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
|
||||
newMeal.title = "";
|
||||
newMeal.text = "";
|
||||
newMeal.entryType = "dinner";
|
||||
newMeal.recipeId = undefined;
|
||||
newMeal.existing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function randomMeal(date: Date, type: PlanEntryType) {
|
||||
async function randomMeal(date: Date, type: PlanEntryType) {
|
||||
const { data } = await api.mealplans.setRandom({
|
||||
date: format(date, "yyyy-MM-dd"),
|
||||
entryType: type,
|
||||
@@ -399,41 +388,15 @@ export default defineNuxtComponent({
|
||||
if (data) {
|
||||
props.actions.refreshAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Search
|
||||
// =====================================================
|
||||
// Search
|
||||
|
||||
const search = useRecipeSearch(api);
|
||||
const planTypeOptions = usePlanTypeOptions();
|
||||
const search = useRecipeSearch(api);
|
||||
const planTypeOptions = usePlanTypeOptions();
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await search.trigger();
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
onMoveCallback,
|
||||
planTypeOptions,
|
||||
getEntryTypeText,
|
||||
requiredRule,
|
||||
isCreateDisabled,
|
||||
normalizeFilter,
|
||||
|
||||
// Dialog
|
||||
dialog,
|
||||
newMeal,
|
||||
newMealDateString,
|
||||
openDialog,
|
||||
editMeal,
|
||||
resetDialog,
|
||||
randomMeal,
|
||||
|
||||
// Search
|
||||
search,
|
||||
firstDayOfWeek,
|
||||
mealplansByDate,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import type { MealsByDate } from "./types";
|
||||
import type { ReadPlanEntry } from "~/lib/api/types/meal-plan";
|
||||
import GroupMealPlanDayContextMenu from "~/components/Domain/Household/GroupMealPlanDayContextMenu.vue";
|
||||
|
||||
@@ -171,65 +171,53 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import type { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
|
||||
import GroupMealPlanRuleForm from "~/components/Domain/Household/GroupMealPlanRuleForm.vue";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
GroupMealPlanRuleForm,
|
||||
RecipeChips,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("meal-plan.meal-plan-settings"),
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================
|
||||
// Manage All
|
||||
const editState = ref<{ [key: string]: boolean }>({});
|
||||
const allRules = ref<PlanRulesOut[]>([]);
|
||||
// ======================================================
|
||||
// Manage All
|
||||
const editState = ref<{ [key: string]: boolean }>({});
|
||||
const allRules = ref<PlanRulesOut[]>([]);
|
||||
|
||||
function toggleEditState(id: string) {
|
||||
function toggleEditState(id: string) {
|
||||
editState.value[id] = !editState.value[id];
|
||||
editState.value = { ...editState.value };
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
async function refreshAll() {
|
||||
const { data } = await api.mealplanRules.getAll();
|
||||
|
||||
if (data) {
|
||||
allRules.value = data.items ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
await refreshAll();
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================
|
||||
// Creating Rules
|
||||
// ======================================================
|
||||
// Creating Rules
|
||||
|
||||
const createDataFormKey = ref(0);
|
||||
const createData = ref<PlanRulesCreate>({
|
||||
const createDataFormKey = ref(0);
|
||||
const createData = ref<PlanRulesCreate>({
|
||||
entryType: "unset",
|
||||
day: "unset",
|
||||
queryFilterString: "",
|
||||
});
|
||||
});
|
||||
|
||||
async function createRule() {
|
||||
async function createRule() {
|
||||
const { data } = await api.mealplanRules.createOne(createData.value);
|
||||
if (data) {
|
||||
refreshAll();
|
||||
@@ -240,33 +228,20 @@ export default defineNuxtComponent({
|
||||
};
|
||||
createDataFormKey.value++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRule(ruleId: string) {
|
||||
async function deleteRule(ruleId: string) {
|
||||
const { data } = await api.mealplanRules.deleteOne(ruleId);
|
||||
if (data) {
|
||||
refreshAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRule(rule: PlanRulesOut) {
|
||||
async function updateRule(rule: PlanRulesOut) {
|
||||
const { data } = await api.mealplanRules.updateOne(rule.id, rule);
|
||||
if (data) {
|
||||
refreshAll();
|
||||
toggleEditState(rule.id);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
allRules,
|
||||
createDataFormKey,
|
||||
createData,
|
||||
createRule,
|
||||
deleteRule,
|
||||
editState,
|
||||
updateRule,
|
||||
toggleEditState,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -115,27 +115,21 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
UserAvatar,
|
||||
},
|
||||
setup() {
|
||||
const auth = useMealieAuth();
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("profile.members"),
|
||||
});
|
||||
});
|
||||
|
||||
const members = ref<UserOut[] | null[]>([]);
|
||||
const members = ref<UserOut[] | null[]>([]);
|
||||
|
||||
const headers = [
|
||||
const headers = [
|
||||
{ title: "", value: "avatar", sortable: false, align: "center" },
|
||||
{ title: i18n.t("user.username"), value: "username" },
|
||||
{ title: i18n.t("user.full-name"), value: "fullName" },
|
||||
@@ -144,16 +138,16 @@ export default defineNuxtComponent({
|
||||
{ title: i18n.t("settings.organize"), value: "organize", sortable: false, align: "center" },
|
||||
{ title: i18n.t("group.invite"), value: "invite", sortable: false, align: "center" },
|
||||
{ title: i18n.t("group.manage-household"), value: "manageHousehold", sortable: false, align: "center" },
|
||||
];
|
||||
];
|
||||
|
||||
async function refreshMembers() {
|
||||
async function refreshMembers() {
|
||||
const { data } = await api.households.fetchMembers();
|
||||
if (data) {
|
||||
members.value = data.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setPermissions(user: UserOut) {
|
||||
async function setPermissions(user: UserOut) {
|
||||
const payload = {
|
||||
userId: user.id,
|
||||
canInvite: user.canInvite,
|
||||
@@ -163,13 +157,9 @@ export default defineNuxtComponent({
|
||||
};
|
||||
|
||||
await api.households.setMemberPermissions(payload);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await refreshMembers();
|
||||
});
|
||||
|
||||
return { members, headers, setPermissions, sessionUser: auth.user };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<v-container class="narrow-container">
|
||||
<BaseDialog
|
||||
v-model="deleteDialog"
|
||||
v-model="state.deleteDialog"
|
||||
color="error"
|
||||
:title="$t('general.confirm')"
|
||||
:icon="$globals.icons.alertCircle"
|
||||
can-confirm
|
||||
@confirm="deleteNotifier(deleteTargetId)"
|
||||
@confirm="deleteNotifier(state.deleteTargetId)"
|
||||
>
|
||||
<v-card-text>
|
||||
{{ $t("general.confirm-delete-generic") }}
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
<BaseDialog
|
||||
v-model="createDialog"
|
||||
v-model="state.createDialog"
|
||||
:title="$t('events.new-notification')"
|
||||
:icon="$globals.icons.bellPlus"
|
||||
can-submit
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
<BaseButton
|
||||
create
|
||||
@click="createDialog = true"
|
||||
@click="state.createDialog = true"
|
||||
/>
|
||||
<v-expansion-panels
|
||||
v-if="notifiers"
|
||||
@@ -182,7 +182,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import type { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/household";
|
||||
@@ -198,67 +198,68 @@ interface OptionSection {
|
||||
options: OptionKey[];
|
||||
}
|
||||
|
||||
export default defineNuxtComponent({
|
||||
definePageMeta({
|
||||
middleware: ["advanced-only"],
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const api = useUserApi();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("profile.notifiers"),
|
||||
});
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
deleteDialog: false,
|
||||
createDialog: false,
|
||||
deleteTargetId: "",
|
||||
});
|
||||
});
|
||||
|
||||
const { data: notifiers } = useAsyncData(useAsyncKey(), async () => {
|
||||
const { data: notifiers } = useAsyncData(useAsyncKey(), async () => {
|
||||
const { data } = await api.groupEventNotifier.getAll();
|
||||
return data?.items;
|
||||
});
|
||||
});
|
||||
|
||||
async function refreshNotifiers() {
|
||||
async function refreshNotifiers() {
|
||||
const { data } = await api.groupEventNotifier.getAll();
|
||||
notifiers.value = data?.items;
|
||||
}
|
||||
}
|
||||
|
||||
const createNotifierData: GroupEventNotifierCreate = reactive({
|
||||
const createNotifierData: GroupEventNotifierCreate = reactive({
|
||||
name: "",
|
||||
enabled: true,
|
||||
appriseUrl: "",
|
||||
});
|
||||
});
|
||||
|
||||
async function createNewNotifier() {
|
||||
async function createNewNotifier() {
|
||||
await api.groupEventNotifier.createOne(createNotifierData);
|
||||
refreshNotifiers();
|
||||
}
|
||||
}
|
||||
|
||||
function openDelete(notifier: GroupEventNotifierOut) {
|
||||
function openDelete(notifier: GroupEventNotifierOut) {
|
||||
state.deleteDialog = true;
|
||||
state.deleteTargetId = notifier.id;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteNotifier(targetId: string) {
|
||||
async function deleteNotifier(targetId: string) {
|
||||
await api.groupEventNotifier.deleteOne(targetId);
|
||||
refreshNotifiers();
|
||||
state.deleteTargetId = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function saveNotifier(notifier: GroupEventNotifierOut) {
|
||||
async function saveNotifier(notifier: GroupEventNotifierOut) {
|
||||
await api.groupEventNotifier.updateOne(notifier.id, notifier);
|
||||
refreshNotifiers();
|
||||
}
|
||||
}
|
||||
|
||||
async function testNotifier(notifier: GroupEventNotifierOut) {
|
||||
async function testNotifier(notifier: GroupEventNotifierOut) {
|
||||
await api.groupEventNotifier.test(notifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// Options Definitions
|
||||
// ===============================================================
|
||||
// Options Definitions
|
||||
|
||||
const optionsSections: OptionSection[] = [
|
||||
const optionsSections: OptionSection[] = [
|
||||
{
|
||||
id: 1,
|
||||
text: i18n.t("events.recipe-events"),
|
||||
@@ -387,21 +388,7 @@ export default defineNuxtComponent({
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
openDelete,
|
||||
notifiers,
|
||||
createNotifierData,
|
||||
optionsSections,
|
||||
deleteNotifier,
|
||||
testNotifier,
|
||||
saveNotifier,
|
||||
createNewNotifier,
|
||||
};
|
||||
},
|
||||
});
|
||||
];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -68,28 +68,19 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks";
|
||||
import GroupWebhookEditor from "~/components/Domain/Household/GroupWebhookEditor.vue";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { GroupWebhookEditor },
|
||||
definePageMeta({
|
||||
middleware: ["advanced-only"],
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const { actions, webhooks } = useGroupWebhooks();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const { actions, webhooks } = useGroupWebhooks();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("settings.webhooks.webhooks"),
|
||||
});
|
||||
|
||||
return {
|
||||
alert,
|
||||
webhooks,
|
||||
actions,
|
||||
timeUTC,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,26 +2,24 @@
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import useDefaultActivity from "~/composables/use-default-activity";
|
||||
import { useUserActivityPreferences } from "~/composables/use-users/preferences";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import type { AppInfo, AppStartupInfo } from "~/lib/api/types/admin";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
});
|
||||
|
||||
const auth = useMealieAuth();
|
||||
const { $axios } = useNuxtApp();
|
||||
const router = useRouter();
|
||||
const activityPreferences = useUserActivityPreferences();
|
||||
const { getDefaultActivityRoute } = useDefaultActivity();
|
||||
const groupSlug = computed(() => auth.user.value?.groupSlug);
|
||||
const auth = useMealieAuth();
|
||||
const { $axios } = useNuxtApp();
|
||||
const router = useRouter();
|
||||
const activityPreferences = useUserActivityPreferences();
|
||||
const { getDefaultActivityRoute } = useDefaultActivity();
|
||||
const groupSlug = computed(() => auth.user.value?.groupSlug);
|
||||
|
||||
async function redirectPublicUserToDefaultGroup() {
|
||||
async function redirectPublicUserToDefaultGroup() {
|
||||
const { data } = await $axios.get<AppInfo>("/api/app/about");
|
||||
if (data?.defaultGroupSlug) {
|
||||
router.push(`/g/${data.defaultGroupSlug}`);
|
||||
@@ -29,9 +27,9 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
router.push("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
if (groupSlug.value) {
|
||||
const data = await $axios.get<AppStartupInfo>("/api/app/about/startup-info");
|
||||
const isDemo = data.data.isDemo;
|
||||
@@ -53,7 +51,5 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
redirectPublicUserToDefaultGroup();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useDark, whenever } from "@vueuse/core";
|
||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||
import { usePasswordField } from "~/composables/use-passwords";
|
||||
@@ -219,35 +219,33 @@ import { useAsyncKey } from "~/composables/use-utils";
|
||||
import type { AppStartupInfo } from "~/lib/api/types/admin";
|
||||
import { useUserActivityPreferences } from "~/composables/use-users/preferences";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
const isDark = useDark();
|
||||
});
|
||||
const isDark = useDark();
|
||||
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $appInfo, $axios } = useNuxtApp();
|
||||
const { loggedIn } = useLoggedInState();
|
||||
const groupSlug = computed(() => auth.user.value?.groupSlug);
|
||||
const isDemo = ref(false);
|
||||
const isFirstLogin = ref(false);
|
||||
const activityPreferences = useUserActivityPreferences();
|
||||
const { getDefaultActivityRoute } = useDefaultActivity();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $appInfo, $axios } = useNuxtApp();
|
||||
const { loggedIn } = useLoggedInState();
|
||||
const groupSlug = computed(() => auth.user.value?.groupSlug);
|
||||
const isDemo = ref(false);
|
||||
const isFirstLogin = ref(false);
|
||||
const activityPreferences = useUserActivityPreferences();
|
||||
const { getDefaultActivityRoute } = useDefaultActivity();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("user.login"),
|
||||
});
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
const form = reactive({
|
||||
email: "",
|
||||
password: "",
|
||||
remember: false,
|
||||
});
|
||||
});
|
||||
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
const data = await $axios.get<AppStartupInfo>("/api/app/about/startup-info");
|
||||
isDemo.value = data.data.isDemo;
|
||||
isFirstLogin.value = data.data.isFirstLogin;
|
||||
@@ -256,9 +254,9 @@ export default defineNuxtComponent({
|
||||
form.email = "changeme@example.com";
|
||||
form.password = "MyPassword";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
whenever(
|
||||
whenever(
|
||||
() => loggedIn.value && groupSlug.value,
|
||||
() => {
|
||||
const defaultActivityRoute = getDefaultActivityRoute(
|
||||
@@ -276,36 +274,36 @@ export default defineNuxtComponent({
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
);
|
||||
|
||||
const loggingIn = ref(false);
|
||||
const oidcLoggingIn = ref(false);
|
||||
const loggingIn = ref(false);
|
||||
const oidcLoggingIn = ref(false);
|
||||
|
||||
const { passwordIcon, inputType, togglePasswordShow } = usePasswordField();
|
||||
const { passwordIcon, inputType, togglePasswordShow } = usePasswordField();
|
||||
|
||||
whenever(
|
||||
whenever(
|
||||
() => $appInfo.enableOidc && $appInfo.oidcRedirect && !isCallback() && !isDirectLogin() /* && !auth.check().valid */,
|
||||
() => oidcAuthenticate(),
|
||||
{ immediate: true },
|
||||
);
|
||||
);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
onBeforeMount(async () => {
|
||||
if (isCallback()) {
|
||||
await oidcAuthenticate(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function isCallback() {
|
||||
function isCallback() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.has("code") || params.has("error");
|
||||
}
|
||||
}
|
||||
|
||||
function isDirectLogin() {
|
||||
function isDirectLogin() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.has("direct") && params.get("direct") === "1";
|
||||
}
|
||||
}
|
||||
|
||||
async function oidcAuthenticate(callback = false) {
|
||||
async function oidcAuthenticate(callback = false) {
|
||||
if (callback) {
|
||||
oidcLoggingIn.value = true;
|
||||
try {
|
||||
@@ -320,9 +318,9 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
navigateTo("/api/auth/oauth", { external: true }); // start the redirect process
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticate() {
|
||||
async function authenticate() {
|
||||
if (form.email.length === 0 || form.password.length === 0) {
|
||||
alert.error(i18n.t("user.please-enter-your-email-and-password"));
|
||||
return;
|
||||
@@ -342,9 +340,9 @@ export default defineNuxtComponent({
|
||||
alertOnError(error);
|
||||
}
|
||||
loggingIn.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function alertOnError(error: any) {
|
||||
function alertOnError(error: any) {
|
||||
// TODO Check if error is an AxiosError, but isAxiosError is not working right now
|
||||
// See https://github.com/nuxt-community/axios-module/issues/550
|
||||
// Import $axios from useContext()
|
||||
@@ -358,22 +356,7 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isDark,
|
||||
form,
|
||||
loggingIn,
|
||||
authenticate,
|
||||
oidcAuthenticate,
|
||||
oidcLoggingIn,
|
||||
passwordIcon,
|
||||
inputType,
|
||||
togglePasswordShow,
|
||||
isFirstLogin,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -298,7 +298,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useDark } from "@vueuse/core";
|
||||
import { States, RegistrationType, useRegistration } from "./states";
|
||||
import { useUserRegistrationForm } from "~/composables/use-users/user-registration-form";
|
||||
@@ -307,50 +307,39 @@ import { validators, useAsyncValidator } from "~/composables/use-validators";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import type { CreateUserRegistration } from "~/lib/api/types/user";
|
||||
import { usePasswordField } from "~/composables/use-passwords";
|
||||
import { usePublicApi } from "~/composables/api/api-client";
|
||||
import { useLocales } from "~/composables/use-locales";
|
||||
import UserRegistrationForm from "~/components/Domain/User/UserRegistrationForm.vue";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
|
||||
const inputAttrs = {
|
||||
variant: "filled",
|
||||
validateOnBlur: true,
|
||||
};
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { UserRegistrationForm },
|
||||
setup() {
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
const i18n = useI18n();
|
||||
const isDark = useDark();
|
||||
|
||||
const i18n = useI18n();
|
||||
const isDark = useDark();
|
||||
|
||||
function safeValidate(form: Ref<VForm | null>) {
|
||||
function safeValidate(form: Ref<VForm | null>) {
|
||||
if (form.value && form.value.validate) {
|
||||
return form.value.validate();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// ================================================================
|
||||
// Registration Context
|
||||
//
|
||||
// State is used to manage the registration process states and provide
|
||||
// a state machine esq interface to interact with the registration workflow.
|
||||
const state = useRegistration();
|
||||
// ================================================================
|
||||
// Handle Token URL / Initialization
|
||||
//
|
||||
const token = useRouteQuery("token");
|
||||
// TODO: We need to have some way to check to see if the site is in a state
|
||||
// Where it needs to be initialized with a user, in that case we'll handle that
|
||||
// somewhere...
|
||||
function initialUser() {
|
||||
}
|
||||
|
||||
// Registration Context
|
||||
const state = useRegistration();
|
||||
|
||||
// Handle Token URL / Initialization
|
||||
const token = useRouteQuery("token");
|
||||
function initialUser() {
|
||||
return false;
|
||||
}
|
||||
onMounted(() => {
|
||||
}
|
||||
onMounted(() => {
|
||||
if (token.value) {
|
||||
state.setState(States.ProvideAccountDetails);
|
||||
state.setType(RegistrationType.JoinGroup);
|
||||
@@ -359,10 +348,10 @@ export default defineNuxtComponent({
|
||||
state.setState(States.ProvideGroupDetails);
|
||||
state.setType(RegistrationType.InitialGroup);
|
||||
}
|
||||
});
|
||||
// ================================================================
|
||||
// Initial
|
||||
const initial = {
|
||||
});
|
||||
|
||||
// Initial
|
||||
const initial = {
|
||||
createGroup: () => {
|
||||
state.setState(States.ProvideGroupDetails);
|
||||
state.setType(RegistrationType.CreateGroup);
|
||||
@@ -374,14 +363,14 @@ export default defineNuxtComponent({
|
||||
state.setState(States.ProvideToken);
|
||||
state.setType(RegistrationType.JoinGroup);
|
||||
},
|
||||
};
|
||||
// ================================================================
|
||||
// Provide Token
|
||||
const domTokenForm = ref<VForm | null>(null);
|
||||
function validateToken() {
|
||||
};
|
||||
|
||||
// Provide Token
|
||||
const domTokenForm = ref<VForm | null>(null);
|
||||
function validateToken() {
|
||||
return true;
|
||||
}
|
||||
const provideToken = {
|
||||
}
|
||||
const provideToken = {
|
||||
next: () => {
|
||||
if (!safeValidate(domTokenForm as Ref<VForm>)) {
|
||||
return;
|
||||
@@ -390,22 +379,22 @@ export default defineNuxtComponent({
|
||||
state.setState(States.ProvideAccountDetails);
|
||||
}
|
||||
},
|
||||
};
|
||||
// ================================================================
|
||||
// Provide Group Details
|
||||
const publicApi = usePublicApi();
|
||||
const domGroupForm = ref<VForm | null>(null);
|
||||
const groupName = ref("");
|
||||
const groupSeed = ref(false);
|
||||
const groupPrivate = ref(false);
|
||||
const groupErrorMessages = ref<string[]>([]);
|
||||
const { validate: validGroupName, valid: groupNameValid } = useAsyncValidator(
|
||||
};
|
||||
|
||||
// Provide Group Details
|
||||
const publicApi = usePublicApi();
|
||||
const domGroupForm = ref<VForm | null>(null);
|
||||
const groupName = ref("");
|
||||
const groupSeed = ref(false);
|
||||
const groupPrivate = ref(false);
|
||||
const groupErrorMessages = ref<string[]>([]);
|
||||
const { validate: validGroupName, valid: groupNameValid } = useAsyncValidator(
|
||||
groupName,
|
||||
(v: string) => publicApi.validators.group(v),
|
||||
i18n.t("validation.group-name-is-taken"),
|
||||
groupErrorMessages,
|
||||
);
|
||||
const groupDetails = {
|
||||
);
|
||||
const groupDetails = {
|
||||
groupName,
|
||||
groupSeed,
|
||||
groupPrivate,
|
||||
@@ -415,30 +404,26 @@ export default defineNuxtComponent({
|
||||
}
|
||||
state.setState(States.ProvideAccountDetails);
|
||||
},
|
||||
};
|
||||
const pwFields = usePasswordField();
|
||||
const {
|
||||
};
|
||||
|
||||
const {
|
||||
accountDetails,
|
||||
credentials,
|
||||
domAccountForm,
|
||||
emailErrorMessages,
|
||||
usernameErrorMessages,
|
||||
validateUsername,
|
||||
validateEmail,
|
||||
} = useUserRegistrationForm();
|
||||
async function accountDetailsNext() {
|
||||
|
||||
} = useUserRegistrationForm();
|
||||
async function accountDetailsNext() {
|
||||
if (!await accountDetails.validate()) {
|
||||
return;
|
||||
}
|
||||
state.setState(States.Confirmation);
|
||||
}
|
||||
// ================================================================
|
||||
// Locale
|
||||
const { locale } = useLocales();
|
||||
const langDialog = ref(false);
|
||||
// ================================================================
|
||||
// Confirmation
|
||||
const confirmationData = computed(() => {
|
||||
}
|
||||
|
||||
// Locale
|
||||
const { locale } = useLocales();
|
||||
const langDialog = ref(false);
|
||||
|
||||
// Confirmation
|
||||
const confirmationData = computed(() => {
|
||||
return [
|
||||
{
|
||||
display: state.ctx.type === RegistrationType.CreateGroup,
|
||||
@@ -476,10 +461,11 @@ export default defineNuxtComponent({
|
||||
value: accountDetails.advancedOptions.value ? i18n.t("general.yes") : i18n.t("general.no"),
|
||||
},
|
||||
];
|
||||
});
|
||||
const api = useUserApi();
|
||||
const router = useRouter();
|
||||
async function submitRegistration() {
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
const router = useRouter();
|
||||
async function submitRegistration() {
|
||||
const payload: CreateUserRegistration = {
|
||||
email: accountDetails.email.value,
|
||||
username: accountDetails.username.value,
|
||||
@@ -507,39 +493,7 @@ export default defineNuxtComponent({
|
||||
else {
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
}
|
||||
}
|
||||
return {
|
||||
accountDetails,
|
||||
accountDetailsNext,
|
||||
confirmationData,
|
||||
credentials,
|
||||
emailErrorMessages,
|
||||
groupDetails,
|
||||
groupErrorMessages,
|
||||
initial,
|
||||
inputAttrs,
|
||||
isDark,
|
||||
langDialog,
|
||||
provideToken,
|
||||
pwFields,
|
||||
RegistrationType,
|
||||
state,
|
||||
States,
|
||||
token,
|
||||
usernameErrorMessages,
|
||||
validators,
|
||||
submitRegistration,
|
||||
// Validators
|
||||
validGroupName,
|
||||
validateUsername,
|
||||
validateEmail,
|
||||
// Dom Refs
|
||||
domAccountForm,
|
||||
domGroupForm,
|
||||
domTokenForm,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="requestLink()">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
v-model="state.email"
|
||||
:prepend-inner-icon="$globals.icons.email"
|
||||
variant="solo-filled"
|
||||
flat
|
||||
@@ -26,7 +26,7 @@
|
||||
type="text"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
v-model="state.password"
|
||||
variant="solo-filled"
|
||||
flat
|
||||
:prepend-inner-icon="$globals.icons.lock"
|
||||
@@ -36,7 +36,7 @@
|
||||
:rules="[validators.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="passwordConfirm"
|
||||
v-model="state.passwordConfirm"
|
||||
variant="solo-filled"
|
||||
flat
|
||||
validate-on="blur"
|
||||
@@ -52,7 +52,7 @@
|
||||
<v-card-actions class="justify-center">
|
||||
<div class="max-button">
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
:loading="state.loading"
|
||||
color="primary"
|
||||
:disabled="token === ''"
|
||||
type="submit"
|
||||
@@ -81,42 +81,40 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { validators } from "@/composables/use-validators";
|
||||
import { useRouteQuery } from "~/composables/use-router";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
layout: "basic",
|
||||
});
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
email: "",
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
const passwordMatch = () => state.password === state.passwordConfirm || i18n.t("user.password-must-match");
|
||||
const i18n = useI18n();
|
||||
const passwordMatch = () => state.password === state.passwordConfirm || i18n.t("user.password-must-match");
|
||||
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
// Set page title
|
||||
useSeoMeta({
|
||||
title: i18n.t("user.login"),
|
||||
});
|
||||
});
|
||||
|
||||
// ===================
|
||||
// Token Getter
|
||||
const token = useRouteQuery("token", "");
|
||||
// ===================
|
||||
// Token Getter
|
||||
const token = useRouteQuery("token", "");
|
||||
|
||||
// ===================
|
||||
// API
|
||||
const api = useUserApi();
|
||||
async function requestLink() {
|
||||
// ===================
|
||||
// API
|
||||
const api = useUserApi();
|
||||
async function requestLink() {
|
||||
state.loading = true;
|
||||
// TODO: Fix Response to send meaningful error
|
||||
const { response } = await api.users.resetPassword({
|
||||
@@ -138,17 +136,7 @@ export default defineNuxtComponent({
|
||||
state.error = true;
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
passwordMatch,
|
||||
token,
|
||||
requestLink,
|
||||
validators,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
|
||||
import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue";
|
||||
@@ -339,48 +339,56 @@ import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.
|
||||
import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
|
||||
import { useShoppingListPage } from "~/composables/shopping-list-page/use-shopping-list-page";
|
||||
import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
|
||||
import { getTextColor } from "~/composables/use-text-color";
|
||||
import { useShoppingListPreferences } from "~/composables/use-users/preferences";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
VueDraggable,
|
||||
MultiPurposeLabelSection,
|
||||
ShoppingListItem,
|
||||
RecipeList,
|
||||
ShoppingListItemEditor,
|
||||
},
|
||||
setup() {
|
||||
const { mdAndUp } = useDisplay();
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const preferences = useShoppingListPreferences();
|
||||
const { mdAndUp } = useDisplay();
|
||||
const i18n = useI18n();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("shopping-list.shopping-list"),
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
|
||||
const id = route.params.id as string;
|
||||
|
||||
const shoppingListPage = useShoppingListPage(id);
|
||||
const { store: allLabels } = useLabelStore();
|
||||
const { store: allUnits } = useUnitStore();
|
||||
const { store: allFoods } = useFoodStore();
|
||||
|
||||
return {
|
||||
groupSlug,
|
||||
preferences,
|
||||
allLabels,
|
||||
allUnits,
|
||||
allFoods,
|
||||
getTextColor,
|
||||
mdAndUp,
|
||||
...shoppingListPage,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const id = route.params.id as string;
|
||||
|
||||
const shoppingListPage = useShoppingListPage(id);
|
||||
const { store: allLabels } = useLabelStore();
|
||||
const { store: allUnits } = useUnitStore();
|
||||
const { store: allFoods } = useFoodStore();
|
||||
|
||||
const {
|
||||
shoppingList,
|
||||
state,
|
||||
checkAll,
|
||||
uncheckAll,
|
||||
deleteChecked,
|
||||
reorderLabelsDialog,
|
||||
localLabels,
|
||||
saveLabelOrder,
|
||||
cancelLabelOrder,
|
||||
updateLabelOrder,
|
||||
edit,
|
||||
threeDot,
|
||||
openCheckAll,
|
||||
copyListItems,
|
||||
toggleReorderLabelsDialog,
|
||||
isOffline,
|
||||
createEditorOpen,
|
||||
createListItemData,
|
||||
createListItem,
|
||||
itemsByLabel,
|
||||
getLabelColor,
|
||||
loadingCounter,
|
||||
updateIndexUncheckedByLabel,
|
||||
recipeMap,
|
||||
saveListItem,
|
||||
deleteListItem,
|
||||
listItems,
|
||||
openUncheckAll,
|
||||
openDeleteChecked,
|
||||
recipeList,
|
||||
removeRecipeReferenceToList,
|
||||
addRecipeReferenceToList,
|
||||
} = shoppingListPage;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
class="narrow-container"
|
||||
>
|
||||
<BaseDialog
|
||||
v-model="createDialog"
|
||||
v-model="state.createDialog"
|
||||
:title="$t('shopping-list.create-shopping-list')"
|
||||
can-submit
|
||||
@submit="createOne"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="createName"
|
||||
v-model="state.createName"
|
||||
autofocus
|
||||
:label="$t('shopping-list.new-list')"
|
||||
/>
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<!-- Settings -->
|
||||
<BaseDialog
|
||||
v-model="ownerDialog"
|
||||
v-model="state.ownerDialog"
|
||||
:icon="$globals.icons.admin"
|
||||
:title="$t('user.edit-user')"
|
||||
can-confirm
|
||||
@@ -41,7 +41,7 @@
|
||||
</BaseDialog>
|
||||
|
||||
<BaseDialog
|
||||
v-model="deleteDialog"
|
||||
v-model="state.deleteDialog"
|
||||
:title="$t('general.confirm')"
|
||||
color="error"
|
||||
can-confirm
|
||||
@@ -73,7 +73,7 @@
|
||||
<BaseButton
|
||||
create
|
||||
class="my-0"
|
||||
@click="createDialog = true"
|
||||
@click="state.createDialog = true"
|
||||
/>
|
||||
</v-container>
|
||||
|
||||
@@ -123,60 +123,57 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { ShoppingListOut } from "~/lib/api/types/household";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import { useShoppingListPreferences } from "~/composables/use-users/preferences";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
const auth = useMealieAuth();
|
||||
const i18n = useI18n();
|
||||
const ready = ref(false);
|
||||
const userApi = useUserApi();
|
||||
const route = useRoute();
|
||||
const auth = useMealieAuth();
|
||||
const i18n = useI18n();
|
||||
const ready = ref(false);
|
||||
const userApi = useUserApi();
|
||||
const route = useRoute();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("shopping-list.shopping-list"),
|
||||
});
|
||||
});
|
||||
|
||||
const groupSlug = computed(() => route.params.groupSlug || auth.user.value?.groupSlug || "");
|
||||
const overrideDisableRedirect = ref(false);
|
||||
const disableRedirect = computed(() => route.query.disableRedirect === "true" || overrideDisableRedirect.value);
|
||||
const preferences = useShoppingListPreferences();
|
||||
const overrideDisableRedirect = ref(false);
|
||||
const disableRedirect = computed(() => route.query.disableRedirect === "true" || overrideDisableRedirect.value);
|
||||
const preferences = useShoppingListPreferences();
|
||||
|
||||
const state = reactive({
|
||||
const state = reactive({
|
||||
createName: "",
|
||||
createDialog: false,
|
||||
deleteDialog: false,
|
||||
deleteTarget: "",
|
||||
ownerDialog: false,
|
||||
ownerTarget: ref<ShoppingListOut | null>(null),
|
||||
});
|
||||
});
|
||||
|
||||
const { data: shoppingLists } = useAsyncData(useAsyncKey(), async () => {
|
||||
const { data: shoppingLists } = useAsyncData(useAsyncKey(), async () => {
|
||||
return await fetchShoppingLists();
|
||||
});
|
||||
});
|
||||
|
||||
const shoppingListChoices = computed(() => {
|
||||
const shoppingListChoices = computed(() => {
|
||||
if (!shoppingLists.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return shoppingLists.value.filter(list => preferences.value.viewAllLists || list.userId === auth.user.value?.id);
|
||||
});
|
||||
});
|
||||
|
||||
// This has to appear before the shoppingListChoices watcher, otherwise that runs first and the redirect is not disabled
|
||||
watch(
|
||||
// This has to appear before the shoppingListChoices watcher, otherwise that runs first and the redirect is not disabled
|
||||
watch(
|
||||
() => preferences.value.viewAllLists,
|
||||
() => {
|
||||
overrideDisableRedirect.value = true;
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => shoppingListChoices,
|
||||
() => {
|
||||
if (!disableRedirect.value && shoppingListChoices.value.length === 1) {
|
||||
@@ -189,9 +186,9 @@ export default defineNuxtComponent({
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
async function fetchShoppingLists() {
|
||||
async function fetchShoppingLists() {
|
||||
const { data } = await userApi.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
|
||||
|
||||
if (!data) {
|
||||
@@ -199,35 +196,35 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
return data.items;
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
async function refresh() {
|
||||
shoppingLists.value = await fetchShoppingLists();
|
||||
}
|
||||
}
|
||||
|
||||
async function createOne() {
|
||||
async function createOne() {
|
||||
const { data } = await userApi.shopping.lists.createOne({ name: state.createName });
|
||||
|
||||
if (data) {
|
||||
refresh();
|
||||
state.createName = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleOwnerDialog(list: ShoppingListOut) {
|
||||
async function toggleOwnerDialog(list: ShoppingListOut) {
|
||||
if (!state.ownerDialog) {
|
||||
state.ownerTarget = list;
|
||||
await fetchAllUsers();
|
||||
}
|
||||
state.ownerDialog = !state.ownerDialog;
|
||||
}
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// Shopping List Edit User/Owner
|
||||
// ===============================================================
|
||||
// Shopping List Edit User/Owner
|
||||
|
||||
const allUsers = ref<UserOut[]>([]);
|
||||
const updateUserId = ref<string | undefined>();
|
||||
async function fetchAllUsers() {
|
||||
const allUsers = ref<UserOut[]>([]);
|
||||
const updateUserId = ref<string | undefined>();
|
||||
async function fetchAllUsers() {
|
||||
const { data } = await userApi.households.fetchMembers();
|
||||
if (!data) {
|
||||
return;
|
||||
@@ -236,9 +233,9 @@ export default defineNuxtComponent({
|
||||
// update current user
|
||||
allUsers.value = data.items.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1));
|
||||
updateUserId.value = state.ownerTarget?.userId;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateOwner() {
|
||||
async function updateOwner() {
|
||||
if (!state.ownerTarget || !updateUserId.value) {
|
||||
return;
|
||||
}
|
||||
@@ -259,34 +256,17 @@ export default defineNuxtComponent({
|
||||
if (data) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openDelete(id: string) {
|
||||
function openDelete(id: string) {
|
||||
state.deleteDialog = true;
|
||||
state.deleteTarget = id;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteOne() {
|
||||
async function deleteOne() {
|
||||
const { data } = await userApi.shopping.lists.deleteOne(state.deleteTarget);
|
||||
if (data) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
ready,
|
||||
groupSlug,
|
||||
preferences,
|
||||
shoppingListChoices,
|
||||
createOne,
|
||||
toggleOwnerDialog,
|
||||
allUsers,
|
||||
updateUserId,
|
||||
updateOwner,
|
||||
deleteOne,
|
||||
openDelete,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,37 +14,22 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||
import { useLazyRecipes } from "~/composables/recipes";
|
||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { RecipeCardSection },
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("general.favorites"),
|
||||
});
|
||||
|
||||
const userId = route.params.id;
|
||||
const query = { queryFilter: `favoritedBy.id = "${userId}"` };
|
||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes();
|
||||
|
||||
return {
|
||||
query,
|
||||
recipes,
|
||||
isOwnGroup,
|
||||
appendRecipes,
|
||||
assignSorted,
|
||||
removeRecipe,
|
||||
replaceRecipes,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const userId = route.params.id;
|
||||
const query = { queryFilter: `favoritedBy.id = "${userId}"` };
|
||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes();
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -106,40 +106,41 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
definePageMeta({
|
||||
middleware: ["advanced-only"],
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("settings.token.api-tokens"),
|
||||
});
|
||||
});
|
||||
|
||||
const user = computed(() => {
|
||||
const user = computed(() => {
|
||||
return auth.user.value;
|
||||
});
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
const api = useUserApi();
|
||||
|
||||
const domNewTokenForm = ref<VForm | null>(null);
|
||||
const domNewTokenForm = ref<VForm | null>(null);
|
||||
|
||||
const createdToken = ref("");
|
||||
const name = ref("");
|
||||
const loading = ref(false);
|
||||
const createdToken = ref("");
|
||||
const name = ref("");
|
||||
const loading = ref(false);
|
||||
|
||||
function resetCreate() {
|
||||
function resetCreate() {
|
||||
createdToken.value = "";
|
||||
loading.value = false;
|
||||
name.value = "";
|
||||
auth.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async function createToken(name: string) {
|
||||
async function createToken(name: string) {
|
||||
if (loading.value) {
|
||||
resetCreate();
|
||||
return;
|
||||
@@ -157,15 +158,11 @@ export default defineNuxtComponent({
|
||||
if (data) {
|
||||
createdToken.value = data.token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteToken(id: number) {
|
||||
async function deleteToken(id: number) {
|
||||
const { data } = await api.users.deleteAPIToken(id);
|
||||
auth.refresh();
|
||||
return data;
|
||||
}
|
||||
|
||||
return { createToken, deleteToken, createdToken, loading, name, user, resetCreate };
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
<section class="mt-5">
|
||||
<ToggleState tag="article">
|
||||
<template #activator="{ toggle, state }">
|
||||
<template #activator="{ toggle, modelValue: toggleState }">
|
||||
<v-btn
|
||||
v-if="!state && $appInfo.allowPasswordLogin"
|
||||
v-if="!toggleState && $appInfo.allowPasswordLogin"
|
||||
color="info"
|
||||
class="mt-2 mb-n3"
|
||||
@click="toggle"
|
||||
@@ -48,13 +48,13 @@
|
||||
{{ $t("settings.profile") }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<template #default="{ state }">
|
||||
<template #default="{ modelValue: toggleState }">
|
||||
<v-slide-x-transition
|
||||
leave-absolute
|
||||
hide-on-leave
|
||||
>
|
||||
<div
|
||||
v-if="!state"
|
||||
v-if="!toggleState"
|
||||
key="personal-info"
|
||||
>
|
||||
<BaseCardSectionTitle
|
||||
@@ -214,64 +214,74 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||
import UserPasswordStrength from "~/components/Domain/User/UserPasswordStrength.vue";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import type { VForm } from "~/types/auto-forms";
|
||||
import { useUserActivityPreferences } from "~/composables/use-users/preferences";
|
||||
import useDefaultActivity from "~/composables/use-default-activity";
|
||||
import { ActivityKey } from "~/lib/api/types/activity";
|
||||
import type { UserBase } from "~/lib/api/types/user";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
UserAvatar,
|
||||
UserPasswordStrength,
|
||||
},
|
||||
setup() {
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { getDefaultActivityLabels, getActivityLabel, getActivityKey } = useDefaultActivity();
|
||||
const user = computed(() => auth.user.value);
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { getDefaultActivityLabels, getActivityLabel, getActivityKey } = useDefaultActivity();
|
||||
const user = computed(() => auth.user.value);
|
||||
|
||||
useSeoMeta({
|
||||
useSeoMeta({
|
||||
title: i18n.t("settings.profile"),
|
||||
});
|
||||
});
|
||||
|
||||
const activityPreferences = useUserActivityPreferences();
|
||||
const activityOptions = getDefaultActivityLabels(i18n);
|
||||
const selectedDefaultActivity = ref(getActivityLabel(i18n, activityPreferences.value.defaultActivity));
|
||||
watch(selectedDefaultActivity, () => {
|
||||
const activityPreferences = useUserActivityPreferences();
|
||||
const activityOptions = getDefaultActivityLabels(i18n);
|
||||
const selectedDefaultActivity = ref(getActivityLabel(i18n, activityPreferences.value.defaultActivity));
|
||||
watch(selectedDefaultActivity, () => {
|
||||
activityPreferences.value.defaultActivity = getActivityKey(i18n, selectedDefaultActivity.value) ?? ActivityKey.RECIPES;
|
||||
});
|
||||
});
|
||||
|
||||
watch(user, () => {
|
||||
const userCopy = ref({ ...user.value });
|
||||
watch(user, () => {
|
||||
userCopy.value = { ...user.value };
|
||||
});
|
||||
});
|
||||
|
||||
const userCopy = ref({ ...user.value });
|
||||
|
||||
const api = useUserApi();
|
||||
|
||||
const domUpdatePassword = ref<VForm | null>(null);
|
||||
const password = reactive({
|
||||
const api = useUserApi();
|
||||
const showPassword = ref(false);
|
||||
const password = reactive({
|
||||
current: "",
|
||||
newOne: "",
|
||||
newTwo: "",
|
||||
});
|
||||
});
|
||||
|
||||
const passwordsMatch = computed(() => password.newOne === password.newTwo && password.newOne.length > 0);
|
||||
const passwordsMatch = computed(() => password.newOne === password.newTwo && password.newOne.length > 0);
|
||||
|
||||
async function updateUser() {
|
||||
if (!userCopy.value?.id) return;
|
||||
const { response } = await api.users.updateOne(userCopy.value.id, userCopy.value);
|
||||
async function updateUser() {
|
||||
const userData = userCopy.value;
|
||||
if (!userData?.id || !userData.email) return;
|
||||
|
||||
const updatePayload: UserBase = {
|
||||
id: userData.id,
|
||||
username: userData.username,
|
||||
fullName: userData.fullName,
|
||||
email: userData.email,
|
||||
authMethod: userData.authMethod,
|
||||
admin: userData.admin,
|
||||
group: userData.group,
|
||||
household: userData.household,
|
||||
advanced: userData.advanced,
|
||||
canInvite: userData.canInvite,
|
||||
canManage: userData.canManage,
|
||||
canManageHousehold: userData.canManageHousehold,
|
||||
canOrganize: userData.canOrganize,
|
||||
};
|
||||
|
||||
const { response } = await api.users.updateOne(userData.id, updatePayload);
|
||||
if (response?.status === 200) {
|
||||
auth.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePassword() {
|
||||
async function updatePassword() {
|
||||
if (!userCopy.value?.id) {
|
||||
return;
|
||||
}
|
||||
@@ -283,28 +293,5 @@ export default defineNuxtComponent({
|
||||
if (response?.status === 200) {
|
||||
console.log("Password Changed");
|
||||
}
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
hideImage: false,
|
||||
passwordLoading: false,
|
||||
showPassword: false,
|
||||
loading: false,
|
||||
});
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
updateUser,
|
||||
updatePassword,
|
||||
userCopy,
|
||||
selectedDefaultActivity,
|
||||
activityOptions,
|
||||
password,
|
||||
domUpdatePassword,
|
||||
passwordsMatch,
|
||||
validators,
|
||||
auth,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import UserProfileLinkCard from "@/components/Domain/User/UserProfileLinkCard.vue";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import UserAvatar from "@/components/Domain/User/UserAvatar.vue";
|
||||
@@ -283,27 +283,22 @@ import StatsCards from "~/components/global/StatsCards.vue";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
import UserInviteDialog from "~/components/Domain/User/UserInviteDialog.vue";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
definePageMeta({
|
||||
name: "UserProfile",
|
||||
components: {
|
||||
UserInviteDialog,
|
||||
UserProfileLinkCard,
|
||||
UserAvatar,
|
||||
StatsCards,
|
||||
},
|
||||
scrollToTop: true,
|
||||
async setup() {
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $appInfo } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug || auth.user.value?.groupSlug || "");
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
const i18n = useI18n();
|
||||
const auth = useMealieAuth();
|
||||
const { $appInfo } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug || auth.user.value?.groupSlug || "");
|
||||
|
||||
useSeoMeta({
|
||||
title: i18n.t("settings.profile"),
|
||||
});
|
||||
});
|
||||
|
||||
const user = computed<UserOut | null>(() => {
|
||||
const user = computed<UserOut | null>(() => {
|
||||
const authUser = auth.user.value;
|
||||
if (!authUser) return null;
|
||||
|
||||
@@ -314,45 +309,45 @@ export default defineNuxtComponent({
|
||||
...authUser,
|
||||
canInvite,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const inviteDialog = ref(false);
|
||||
const api = useUserApi();
|
||||
const inviteDialog = ref(false);
|
||||
const api = useUserApi();
|
||||
|
||||
const { data: stats } = useAsyncData(useAsyncKey(), async () => {
|
||||
const { data: stats } = useAsyncData(useAsyncKey(), async () => {
|
||||
const { data } = await api.households.statistics();
|
||||
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const statsText: { [key: string]: string } = {
|
||||
const statsText: { [key: string]: string } = {
|
||||
totalRecipes: i18n.t("general.recipes"),
|
||||
totalUsers: i18n.t("user.users"),
|
||||
totalCategories: i18n.t("sidebar.categories"),
|
||||
totalTags: i18n.t("sidebar.tags"),
|
||||
totalTools: i18n.t("tool.tools"),
|
||||
};
|
||||
};
|
||||
|
||||
function getStatsTitle(key: string) {
|
||||
function getStatsTitle(key: string) {
|
||||
return statsText[key] ?? "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const { $globals } = useNuxtApp();
|
||||
const { $globals } = useNuxtApp();
|
||||
|
||||
const iconText: { [key: string]: string } = {
|
||||
const iconText: { [key: string]: string } = {
|
||||
totalUsers: $globals.icons.user,
|
||||
totalCategories: $globals.icons.categories,
|
||||
totalTags: $globals.icons.tags,
|
||||
totalTools: $globals.icons.potSteam,
|
||||
};
|
||||
};
|
||||
|
||||
function getStatsIcon(key: string) {
|
||||
function getStatsIcon(key: string) {
|
||||
return iconText[key] ?? $globals.icons.primary;
|
||||
}
|
||||
}
|
||||
|
||||
const statsTo = computed<{ [key: string]: string }>(() => {
|
||||
const statsTo = computed<{ [key: string]: string }>(() => {
|
||||
return {
|
||||
totalRecipes: `/g/${groupSlug.value}/`,
|
||||
totalUsers: "/household/members",
|
||||
@@ -360,21 +355,9 @@ export default defineNuxtComponent({
|
||||
totalTags: `/g/${groupSlug.value}/recipes/tags`,
|
||||
totalTools: `/g/${groupSlug.value}/recipes/tools`,
|
||||
};
|
||||
});
|
||||
|
||||
function getStatsTo(key: string) {
|
||||
return statsTo.value[key] ?? "unknown";
|
||||
}
|
||||
|
||||
return {
|
||||
groupSlug,
|
||||
getStatsTitle,
|
||||
getStatsIcon,
|
||||
getStatsTo,
|
||||
inviteDialog,
|
||||
stats,
|
||||
user,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function getStatsTo(key: string) {
|
||||
return statsTo.value[key] ?? "unknown";
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user