fix(link share): add better error handling, ensure projects are shown (#1481)

This commit is contained in:
kolaente 2025-09-11 22:39:16 +02:00 committed by GitHub
parent decee24e12
commit fcc204dc88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 155 additions and 10 deletions

View File

@ -12,12 +12,37 @@
v-if="logoVisible"
class="logo"
/>
<h1
<Message
v-if="projectLoadError"
variant="danger"
class="mbe-4"
>
{{ $t('sharing.projectLoadError') }}
<BaseButton
variant="secondary"
class="mls-2"
@click="retryProjectLoad"
>
{{ $t('sharing.retry') }}
</BaseButton>
</Message>
<BaseButton
v-if="!projectLoadError && currentProject"
:to="getProjectRoute()"
variant="text"
class="project-title-button"
:class="{'m-0': !logoVisible}"
>
<h1 class="title clickable-title">
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
</h1>
</BaseButton>
<h1
v-else-if="!projectLoadError"
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
class="title"
>
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
{{ $t('misc.loading') }}
</h1>
<div class="box has-text-start view">
<RouterView />
@ -28,29 +53,88 @@
</template>
<script lang="ts" setup>
import {computed} from 'vue'
import {computed, ref, watch, onMounted} from 'vue'
import {useRoute} from 'vue-router'
import {useBaseStore} from '@/stores/base'
import {useRoute} from 'vue-router'
import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
import {useAuthStore} from '@/stores/auth'
import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'
import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/Message.vue'
import {PROJECT_VIEW_KINDS} from '@/modelTypes/IProjectView'
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const authStore = useAuthStore()
const route = useRoute()
const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background)
const logoVisible = computed(() => baseStore.logoVisible)
const projectLoadError = ref(false)
const projectStore = useProjectStore()
projectStore.loadAllProjects()
const labelStore = useLabelStore()
labelStore.loadAllLabels()
const route = useRoute()
// Ensure project is loaded for link share
async function ensureProjectLoaded() {
if (!authStore.authLinkShare || !route.params.projectId) {
return
}
try {
projectLoadError.value = false
// Load project if not already loaded
const projectId = Number(route.params.projectId)
if (!currentProject.value || currentProject.value.id !== projectId) {
await projectStore.loadProject(projectId)
}
} catch (e) {
console.error('Failed to load project for link share:', e)
projectLoadError.value = true
}
}
async function retryProjectLoad() {
await ensureProjectLoaded()
}
// Watch for route changes and ensure project is loaded
watch(() => route.params.projectId, ensureProjectLoaded, { immediate: true })
onMounted(ensureProjectLoaded)
function getProjectRoute() {
if (!currentProject.value) return null
const hash = route.hash // Preserve link share hash
// Default to the first available view or list view
const projectId = currentProject.value.id
const firstView = projectStore.projects[projectId]?.views?.[0]
if (firstView) {
return {
name: 'project.view',
params: { projectId, viewId: firstView.id },
hash,
}
}
return {
name: 'project.index',
params: { projectId },
hash,
}
}
const isFullWidth = computed(() => {
const viewId = route.params?.viewId ?? null
const projectId = route.params?.projectId ?? null
@ -82,6 +166,27 @@ const isFullWidth = computed(() => {
text-shadow: 0 0 1rem var(--white);
}
.project-title-button {
background: none !important;
border: none !important;
padding: 0 !important;
text-decoration: none !important;
&:hover .clickable-title {
opacity: 0.8;
cursor: pointer;
}
}
.clickable-title {
text-shadow: 0 0 1rem var(--white);
margin: 0;
&:hover {
text-decoration: underline;
}
}
.link-share-view {
inline-size: 100%;
max-inline-size: $desktop;

View File

@ -588,7 +588,11 @@
"authenticating": "Authenticating…",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "An error occurred.",
"invalidPassword": "The password is invalid."
"invalidPassword": "The password is invalid.",
"accessDenied": "Access denied. Please check your permissions and try again.",
"serverError": "Server error occurred. Please try again later.",
"projectLoadError": "Failed to load project information.",
"retry": "Retry"
},
"navigation": {
"overview": "Overview",

View File

@ -255,6 +255,24 @@ export const useProjectStore = defineStore('project', () => {
views: updatedViews,
})
}
// Add method to ensure single project loading works for link shares
async function loadProject(projectId: number) {
const project = projects.value[projectId]
if (project) {
return project
}
try {
const projectService = new ProjectService()
const loadedProject = await projectService.get({id: projectId})
setProject(loadedProject)
return loadedProject
} catch (e) {
console.error(`Failed to load project ${projectId}:`, e)
throw e
}
}
return {
isLoading: readonly(isLoading),
@ -277,6 +295,7 @@ export const useProjectStore = defineStore('project', () => {
removeProjectById,
toggleProjectFavorite,
loadAllProjects,
loadProject,
createProject,
updateProject,
deleteProject,

View File

@ -137,6 +137,23 @@ function useAuth() {
return
}
// Handle generic 403 errors that might occur after initial auth
if (e?.response?.status === 403 && !e?.response?.data?.code) {
errorMessage.value = t('sharing.accessDenied')
authenticateWithPassword.value = false
return
}
// Handle network/server errors
if (e?.response?.status >= 500 || !e?.response) {
errorMessage.value = t('sharing.serverError')
authenticateWithPassword.value = false
return
}
// Log unexpected errors for debugging
console.error('Link share authentication error:', e)
// TODO: Put this logic in a global errorMessage handler method which checks all auth codes
let err = t('sharing.error')
if (e?.response?.data?.message) {