Skip to content

Commit 25a994d

Browse files
committed
progress commit
1 parent 295a215 commit 25a994d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3809
-214
lines changed

Diff for: app-react-router-framework/.react-router/types/app/~/routes/+types/home.ts renamed to app-react-router-framework/.react-router/types/app/routes/+types/home.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
// React Router generated types for route:
2-
// ~/routes/home.tsx
2+
// ./routes/home.tsx
33

44
import type * as T from "react-router/route-module"
55

6-
import type { Info as Parent0 } from "../../../+types/root.js"
6+
import type { Info as Parent0 } from "../../+types/root.js"
7+
import type { Info as Parent1 } from "./products-layout.js"
78

89
type Module = typeof import("../home.js")
910

1011
export type Info = {
11-
parents: [Parent0],
12-
id: "~/routes/home"
13-
file: "~/routes/home.tsx"
12+
parents: [Parent0, Parent1],
13+
id: "routes/home"
14+
file: "./routes/home.tsx"
1415
path: "undefined"
1516
params: {} & { [key: string]: string | undefined }
1617
module: Module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// React Router generated types for route:
2+
// ./routes/products-layout.tsx
3+
4+
import type * as T from "react-router/route-module"
5+
6+
import type { Info as Parent0 } from "../../+types/root.js"
7+
8+
type Module = typeof import("../products-layout.js")
9+
10+
export type Info = {
11+
parents: [Parent0],
12+
id: "routes/products-layout"
13+
file: "./routes/products-layout.tsx"
14+
path: "undefined"
15+
params: {} & { [key: string]: string | undefined }
16+
module: Module
17+
loaderData: T.CreateLoaderData<Module>
18+
actionData: T.CreateActionData<Module>
19+
}
20+
21+
export namespace Route {
22+
export type LinkDescriptors = T.LinkDescriptors
23+
export type LinksFunction = () => LinkDescriptors
24+
25+
export type MetaArgs = T.CreateMetaArgs<Info>
26+
export type MetaDescriptors = T.MetaDescriptors
27+
export type MetaFunction = (args: MetaArgs) => MetaDescriptors
28+
29+
export type HeadersArgs = T.HeadersArgs
30+
export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit
31+
32+
export type LoaderArgs = T.CreateServerLoaderArgs<Info>
33+
export type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
34+
export type ActionArgs = T.CreateServerActionArgs<Info>
35+
export type ClientActionArgs = T.CreateClientActionArgs<Info>
36+
37+
export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
38+
export type ComponentProps = T.CreateComponentProps<Info>
39+
export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router'
2+
3+
import type { Route } from './+types/root'
4+
5+
export function Layout({ children }: { children: React.ReactNode }) {
6+
return (
7+
<html lang="en">
8+
<head>
9+
<meta charSet="utf-8" />
10+
<meta name="viewport" content="width=device-width, initial-scale=1" />
11+
<Meta />
12+
<Links />
13+
</head>
14+
<body>
15+
{children}
16+
<ScrollRestoration />
17+
<Scripts />
18+
</body>
19+
</html>
20+
)
21+
}
22+
23+
export default function App() {
24+
return <Outlet />
25+
}
26+
27+
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
28+
let message = 'Oops!'
29+
let details = 'An unexpected error occurred.'
30+
let stack: string | undefined
31+
32+
if (isRouteErrorResponse(error)) {
33+
message = error.status === 404 ? '404' : 'Error'
34+
details =
35+
error.status === 404 ? 'The requested page could not be found.' : error.statusText || details
36+
} else if (import.meta.env.DEV && error && error instanceof Error) {
37+
details = error.message
38+
stack = error.stack
39+
}
40+
41+
return (
42+
<main className="pt-16 p-4 container mx-auto">
43+
<h1>{message}</h1>
44+
<p>{details}</p>
45+
{stack && (
46+
<pre className="w-full p-4 overflow-x-auto">
47+
<code>{stack}</code>
48+
</pre>
49+
)}
50+
</main>
51+
)
52+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { type RouteConfig, index } from '@react-router/dev/routes'
22

3-
export default [index('~/routes/home.tsx')] satisfies RouteConfig
3+
export default [index('./routes/home.tsx')] satisfies RouteConfig
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export default function Home() {
2-
return <div>01 Routes Home Page</div>
2+
return <div>01 Lesson Home</div>
33
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Link } from 'react-router'
2+
import { Avatar } from '~/components/Avatar'
3+
import { useAuth } from '~/state/AuthContext'
4+
5+
export function AuthenticatedUserNav() {
6+
const { user } = useAuth()
7+
8+
return (
9+
<div>
10+
{user ? (
11+
<Avatar src={user.avatarUrl} />
12+
) : (
13+
<Link
14+
to="/login"
15+
className="border-current border rounded-md text-sky-500 bg-black/20 px-4 py-2 shadow-md"
16+
>
17+
Login
18+
</Link>
19+
)}
20+
</div>
21+
)
22+
}

Diff for: app-react-router-framework/app/components/Avatar.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
type Props = {
2+
src: string
3+
size?: number
4+
className?: string
5+
} & React.HTMLAttributes<HTMLImageElement>
6+
7+
export function Avatar({ src, size = 2, className, ...props }: Props) {
8+
return (
9+
<img
10+
alt="User Avatar"
11+
loading="lazy"
12+
{...props}
13+
src={src}
14+
style={{ fontSize: `${size}rem` }}
15+
className="aspect-square w-[1em] rounded-full"
16+
/>
17+
)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Link } from 'react-router'
2+
import { Tiles } from '~/components/Tiles'
3+
import { CartButtons } from '~/components/CartButtons'
4+
import type { ProductType } from '~/utils/db.server'
5+
import { useCart } from '~/state/CartContext'
6+
7+
type BrowseProductsProps = {
8+
products: ProductType[]
9+
}
10+
11+
export function BrowseProducts({ products }: BrowseProductsProps) {
12+
const { cart } = useCart()
13+
14+
return (
15+
<div>
16+
<Tiles>
17+
{products.map((product) => {
18+
const quantityInCart = cart?.find((item) => item.productId === product.id)?.quantity || 0
19+
return (
20+
<div key={product.id} className="rounded-lg bg-white shadow-sm">
21+
<BrowseProductItem product={product} quantityInCart={quantityInCart || 0} />
22+
</div>
23+
)
24+
})}
25+
</Tiles>
26+
</div>
27+
)
28+
}
29+
30+
type BrowseProductItemProps = {
31+
product: ProductType
32+
quantityInCart: number
33+
}
34+
35+
export function BrowseProductItem({ product, quantityInCart }: BrowseProductItemProps) {
36+
return (
37+
<div className="p-3 overflow-hidden flex flex-col">
38+
<img
39+
src={`/images/products/${product.image}`}
40+
alt={product.name}
41+
className="block object-contain h-52"
42+
/>
43+
<div className="space-y-3 mt-3 border-t">
44+
<div className="mt-3 flex justify-between items-center">
45+
<div className="">{product.name}</div>
46+
<b className="block">${product.price}</b>
47+
</div>
48+
<div className="flex gap-2">
49+
<CartButtons productId={product.id} quantityInCart={quantityInCart} />
50+
<div className="w-full flex flex-col">
51+
<Link to={`/products/${product.id}`} className="button">
52+
View
53+
</Link>
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
)
59+
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useFetcher } from 'react-router'
2+
import { Icon } from '~/components/Icon'
3+
4+
type AddToCartButtonProps = {
5+
productId: number
6+
quantityInCart: number
7+
}
8+
9+
export function CartButtons({ productId, quantityInCart }: AddToCartButtonProps) {
10+
const addFetcher = useFetcher()
11+
const removeFetcher = useFetcher()
12+
13+
// Optimistic UI
14+
let quantity = quantityInCart
15+
if (addFetcher.formData) {
16+
quantity = parseInt(addFetcher.formData.get('quantity') as string)
17+
}
18+
19+
if (removeFetcher.formData) {
20+
quantity = 0
21+
}
22+
23+
return (
24+
<>
25+
<addFetcher.Form method="post" action="/cart">
26+
<input type="hidden" name="productId" value={productId} />
27+
<input type="hidden" name="quantity" value={quantity + 1} />
28+
<button className="button button-outline whitespace-nowrap" type="submit">
29+
<Icon name="cart" />
30+
{quantity > 0 && (
31+
<span className="ml-2 align-middle inline-block min-w-[1.5em]">{quantity}</span>
32+
)}
33+
</button>
34+
</addFetcher.Form>
35+
{quantity > 0 && (
36+
<removeFetcher.Form method="delete" action="/cart">
37+
<input type="hidden" name="productId" value={productId} />
38+
<button className="button" type="submit">
39+
Remove
40+
</button>
41+
</removeFetcher.Form>
42+
)}
43+
</>
44+
)
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
type CenterContentProps = {
2+
className?: string
3+
children: React.ReactNode
4+
}
5+
6+
export function CenterContent({ children, className }: CenterContentProps) {
7+
return (
8+
<div className={className}>
9+
<div className="ml-auto mr-auto max-w-[1200px] pl-3 pr-3">{children}</div>
10+
</div>
11+
)
12+
}
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useId } from 'react'
2+
import { Icon } from './Icon'
3+
import { Link, useSearchParams } from 'react-router'
4+
5+
/****************************************
6+
Filter Link
7+
*****************************************/
8+
9+
type Props = {
10+
children: React.ReactNode
11+
url: string
12+
filter: string
13+
value: string
14+
}
15+
16+
export function FilterLink({ children, url, filter, value }: Props) {
17+
const [search] = useSearchParams()
18+
19+
// The current URL
20+
const urlValue = search.get(filter)?.toLowerCase().split(',')
21+
const on = Array.isArray(urlValue) && urlValue.includes(value.toLowerCase())
22+
23+
// The next URL
24+
const nextSearch = new URLSearchParams(search.toString())
25+
const valuesFiltered = Array.isArray(urlValue) ? urlValue.filter((v) => v && v !== value) : []
26+
27+
if (on) {
28+
nextSearch.set(filter, valuesFiltered.join(','))
29+
} else {
30+
nextSearch.set(filter, valuesFiltered.concat(value).join(','))
31+
}
32+
33+
return <FilterLinkUI on={on} to={`${url}?${nextSearch.toString()}`} children={children} />
34+
}
35+
36+
/****************************************
37+
Filter Link All
38+
*****************************************/
39+
40+
export function FilterLinkAll({ children, url, filter }: Omit<Props, 'value'>) {
41+
const [search] = useSearchParams()
42+
const on = [null, ''].includes(search.get(filter))
43+
const nextSearch = new URLSearchParams(search.toString())
44+
nextSearch.delete(filter)
45+
46+
return <FilterLinkUI on={on} to={`${url}?${nextSearch.toString()}`} children={children} />
47+
}
48+
49+
/****************************************
50+
UI for both the Filter Links Above
51+
*****************************************/
52+
53+
type FilterLinkUIProps = {
54+
children: React.ReactNode
55+
on: boolean
56+
to: string
57+
}
58+
59+
function FilterLinkUI({ children, on, to }: FilterLinkUIProps) {
60+
const id = useId()
61+
return (
62+
<Link to={to} className="block">
63+
<input id={id} type="checkbox" className="hidden" />
64+
<span className="text-brandColor mr-2">
65+
{on ? <Icon name="checkboxOn" /> : <Icon name="checkboxOff" />}
66+
</span>
67+
<label htmlFor={id} className="cursor-pointer">
68+
{children}
69+
</label>
70+
</Link>
71+
)
72+
}
73+
74+
// // The current URL
75+
// const urlValue = search.get(filter)?.toLowerCase()
76+
// const on = urlValue === value.toLowerCase()
77+
78+
// // The next URL
79+
// const nextSearch = new URLSearchParams(search.toString())
80+
// nextSearch.set(filter, value)
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useId } from 'react'
2+
import classnames from 'classnames'
3+
4+
type FieldProps = {
5+
id: string
6+
required: boolean
7+
}
8+
9+
type FieldWrapProps = {
10+
children: React.ReactNode | ((fieldProps: FieldProps) => React.ReactNode)
11+
label: string
12+
required?: boolean
13+
errors?: string[] | undefined
14+
}
15+
16+
export function FieldWrap({ children, label, required = false, errors }: FieldWrapProps) {
17+
const id = useId()
18+
const fieldProps = { id, required }
19+
return (
20+
<div className={classnames('form-field-wrap space-y-1', { required })}>
21+
<label htmlFor={id} className="text-lg text-headingColor">
22+
{label}
23+
</label>
24+
<div>{typeof children === 'function' ? children(fieldProps) : children}</div>
25+
{errors && <div className="text-red-500">{errors.join(', ')}</div>}
26+
</div>
27+
)
28+
}

0 commit comments

Comments
 (0)