Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
95 changes: 95 additions & 0 deletions examples/muffin-shop-checkout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Muffin Shop Checkout
// Build a simple receipt for a muffin shop order.

const shopName = 'Morning Muffins'
const customerName = 'Avery'
const isMember = true
const muffinCount = 3
const coffeeCount = 2
const muffinPrice = 3.25
const coffeePrice = 4
const tipPercent = 0.15
const taxRate = 0.0825
const pickupMethod = 'counter'

function buildDivider(char: string, length: number) {
// 🐨 Build and return a divider string by repeating char
// 💰 Start with let line = '' and add char in a for loop
let line = ''
return line
}

function formatMoney(amount: number) {
// 🐨 Return a string like `$4.5` using a template literal
return `$${amount}`
}

function calculateDiscount(subtotal: number, member: boolean) {
// 🐨 If the customer is a member and subtotal is at least 15,
// return 10% of subtotal. Otherwise return 0.
return 0
}

function calculateTax(subtotal: number, rate: number) {
// 🐨 Return subtotal multiplied by rate
return 0
}

function calculateTip(totalBeforeTip: number, percent: number) {
// 🐨 Return totalBeforeTip multiplied by percent
return 0
}

function calculatePickupFee(method: string) {
// 🐨 Use a switch statement to return:
// 'counter' -> 0
// 'curbside' -> 3
// 'delivery' -> 7
return 0
}

let subtotal = 0
// 🐨 Set subtotal using item counts and prices
// 💰 (muffinCount * muffinPrice) + (coffeeCount * coffeePrice)

const discount = calculateDiscount(subtotal, isMember)
const taxableAmount = subtotal - discount
const tax = calculateTax(taxableAmount, taxRate)
const tip = calculateTip(taxableAmount + tax, tipPercent)
const pickupFee = calculatePickupFee(pickupMethod)

let pickupLabel = ''
// 🐨 If pickupFee is 0, set pickupLabel to 'FREE'
// otherwise set it to formatMoney(pickupFee)

let memberMessage = ''
// 🐨 If isMember is true, set memberMessage to 'Member discount applied'
// otherwise set it to 'Join the club for 10% off'

let total = 0
// 🐨 Set total using subtotal, discount, tax, tip, and pickupFee

const header = buildDivider('=', 32)
const divider = buildDivider('-', 32)

const receipt =
`${header}\n` +
`${shopName}\n` +
`Order for ${customerName}\n` +
`${divider}\n` +
`Muffins: ${muffinCount} x ${formatMoney(muffinPrice)}\n` +
`Coffee: ${coffeeCount} x ${formatMoney(coffeePrice)}\n` +
`${divider}\n` +
`Subtotal: ${formatMoney(subtotal)}\n` +
`Discount: -${formatMoney(discount)}\n` +
`Tax: ${formatMoney(tax)}\n` +
`Tip: ${formatMoney(tip)}\n` +
`Pickup fee: ${pickupLabel}\n` +
`${divider}\n` +
`Total: ${formatMoney(total)}\n` +
`${memberMessage}\n` +
`${header}`

console.log(receipt)

export { receipt }
7 changes: 7 additions & 0 deletions examples/muffin-shop-checkout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "examples_muffin-shop-checkout",
"type": "module",
"scripts": {
"start": "npx @kentcdodds/log-module ./index.ts"
}
}
38 changes: 38 additions & 0 deletions examples/muffin-shop-checkout/readme.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Muffin Shop Checkout

👨‍💼 The muffin shop needs a simple receipt that totals an order and prints a
clean summary for customers.

📜 This example sticks to the skills from the workshop: numbers, strings,
booleans, variables, functions, if/else, switch statements, and loops.

🐨 Open <InlineFile file="index.ts" /> and complete the following tasks:

1. Build the divider string with a loop.
2. Calculate `subtotal`.
3. Implement `calculateDiscount`.
4. Implement `calculateTax`.
5. Implement `calculateTip`.
6. Implement `calculatePickupFee` with a switch statement.
7. Set `pickupLabel` with an if/else.
8. Set `memberMessage` with an if/else.
9. Calculate `total`.
10. Make sure the receipt prints with `console.log`.

💰 Flip `isMember` to see the discount and message change.
💰 Try `pickupMethod` as `curbside` or `delivery` to test the switch logic.

## Run the example

```sh nonumber
npm run start --workspace examples/muffin-shop-checkout
```

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursor give an example of what the output might look like to give them an idea of what they're doing

Copy link

@cursor cursor bot Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Added examples/README.mdx with purpose, no-solution/no-tests rationale, guidance on unfamiliar code, spaced repetition benefits, and a sample output block.【F:examples/README.mdx†L1-L45】

Testing

  • ⚠️ Not run (docs-only change; no runtime behavior to verify).

Open in Cursor Open in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursor, move the example output to this mdx file instead of the example/REAMDE.mdx

Copy link

@cursor cursor bot Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the sample output block from examples/README.mdx into the muffin checkout example’s readme.mdx so it lives alongside the exercise instructions.【F:examples/muffin-shop-checkout/readme.mdx†L25-L54】【F:examples/README.mdx†L1-L19】

Open in Cursor Open in Web

## Try next

- 🐨 Add a `dailySpecialCount` and `dailySpecialPrice`, then include them in the
`subtotal`.
- 🐨 Create a `hasCoupon` boolean and apply an extra discount with an `if` check.
- 🐨 Use a loop to build a `summaryLine` like `Items: 5 total`.
- 🐨 Add a `rushOrder` flag that increases the `tipPercent` when true.
- 💰 Try a new `pickupMethod` value and update the switch to handle it.
12 changes: 12 additions & 0 deletions examples/muffin-shop-dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Muffin Shop Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
21 changes: 21 additions & 0 deletions examples/muffin-shop-dashboard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "examples_muffin-shop-dashboard",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
}
40 changes: 40 additions & 0 deletions examples/muffin-shop-dashboard/readme.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Muffin Shop Dashboard

👨‍💼 The muffin shop wants a simple dashboard for a single order so the team can
double-check totals before handing it off.

📜 This example uses React + Vite, but everything React-related is already done
for you. You only need the workshop skills: numbers, strings, booleans,
variables, functions, if/else, switch statements, loops, and `never`.

🐨 Open <InlineFile file="src/muffin-utils.ts" /> and complete the following tasks:

1. Format money with a template literal.
2. Add the `(GF)` label for gluten-free items.
3. Sum the subtotal with a loop.
4. Apply the member discount.
5. Calculate tax and tip.
6. Calculate the final total.
7. Return the pickup fee with a switch statement.
8. Return the pickup label with a switch statement.
9. Format the special note when it's empty.
10. Log the ready message.
11. Implement `assertNever`.

💰 Toggle `memberStatus` or `pickupMethod` in <InlineFile file="src/app.tsx" /> to
see different outputs.

## Run the example

```sh nonumber
npm run dev --workspace examples/muffin-shop-dashboard
```

## Try next

- 🐨 Add another `OrderItem` and watch the totals change.
- 🐨 Add a `rushOrder` boolean and increase the tip when it is true.
- 🐨 Add a `itemCount` helper that uses a loop to count total quantity.
- 🐨 Add a `happyHour` boolean and reduce the muffin price when true.
- 🐨 Add a `memberMessage` string in `app.tsx` using an `if` check.
- 💰 Change `specialNote` to a real note and confirm the fallback logic.
114 changes: 114 additions & 0 deletions examples/muffin-shop-dashboard/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
.app {
max-width: 960px;
margin: 0 auto;
padding: 32px 24px 48px;
}

.app-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 24px;
}

.app-header h1 {
margin: 4px 0 0;
font-size: 32px;
}

.eyebrow {
margin: 0;
font-size: 12px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #8a6f5a;
}

.badge {
background: #fff;
border-radius: 999px;
padding: 8px 16px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #5b4a3f;
box-shadow: 0 8px 24px rgba(59, 45, 34, 0.08);
}

.layout {
display: grid;
gap: 24px;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.card {
background: #fff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 16px 40px rgba(59, 45, 34, 0.08);
}

.card h2 {
margin-top: 0;
margin-bottom: 16px;
}

.line-items {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 12px;
}

.line-item {
display: flex;
justify-content: space-between;
gap: 16px;
font-weight: 600;
}

.note {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #efe5db;
display: flex;
justify-content: space-between;
gap: 12px;
font-size: 14px;
color: #6f655b;
}

.summary-row {
display: flex;
justify-content: space-between;
gap: 16px;
margin-top: 8px;
}

.summary-row.total {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #efe5db;
font-size: 18px;
font-weight: 700;
}

.pill {
margin-top: 16px;
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
background: #f1e4d7;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #6a4d3b;
}

.helper {
margin-top: 12px;
font-size: 14px;
color: #6f655b;
}
Loading