Skip to content

Commit

Permalink
start putting together exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Feb 27, 2024
1 parent 091aa3c commit 47844e7
Show file tree
Hide file tree
Showing 54 changed files with 2,980 additions and 35 deletions.
1 change: 1 addition & 0 deletions exercises/01.raw/01.solution.throw/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Throwing Promises
35 changes: 35 additions & 0 deletions exercises/01.raw/01.solution.throw/api.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getShip, searchShips } from '#shared/ship-api-utils.server'

export type Ship = Awaited<ReturnType<typeof getShip>>
export type ShipSearch = Awaited<ReturnType<typeof searchShips>>

export async function loader({
request,
params,
}: {
request: Request
params: Record<string, string>
}) {
const path = params['*']
switch (path) {
case 'search-ships': {
const result = await searchShips(request)
return new Response(JSON.stringify(result), {
headers: {
'content-type': 'application/json',
},
})
}
case 'get-ship': {
const result = await getShip(request)
return new Response(JSON.stringify(result), {
headers: {
'content-type': 'application/json',
},
})
}
default: {
return new Response('Not found', { status: 404 })
}
}
}
143 changes: 143 additions & 0 deletions exercises/01.raw/01.solution.throw/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
body {
margin: 0;
}

* {
box-sizing: border-box;
}

.app-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}

.app {
display: flex;
max-width: 1024px;
border: 1px solid #000;
border-start-end-radius: 0.5rem;
border-start-start-radius: 0.5rem;
border-end-start-radius: 50% 8%;
border-end-end-radius: 50% 8%;
overflow: hidden;
}

.search {
width: 150px;
max-height: 400px;
overflow: hidden;
display: flex;
flex-direction: column;

input {
width: 100%;
border: 0;
border-bottom: 1px solid #000;
padding: 8px;
line-height: 1.5;
border-top-left-radius: 0.5rem;
}

ul {
flex: 1;
list-style: none;
padding: 4px;
padding-bottom: 30px;
margin: 0;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
li {
button {
display: flex;
align-items: center;
gap: 4px;
border: none;
background-color: transparent;
&:hover {
text-decoration: underline;
}
img {
width: 20px;
height: 20px;
object-fit: contain;
border-radius: 50%;
}
}
}
}
}

.details {
flex: 1;
height: 400px;
position: relative;
overflow: hidden;
}

.ship-info {
height: 100%;
width: 300px;
margin: auto;
overflow: auto;
background-color: #eee;
border-radius: 4px;
padding: 20px;
position: relative;
}

.ship-info.ship-loading {
opacity: 0.6;
}

.ship-info h2 {
font-weight: bold;
text-align: center;
margin-top: 0.3em;
}

.ship-info img {
width: 100%;
height: 100%;
aspect-ratio: 1;
object-fit: contain;
}

.ship-info .ship-info__img-wrapper {
margin-top: 20px;
width: 100%;
height: 200px;
}

.ship-info .ship-info__fetch-time {
position: absolute;
top: 6px;
right: 10px;
}

.app-error {
position: relative;
background-image: url('/img/broken-ship.webp');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 400px;
height: 400px;
p {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 6px 12px;
border-radius: 1rem;
font-size: 1.5rem;
font-weight: bold;
width: 300px;
text-align: center;
}
}
95 changes: 95 additions & 0 deletions exercises/01.raw/01.solution.throw/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Suspense } from 'react'
import * as ReactDOM from 'react-dom/client'
import { getImageUrlForShip, getShip, type Ship } from './utils'

const shipName = 'Dreadnought'

function App() {
return (
<div className="app-wrapper">
<div className="app">
<div className="details">
<Suspense fallback={<ShipFallback />}>
<ShipDetails />
</Suspense>
</div>
</div>
</div>
)
}

let ship: Ship
const shipPromise = getShip(shipName).then(s => (ship = s))
function ShipDetails() {
if (!ship) throw shipPromise

return (
<div className="ship-info">
<div className="ship-info__img-wrapper">
<img
src={getImageUrlForShip(ship.name, { size: 200 })}
alt={ship.name}
/>
</div>
<section>
<h2>
{ship.name}
<sup>
{ship.topSpeed} <small>lyh</small>
</sup>
</h2>
</section>
<section>
{ship.weapons.length ? (
<ul>
{ship.weapons.map(weapon => (
<li key={weapon.name}>
<label>{weapon.name}</label>:{' '}
<span>
{weapon.damage} <small>({weapon.type})</small>
</span>
</li>
))}
</ul>
) : (
<p>NOTE: This ship is not equipped with any weapons.</p>
)}
</section>
<small className="ship-info__fetch-time">{ship.fetchedAt}</small>
</div>
)
}

function ShipFallback() {
return (
<div className="ship-info">
<div className="ship-info__img-wrapper">
<img src="/img/fallback-ship.png" alt={shipName} />
</div>
<section>
<h2>
{shipName}
<sup>
XX <small>lyh</small>
</sup>
</h2>
</section>
<section>
<ul>
{Array.from({ length: 3 }).map((_, i) => (
<li key={i}>
<label>loading</label>:{' '}
<span>
XX <small>(loading)</small>
</span>
</li>
))}
</ul>
</section>
</div>
)
}

const rootEl = document.createElement('div')
document.body.append(rootEl)
ReactDOM.createRoot(rootEl).render(<App />)
21 changes: 21 additions & 0 deletions exercises/01.raw/01.solution.throw/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type Ship } from './api.server'

export type { Ship }

export async function getShip(name: string, delay?: number) {
const searchParams = new URLSearchParams({ name })
if (delay) searchParams.set('delay', String(delay))
const response = await fetch(`api/get-ship?${searchParams.toString()}`)
if (!response.ok) {
return Promise.reject(new Error(await response.text()))
}
const ship = await response.json()
return ship as Ship
}

export function getImageUrlForShip(
shipName: string,
{ size }: { size: number },
) {
return `/img/ships/${shipName.toLowerCase().replaceAll(' ', '-')}.webp?size=${size}`
}
1 change: 1 addition & 0 deletions exercises/01.raw/02.solution.errors/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Error Handling
35 changes: 35 additions & 0 deletions exercises/01.raw/02.solution.errors/api.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getShip, searchShips } from '#shared/ship-api-utils.server'

export type Ship = Awaited<ReturnType<typeof getShip>>
export type ShipSearch = Awaited<ReturnType<typeof searchShips>>

export async function loader({
request,
params,
}: {
request: Request
params: Record<string, string>
}) {
const path = params['*']
switch (path) {
case 'search-ships': {
const result = await searchShips(request)
return new Response(JSON.stringify(result), {
headers: {
'content-type': 'application/json',
},
})
}
case 'get-ship': {
const result = await getShip(request)
return new Response(JSON.stringify(result), {
headers: {
'content-type': 'application/json',
},
})
}
default: {
return new Response('Not found', { status: 404 })
}
}
}
Loading

0 comments on commit 47844e7

Please sign in to comment.