feat: implement beautiful toast notifications with Sonner
- Install sonner package for toast notifications - Add Toaster component to root layout with dark theme styling - Replace all alert() calls with toast.success() and toast.error() - Add descriptive messages for success states - Toast notifications match the app's dark theme design
This commit is contained in:
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import { Geist } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -27,6 +28,17 @@ export default function RootLayout({
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<Toaster
|
||||
theme="dark"
|
||||
position="top-right"
|
||||
toastOptions={{
|
||||
style: {
|
||||
background: '#171717',
|
||||
border: '1px solid #262626',
|
||||
color: '#fff',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Upload, Film, Image as ImageIcon, User, Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const BUCKETS = [
|
||||
{ id: "videos", label: "Videos", icon: Film, types: "video/*" },
|
||||
@@ -34,10 +35,10 @@ export default function MediaPage() {
|
||||
|
||||
if (uploadError) throw uploadError;
|
||||
|
||||
alert("File uploaded successfully!");
|
||||
toast.success("File uploaded successfully!");
|
||||
} catch (error) {
|
||||
console.error("Upload failed:", error);
|
||||
alert("Upload failed: " + (error instanceof Error ? error.message : "Unknown error"));
|
||||
toast.error("Upload failed: " + (error instanceof Error ? error.message : "Unknown error"));
|
||||
} finally {
|
||||
setUploading(false);
|
||||
if (fileInputRef.current) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Plus, Trash2, Edit, Loader2, Users, AlertCircle } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { toast } from "sonner";
|
||||
import type { Database } from "@/lib/supabase";
|
||||
|
||||
type Trainer = Database["public"]["Tables"]["trainers"]["Row"];
|
||||
@@ -56,9 +57,12 @@ export default function TrainersPage() {
|
||||
const { error } = await supabase.from("trainers").delete().eq("id", id);
|
||||
if (error) throw error;
|
||||
setTrainers(trainers.filter((t) => t.id !== id));
|
||||
toast.success("Trainer deleted", {
|
||||
description: "The trainer has been removed successfully."
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete trainer:", error);
|
||||
alert("Failed to delete trainer");
|
||||
toast.error("Failed to delete trainer");
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Plus, Trash2, Edit, Loader2, Eye } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import type { Database } from "@/lib/supabase";
|
||||
|
||||
type Workout = Database["public"]["Tables"]["workouts"]["Row"];
|
||||
@@ -54,9 +55,12 @@ export default function WorkoutsPage() {
|
||||
const { error } = await supabase.from("workouts").delete().eq("id", id);
|
||||
if (error) throw error;
|
||||
setWorkouts(workouts.filter((w) => w.id !== id));
|
||||
toast.success("Workout deleted", {
|
||||
description: "The workout has been removed successfully."
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete workout:", error);
|
||||
alert("Failed to delete workout");
|
||||
toast.error("Failed to delete workout");
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { TagInput } from "@/components/tag-input"
|
||||
import { ExerciseList, Exercise } from "@/components/exercise-list"
|
||||
import { MediaUpload } from "@/components/media-upload"
|
||||
import { moveFilesFromTempToWorkout } from "@/lib/storage"
|
||||
import { toast } from "sonner"
|
||||
import type { Database } from "@/lib/supabase"
|
||||
|
||||
type Workout = Database["public"]["Tables"]["workouts"]["Row"]
|
||||
@@ -215,7 +216,7 @@ export default function WorkoutForm({ initialData, mode = "create" }: WorkoutFor
|
||||
router.push(`/workouts/${result.data.id}`)
|
||||
} catch (err) {
|
||||
console.error("Failed to save workout:", err)
|
||||
alert("Failed to save workout. Please try again.")
|
||||
toast.error("Failed to save workout. Please try again.")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
11
admin-web/package-lock.json
generated
11
admin-web/package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -12066,6 +12067,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sonner": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
||||
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user