Skip to content

Commit ba9adf6

Browse files
committed
progress commit
1 parent 73bd13c commit ba9adf6

File tree

14 files changed

+341
-87
lines changed

14 files changed

+341
-87
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ dist
44
dist-ssr
55
*.local
66

7+
.react-router
8+
79
# misc
810
db.json
911
preferences.json

app-react-router-framework/app/components/ProductsSidebar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function FilterByPrice() {
9393
<b>$5000</b>
9494
</div>
9595
</div>
96-
<input type="range" className="block w-full" />
96+
<input type="range" className="block w-full bg-slate-200" />
9797
<div className="flex justify-between items-center">
9898
<div className="text-sm">$10</div>
9999
<div className="text-sm">$5000</div>

app-react-router-framework/app/styles/app.css app-react-router-framework/app/index.css

+20-20
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ body {
1313
min-height: 100vh;
1414
width: 100vw;
1515
overflow-x: hidden;
16-
color: var(--colors-textColor);
16+
color: var(--color-textColor);
1717
}
1818

1919
body {
2020
/* https://twitter.com/AllThingsSmitty/status/1254151507412496384 */
2121
min-height: -webkit-fill-available;
22-
background-image: linear-gradient(to bottom, var(--colors-slate-200), var(--colors-slate-100));
22+
background-image: linear-gradient(to bottom, var(--color-slate-200), var(--color-slate-100));
2323
background-repeat: no-repeat;
2424
}
2525

@@ -46,7 +46,7 @@ body {
4646
h5,
4747
h6 {
4848
font-family: 'Inter';
49-
color: var(--colors-headingColor);
49+
color: var(--color-headingColor);
5050
font-weight: 700;
5151
margin: 0;
5252
letter-spacing: 0.03em;
@@ -59,7 +59,7 @@ body {
5959

6060
@layer base {
6161
a {
62-
color: var(--colors-brandColor);
62+
color: var(--color-brandColor);
6363
text-decoration: none;
6464
}
6565
a:focus {
@@ -122,7 +122,7 @@ body {
122122
font-size: 0.8rem;
123123
letter-spacing: 0.08rem;
124124
line-height: 1.2em;
125-
background-color: var(--colors-brandColor);
125+
background-color: var(--color-brandColor);
126126
color: #fff;
127127
border: 1px solid transparent;
128128
border-radius: 0.2rem;
@@ -133,17 +133,17 @@ body {
133133

134134
.button:hover,
135135
.button:focus {
136-
background-color: var(--colors-sky-700);
136+
background-color: var(--color-sky-700);
137137
}
138138

139139
.button.button-outline {
140140
border-color: currentColor;
141141
background-color: #fff;
142-
color: var(--colors-brandColor);
142+
color: var(--color-brandColor);
143143
}
144144

145145
.button[disabled] {
146-
background-color: var(--colors-slate-400);
146+
background-color: var(--color-slate-400);
147147
color: #fff;
148148
}
149149
}
@@ -157,17 +157,17 @@ body {
157157
width: 100%;
158158
padding: 0.5em 0.7em;
159159
background-color: #fff;
160-
border: 1px solid var(--colors-slate-300);
160+
border: 1px solid var(--color-slate-300);
161161
font-size: 1.1rem;
162162
font-weight: 300;
163-
color: var(--colors-slate-700);
163+
color: var(--color-slate-700);
164164
border-radius: 5px;
165165
}
166166

167167
.form-field:focus,
168168
.form-field:focus-within {
169169
outline: none;
170-
border-color: var(--colors-sky-400);
170+
border-color: var(--color-sky-400);
171171
}
172172

173173
textarea.form-field {
@@ -200,7 +200,7 @@ body {
200200

201201
@layer base {
202202
input {
203-
accent-color: var(--colors-brandColor);
203+
accent-color: var(--color-brandColor);
204204
}
205205

206206
input[type='range'] {
@@ -210,7 +210,7 @@ body {
210210
cursor: pointer;
211211
border-radius: 1em;
212212

213-
background: var(--colors-slate-300);
213+
background: var(--color-slate-300);
214214
height: 0.5rem;
215215
}
216216

@@ -229,14 +229,14 @@ body {
229229
.notice {
230230
padding: 0.5em 1em;
231231
border-radius: 0.5em;
232-
background-color: var(--colors-slate-300);
233-
border: 1px solid var(--colors-slate-400);
234-
color: var(--colors-slate-700);
232+
background-color: var(--color-slate-300);
233+
border: 1px solid var(--color-slate-400);
234+
color: var(--color-slate-700);
235235
}
236236
.notice.error {
237-
background-color: var(--colors-rose-200);
238-
border: 1px solid var(--colors-rose-400);
239-
color: var(--colors-rose-700);
237+
background-color: var(--color-rose-200);
238+
border: 1px solid var(--color-rose-400);
239+
color: var(--color-rose-700);
240240
}
241241
}
242242

@@ -246,6 +246,6 @@ body {
246246

247247
@layer components {
248248
.primary-nav a.active {
249-
border-color: var(--colors-brandColor);
249+
border-color: var(--color-brandColor);
250250
}
251251
}

app-react-router-framework/app/root.tsx

+7-55
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ import {
66
ScrollRestoration,
77
isRouteErrorResponse,
88
useRouteLoaderData,
9+
useMatches,
910
type LinksFunction,
1011
type LoaderFunctionArgs,
1112
} from 'react-router'
1213
import { MainLayout } from '~/components/MainLayout'
1314
import { Heading } from '~/components/Heading'
1415
import { CenterContent } from '~/components/CenterContent'
16+
// import { useRouteLoaderData } from '~/utils/hooks'
17+
1518
import { getSessionUser } from '~/utils/auth.server'
1619
import { AuthProvider } from '~/state/AuthContext'
1720
import { CartProvider } from '~/state/CartContext'
1821
import { getCart } from '~/utils/cart.server'
19-
import stylesheet from '~/styles/app.css?url'
22+
import stylesheet from '~/index.css?url'
2023
import type { PropsWithChildren } from 'react'
2124

2225
import type { Route } from './+types/root'
@@ -28,12 +31,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
2831
return { sessionUser, cart }
2932
}
3033

34+
type LoaderData = Awaited<ReturnType<typeof loader>>
35+
3136
export default function App() {
3237
return <Outlet />
3338
}
3439

3540
export function Layout({ children }: PropsWithChildren) {
36-
const { sessionUser, cart } = useRouteLoaderData<typeof loader>('root')!
41+
const { sessionUser, cart } = useRouteLoaderData<LoaderData>('root')!
3742

3843
return (
3944
<html lang="en">
@@ -82,56 +87,3 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
8287
</CenterContent>
8388
)
8489
}
85-
86-
// import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router'
87-
88-
// import type { Route } from './+types/root'
89-
90-
// export function Layout({ children }: { children: React.ReactNode }) {
91-
// return (
92-
// <html lang="en">
93-
// <head>
94-
// <meta charSet="utf-8" />
95-
// <meta name="viewport" content="width=device-width, initial-scale=1" />
96-
// <Meta />
97-
// <Links />
98-
// </head>
99-
// <body>
100-
// {children}
101-
// <ScrollRestoration />
102-
// <Scripts />
103-
// </body>
104-
// </html>
105-
// )
106-
// }
107-
108-
// export default function App() {
109-
// return <Outlet />
110-
// }
111-
112-
// export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
113-
// let message = 'Oops!'
114-
// let details = 'An unexpected error occurred.'
115-
// let stack: string | undefined
116-
117-
// if (isRouteErrorResponse(error)) {
118-
// message = error.status === 404 ? '404' : 'Error'
119-
// details =
120-
// error.status === 404 ? 'The requested page could not be found.' : error.statusText || details
121-
// } else if (import.meta.env.DEV && error && error instanceof Error) {
122-
// details = error.message
123-
// stack = error.stack
124-
// }
125-
126-
// return (
127-
// <main className="pt-16 p-4 container mx-auto">
128-
// <h1>{message}</h1>
129-
// <p>{details}</p>
130-
// {stack && (
131-
// <pre className="w-full p-4 overflow-x-auto">
132-
// <code>{stack}</code>
133-
// </pre>
134-
// )}
135-
// </main>
136-
// )
137-
// }
+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { type RouteConfig, index, layout, route } from '@react-router/dev/routes'
22

33
export default [
4-
//
54
layout('./routes/products-layout.tsx', [
65
index('./routes/home.tsx'),
7-
// route("login", "./auth/login.tsx"),
8-
// route("register", "./auth/register.tsx"),
6+
route('products', './routes/products-home.tsx'),
7+
route('products/:productId', './routes/product-profile.tsx'),
98
]),
9+
route('login', './routes/login.tsx'),
10+
route('register', './routes/register.tsx'),
11+
route('logout', './routes/logout.tsx'),
12+
route('cart', './routes/cart.tsx'),
1013
] satisfies RouteConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { type ActionFunctionArgs } from 'react-router'
2+
import { addToCart, removeFromCart } from '~/utils/cart.server'
3+
4+
export async function action({ request }: ActionFunctionArgs) {
5+
const formData = await request.formData()
6+
const productId = parseInt(formData.get('productId') as string)
7+
const quantity = parseInt(formData.get('quantity') as string)
8+
9+
switch (request.method) {
10+
case 'POST': {
11+
return await addToCart(request, productId, quantity)
12+
}
13+
case 'DELETE': {
14+
return await removeFromCart(request, productId)
15+
}
16+
default: {
17+
return null
18+
}
19+
}
20+
}

app-react-router-framework/app/routes/home.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Link, useRouteLoaderData } from 'react-router'
1+
import { type MetaFunction, Link, useRouteLoaderData } from 'react-router'
22
import { BrowseProducts } from '~/components/BrowseProducts'
3-
import type { MetaFunction } from 'react-router'
3+
import type { LoaderData as ProductsLayoutLoaderData } from './products-layout'
44

55
export const meta: MetaFunction = () => {
66
return [{ title: 'Tech Shopper' }]
77
}
88

9-
export default function () {
10-
// const { products } = useRouteLoaderData('routes/_products-layout') as LoaderData
9+
export default function Page() {
10+
const { products } = useRouteLoaderData('routes/products-layout') as ProductsLayoutLoaderData
1111

1212
return (
1313
<>
@@ -27,7 +27,7 @@ export default function () {
2727
<img src="/images/hero.png" alt="iphone hero" className="absolute left-0 bottom-0 w-1/2" />
2828
</div>
2929

30-
{/* <BrowseProducts products={products} /> */}
30+
{products && <BrowseProducts products={products} />}
3131
</>
3232
)
3333
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useState } from 'react'
2+
import * as z from 'zod'
3+
import { data } from 'react-router'
4+
import { Form, Link, useActionData } from 'react-router'
5+
import { createUserSession, verifyUser } from '~/utils/auth.server'
6+
import { FieldWrap } from '~/components/FormFields'
7+
import { Heading } from '~/components/Heading'
8+
import type { ActionFunctionArgs } from 'react-router'
9+
10+
const formSchema = z.object({
11+
username: z.string().min(5, 'Must be at least 5 characters'),
12+
password: z.string().min(5, 'Must be at least 5 characters'),
13+
})
14+
15+
type FormDataType = z.infer<typeof formSchema>
16+
type FormErrorType = {
17+
[k in keyof FormDataType]?: string[] | undefined
18+
}
19+
20+
export async function action({ request }: ActionFunctionArgs) {
21+
const formData = await request.formData()
22+
const formValues = Object.fromEntries(formData)
23+
const results = formSchema.safeParse(formValues)
24+
if (!results.success) return data({ error: 'Invalid Data' }, { status: 400 })
25+
26+
const { username, password } = results.data
27+
const userId = await verifyUser(username, password)
28+
if (!userId) return data({ error: 'User not found' }, { status: 400 })
29+
30+
return createUserSession(userId, '/')
31+
}
32+
33+
export default function Login() {
34+
const [formErrors, setFormErrors] = useState<FormErrorType>()
35+
const { error } = useActionData<typeof action>() || {}
36+
37+
function onSubmit(event: React.FormEvent<HTMLFormElement>) {
38+
const formValues = Object.fromEntries(new FormData(event.currentTarget))
39+
const results = formSchema.safeParse(formValues)
40+
if (!results.success) {
41+
event.preventDefault()
42+
setFormErrors(results.error.flatten().fieldErrors)
43+
}
44+
}
45+
46+
return (
47+
<div className="ml-auto mr-auto max-w-[600px]">
48+
<div className="bg-white rounded-md shadow-md p-6 space-y-6">
49+
<Heading size={4}>Login</Heading>
50+
{error && <div className="notice">{error}</div>}
51+
<Form onSubmit={onSubmit} method="post" className="space-y-3" autoComplete="off">
52+
<FieldWrap label="Username" required errors={formErrors?.username}>
53+
{(field) => <input {...field} className="form-field" type="text" name="username" />}
54+
</FieldWrap>
55+
<FieldWrap label="Password" required errors={formErrors?.password}>
56+
{(field) => <input {...field} className="form-field" type="password" name="password" />}
57+
</FieldWrap>
58+
<footer className="flex justify-between items-center">
59+
<div>
60+
<button type="submit" className="button">
61+
Login
62+
</button>
63+
</div>
64+
<div>
65+
<Link to="/register" type="button">
66+
Need an account? Register
67+
</Link>
68+
</div>
69+
</footer>
70+
</Form>
71+
</div>
72+
</div>
73+
)
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { LoaderFunctionArgs } from 'react-router'
2+
import { logout } from '~/utils/auth.server'
3+
4+
export async function loader({ request }: LoaderFunctionArgs) {
5+
return logout(request)
6+
}

0 commit comments

Comments
 (0)