mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-31 20:25:14 -04:00
feat: implement user favorites page (#1376)
* fix geFavorites return * add support for toggling to dense cards on desktop * add favorites page link * implement basic favorites page
This commit is contained in:
parent
f6c18ec73d
commit
3030e3e7f4
@ -1,5 +1,15 @@
|
|||||||
import { BaseCRUDAPI } from "../_base";
|
import { BaseCRUDAPI } from "../_base";
|
||||||
import { ChangePassword, DeleteTokenResponse, LongLiveTokenIn, LongLiveTokenOut, ResetPassword, UserBase, UserIn, UserOut } from "~/types/api-types/user";
|
import {
|
||||||
|
ChangePassword,
|
||||||
|
DeleteTokenResponse,
|
||||||
|
LongLiveTokenIn,
|
||||||
|
LongLiveTokenOut,
|
||||||
|
ResetPassword,
|
||||||
|
UserBase,
|
||||||
|
UserFavorites,
|
||||||
|
UserIn,
|
||||||
|
UserOut,
|
||||||
|
} from "~/types/api-types/user";
|
||||||
|
|
||||||
const prefix = "/api";
|
const prefix = "/api";
|
||||||
|
|
||||||
@ -32,7 +42,7 @@ export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getFavorites(id: string) {
|
async getFavorites(id: string) {
|
||||||
await this.requests.get(routes.usersIdFavorites(id));
|
return await this.requests.get<UserFavorites>(routes.usersIdFavorites(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(id: string, changePassword: ChangePassword) {
|
async changePassword(id: string, changePassword: ChangePassword) {
|
||||||
|
@ -14,6 +14,17 @@
|
|||||||
</v-icon>
|
</v-icon>
|
||||||
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.random") }}
|
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.random") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<ContextMenu
|
||||||
|
v-if="!$vuetify.breakpoint.xsOnly"
|
||||||
|
:items="[
|
||||||
|
{
|
||||||
|
title: 'Toggle View',
|
||||||
|
icon: $globals.icons.eye,
|
||||||
|
event: 'toggle-dense-view',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@toggle-dense-view="mobileCards = !mobileCards"
|
||||||
|
/>
|
||||||
<v-menu v-if="$listeners.sort" offset-y left>
|
<v-menu v-if="$listeners.sort" offset-y left>
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn text :icon="$vuetify.breakpoint.xsOnly" v-bind="attrs" :loading="sortLoading" v-on="on">
|
<v-btn text :icon="$vuetify.breakpoint.xsOnly" v-bind="attrs" :loading="sortLoading" v-on="on">
|
||||||
@ -103,11 +114,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs, useContext, useRouter, ref } from "@nuxtjs/composition-api";
|
||||||
import RecipeCard from "./RecipeCard.vue";
|
import RecipeCard from "./RecipeCard.vue";
|
||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import { useSorter } from "~/composables/recipes";
|
import { useSorter } from "~/composables/recipes";
|
||||||
import {Recipe} from "~/types/api-types/recipe";
|
import { Recipe } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
const SORT_EVENT = "sort";
|
const SORT_EVENT = "sort";
|
||||||
|
|
||||||
@ -129,10 +140,6 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
mobileCards: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
singleColumn: {
|
singleColumn: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -143,6 +150,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
|
const mobileCards = ref(false);
|
||||||
const utils = useSorter();
|
const utils = useSorter();
|
||||||
|
|
||||||
const EVENTS = {
|
const EVENTS = {
|
||||||
@ -155,7 +163,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const { $globals, $vuetify } = useContext();
|
const { $globals, $vuetify } = useContext();
|
||||||
const viewScale = computed(() => {
|
const viewScale = computed(() => {
|
||||||
return props.mobileCards || $vuetify.breakpoint.smAndDown;
|
return mobileCards.value || $vuetify.breakpoint.smAndDown;
|
||||||
});
|
});
|
||||||
|
|
||||||
const displayTitleIcon = computed(() => {
|
const displayTitleIcon = computed(() => {
|
||||||
@ -164,7 +172,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
sortLoading: false,
|
sortLoading: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
function navigateRandom() {
|
function navigateRandom() {
|
||||||
@ -204,6 +212,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
mobileCards,
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
EVENTS,
|
EVENTS,
|
||||||
viewScale,
|
viewScale,
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title> {{ $auth.user.fullName }}</v-list-item-title>
|
<v-list-item-title> {{ $auth.user.fullName }}</v-list-item-title>
|
||||||
<v-list-item-subtitle> {{ $auth.user.admin ? $t("user.admin") : $t("user.user") }}</v-list-item-subtitle>
|
<v-list-item-subtitle>
|
||||||
|
<NuxtLink class="favorites-link" :to="`/user/${$auth.user.id}/favorites`"> Favorite Recipes </NuxtLink>
|
||||||
|
</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
@ -200,4 +202,12 @@ export default defineComponent({
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.favorites-link {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.favorites-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,13 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<v-container>
|
||||||
|
<RecipeCardSection v-if="user" :icon="$globals.icons.heart" title="User Favorites" :recipes="user.favoriteRecipes">
|
||||||
|
</RecipeCardSection>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
|
||||||
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: { RecipeCardSection },
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
const api = useUserApi();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const userId = route.value.params.id;
|
||||||
|
|
||||||
|
const user = useAsync(async () => {
|
||||||
|
const { data } = await api.users.getFavorites(userId);
|
||||||
|
return data;
|
||||||
|
}, useAsyncKey());
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
head() {
|
head() {
|
||||||
return {
|
return {
|
||||||
@ -16,6 +35,5 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user