Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/components/layout/mobile-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ export function MobileNav() {
<Link
key={link.href}
href={link.href}
aria-label={link.label}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex flex-col items-center gap-0.5 rounded-lg px-1.5 py-1.5 text-[10px] font-medium transition-colors min-w-0 sm:px-3 sm:py-2 sm:text-xs",
"flex min-h-11 min-w-11 flex-col items-center gap-0.5 rounded-lg px-1.5 py-1.5 text-[10px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 sm:px-3 sm:py-2 sm:text-xs",
isActive
? "text-primary-600"
: "text-gray-500 hover:text-gray-900"
)}
>
<link.icon className="h-5 w-5 shrink-0" />
<link.icon className="h-5 w-5 shrink-0" aria-hidden="true" />
<span className="max-sm:hidden">{link.label}</span>
</Link>
);
Expand Down
12 changes: 4 additions & 8 deletions src/components/shared/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,11 @@ class ErrorBoundaryInner extends React.Component<
};

private handleRetry = async () => {
const { retryCount } = this.state;
const delay = this.getExponentialBackoffDelay(retryCount);

this.setState({ isRetrying: true });

try {
// Wait before retrying
await new Promise((resolve) => setTimeout(resolve, delay));

// Reset error state to retry rendering children
const { retryCount } = this.state;
this.setState({
hasError: false,
error: null,
Expand Down Expand Up @@ -214,15 +209,15 @@ class ErrorBoundaryInner extends React.Component<

// Default error UI
return (
<div className="flex flex-col items-center justify-center py-16 px-4 text-center">
<div className="flex flex-col items-center justify-center py-16 px-4 text-center" role="alert">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-red-100 mb-4">
<AlertTriangle className="h-8 w-8 text-red-500" />
</div>
<h2 className="text-lg font-semibold text-gray-900 mb-2">
Something went wrong
</h2>
<p className="text-sm text-gray-600 max-w-md mb-2">
{error?.message || "An unexpected error occurred."}
We could not render this section. Try again or reload the page.
</p>
{retryCount > 0 && (
<p className="text-xs text-gray-500 mb-6">
Expand Down Expand Up @@ -260,6 +255,7 @@ class ErrorBoundaryInner extends React.Component<
typeof window !== "undefined" && window.location.reload()
}
variant="outline"
aria-label="Reload the current page"
>
Reload Page
</Button>
Expand Down
85 changes: 52 additions & 33 deletions src/components/shared/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ const variantIcons = {
};

const SWIPE_DISMISS_THRESHOLD = 80;
const MAX_VISIBLE_TOASTS = 4;

function createToastId() {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
return crypto.randomUUID();
}
return Math.random().toString(36).slice(2);
}

export function Toast({
message,
Expand Down Expand Up @@ -167,17 +175,21 @@ export function useToast() {
(message: string, options: ToastVariant | AddToastOptions = "info") => {
const opts: AddToastOptions =
typeof options === "string" ? { variant: options } : options;
const id = Math.random().toString(36).slice(2);
setToasts((prev) => [
...prev,
{
id,
message,
variant: opts.variant ?? "info",
autoClose: opts.autoClose,
action: opts.action,
},
]);
setToasts((prev) => {
const withoutDuplicate = prev.filter(
(toast) => toast.message !== message || toast.variant !== (opts.variant ?? "info")
);
return [
...withoutDuplicate,
{
id: createToastId(),
message,
variant: opts.variant ?? "info",
autoClose: opts.autoClose,
action: opts.action,
},
].slice(-MAX_VISIBLE_TOASTS);
});
},
[]
);
Expand Down Expand Up @@ -224,17 +236,22 @@ export function ToastContextProvider({ children }: { children: ReactNode }) {
(message: string, options: ToastVariant | AddToastOptions = "info") => {
const opts: AddToastOptions =
typeof options === "string" ? { variant: options } : options;
const id = Math.random().toString(36).slice(2);
setToasts((prev) => [
...prev,
{
id,
message,
variant: opts.variant ?? "info",
autoClose: opts.autoClose,
action: opts.action,
},
]);
setToasts((prev) => {
const variant = opts.variant ?? "info";
const withoutDuplicate = prev.filter(
(toast) => toast.message !== message || toast.variant !== variant
);
return [
...withoutDuplicate,
{
id: createToastId(),
message,
variant,
autoClose: opts.autoClose,
action: opts.action,
},
].slice(-MAX_VISIBLE_TOASTS);
});
},
[]
);
Expand All @@ -246,17 +263,19 @@ export function ToastContextProvider({ children }: { children: ReactNode }) {
return (
<ToastContext.Provider value={{ addToast }}>
{children}
{toasts.map((toast, index) => (
<Toast
key={toast.id}
message={toast.message}
variant={toast.variant}
autoClose={toast.autoClose}
action={toast.action}
onClose={() => removeToast(toast.id)}
index={index}
/>
))}
<div aria-live="polite" aria-relevant="additions text">
{toasts.map((toast, index) => (
<Toast
key={toast.id}
message={toast.message}
variant={toast.variant}
autoClose={toast.autoClose}
action={toast.action}
onClose={() => removeToast(toast.id)}
index={index}
/>
))}
</div>
</ToastContext.Provider>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/tests/shared/error-boundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("ErrorBoundary", () => {
</ErrorBoundary>
);
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
expect(screen.getByText("Boom!")).toBeInTheDocument();
expect(screen.getByText(/could not render this section/i)).toBeInTheDocument();
});

it("renders a custom fallback when provided", () => {
Expand Down