Skip to content

Commit f30e759

Browse files
committed
feat: Add middleware and loading splash screen to prevent access when server is not ready yet
1 parent 51cb2c5 commit f30e759

3 files changed

Lines changed: 296 additions & 199 deletions

File tree

src/app.vue

Lines changed: 30 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Created Date: 2025-09-08 15:54:21
66
* Author: 3urobeat
77
*
8-
* Last Modified: 2026-05-16 17:58:13
8+
* Last Modified: 2026-05-20 22:22:28
99
* Modified By: 3urobeat
1010
*
1111
* Copyright (c) 2025 - 2026 3urobeat <https://github.com/3urobeat>
@@ -19,170 +19,33 @@
1919

2020
<template>
2121

22-
<header id="titlebar">
23-
<PhList :class="!showNavbar ? 'block' : 'opacity-0'" class="fixed z-50 cursor-pointer left-3 top-4.5 dark:text-text-dark lg:hidden block transition-opacity" size="25px" @click="showNavbar = !showNavbar"></PhList>
24-
<PhCaretLeft :class="showNavbar ? 'block' : 'opacity-0'" class="fixed z-50 cursor-pointer left-3 top-4.5 dark:text-text-dark lg:hidden block transition-opacity" size="25px" @click="showNavbar = !showNavbar"></PhCaretLeft>
25-
26-
<GlobalTitleBar></GlobalTitleBar>
27-
</header>
28-
29-
30-
<!-- Left navigation bar which offsets everything else to the right on desktop and overlays everything on mobile -->
31-
<nav
32-
id="navbar"
33-
:class="showNavbar ? '' : 'invisible lg:visible w-0 min-w-0 opacity-0'"
34-
class="fixed top-15 z-20 w-52 min-w-52 min-h-screen backdrop-blur-md lg:opacity-100 dark:text-text-dark border-x border-x-border-primary-light dark:border-x-border-primary-dark border-l-0 select-none duration-500 transition-[width,opacity,visibility]"
35-
>
36-
37-
<div class="absolute left-1/2 transform -translate-x-1/2 top-2 w-34">
38-
<div class="my-3"></div> <!-- Add some space above everything-->
39-
40-
<NuxtLink to="/" class="group custom-navbar-link">
41-
<span class="fixed self-center mb-1 text-xl font-bold text-green-600" v-show="route.name === 'index' || route.name === 'clothing'">|</span>
42-
43-
<TextOverflowAutoScroll class="ml-4">
44-
<PhHouse class="mr-2" /> {{ $t("browse") }}
45-
</TextOverflowAutoScroll>
46-
</NuxtLink>
47-
48-
<div class="my-2 h-0.5 bg-border-secondary-light dark:bg-border-secondary-dark opacity-50"></div> <!-- Divider to give Browse more presence -->
49-
50-
<NuxtLink to="/outfits" class="group custom-navbar-link">
51-
<span class="fixed self-center mb-1 text-xl font-bold text-green-600" v-show="route.name === 'outfits'">|</span>
52-
53-
<TextOverflowAutoScroll class="ml-4">
54-
<PhCoatHanger class="mr-2" /> {{ $t("outfits") }}
55-
</TextOverflowAutoScroll>
56-
</NuxtLink>
57-
<NuxtLink to="/labels" class="group custom-navbar-link">
58-
<span class="fixed self-center mb-1 text-xl font-bold text-green-600" v-show="route.name === 'labels'">|</span>
59-
60-
<TextOverflowAutoScroll class="ml-4">
61-
<PhTag class="mr-2" /> {{ $t("labels") }}
62-
</TextOverflowAutoScroll>
63-
</NuxtLink>
64-
<NuxtLink to="/settings" class="group custom-navbar-link">
65-
<span class="fixed self-center mb-1 text-xl font-bold text-green-600" v-show="route.name === 'settings'">|</span>
66-
67-
<TextOverflowAutoScroll class="ml-4">
68-
<PhGear class="mr-2" /> {{ $t("settings") }}
69-
</TextOverflowAutoScroll>
70-
</NuxtLink>
71-
</div>
72-
73-
</nav>
74-
75-
<!-- Footer for project details. Separated from nav container because backdrop caused positioning issues -->
76-
<footer
77-
:class="showNavbar ? '' : 'invisible lg:visible opacity-0'"
78-
class="fixed z-20 text-nowrap bottom-0 left-0 pb-2 px-2.5 group lg:opacity-100 dark:text-text-dark select-none duration-500 transition-all"
79-
>
80-
<div class="flex flex-col text-sm opacity-50">
81-
<div :class="onlineVersion && onlineVersion != packagejson.version ? '' : 'hidden'" class="mb-4 px-1 py-0.5 bg-bg-embed-light dark:bg-bg-embed-dark outline-2 outline-border-secondary-light dark:outline-border-secondary-dark rounded-lg">
82-
<p class="font-semibold">{{ $t("navbarUpdateAvailable") }}</p>
83-
<p>{{ $t("navbarNewVersion") }} <span class="text-green-500 font-extrabold">{{ onlineVersion }}</span></p>
84-
{{ $t("navbarPatchNotesText") }} <a class="underline hover:text-gray-500" :href="'https://github.com/wardrobe-hq/wardrobe/releases/tag/' + onlineVersion" target="_blank">{{ $t("navbarPatchNotesTextLink") }}</a>
22+
<!-- Fullscreen loading page shown until server startup is complete -->
23+
<div v-if="isReady.error.value">
24+
<div class="flex flex-col gap-8 justify-center items-center min-h-[60vh] select-none text-text-light dark:text-text-dark">
25+
<div>
26+
<div class="w-26 -mb-1.5 inline-block">
27+
<img src="/logo-dark.png" class="h-20 object-left object-cover hidden dark:block" />
28+
<img src="/logo-light.png" class="h-20 object-left object-cover block dark:hidden" />
29+
</div>
30+
31+
<span class="text-8xl font-extrabold text-transparent bg-clip-text bg-linear-to-br from-wardrobe-blue to-wardrobe-blue/50">
32+
Wardrobe
33+
</span>
8534
</div>
86-
87-
wardrobe v{{ packagejson.version }}
88-
89-
<a class="flex w-fit items-center mt-0.5 -ml-1 rounded-xl px-2 text-gray-100 bg-gray-600 hover:bg-gray-400 hover:transition-all" href="http://github.com/wardrobe-hq/wardrobe" target="_blank">
90-
91-
<!-- GitHub logo -->
92-
<svg class="mr-1" width="1em" height="1em" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
93-
<path fill-rule="evenodd" d="M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z" clip-rule="evenodd"/>
94-
</svg>
95-
96-
<span class="text-white rounded-lg text-xm" href="https://github.com/wardrobe-hq/wardrobe" target="_blank">{{ $t("navbarSourceCodeLink") }}</span>
97-
98-
</a>
99-
100-
<!-- Becomes visible on group hover -->
101-
<div class="h-0 opacity-0 group-hover:h-10 group-hover:opacity-100 duration-500 transition-all">
102-
<p>{{ $t("navbarLicensedUnder") }} <a class="underline hover:text-gray-500" href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">AGPLv3</a></p>
103-
<p>Copyright (c) 2026 <a class="underline hover:text-gray-500" href="https://github.com/3urobeat" target="_blank">3urobeat</a></p>
104-
</div>
105-
</div>
106-
</footer>
107-
108-
<!-- The main content itself, pushed to the side by the navbar - The extra lg: tags in :class fix a bg color bug when the window is resized while the navbar was open -->
109-
<main
110-
:class="showNavbar ? 'opacity-30 dark:opacity-70 lg:opacity-100 lg:dark:opacity-100' : ''"
111-
class="fixed top-15 dark:text-text-dark transition-all duration-500"
112-
@click="showNavbar = false"
113-
>
114-
<!-- Dummy to prevent NuxtPage button presses when the navbar is open -->
115-
<div :class="showNavbar ? 'fixed min-h-screen min-w-screen opacity-0 lg:w-0 lg:h-0' : ''" class="z-50"></div>
116-
117-
<!-- JS disabled warning, gets hidden by global.js. Cannot use noscript tag as it causes a Vue hydration mismatch :( -->
118-
<div id="js-disabled-banner" class="fixed z-50 select-none min-h-screen min-w-screen bg-bg-field-light/90 dark:bg-bg-field-dark/90">
119-
<p class="translate-y-1/3 min-h-screen min-w-screen text-red-500 font-bold text-center">JavaScript is disabled :(<br />Please enable JavaScript to use Wardrobe.</p>
35+
<p class="text-2xl font-bold dark:text-text-dark">v{{ packagejson.version }}</p>
36+
<div class="loader"></div>
12037
</div>
38+
</div>
12139

122-
<!-- Page content wrapped into a border container, used to indicate success or failure for actions -->
123-
<div
124-
id="color-border"
125-
class="fixed left-0 lg:left-52 top-15 bottom-0 right-0 border-8 border-transparent rounded-2xl duration-500 overflow-auto"
126-
>
127-
<!-- Global notification component -->
128-
<Notification class="fixed top-18 md:top-25 right-2 md:right-10" />
129-
130-
<div id="page-content" class="p-1 md:px-5">
131-
<NuxtPage></NuxtPage> <!-- Links to index.vue -->
132-
</div>
133-
</div>
134-
</main>
40+
<!-- Server is ready, show app content -->
41+
<Main v-else></Main>
13542

13643
</template>
13744

13845

13946
<script setup lang="ts">
140-
import { PhList, PhCaretLeft, PhHouse, PhGear, PhCoatHanger, PhTag } from "@phosphor-icons/vue";
14147
import packagejson from "../package.json";
142-
import type { PageProperties } from "./model/page";
143-
import TextOverflowAutoScroll from "./components/textOverflowAutoScroll.vue";
144-
import Notification from './components/notification.vue';
145-
import { closeServerSubscriptionConnection, initServerSubscriptionHandler } from "./composables/subscription";
146-
import { NotificationLevel, NotificationType, type NotificationData } from "./model/notification";
147-
import { initState, State } from "./composables/state";
148-
149-
const route = useRoute();
150-
let changesMade = false;
151-
152-
153-
// Refs
154-
const showNavbar = ref(false);
155-
const onlineVersion = ref("");
156-
157-
// Init
158-
initState();
159-
await initGlobalCache();
160-
161-
162-
// Handle global events
163-
useNuxtApp().hook("app:user:changesMade", (val: boolean = true) => {
164-
console.debug(`[DEBUG] Received changesMade = '${val}' event!`)
165-
changesMade = val;
166-
});
167-
168-
useNuxtApp().hook("app:notification:action", (data: NotificationData) => {
169-
if (data.type == NotificationType.RELOAD_PAGE) {
170-
console.debug("[DEBUG] Got 'RELOAD_PAGE' event, reloading page...");
171-
reloadNuxtApp();
172-
}
173-
});
174-
175-
// Handle page switch
176-
addRouteMiddleware("page-switch", (to, from) => {
177-
if (changesMade) {
178-
if (!confirm("You have unsaved changes!\nWould you still like to continue?")) {
179-
return abortNavigation();
180-
}
181-
}
182-
changesMade = false;
183-
184-
updateGlobalSearchBar(to.meta);
185-
}, { global: true });
48+
import Main from "./main.vue";
18649
18750
18851
// Specify page information
@@ -198,52 +61,20 @@
19861
script: [{ src: "/global.js" }] // Sets initial dark mode. Defined in header to fix transition load - https://stackoverflow.com/a/14416030
19962
});
20063
201-
// Do initial page load stuff
202-
updateGlobalSearchBar(useRoute().meta);
20364
204-
onMounted(() => { // Client side only
205-
console.debug("Wardrobe mounted!");
206-
checkForUpdate();
207-
initServerSubscriptionHandler();
208-
});
209-
210-
onUnmounted(() => {
211-
closeServerSubscriptionConnection();
212-
});
213-
214-
215-
// Resets and toggles global search bar visibility
216-
function updateGlobalSearchBar(pageProps: PageProperties) {
217-
useState(State.GLOBAL_SEARCH_STRING).value = null;
218-
219-
if (pageProps && pageProps.showGlobalSearchBar) {
220-
useState(State.GLOBAL_SEARCH_BAR_SHOWN).value = true;
221-
} else {
222-
useState(State.GLOBAL_SEARCH_BAR_SHOWN).value = false;
223-
}
224-
}
225-
226-
// Checks for an available update and displays a notification in the navbar
227-
async function checkForUpdate() {
228-
try {
229-
let output = await fetch("https://raw.githubusercontent.com/wardrobe-hq/wardrobe/main/package.json");
230-
let parsed = await output.json();
231-
232-
console.log("Successfully checked for an Update; Local: %s | Online: %s ", packagejson.version, parsed.version);
65+
// Check if server is ready once during SSR
66+
const isReady = await useFetch("/api/ping"); // Middleware will deny request if !ready
23367
234-
onlineVersion.value = parsed.version;
235-
236-
if (onlineVersion.value != packagejson.version) {
237-
emitNotificationShowEvent({
238-
level: NotificationLevel.INFO,
239-
title: $t("navbarUpdateAvailable"),
240-
message: `${$t("navbarNewVersion")} ${onlineVersion.value}`
241-
});
242-
}
243-
} catch (err) {
68+
onMounted(async () => { // Client side only
69+
console.debug("Wardrobe mounted!");
24470
245-
console.error("checkForUpdate: Failed to check GitHub repository for an available update. " + err);
71+
// If server was not ready during SSR, re-fetch API endpoint
72+
if (!isReady.data.value) {
73+
const isReadyCheckInterval = setInterval(() => {
74+
isReady.refresh();
75+
if (isReady.data.value) clearInterval(isReadyCheckInterval);
76+
}, 1000);
24677
}
247-
}
78+
});
24879
24980
</script>

0 commit comments

Comments
 (0)