Add COSS toast notifications and swap in the new logo

Wire COSS's (Base UI) Sonner-style stacked toast into the app:

- Install @coss/toast (components/ui/toast.tsx) and mount <ToastProvider> in
  the root layout; add a small notify.{success,error,info,warning} helper
  (lib/toast.ts) over toastManager so callers don't repeat the toast shape.
- Toast on the events requested: clinic created (onboarding), auth actions
  (sign in / sign up / sign out / password reset request + reset), patient
  saved (create/edit), and all failure branches as error toasts (kept inline
  errors as a fallback). New auth toast strings added to the en translation.
- Replace the logo everywhere: overwrite public/temetro-logo.png with the new
  1024² square mark (same aspect ratio as the old 500², so existing 32×32
  next/image usages stay undistorted; no reference changes needed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-04 18:58:04 +03:00
parent 415961ab52
commit 2c5624e049
13 changed files with 435 additions and 13 deletions
+5 -1
View File
@@ -7,6 +7,7 @@ import { AuthShell, Field, FormAlert } from "@/components/auth/auth-ui";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { authClient } from "@/lib/auth-client";
import { notify } from "@/lib/toast";
export default function ForgotPasswordPage() {
const [email, setEmail] = useState("");
@@ -27,9 +28,12 @@ export default function ForgotPasswordPage() {
setSubmitting(false);
if (err) {
setError(err.message ?? "Could not send the reset email.");
const message = err.message ?? "Could not send the reset email.";
setError(message);
notify.error("Couldn't send reset link", message);
return;
}
notify.success("Reset link sent", "Check your inbox for the link.");
setSent(true);
};
+5 -1
View File
@@ -7,6 +7,7 @@ import { AuthShell, Field, FormAlert } from "@/components/auth/auth-ui";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { authClient } from "@/lib/auth-client";
import { notify } from "@/lib/toast";
function slugify(value: string): string {
return value
@@ -45,12 +46,15 @@ export default function OnboardingPage() {
);
if (createErr || !org) {
setError(createErr?.message ?? "Could not create the clinic.");
const message = createErr?.message ?? "Could not create the clinic.";
setError(message);
notify.error("Couldn't create clinic", message);
setSubmitting(false);
return;
}
await authClient.organization.setActive({ organizationId: org.id });
notify.success("Clinic created", `${org.name} is ready.`);
router.push("/");
};
+5 -1
View File
@@ -8,6 +8,7 @@ import { AuthShell, Field, FormAlert } from "@/components/auth/auth-ui";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { authClient } from "@/lib/auth-client";
import { notify } from "@/lib/toast";
const MIN_PASSWORD = 12;
@@ -46,9 +47,12 @@ function ResetPasswordInner() {
});
setSubmitting(false);
if (err) {
setError(err.message ?? "Could not reset your password.");
const message = err.message ?? "Could not reset your password.";
setError(message);
notify.error("Couldn't reset password", message);
return;
}
notify.success("Password updated", "Redirecting you to sign in…");
setDone(true);
setTimeout(() => router.push("/login"), 1500);
};
+45 -1
View File
@@ -59,7 +59,51 @@
background-position: -200% 0;
}
}
}
--animate-toast-success-odd: toast-success-odd 0.32s cubic-bezier(0.5, 1, 0.89, 1);
--animate-toast-success-even: toast-success-even 0.32s cubic-bezier(0.5, 1, 0.89, 1);
--animate-toast-error-odd: toast-error-odd 0.28s cubic-bezier(0.5, 1, 0.89, 1);
--animate-toast-error-even: toast-error-even 0.28s cubic-bezier(0.5, 1, 0.89, 1)
;
@keyframes toast-success-odd {
0% {
scale: 1;}
30% {
scale: 1.025;}
60% {
scale: 0.99;}
100% {
scale: 1;}}
@keyframes toast-error-odd {
0% {
translate: 0 0;}
25% {
translate: -3px 0;}
50% {
translate: 3px 0;}
75% {
translate: -3px 0;}
100% {
translate: 0 0;}}
@keyframes toast-success-even {
0% {
scale: 1;}
30% {
scale: 1.025;}
60% {
scale: 0.99;}
100% {
scale: 1;}}
@keyframes toast-error-even {
0% {
translate: 0 0;}
25% {
translate: -3px 0;}
50% {
translate: 3px 0;}
75% {
translate: -3px 0;}
100% {
translate: 0 0;}}}
:root {
--card: var(--color-white);
+4 -1
View File
@@ -4,6 +4,7 @@ import "./globals.css";
import { cn } from "@/lib/utils";
import { ThemeProvider } from "@/components/theme-provider";
import { I18nProvider } from "@/components/i18n-provider";
import { ToastProvider } from "@/components/ui/toast";
// COSS font-variable contract: --font-sans, --font-heading, --font-mono.
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
@@ -48,7 +49,9 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
<I18nProvider>{children}</I18nProvider>
<ToastProvider>
<I18nProvider>{children}</I18nProvider>
</ToastProvider>
</ThemeProvider>
</body>
</html>