mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add "Hello from the otter side" easter eggs
Custom 404 pages for landing and docs sites, otter greeting on the first-login sidebar, and a one-time welcome toast after password change.
This commit is contained in:
@@ -16,5 +16,59 @@ const { Layout } = DefaultTheme;
|
|||||||
<GitHubStars />
|
<GitHubStars />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #not-found>
|
||||||
|
<div class="not-found-page">
|
||||||
|
<span class="not-found-otter">🦦</span>
|
||||||
|
<h1 class="not-found-heading">Hello from the otter side!</h1>
|
||||||
|
<p class="not-found-text">This page swam away. Let's get you back on track.</p>
|
||||||
|
<a href="/" class="not-found-link">Back to docs</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</Layout>
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.not-found-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found-otter {
|
||||||
|
font-size: 4.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found-heading {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
font-size: 2.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found-text {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found-link {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.625rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: var(--vp-c-brand-1);
|
||||||
|
color: var(--vp-c-white);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found-link:hover {
|
||||||
|
background: var(--vp-c-brand-2);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Footer } from "@/components/footer";
|
||||||
|
import { Navbar } from "@/components/navbar";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex min-h-screen flex-col items-center justify-center px-6 pt-16">
|
||||||
|
<span className="text-7xl">🦦</span>
|
||||||
|
<h1 className="mt-6 text-4xl font-bold tracking-tight sm:text-5xl">
|
||||||
|
Hello from the otter side!
|
||||||
|
</h1>
|
||||||
|
<p className="mt-4 text-lg text-muted">
|
||||||
|
This page swam away. Let's get you back on track.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="mt-8 rounded-lg bg-accent px-6 py-3 text-sm font-medium text-accent-foreground transition-colors hover:bg-accent-hover"
|
||||||
|
>
|
||||||
|
Back to homepage
|
||||||
|
</a>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
+10
-1
@@ -1,7 +1,7 @@
|
|||||||
import { APP_VERSION, en, shouldShowConsent } from "@snapotter/shared";
|
import { APP_VERSION, en, shouldShowConsent } from "@snapotter/shared";
|
||||||
import { Component, type ErrorInfo, lazy, type ReactNode, Suspense, useEffect } from "react";
|
import { Component, type ErrorInfo, lazy, type ReactNode, Suspense, useEffect } from "react";
|
||||||
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
|
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster, toast } from "sonner";
|
||||||
import { ConnectionMonitor } from "./components/common/connection-monitor";
|
import { ConnectionMonitor } from "./components/common/connection-monitor";
|
||||||
import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider";
|
import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider";
|
||||||
import { I18nProvider } from "./contexts/i18n-context";
|
import { I18nProvider } from "./contexts/i18n-context";
|
||||||
@@ -186,6 +186,15 @@ export function App() {
|
|||||||
fetchAnalyticsConfig();
|
fetchAnalyticsConfig();
|
||||||
}, [fetchAnalyticsConfig]);
|
}, [fetchAnalyticsConfig]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localStorage.getItem("snapotter-welcome") === "1") {
|
||||||
|
localStorage.removeItem("snapotter-welcome");
|
||||||
|
toast("Hello from the otter side! 🦦", {
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!analyticsConfigLoaded ||
|
!analyticsConfigLoaded ||
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export function ChangePasswordPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("snapotter-welcome", "1");
|
||||||
// Trigger browser password save prompt via real form submission + navigation
|
// Trigger browser password save prompt via real form submission + navigation
|
||||||
const username = localStorage.getItem("snapotter-username") || "admin";
|
const username = localStorage.getItem("snapotter-username") || "admin";
|
||||||
triggerBrowserPasswordSave(username, newPassword);
|
triggerBrowserPasswordSave(username, newPassword);
|
||||||
@@ -218,6 +219,7 @@ export function ChangePasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:flex flex-1 bg-primary/90 items-center justify-center p-12 text-white rounded-s-3xl">
|
<div className="hidden lg:flex flex-1 bg-primary/90 items-center justify-center p-12 text-white rounded-s-3xl">
|
||||||
<div className="max-w-lg space-y-6 text-center">
|
<div className="max-w-lg space-y-6 text-center">
|
||||||
|
<span className="text-7xl">🦦</span>
|
||||||
<h2 className="text-3xl font-bold">{t.changePassword.sidebarTitle}</h2>
|
<h2 className="text-3xl font-bold">{t.changePassword.sidebarTitle}</h2>
|
||||||
<p className="text-lg text-white/80">{t.changePassword.sidebarDescription}</p>
|
<p className="text-lg text-white/80">{t.changePassword.sidebarDescription}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1687,8 +1687,8 @@ export const en = {
|
|||||||
connectionError: "Connection error",
|
connectionError: "Connection error",
|
||||||
changingButton: "Changing...",
|
changingButton: "Changing...",
|
||||||
changeButton: "Change password",
|
changeButton: "Change password",
|
||||||
sidebarTitle: "Almost there",
|
sidebarTitle: "Hello from the otter side!",
|
||||||
sidebarDescription: "Set a strong password to secure your account, then you are good to go.",
|
sidebarDescription: "Set a strong password to secure your account, then you're good to go.",
|
||||||
},
|
},
|
||||||
automate: {
|
automate: {
|
||||||
title: "Automate",
|
title: "Automate",
|
||||||
|
|||||||
Reference in New Issue
Block a user