feat: simplify error handling in login and registration pages, add install prompt component, and update favicon and icons

This commit is contained in:
2025-09-07 18:43:56 +02:00
parent c6f5bab1eb
commit 5f8cf49ec9
20 changed files with 160 additions and 23 deletions

View File

@@ -36,7 +36,7 @@ export function FollowButton({
setIsFollowing(!isFollowing);
await action(username, token);
router.refresh(); // Re-fetch server component data to get the latest follower count etc.
} catch (err) {
} catch {
// Revert on error
setIsFollowing(isFollowing);
toast.error(`Failed to ${isFollowing ? "unfollow" : "follow"} user.`);

View File

@@ -0,0 +1,107 @@
"use client";
import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Download } from "lucide-react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardAction,
} from "@/components/ui/card";
interface CustomWindow extends Window {
MSStream?: unknown;
}
interface BeforeInstallPromptEvent extends Event {
prompt: () => void;
userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>;
}
export default function InstallPrompt() {
const [isIOS, setIsIOS] = useState(false);
const [isStandalone, setIsStandalone] = useState(false);
const [deferredPrompt, setDeferredPrompt] =
useState<BeforeInstallPromptEvent | null>(null);
useEffect(() => {
// Cast window to our custom type instead of 'any'
const customWindow = window as CustomWindow;
setIsIOS(
/iPad|iPhone|iPod/.test(navigator.userAgent) && !customWindow.MSStream
);
setIsStandalone(window.matchMedia("(display-mode: standalone)").matches);
const handleBeforeInstallPrompt = (e: Event) => {
e.preventDefault();
setDeferredPrompt(e as BeforeInstallPromptEvent);
};
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
return () => {
window.removeEventListener(
"beforeinstallprompt",
handleBeforeInstallPrompt
);
};
}, []);
const handleInstallClick = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === "accepted") {
console.log("User accepted the install prompt");
} else {
console.log("User dismissed the install prompt");
}
setDeferredPrompt(null);
};
if (isStandalone || (!isIOS && !deferredPrompt)) {
return null;
}
return (
<div className="fixed bottom-0 z-50">
<Card className="w-full max-w-sm glass-effect glossy-effect bottom shadow-fa-lg">
<CardHeader>
<CardTitle>Install Thoughts</CardTitle>
<CardDescription>
Get the full app experience on your device.
</CardDescription>
<CardAction>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2"
onClick={() => setIsStandalone(true)}
>
&times;
</Button>
</CardAction>
</CardHeader>
<CardContent>
{!isIOS && deferredPrompt && (
<Button className="w-full" onClick={handleInstallClick}>
<Download className="mr-2 h-4 w-4" />
Add to Home Screen
</Button>
)}
{isIOS && (
<p className="text-sm text-muted-foreground">
To install, tap the Share icon
<span className="mx-1 text-lg"></span>
and then &quot;Add to Home Screen&quot;
<span className="mx-1 text-lg"></span>.
</p>
)}
</CardContent>
</Card>
</div>
);
}

View File

@@ -17,7 +17,7 @@ const buttonVariants = cva(
"glass-effect fa-gradient-green text-secondary-foreground shadow-fa-md hover:bg-secondary/90 active:shadow-fa-inner transition-transform active:scale-[0.98] glossy-effect",
// Ghost and Link should be more subtle
ghost:
"glass-effect hover:bg-accent hover:text-accent-foreground rounded-lg", // Keep them simple, maybe a slight blur/gloss on hover
"glass-effect hover:bg-accent hover:text-accent-foreground rounded-lg",
link: "text-primary underline-offset-4 hover:underline",
// Outline button for a transparent-ish, glassy feel
outline: