allow opening shared recipes and redirect old urls

This commit is contained in:
vabene1111
2025-02-09 12:18:06 +01:00
parent 1b09234e91
commit 4234c5b35f
10 changed files with 126 additions and 47 deletions

View File

@@ -1,6 +1,9 @@
<template>
<v-app>
<v-app-bar color="tandoor" flat density="comfortable">
<v-app-bar color="tandoor" flat density="comfortable" v-if="!useUserPreferenceStore().isAuthenticated">
</v-app-bar>
<v-app-bar color="tandoor" flat density="comfortable" v-if="useUserPreferenceStore().isAuthenticated">
<router-link :to="{ name: 'view_home', params: {} }">
<v-img src="../../assets/brand_logo.svg" width="140px" class="ms-2"></v-img>
</router-link>
@@ -42,10 +45,10 @@
</v-list-item>
<!-- <v-list-item><template #prepend><v-icon icon="fa-solid fa-user-shield"></v-icon></template>Admin</v-list-item>-->
<!-- <v-list-item><template #prepend><v-icon icon="fa-solid fa-question"></v-icon></template>Help</v-list-item>-->
<template v-if="spaces.length > 1">
<template v-if="useUserPreferenceStore().spaces.length > 1">
<v-divider></v-divider>
<v-list-subheader>{{ $t('YourSpaces') }}</v-list-subheader>
<v-list-item v-for="s in spaces" :key="s.id" @click="useUserPreferenceStore().switchSpace(s)">
<v-list-item v-for="s in useUserPreferenceStore().spaces" :key="s.id" @click="useUserPreferenceStore().switchSpace(s)">
<template #prepend>
<v-icon icon="fa-solid fa-circle-dot" v-if="s.id == useUserPreferenceStore().activeSpace.id"></v-icon>
<v-icon icon="fa-solid fa-circle" v-else></v-icon>
@@ -78,20 +81,20 @@
</v-menu>
</v-avatar>
</v-app-bar>
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().activeSpace.maxRecipes == 10 && useUserPreferenceStore().serverSettings.hosted">
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.maxRecipes == 10 && useUserPreferenceStore().serverSettings.hosted">
<p class="text-center w-100">
{{ $t('HostedFreeVersion') }}
<v-btn color="success" variant="flat" href="https://tandoor.dev/manage">{{ $t('UpgradeNow') }}</v-btn>
</p>
</v-app-bar>
<v-app-bar color="warning" density="compact" v-if="isSpaceAboveLimit(useUserPreferenceStore().activeSpace)">
<v-app-bar color="warning" density="compact" v-if="useUserPreferenceStore().isAuthenticated && isSpaceAboveLimit(useUserPreferenceStore().activeSpace)">
<p class="text-center w-100">
{{ $t('SpaceLimitExceeded') }}
<v-btn color="success" variant="flat" :to="{name: 'view_settings_space'}">{{ $t('SpaceSettings') }}</v-btn>
</p>
</v-app-bar>
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().activeSpace.message != ''">
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.message != ''">
<p class="text-center w-100">
{{ useUserPreferenceStore().activeSpace.message }}
</p>
@@ -101,7 +104,7 @@
<router-view></router-view>
</v-main>
<v-navigation-drawer v-if="lgAndUp">
<v-navigation-drawer v-if="lgAndUp && useUserPreferenceStore().isAuthenticated">
<v-list nav>
<v-list-item :to="{ name: 'view_settings', params: {} }">
<template #prepend>
@@ -132,7 +135,7 @@
</v-navigation-drawer>
<v-bottom-navigation grow v-if="!lgAndUp">
<v-bottom-navigation grow v-if="useUserPreferenceStore().isAuthenticated && !lgAndUp">
<v-btn value="recent" :to="{ name: 'view_home', params: {} }">
<v-icon icon="fa-fw fas fa-book "/>
</v-btn>
@@ -183,18 +186,8 @@ import {isSpaceAboveLimit, isSpaceAtLimit} from "@/utils/logic_utils";
const {lgAndUp} = useDisplay()
const {getDjangoUrl} = useDjangoUrls()
const spaces = ref([] as Space[])
onMounted(() => {
let api = new ApiApi()
useUserPreferenceStore()
api.apiSpaceList().then(r => {
spaces.value = r.results
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
})
})
</script>

View File

@@ -51,7 +51,10 @@ const routes = [
{path: '/mealplan', component: MealPlanPage, name: 'view_mealplan'},
{path: '/books', component: ShoppingListPage, name: 'view_books'},
{path: '/recipe/import', component: RecipeImportPage, name: 'RecipeImportPage'},
{path: '/recipe/:id', component: RecipeViewPage, name: 'view_recipe', props: true},
{path: '/view/recipe/:id', redirect: {name: 'view_recipe'}}, // old Tandoor v1 url pattern
{path: '/recipe/edit/:recipe_id', component: RecipeEditPage, name: 'edit_recipe', props: true},
{path: '/list/:model?', component: ModelListPage, props: true, name: 'ModelListPage'},

View File

@@ -1225,6 +1225,7 @@ export interface ApiRecipeRelatedListRequest {
export interface ApiRecipeRetrieveRequest {
id: number;
share?: string;
}
export interface ApiRecipeShoppingUpdateRequest {
@@ -8989,6 +8990,10 @@ export class ApiApi extends runtime.BaseAPI {
const queryParameters: any = {};
if (requestParameters['share'] != null) {
queryParameters['share'] = requestParameters['share'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {

View File

@@ -1,5 +1,5 @@
<template>
<v-container class="h-100 pt-0 pl-0 pb-0" fluid>
<v-container class="h-100 pt-0 pl-0 pb-0" style="max-width: 100%" fluid>
<meal-plan-view></meal-plan-view>
</v-container>

View File

@@ -6,15 +6,19 @@
</template>
<script setup lang="ts">
import {defineComponent, onMounted, ref, watch} from 'vue'
import {ApiApi, Recipe, ViewLog} from "@/openapi";
import {onMounted, ref, watch} from 'vue'
import {ApiApi, ApiRecipeRetrieveRequest, Recipe, ViewLog} from "@/openapi";
import RecipeView from "@/components/display/RecipeView.vue";
import {useDisplay} from "vuetify";
import {useUrlSearchParams} from "@vueuse/core";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
const props = defineProps({
id: {type: String, required: true}
})
const params = useUrlSearchParams('history')
const {mobile} = useDisplay()
const recipe = ref({} as Recipe)
@@ -30,14 +34,24 @@ onMounted(() => {
function refreshData(recipeId: string) {
const api = new ApiApi()
recipe.value = {} as Recipe
api.apiRecipeRetrieve({id: Number(recipeId)}).then(r => {
recipe.value = r
})
api.apiViewLogCreate({
viewLog: {
recipe: Number(recipeId)
} as ViewLog
let requestParameters: ApiRecipeRetrieveRequest = {id: props.id}
if (params.share && typeof params.share == "string") {
requestParameters.share = params.share
}
api.apiRecipeRetrieve(requestParameters).then(r => {
recipe.value = r
if (useUserPreferenceStore().isAuthenticated) {
api.apiViewLogCreate({viewLog: {recipe: Number(recipeId)} as ViewLog})
}
}).catch(err => {
if (err.response.status == 403) {
// TODO maybe redirect to login if fails with 403? or conflict with group/sapce system?
} else {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}
})
}

View File

@@ -3,7 +3,7 @@ import {useStorage} from "@vueuse/core";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import {ApiApi, ServerSettings, Space, Supermarket, UserPreference, UserSpace} from "@/openapi";
import {ShoppingGroupingOptions} from "@/types/Shopping";
import {computed, ComputedRef} from "vue";
import {computed, ComputedRef, ref} from "vue";
import {DeviceSettings} from "@/types/settings";
const DEVICE_SETTINGS_KEY = 'TANDOOR_DEVICE_SETTINGS'
@@ -11,6 +11,7 @@ const USER_PREFERENCE_KEY = 'TANDOOR_USER_PREFERENCE'
const SERVER_SETTINGS_KEY = 'TANDOOR_SERVER_SETTINGS'
const ACTIVE_SPACE_KEY = 'TANDOOR_ACTIVE_SPACE'
const USER_SPACES_KEY = 'TANDOOR_USER_SPACES'
const SPACES_KEY = 'TANDOOR_SPACES'
export const useUserPreferenceStore = defineStore('user_preference_store', () => {
/**
@@ -30,10 +31,17 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
*/
let activeSpace = useStorage(ACTIVE_SPACE_KEY, {} as Space)
/**
* list of spaces the user has access to and the relevant permissions, cache in local storage in case application is started offline
* list of user spaces the user has access to and the relevant permissions, cache in local storage in case application is started offline
*/
let userSpaces = useStorage(USER_SPACES_KEY, [] as UserSpace[])
/**
* list of spaces the user has access and their space settings/Data, cache in local storage in case application is started offline
*/
let spaces = useStorage(SPACES_KEY, [] as Space[])
/**
* some views can be viewed without authentication, this variable centrally detects the authentication state by the response (403) of the settings views
*/
let isAuthenticated = ref(false)
/**
* holds the active user space if there is one or null if not
@@ -57,11 +65,14 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
api.apiUserPreferenceList().then(r => {
if (r.length == 1) {
userSettings.value = r[0]
isAuthenticated.value = true
} else {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, r)
}
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
if (err.response.status != 403) {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}
})
}
@@ -99,19 +110,39 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
api.apiSpaceCurrentRetrieve().then(r => {
activeSpace.value = r
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
if (err.response.status != 403) {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}
})
}
/**
* load user spaces
* load user spaces (permission mapping ot space)
*/
function loadUserSpaces() {
let api = new ApiApi()
api.apiUserSpaceList().then(r => {
userSpaces.value = r.results
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
if (err.response.status != 403) {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}
})
}
/**
* list all spaces (with their data) a user has access to
*/
// TODO maybe change userspace api to include space as nested property to make this call redundant
function loadSpaces() {
let api = new ApiApi()
api.apiSpaceList().then(r => {
spaces.value = r.results
}).catch(err => {
if (err.response.status != 403) {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}
})
}
@@ -166,6 +197,7 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
loadServerSettings()
loadActiveSpace()
loadUserSpaces()
loadSpaces()
return {
deviceSettings,
@@ -173,7 +205,9 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
serverSettings,
activeSpace,
userSpaces,
spaces,
activeUserSpace,
isAuthenticated,
loadUserSettings,
loadServerSettings,
updateUserSettings,

View File

@@ -15,6 +15,10 @@ export default createVuetify({
// without this action buttons are left aligned in normal cards but right aligned in dialogs (I think)
VCardActions: {
class: 'float-right'
},
// limiting max width of base container so UIs dont become too wide
VContainer: {
maxWidth: '1400px'
}
},
theme: {