add not-found handling to ZK and hotlink viewer pages.
Rename ViewerView and view store to ZkViewerView and useZkViewStore; unify hotlink load and ZK unlock error copy.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<main
|
||||
class="mx-auto flex w-full max-w-lg flex-1 flex-col items-center justify-center gap-3 px-4 py-6 text-center sm:px-6 sm:py-8"
|
||||
>
|
||||
<p v-if="state === 'loading'" class="text-sm text-muted-foreground sm:text-base">Loading image...</p>
|
||||
|
||||
<p v-else-if="state === 'invalidLink'" class="text-sm text-muted-foreground">
|
||||
This viewing link is incomplete. Use the viewer URL from your upload results.
|
||||
</p>
|
||||
|
||||
<p v-else-if="state === 'notFound'" class="text-sm text-muted-foreground">This image was not found.</p>
|
||||
|
||||
<form
|
||||
v-else-if="state === 'password'"
|
||||
class="flex w-full max-w-sm flex-col gap-3 text-left"
|
||||
@submit.prevent="onUnlockSubmit"
|
||||
>
|
||||
<label class="flex flex-col gap-1.5 text-sm">
|
||||
<span>Password</span>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
class="rounded-md border border-input bg-background px-3 py-2.5 text-sm sm:py-2"
|
||||
:disabled="unlocking"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<p v-if="unlockError" class="text-sm text-destructive">{{ unlockError }}</p>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60 sm:py-2"
|
||||
:disabled="unlocking || !password"
|
||||
>
|
||||
{{ unlocking ? 'Unlocking...' : 'View image' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<img
|
||||
v-else-if="state === 'image' && imageObjectUrl"
|
||||
:src="imageObjectUrl"
|
||||
class="max-h-[min(80vh,100dvh)] w-full object-contain"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onBeforeMount, onBeforeUnmount, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useZkViewStore } from '@/stores/zkView';
|
||||
import type { GetZkUploadCiphertextResult } from '@/types/upload/GetZkUploadCiphertextResult';
|
||||
import type { ZkViewerViewState } from '@/types/view/ZkViewerViewState';
|
||||
|
||||
const route = useRoute();
|
||||
const zkViewStore = useZkViewStore();
|
||||
const { imageObjectUrl } = storeToRefs(zkViewStore);
|
||||
|
||||
const state = ref<ZkViewerViewState>('loading');
|
||||
const unlocking = ref(false);
|
||||
const zkPayload = ref<GetZkUploadCiphertextResult | null>(null);
|
||||
const fragmentKey = ref('');
|
||||
const password = ref('');
|
||||
const unlockError = ref('');
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const uploadId = route.params.id;
|
||||
|
||||
if (typeof uploadId !== 'string' || !uploadId) {
|
||||
state.value = 'invalidLink';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const key = route.hash.slice(1);
|
||||
|
||||
if (!key) {
|
||||
state.value = 'invalidLink';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fragmentKey.value = key;
|
||||
state.value = 'loading';
|
||||
|
||||
try {
|
||||
const payload = await zkViewStore.fetchZkCiphertext(uploadId);
|
||||
|
||||
if (payload.passwordProtected) {
|
||||
zkPayload.value = payload;
|
||||
state.value = 'password';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await zkViewStore.loadZk(payload, key);
|
||||
|
||||
state.value = 'image';
|
||||
} catch (error) {
|
||||
console.error('[zk-viewer] load failed', { uploadId, error });
|
||||
|
||||
state.value = 'notFound';
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanRefs();
|
||||
zkViewStore.clearImage();
|
||||
});
|
||||
|
||||
const onUnlockSubmit = async (): Promise<void> => {
|
||||
const payload = zkPayload.value;
|
||||
const key = fragmentKey.value;
|
||||
|
||||
if (!payload || !key) {
|
||||
return;
|
||||
}
|
||||
|
||||
unlocking.value = true;
|
||||
unlockError.value = '';
|
||||
|
||||
try {
|
||||
await zkViewStore.loadZk(payload, key, password.value);
|
||||
|
||||
zkPayload.value = null;
|
||||
password.value = '';
|
||||
state.value = 'image';
|
||||
} catch (error) {
|
||||
console.error('[zk-viewer] unlock failed', { error });
|
||||
|
||||
unlockError.value = 'Could not unlock this image. Check the viewer link and password, then try again.';
|
||||
} finally {
|
||||
unlocking.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const cleanRefs = (): void => {
|
||||
zkPayload.value = null;
|
||||
fragmentKey.value = '';
|
||||
password.value = '';
|
||||
unlockError.value = '';
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user