Skip to content

Commit a764722

Browse files
Merge pull request #5 from netlify-templates/updates
added error handling, updated sidebar links
2 parents c130b83 + bfeb048 commit a764722

File tree

11 files changed

+195
-138
lines changed

11 files changed

+195
-138
lines changed

app/components/Button.tsx

Lines changed: 54 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import LoadingSpinner from "./icons/LoadingSpinner";
77
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
88
className?: string;
99
variant?: "contained" | "outlined";
10-
size?: "small" | "medium" | "large";
1110
type?: "submit" | "reset" | "button";
1211
loading?: boolean;
1312
to?: string;
@@ -18,84 +17,66 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
1817
}
1918

2019
const baseStyles =
21-
"group inline-flex items-center justify-center gap-1 text-sm/5 tracking-wide rounded-md transition focus:outline-none";
20+
"group inline-flex items-center justify-center gap-1 py-3 px-6 text-sm/5 tracking-wide rounded-md transition focus:outline-none";
2221

2322
const variantStyles = {
2423
contained: "bg-cyan-500 text-white hover:bg-cyan-500/90",
2524
outlined: "bg-transparent text-cyan-600 ring ring-cyan-300 hover:bg-cyan-50",
2625
};
2726

28-
const sizeStyles = {
29-
small: "py-2 px-3",
30-
medium: "py-2 px-4",
31-
large: "py-3 px-6",
32-
};
33-
34-
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
35-
(
36-
{
37-
variant = "contained",
38-
size = "medium",
39-
children,
40-
className,
41-
loading = false,
42-
disabled = false,
43-
type,
44-
target,
45-
to,
46-
onClick,
47-
...rest
48-
},
49-
ref
50-
) => {
51-
return (
52-
<>
53-
{to ? (
54-
<Link
55-
to={to}
56-
className={[
57-
baseStyles,
58-
sizeStyles[size],
59-
variantStyles[variant],
60-
className,
61-
]
62-
.filter(Boolean)
63-
.join(" ")}
64-
target={target}
65-
>
66-
{children}
67-
</Link>
68-
) : (
69-
<button
70-
ref={ref}
71-
disabled={disabled}
72-
onClick={onClick}
73-
className={[
74-
baseStyles,
75-
sizeStyles[size],
76-
disabled || loading
77-
? "opacity-50 bg-slate-700 text-white hover:bg-slate-700 hover:text-white"
78-
: variantStyles[variant],
79-
"cursor-pointer relative",
80-
className,
81-
]
82-
.filter(Boolean)
83-
.join(" ")}
84-
{...rest}
85-
>
86-
{loading && (
87-
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
88-
<LoadingSpinner />
89-
</span>
90-
)}
91-
<span className={loading ? "invisible" : undefined}>
92-
{children}
27+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
28+
{
29+
variant = "contained",
30+
children,
31+
className,
32+
loading = false,
33+
disabled = false,
34+
target,
35+
to,
36+
onClick,
37+
...rest
38+
},
39+
ref
40+
) {
41+
return (
42+
<>
43+
{to ? (
44+
<Link
45+
to={to}
46+
className={[baseStyles, variantStyles[variant], className]
47+
.filter(Boolean)
48+
.join(" ")}
49+
target={target}
50+
>
51+
{children}
52+
</Link>
53+
) : (
54+
<button
55+
ref={ref}
56+
disabled={disabled}
57+
onClick={onClick}
58+
className={[
59+
baseStyles,
60+
disabled || loading
61+
? "opacity-50 bg-slate-700 text-white hover:bg-slate-700 hover:text-white"
62+
: variantStyles[variant],
63+
"cursor-pointer relative",
64+
className,
65+
]
66+
.filter(Boolean)
67+
.join(" ")}
68+
{...rest}
69+
>
70+
{loading && (
71+
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
72+
<LoadingSpinner />
9373
</span>
94-
</button>
95-
)}
96-
</>
97-
);
98-
}
99-
);
74+
)}
75+
<span className={loading ? "invisible" : undefined}>{children}</span>
76+
</button>
77+
)}
78+
</>
79+
);
80+
});
10081

10182
export default Button;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { isRouteErrorResponse, useRouteError } from "@remix-run/react";
2+
3+
export function GlobalErrorBoundary() {
4+
const error = useRouteError();
5+
6+
if (isRouteErrorResponse(error)) {
7+
return (
8+
<div className="px-8 py-10 space-y-4 border rounded-sm shadow-sm border-cyan-500 bg-cyan-100/20">
9+
<h1 className="text-2xl font-semibold text-slate-900 sm:text-3xl lg:text-4xl">
10+
{error.status} {error.statusText}
11+
</h1>
12+
<p>{error.data}</p>
13+
</div>
14+
);
15+
} else if (error instanceof Error) {
16+
return (
17+
<div className="px-8 py-10 space-y-4 border rounded-sm shadow-sm border-cyan-500 bg-cyan-100/20">
18+
<h1 className="text-2xl font-semibold text-slate-900 sm:text-3xl lg:text-4xl">
19+
Error
20+
</h1>
21+
<p>{error.message}</p>
22+
<p>The stack trace is:</p>
23+
<pre>{error.stack}</pre>
24+
</div>
25+
);
26+
} else {
27+
return (
28+
<div className="px-8 py-10 space-y-4 border rounded-sm shadow-sm border-cyan-500 bg-cyan-100/20">
29+
<h1 className="text-2xl font-semibold text-slate-900 sm:text-3xl lg:text-4xl">
30+
Unknown Error
31+
</h1>
32+
</div>
33+
);
34+
}
35+
}

app/components/Sidebar.tsx

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Link } from "@remix-run/react";
1+
import { NavLink } from "@remix-run/react";
22
import React from "react";
33

44
import Logo from "~/components/Logo";
@@ -8,34 +8,25 @@ import CloseIcon from "./icons/Close";
88

99
const NAV_ITEMS = [
1010
{
11-
id: 1,
1211
label: "Member List",
13-
href: "/",
12+
href: "/dashboard",
1413
},
1514
{
16-
id: 1,
17-
label: "New Member",
18-
href: "/dashboard/new",
19-
},
20-
{
21-
id: 2,
2215
label: "Member Details",
2316
href: "/dashboard/14c8afd0-50cc-4aca-9547-c997ed306065",
2417
},
2518
{
26-
id: 3,
27-
label: "Reset Pasword",
28-
href: "/reset-password",
19+
label: "New Member",
20+
href: "/dashboard/new",
2921
},
3022
{
31-
id: 4,
32-
label: "Sign Up",
33-
href: "/signup",
23+
label: "User Profile",
24+
href: "/dashboard/user",
3425
},
3526
{
36-
id: 5,
37-
label: "Docs: Remix on Netlify",
27+
label: "Remix on Netlify",
3828
href: "https://docs.netlify.com/frameworks/remix/",
29+
newTab: true,
3930
},
4031
];
4132

@@ -64,22 +55,40 @@ export default function Sidebar({ isOpen, setIsOpen }: Props) {
6455

6556
<div className="overflow-x-hidden overflow-y-scroll hide-scrollbar">
6657
<ul className="border-t border-slate-200">
67-
{NAV_ITEMS.map((item, index) => (
68-
<li key={item.id}>
69-
<Link
70-
key={item.id}
58+
{NAV_ITEMS.map((item) => (
59+
<li key={item.label}>
60+
<NavLink
7161
to={item.href}
72-
className="flex items-center justify-between px-2 py-4 border-b border-slate-200 group hover:border-cyan-300"
62+
className={({ isActive }) =>
63+
isActive
64+
? "flex items-center justify-between px-2 py-4 border-b border-cyan-300"
65+
: "flex items-center justify-between px-2 py-4 border-b border-slate-200 group hover:border-cyan-300"
66+
}
67+
{...(item.newTab && {
68+
target: "_blank",
69+
rel: "noopener noreferer",
70+
})}
71+
end
7372
>
74-
{item.label}
75-
<span className="text-slate-300 group-hover:text-cyan-300">
76-
{index + 1 === NAV_ITEMS.length ? (
77-
<ArrowTopRightIcon />
78-
) : (
79-
<ArrowRightIcon />
80-
)}
81-
</span>
82-
</Link>
73+
{({ isActive }) => (
74+
<>
75+
{item.label}
76+
<span
77+
className={
78+
isActive
79+
? "text-cyan-300"
80+
: "text-slate-300 group-hover:text-cyan-300"
81+
}
82+
>
83+
{item.newTab ? (
84+
<ArrowTopRightIcon />
85+
) : (
86+
<ArrowRightIcon />
87+
)}
88+
</span>
89+
</>
90+
)}
91+
</NavLink>
8392
</li>
8493
))}
8594
</ul>

app/components/TextField.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ interface TextFieldProps extends InputHTMLAttributes<HTMLInputElement> {
1010
placeholder?: string;
1111
className?: string;
1212
required?: boolean;
13-
error?: string | null | undefined;
1413
}
1514

1615
const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
17-
(
16+
function TextField(
1817
{
1918
id,
2019
name,
@@ -23,11 +22,10 @@ const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
2322
placeholder,
2423
className,
2524
label,
26-
error,
2725
...rest
2826
},
2927
ref
30-
) => {
28+
) {
3129
return (
3230
<div>
3331
{label && (
@@ -55,21 +53,13 @@ const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
5553
required={required}
5654
placeholder={placeholder}
5755
className={[
58-
"block w-full rounded-md border p-3 text-sm text-slate-700 transition placeholder:font-light",
59-
error
60-
? "border-rose-300 focus:border-rose-300 focus:ring-2 focus:ring-rose-100 focus:outline-none"
61-
: "border-slate-200 focus:border-cyan-300 focus:ring-2 focus:ring-cyan-100 focus:outline-none",
56+
"block w-full rounded-md border p-3 text-sm text-slate-700 transition placeholder:font-light border-slate-200 focus:border-cyan-300 focus:ring-2 focus:ring-cyan-100 focus:outline-none",
6257
className,
6358
]
6459
.filter(Boolean)
6560
.join(" ")}
6661
{...rest}
6762
/>
68-
{error ? (
69-
<span className="text-xs text-rose-700" id={id}>
70-
{error}
71-
</span>
72-
) : null}
7363
</div>
7464
);
7565
}

app/routes/$.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Link } from "@remix-run/react";
2+
3+
export default function PrivateNotFound() {
4+
return (
5+
<main className="grow px-8 py-12 flex items-center justify-center">
6+
<div className="px-8 py-10 space-y-4 border rounded-sm shadow-sm border-cyan-500 bg-cyan-100/20 w-full max-w-4xl text-center">
7+
<h1 className="text-2xl font-semibold text-slate-900 sm:text-3xl lg:text-4xl">
8+
Page Not Found
9+
</h1>
10+
<p>The page you’re looking for doesn’t exist.</p>
11+
<Link to="/" className="underline text-cyan-600">
12+
Go back home
13+
</Link>
14+
</div>
15+
</main>
16+
);
17+
}

app/routes/_auth.login.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,10 @@ export default function LogIn() {
118118
<Link
119119
to="/reset-password"
120120
className="block text-sm tracking-wide underline text-cyan-600"
121-
>
121+
>
122122
Forgot password?
123123
</Link>
124-
<Button
125-
type="submit"
126-
size="large"
127-
className="w-full"
128-
loading={isSubmitting}
129-
>
124+
<Button type="submit" className="w-full" loading={isSubmitting}>
130125
Login
131126
</Button>
132127
<p className="text-sm text-center">

app/routes/_auth.reset-password.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function ResetPassword() {
2727
type="email"
2828
placeholder="Email address"
2929
/>
30-
<Button type="submit" size="large" className="w-full">
30+
<Button type="submit" className="w-full">
3131
Reset Password
3232
</Button>
3333
</Form>

app/routes/_auth.signup.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@ export default function SignUp() {
5858
type="password"
5959
placeholder="password"
6060
/>
61-
<Button
62-
type="submit"
63-
size="large"
64-
className="w-full"
65-
loading={isSubmitting}
66-
>
61+
<Button type="submit" className="w-full" loading={isSubmitting}>
6762
Login
6863
</Button>
6964
</fieldset>

0 commit comments

Comments
 (0)