Merge branch staging into mason

Resolve habit-tracker conflict by keeping the daily tasks countdown header and adopting check-in terminology from staging.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
masongga
2026-05-24 10:24:27 -06:00
22 changed files with 732 additions and 110 deletions

View File

@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
import { DailyQuizForm } from "@/components/daily-quiz/daily-quiz-form";
import { HabitTracker } from "@/components/habits/habit-tracker";
import { JournalCalendar } from "@/components/journal/journal-calendar";
import { PetHabitat } from "@/components/pet/pet-habitat";
import { HABIT_PET_DATA_UPDATED_EVENT } from "@/lib/app-events";
import {
@@ -37,6 +38,14 @@ export function AvatarPageContent() {
};
}, [loadCustomization]);
useEffect(() => {
const hash = window.location.hash.replace("#", "");
if (hash === "bit-daily-quiz") {
window.location.hash = "bit-daily-check-in";
}
}, []);
if (loadError) {
return <p className="text-xs text-red-500">{loadError}</p>;
}
@@ -66,10 +75,14 @@ export function AvatarPageContent() {
<div id="bit-daily-tasks">
<HabitTracker mode="daily" />
</div>
<div id="bit-daily-quiz">
<div id="bit-daily-check-in">
<DailyQuizForm />
</div>
</section>
<section className="w-full pt-8">
<JournalCalendar />
</section>
</div>
);
}

View File

@@ -94,21 +94,21 @@ export function AvatarStatusCard({ customization }: AvatarStatusCardProps) {
Health {condition.health}/{WELLNESS_SCALE_MAX}
</Badge>
<Badge variant={hasCheckedInToday ? "default" : "outline"}>
{hasCheckedInToday ? "Checked in today" : "Quiz pending"}
{hasCheckedInToday ? "Checked in today" : "Check-in pending"}
</Badge>
</div>
<p className="text-center text-sm text-muted-foreground">
{hasCheckedInToday
? completedTaskCount > 0
? `Your bit reacts to today's quiz and ${completedTaskCount} completed task${completedTaskCount === 1 ? "" : "s"}. Meals, hydration, sleep, and relaxing habits boost health and mood.`
? `Your bit reacts to today's check-in and ${completedTaskCount} completed task${completedTaskCount === 1 ? "" : "s"}. Meals, hydration, sleep, and relaxing habits boost health and mood.`
: "Your bit is reacting to today's wellness check-in. Complete habits to boost mood, energy, and health."
: completedTaskCount > 0
? "Complete today's quiz for a full wellness read, then keep completing habits to boost your bit."
: "Complete today's quiz to update your bit's mood, energy, and health."}
? "Complete today's check-in for a full wellness read, then keep completing habits to boost your bit."
: "Complete today's check-in to update your bit's mood, energy, and health."}
</p>
{!hasCheckedInToday ? (
<Button asChild className="w-full">
<Link href={`${routes.avatar}#bit-daily-quiz`}>Take today&apos;s quiz</Link>
<Link href={`${routes.avatar}#bit-daily-check-in`}>Start daily check-in</Link>
</Button>
) : null}
</CardContent>

View File

@@ -138,7 +138,7 @@ export function DailyQuizForm() {
if (!isReady) {
return (
<p className="text-sm text-muted-foreground">Loading today&apos;s quiz...</p>
<p className="text-sm text-muted-foreground">Loading today&apos;s check-in...</p>
);
}
@@ -155,7 +155,7 @@ export function DailyQuizForm() {
</div>
<div className="space-y-1">
<p className="text-lg font-semibold tracking-tight">
Today&apos;s quiz is complete
Today&apos;s check-in is complete
</p>
<p className="text-sm text-muted-foreground">
Your answers are saved below. Come back tomorrow for your next
@@ -264,7 +264,7 @@ export function DailyQuizForm() {
{!isCompleted ? (
<Button className="w-full" disabled={!canSubmit} onClick={handleSubmit}>
{isSubmitting ? "Saving..." : "Complete daily quiz"}
{isSubmitting ? "Saving..." : "Complete daily check-in"}
</Button>
) : null}
</div>

View File

@@ -23,14 +23,14 @@ export function DailyQuizLinkButton() {
if (!isReady || isCompleted) {
return (
<Button className="w-full" variant="outline" disabled>
Open daily quiz
Open daily check-in
</Button>
);
}
return (
<Button asChild className="w-full" variant="outline">
<Link href={`${routes.avatar}#bit-daily-quiz`}>Open daily quiz</Link>
<Link href={`${routes.avatar}#bit-daily-check-in`}>Open daily check-in</Link>
</Button>
);
}

View File

@@ -81,7 +81,7 @@ function DailyTaskList({
if (tasks.length === 0) {
return (
<p className="text-sm text-muted-foreground">
No daily tasks yet. Complete your daily quiz or update your profile so
No daily tasks yet. Complete your daily check-in or update your profile so
AI can suggest habits for you.
</p>
);
@@ -450,7 +450,7 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) {
<Card>
<DailyTasksCardHeader
description={`Personalized from your focus topics${
quizCompletedToday ? ", today's quiz," : ""
quizCompletedToday ? ", today's check-in," : ""
} and custom habits.`}
onDayChange={refresh}
/>
@@ -508,17 +508,17 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) {
<CardTitle className="text-base">How daily tasks are picked</CardTitle>
<CardDescription>
Your tasks combine focus preferences, custom habits, and today&apos;s
quiz.
check-in.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>Focus topics: {focusTopic ?? "None selected"}</p>
<p>
Daily quiz today:{" "}
Daily check-in today:{" "}
{quizCompletedToday ? "Completed" : "Not completed yet"}
</p>
<p>
Suggested habits can appear when your quiz shows low energy, high
Suggested habits can appear when your check-in shows low energy, high
stress, or poor sleep.
</p>
</CardContent>
@@ -571,7 +571,7 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) {
<CardTitle className="text-base">How daily tasks are picked</CardTitle>
<CardDescription>
Your tasks combine custom habits and AI suggestions based on your
profile, onboarding, daily quiz, and journal.
profile, onboarding, daily check-in, and journal.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
@@ -580,7 +580,7 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) {
{focusTopic ?? "None selected"}
</p>
<p>
Daily quiz today:{" "}
Daily check-in today:{" "}
{quizCompletedToday ? "Completed" : "Not completed yet"}
</p>
<p>
@@ -596,7 +596,7 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) {
return (
<Card>
<DailyTasksCardHeader
description="Personalized from your profile, daily quiz, journal, and custom habits."
description="Personalized from your profile, daily check-in, journal, and custom habits."
onDayChange={refresh}
/>
<CardContent className="space-y-4">

View File

@@ -0,0 +1,329 @@
"use client";
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { JournalEntryModal } from "@/components/journal/journal-entry-modal";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { HABIT_PET_DATA_UPDATED_EVENT } from "@/lib/app-events";
import type { DailyQuizSubmission } from "@/lib/avatar-state";
import {
getDailyEntryByDate,
getDailyEntrySummariesForMonth,
getTodayDateKey,
type DailyEntrySummary,
} from "@/lib/daily-quiz-storage";
import { cn } from "@/lib/utils";
const WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
type CalendarCell = {
dateKey: string;
day: number;
inMonth: boolean;
};
function buildMonthGrid(year: number, month: number): CalendarCell[] {
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const grid: CalendarCell[] = [];
for (let index = 0; index < firstDay.getDay(); index += 1) {
const date = new Date(year, month, index - firstDay.getDay() + 1);
grid.push({
dateKey: date.toLocaleDateString("en-CA"),
day: date.getDate(),
inMonth: false,
});
}
for (let day = 1; day <= lastDay.getDate(); day += 1) {
const date = new Date(year, month, day);
grid.push({
dateKey: date.toLocaleDateString("en-CA"),
day,
inMonth: true,
});
}
let trailingDay = 1;
while (grid.length % 7 !== 0) {
const date = new Date(year, month + 1, trailingDay);
grid.push({
dateKey: date.toLocaleDateString("en-CA"),
day: date.getDate(),
inMonth: false,
});
trailingDay += 1;
}
return grid;
}
function formatMonthLabel(year: number, month: number) {
return new Date(year, month, 1).toLocaleDateString(undefined, {
month: "long",
year: "numeric",
});
}
export function JournalCalendar() {
const todayKey = getTodayDateKey();
const todayDate = new Date();
const [visibleYear, setVisibleYear] = useState(todayDate.getFullYear());
const [visibleMonth, setVisibleMonth] = useState(todayDate.getMonth());
const [summaries, setSummaries] = useState<DailyEntrySummary[]>([]);
const [selectedDateKey, setSelectedDateKey] = useState<string | null>(null);
const [selectedEntry, setSelectedEntry] = useState<DailyQuizSubmission | null>(
null,
);
const [isExpanded, setIsExpanded] = useState(false);
const [isLoadingMonth, setIsLoadingMonth] = useState(false);
const [isLoadingEntry, setIsLoadingEntry] = useState(false);
const [error, setError] = useState<string | null>(null);
const summaryByDate = useMemo(
() => new Map(summaries.map((summary) => [summary.date, summary])),
[summaries],
);
const monthGrid = useMemo(
() => buildMonthGrid(visibleYear, visibleMonth),
[visibleYear, visibleMonth],
);
const loadMonth = useCallback(async () => {
setIsLoadingMonth(true);
setError(null);
try {
setSummaries(
await getDailyEntrySummariesForMonth(visibleYear, visibleMonth),
);
} catch (loadError) {
setError(
loadError instanceof Error
? loadError.message
: "Could not load journal history.",
);
} finally {
setIsLoadingMonth(false);
}
}, [visibleMonth, visibleYear]);
useEffect(() => {
if (!isExpanded) {
return;
}
void loadMonth();
}, [isExpanded, loadMonth]);
useEffect(() => {
if (!isExpanded) {
return;
}
const handleDataUpdated = () => {
void loadMonth();
};
window.addEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated);
return () => {
window.removeEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated);
};
}, [isExpanded, loadMonth]);
const goToPreviousMonth = () => {
if (visibleMonth === 0) {
setVisibleYear((current) => current - 1);
setVisibleMonth(11);
return;
}
setVisibleMonth((current) => current - 1);
};
const goToNextMonth = () => {
if (visibleMonth === 11) {
setVisibleYear((current) => current + 1);
setVisibleMonth(0);
return;
}
setVisibleMonth((current) => current + 1);
};
const handleDaySelect = async (dateKey: string) => {
const summary = summaryByDate.get(dateKey);
if (!summary) {
return;
}
setSelectedDateKey(dateKey);
setIsLoadingEntry(true);
setError(null);
try {
setSelectedEntry(await getDailyEntryByDate(dateKey));
} catch (loadError) {
setSelectedDateKey(null);
setError(
loadError instanceof Error
? loadError.message
: "Could not load that journal entry.",
);
} finally {
setIsLoadingEntry(false);
}
};
const closeEntryModal = () => {
setSelectedEntry(null);
setSelectedDateKey(null);
};
return (
<>
<Card>
<CardHeader className="pb-4">
<div className="flex items-start justify-between gap-3">
<button
type="button"
className="min-w-0 flex-1 text-left"
aria-expanded={isExpanded}
onClick={() => setIsExpanded((current) => !current)}
>
<CardTitle className="text-base">Journal history</CardTitle>
<CardDescription>
Browse past check-ins and journal entries.
</CardDescription>
</button>
<Button
type="button"
variant="outline"
size="icon"
className="shrink-0"
aria-label={isExpanded ? "Collapse journal history" : "Expand journal history"}
onClick={() => setIsExpanded((current) => !current)}
>
{isExpanded ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div>
</CardHeader>
{isExpanded ? (
<CardContent className="space-y-4 border-t pt-4">
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-medium">
{formatMonthLabel(visibleYear, visibleMonth)}
</p>
<div className="flex items-center gap-1">
<Button
type="button"
variant="outline"
size="icon"
aria-label="Previous month"
onClick={goToPreviousMonth}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
type="button"
variant="outline"
size="icon"
aria-label="Next month"
onClick={goToNextMonth}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
<div className="grid grid-cols-7 gap-1 text-center text-xs text-muted-foreground">
{WEEKDAY_LABELS.map((label) => (
<div key={label} className="py-1 font-medium">
{label}
</div>
))}
</div>
<div className="grid grid-cols-7 gap-1">
{monthGrid.map((cell) => {
const summary = summaryByDate.get(cell.dateKey);
const hasEntry = Boolean(summary);
const isToday = cell.dateKey === todayKey;
const isSelected = cell.dateKey === selectedDateKey;
return (
<button
key={cell.dateKey}
type="button"
disabled={!hasEntry || isLoadingEntry}
aria-label={
hasEntry
? `View journal entry for ${cell.dateKey}`
: undefined
}
onClick={() => void handleDaySelect(cell.dateKey)}
className={cn(
"relative flex aspect-square flex-col items-center justify-center rounded-md text-sm transition-colors",
cell.inMonth
? "text-foreground"
: "text-muted-foreground/45",
hasEntry
? "cursor-pointer hover:bg-muted/60"
: "cursor-default",
isToday && "ring-1 ring-primary/50",
isSelected && "bg-primary/10 ring-1 ring-primary",
!hasEntry && "opacity-70",
)}
>
<span>{cell.day}</span>
{hasEntry ? (
<span
aria-hidden="true"
className={cn(
"absolute bottom-1 h-1.5 w-1.5 rounded-full",
summary?.hasJournal ? "bg-primary" : "bg-muted-foreground/50",
)}
/>
) : null}
</button>
);
})}
</div>
<div className="flex flex-wrap items-center gap-4 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1.5">
<span className="h-1.5 w-1.5 rounded-full bg-primary" />
Journal written
</span>
<span className="inline-flex items-center gap-1.5">
<span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/50" />
Check-in only
</span>
</div>
{isLoadingMonth ? (
<p className="text-sm text-muted-foreground">Loading calendar...</p>
) : null}
{error ? <p className="text-sm text-red-500">{error}</p> : null}
</CardContent>
) : null}
</Card>
<JournalEntryModal entry={selectedEntry} onClose={closeEntryModal} />
</>
);
}

View File

@@ -0,0 +1,147 @@
"use client";
import { X } from "lucide-react";
import { useEffect } from "react";
import { WellnessSlider } from "@/components/daily-quiz/wellness-slider";
import { Button } from "@/components/ui/button";
import {
formatSleepHours,
SLEEP_HOURS_MAX,
SLEEP_HOURS_MIN,
SLEEP_HOURS_STEP,
type DailyQuizSubmission,
} from "@/lib/avatar-state";
type JournalEntryModalProps = {
entry: DailyQuizSubmission | null;
onClose: () => void;
};
function formatEntryDateLabel(dateKey: string) {
const [year, month, day] = dateKey.split("-").map(Number);
const date = new Date(year, month - 1, day);
return date.toLocaleDateString(undefined, {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
});
}
export function JournalEntryModal({ entry, onClose }: JournalEntryModalProps) {
useEffect(() => {
if (!entry) {
return;
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [entry, onClose]);
if (!entry) {
return null;
}
const journalText = entry.journal.trim();
return (
<div
className="fixed inset-0 z-[70] flex items-end justify-center bg-black/70 p-4 sm:items-center"
role="dialog"
aria-modal="true"
aria-labelledby="journal-entry-title"
onClick={onClose}
>
<div
className="max-h-[85vh] w-full max-w-lg overflow-y-auto rounded-xl border bg-background p-5 shadow-xl"
onClick={(event) => event.stopPropagation()}
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
Journal entry
</p>
<h2
id="journal-entry-title"
className="truncate text-lg font-semibold tracking-tight"
>
{formatEntryDateLabel(entry.date)}
</h2>
</div>
<Button
type="button"
variant="ghost"
size="icon"
className="shrink-0"
aria-label="Close journal entry"
onClick={onClose}
>
<X className="h-4 w-4" />
</Button>
</div>
<div className="mt-5 space-y-5">
<div className="space-y-2">
<p className="text-sm font-medium">Daily journal</p>
<p className="whitespace-pre-wrap rounded-lg border bg-muted/20 px-3 py-3 text-sm text-foreground">
{journalText || "No journal written."}
</p>
</div>
<div className="space-y-4 rounded-lg border px-3 py-4">
<p className="text-sm font-medium">Wellness</p>
<WellnessSlider
id="journal-feeling"
label="How were you feeling?"
value={entry.answers.feeling}
disabled
onChange={() => undefined}
/>
<WellnessSlider
id="journal-stress"
label="Stress level"
value={entry.answers.stress}
disabled
onChange={() => undefined}
/>
<WellnessSlider
id="journal-energy"
label="Energy level"
value={entry.answers.energy}
disabled
onChange={() => undefined}
/>
<WellnessSlider
id="journal-sleep-length"
label="Sleep length"
value={entry.answers.sleepLength}
min={SLEEP_HOURS_MIN}
max={SLEEP_HOURS_MAX}
step={SLEEP_HOURS_STEP}
formatValue={formatSleepHours}
disabled
onChange={() => undefined}
/>
<WellnessSlider
id="journal-sleep-quality"
label="Sleep quality"
value={entry.answers.sleepQuality}
disabled
onChange={() => undefined}
/>
</div>
</div>
</div>
</div>
);
}

View File

@@ -247,13 +247,13 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
try {
await resetTodaysDailyQuiz();
setQuizResetMessage(
"Today's quiz was reset. Open the Quiz tab to take it again.",
"Today's check-in was reset. Open the Bit page to complete it again.",
);
} catch (resetError) {
setError(
resetError instanceof Error
? resetError.message
: "Could not reset today's quiz.",
: "Could not reset today's check-in.",
);
} finally {
setIsResettingQuiz(false);
@@ -337,7 +337,7 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
<CardHeader>
<CardTitle className="text-base">Notification preferences</CardTitle>
<CardDescription>
Control reminders for your daily quiz and habits.
Control reminders for your daily check-in and habits.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@@ -425,7 +425,7 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
</CardHeader>
<CardContent className="space-y-4">
<p className="text-xs text-muted-foreground">
Reset clears today&apos;s wellness quiz entry so you can complete it
Reset clears today&apos;s wellness check-in entry so you can complete it
again immediately. Demo points are for testing the shop only.
</p>
<Button
@@ -449,7 +449,7 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
disabled={isSaving || isResettingQuiz}
onClick={() => void handleResetDailyQuiz()}
>
{isResettingQuiz ? "Resetting..." : "Reset today's quiz"}
{isResettingQuiz ? "Resetting..." : "Reset today's check-in"}
</Button>
{quizResetMessage ? (
<p className="text-xs text-emerald-600">{quizResetMessage}</p>

View File

@@ -22,6 +22,7 @@ import {
} from "@/lib/shop-storage";
import {
getLayerLabel,
getShopItemDisplayName,
type ShopItemRecord,
type ShopLayerId,
} from "@/lib/shop-catalog";
@@ -103,6 +104,8 @@ function ShopItemCard({
? null
: getShopItemThumbnailColors(item, characterColors);
const displayName = getShopItemDisplayName(item);
const actionLabel = (() => {
if (isPending) {
return owned ? "Updating..." : "Processing...";
@@ -123,27 +126,33 @@ function ShopItemCard({
<Card className="flex h-full flex-col overflow-hidden">
<CardContent className="flex h-full flex-col gap-3 p-3">
<div className="flex min-w-0 items-start gap-3">
<div className="flex size-12 shrink-0 items-center justify-center overflow-hidden border-2 border-border bg-zinc-950/80 p-1">
{room ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={item.image_path || room.sceneImage}
alt=""
className="size-full object-cover image-pixelated"
/>
) : thumbnailColors ? (
<TintedSpriteIcon
src={item.image_path}
color={thumbnailColors.color}
skinColor={thumbnailColors.skinColor}
size={32}
/>
<div className="shop-item-thumbnail relative flex size-12 shrink-0 items-center justify-center overflow-hidden border-2 border-border p-1">
{thumbnailColors || room ? (
<div aria-hidden className="shop-item-thumbnail-glow pointer-events-none absolute inset-0" />
) : null}
<div className="relative z-10 flex size-full items-center justify-center">
{room ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={item.image_path || room.sceneImage}
alt=""
className="size-full object-cover image-pixelated"
/>
) : thumbnailColors ? (
<TintedSpriteIcon
src={item.image_path}
color={thumbnailColors.color}
skinColor={thumbnailColors.skinColor}
size={32}
className="drop-shadow-[0_0_3px_hsl(52_55%_90%_/_0.85)]"
/>
) : null}
</div>
</div>
<div className="flex min-w-0 flex-1 items-start justify-between gap-2">
<p className="min-w-0 pt-1 text-sm font-medium leading-snug">
{item.name}
{displayName}
</p>
<ShopItemPriceBadge
price={item.price}
@@ -249,7 +258,7 @@ export function ShopContent() {
try {
const item = await purchaseShopItem(itemId);
await equipShopItem(itemId);
toast(`Purchased and equipped ${item.name}.`, "success");
toast(`Purchased and equipped ${getShopItemDisplayName(item)}.`, "success");
await refreshShop();
} catch (purchaseError) {
toast(
@@ -274,10 +283,10 @@ export function ShopContent() {
if (item.type !== "room" && isEquipped) {
await unequipShopItem(item.id);
toast(`Unequipped ${item.name}.`, "default");
toast(`Unequipped ${getShopItemDisplayName(item)}.`, "default");
} else if (!isEquipped) {
await equipShopItem(item.id);
toast(`Equipped ${item.name}.`, "success");
toast(`Equipped ${getShopItemDisplayName(item)}.`, "success");
}
await refreshShop();

View File

@@ -12,7 +12,7 @@ import {
buildShopItemPreviewCustomization,
getShopPreviewRoomId,
} from "@/lib/shop-preview";
import type { ShopItemRecord } from "@/lib/shop-catalog";
import { getShopItemDisplayName, type ShopItemRecord } from "@/lib/shop-catalog";
import { routes } from "@/lib/routes";
type ShopItemPreviewModalProps = {
@@ -89,7 +89,7 @@ export function ShopItemPreviewModal({
>
Preview on your bit
</p>
<p className="mt-1 truncate text-sm">{item.name}</p>
<p className="mt-1 truncate text-sm">{getShopItemDisplayName(item)}</p>
</div>
<Button
type="button"