Skip to content

Commit

Permalink
WEB-27: Add error state
Browse files Browse the repository at this point in the history
  • Loading branch information
vinnie4k committed Dec 12, 2024
1 parent 0864693 commit 6adae61
Show file tree
Hide file tree
Showing 13 changed files with 381 additions and 35 deletions.
19 changes: 0 additions & 19 deletions actions/upload.ts

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@tanstack/react-query": "^5.51.5",
"axios": "^1.7.2",
"class-variance-authority": "^0.7.1",
Expand Down
8 changes: 6 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import localFont from "next/font/local";
import "./globals.css";

// Fonts
const sfProDisplay = localFont({
Expand Down Expand Up @@ -30,7 +31,10 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className="bg-other-background">{children}</body>
<body className="bg-other-background">
{children}
<Toaster />
</body>
</html>
);
}
2 changes: 2 additions & 0 deletions src/components/announcement/announcementForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useUserStore } from "@/stores/useUserStore";
import { addDays } from "date-fns";
import { useMemo, useState } from "react";
import ButtonPrimary1 from "../system/button/buttonPrimary1";
import errorToast from "../system/errorToast";
import InputDatePicker from "../system/input/inputDatePicker";
import InputMultiSelect from "../system/input/inputMultiselect";
import InputText from "../system/input/inputText";
Expand Down Expand Up @@ -64,6 +65,7 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props
console.log("Scheduling", announcement);
} catch (err) {
console.error(err);
errorToast();
}
};

Expand Down
15 changes: 8 additions & 7 deletions src/components/authGuard/authGuard.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"use client";

import { useUserStore } from "@/stores/useUserStore";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { User } from "@/models/user";
import ApiClient from "@/services/apiClient";
import { useUserStore } from "@/stores/useUserStore";
import { Constants } from "@/utils/constants";
import { User } from "@/models/user";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import errorToast from "../system/errorToast";

export default function AuthGuard({ children }: { children: React.ReactNode }) {
const apiClient = ApiClient.createInstance();
Expand All @@ -32,10 +33,10 @@ export default function AuthGuard({ children }: { children: React.ReactNode }) {
ApiClient.setAuthToken(apiClient, user.idToken);
const userData = await ApiClient.post<User>(apiClient, "/users/login");
setUser({ ...userData, idToken: user.idToken });
} catch (error) {
} catch (err) {
// Token has expired or unauthorized
// TODO: Show error toast
console.error(error);
console.error(err);
errorToast();
setUser(undefined);
router.push(Constants.pagePath.default);
}
Expand Down
12 changes: 7 additions & 5 deletions src/components/landing/landing.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
"use client";

import Footer from "@/components/common/footer";
import NavBar from "@/components/common/navBar";
import PageHeader from "@/components/common/pageHeader";
import LandingActiveSection from "@/components/landing/landingActiveSection";
import LandingCreateAnnouncement from "@/components/landing/landingCreateAnnouncement";
import Footer from "@/components/common/footer";
import LandingPastSection from "@/components/landing/landingPastSection";
import LandingUpcomingSection from "@/components/landing/landingUpcomingSection";
import { Announcement } from "@/models/announcement";
import PageHeader from "@/components/common/pageHeader";
import NavBar from "@/components/common/navBar";
import { useUserStore } from "@/stores/useUserStore";
import ApiClient from "@/services/apiClient";
import { useQuery } from "@tanstack/react-query";
import { useUserStore } from "@/stores/useUserStore";
import { Constants } from "@/utils/constants";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import AnnouncementForm from "../announcement/announcementForm";
import errorToast from "../system/errorToast";

export default function Landing() {
const apiClient = ApiClient.createInstance();
Expand All @@ -34,6 +35,7 @@ export default function Landing() {
return await ApiClient.get<Announcement[]>(apiClient, "/announcements", { params: { debug } });
} catch (err) {
console.error(err);
errorToast();
return [];
}
};
Expand Down
10 changes: 10 additions & 0 deletions src/components/system/errorToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { toast } from "@/hooks/use-toast";

export default function errorToast() {
toast({
variant: "destructive",
duration: 5 * 1000,
title: "Oops! Something went wrong.",
description: "We're having trouble completing your request. Please try again later.",
});
}
112 changes: 112 additions & 0 deletions src/components/ui/toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use client";

import { cn } from "@/lib/utils";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import * as React from "react";

const ToastProvider = ToastPrimitives.Provider;

const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-[0px] z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-2 sm:right-2 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;

const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-5 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive: "destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);

const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
});
Toast.displayName = ToastPrimitives.Root.displayName;

const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-7 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;

const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;

const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold [&+div]:text-xs", className)} {...props} />
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;

const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description ref={ref} className={cn("text-sm opacity-90", className)} {...props} />
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;

type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;

type ToastActionElement = React.ReactElement<typeof ToastAction>;

export {
Toast,
ToastAction,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
type ToastActionElement,
type ToastProps,
};
26 changes: 26 additions & 0 deletions src/components/ui/toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from "@/components/ui/toast";
import { useToast } from "@/hooks/use-toast";

export function Toaster() {
const { toasts } = useToast();

return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && <ToastDescription>{description}</ToastDescription>}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}
Loading

0 comments on commit 6adae61

Please sign in to comment.