Skip to content

Commit

Permalink
92 improve the styles and layout on the main awards page (#93)
Browse files Browse the repository at this point in the history
* Update styles on the award pages

* Add a placeholder image
  • Loading branch information
byronwall committed Sep 26, 2023
1 parent ff51c19 commit 5dc7fe4
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 216 deletions.
Binary file added public/placeholder.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions src/app/awards/AwardCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import Image from "next/image";

import { cn } from "~/utils";

import { type Award } from "./page";

export function AwardCard({ award }: { award: Award }) {
const label = award.awardType === "WORD_COUNT" ? "words" : "sentences";

const numberAward = (
<p className="text-2xl">
{award.awardValue ?? 0} {label}
</p>
);

const masteryAward = (
<p className="text-2xl">{award.word && <p>{award.word?.word}</p>}</p>
);

return (
<div className="flex flex-col items-center bg-gray-200">
{award.word ? masteryAward : numberAward}

<Image
key={award.id}
src={award.image?.imageUrl ?? "/placeholder.jpeg"}
alt={"Award image"}
width={256}
height={256}
className={cn("rounded-md", {
"border-4 border-yellow-400": !award.imageId,
})}
/>
</div>
);
}
65 changes: 65 additions & 0 deletions src/app/awards/AwardImageChoice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use client";

import Image from "next/image";

import { trpc } from "~/app/_trpc/client";
import { ButtonLoading } from "~/components/ButtonLoading";

import { type AwardImage } from "./page";

export function AwardImageChoice({
image,
shouldClickToClaim,
}: {
image: AwardImage;
shouldClickToClaim: boolean;
}) {
const utils = trpc.useContext();

const addImageIdToAward = trpc.awardRouter.addImageIdToAward.useMutation();

const handleAddImageIdToAward = async (imageId: string) => {
// confirm add
const shouldAdd = confirm(
"Are you sure you want to add this image to the award?"
);
if (!shouldAdd) {
return;
}

await addImageIdToAward.mutateAsync({
imageId,
});

await utils.awardRouter.getAllAwardsForProfile.invalidate();
};

const deleteImage = trpc.awardRouter.deleteImage.useMutation();

const handleDeleteImage = async (imageId: string) => {
await deleteImage.mutateAsync({
imageId,
});

await utils.awardRouter.getAllAwardImages.invalidate();
};

return (
<div>
<Image
key={image.id}
src={image.imageUrl}
alt={"Award image"}
width={256}
height={256}
onClick={() => shouldClickToClaim && handleAddImageIdToAward(image.id)}
/>
<ButtonLoading
onClick={() => handleDeleteImage(image.id)}
isLoading={deleteImage.isLoading}
>
Delete
</ButtonLoading>
</div>
);
}
14 changes: 14 additions & 0 deletions src/app/awards/AwardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";

import { AwardCard } from "./AwardCard";
import { type Award } from "./page";

export function AwardList({ awards = [] }: { awards?: Award[] }) {
return (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{awards.map((award) => (
<AwardCard key={award.id} award={award} />
))}
</div>
);
}
223 changes: 94 additions & 129 deletions src/app/awards/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
"use client";

import { useState } from "react";
import Image from "next/image";

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { trpc } from "~/app/_trpc/client";
import { ButtonLoading } from "~/components/ButtonLoading";
import { Textarea } from "~/components/ui/textarea";
import { type RouterOutputs } from "~/utils/api";

type Award = RouterOutputs["awardRouter"]["getAllAwardsForProfile"][number];
import { AwardImageChoice } from "./AwardImageChoice";
import { AwardList } from "./AwardList";

type AwardImage = RouterOutputs["awardRouter"]["getAllAwardImages"][number];
export type Award =
RouterOutputs["awardRouter"]["getAllAwardsForProfile"][number];

export type AwardImage =
RouterOutputs["awardRouter"]["getAllAwardImages"][number];

export default function AwardsPage() {
const { data: awards } = trpc.awardRouter.getAllAwardsForProfile.useQuery();
Expand Down Expand Up @@ -61,139 +72,93 @@ export default function AwardsPage() {
Math.ceil(((currentSentenceCount ?? 0) + 1) / 10) * 10;

return (
<div>
<div className="flex flex-col items-center gap-4">
<h1>Awards</h1>

<h2>Word count awards</h2>

<p>Current word count: {currentWordCount}</p>
<p>Next award at: {nextWordAward}</p>

<AwardList awards={wordCountAwards} />

<h2>Sentence count awards</h2>

<p>Current sentence count: {currentSentenceCount}</p>
<p>Next award at: {nextSentenceAward}</p>

<AwardList awards={sentenceCountAwards} />

<h2>Word mastery awards</h2>

<AwardList awards={wordMasteryAwards} />
<Card className="max-w-4xl">
<CardHeader>
<CardTitle>Word count awards</CardTitle>
<CardDescription>
Word count awards are given every 100 correct words.
</CardDescription>
</CardHeader>
<CardContent>
<p>Current word count: {currentWordCount}</p>
<p>Next award at: {nextWordAward}</p>
<AwardList awards={wordCountAwards} />
</CardContent>
</Card>

<Card className="max-w-4xl">
<CardHeader>
<CardTitle>Sentence count awards</CardTitle>
<CardDescription>
Awards are given every 10 sentences.
</CardDescription>
</CardHeader>
<CardContent>
<p>Current sentence count: {currentSentenceCount}</p>
<p>Next award at: {nextSentenceAward}</p>
<AwardList awards={sentenceCountAwards} />
</CardContent>
</Card>

<Card className="max-w-4xl">
<CardHeader>
<CardTitle>Word mastery awards</CardTitle>
<CardDescription>
Given when the interval on a word reaches the max: 60d.
</CardDescription>
</CardHeader>
<CardContent>
<AwardList awards={wordMasteryAwards} />
</CardContent>
</Card>

{hasUnclaimedAwards && (
<>
<h2>Award images</h2>

<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{(allAwardImages ?? []).map((image) => (
<AwardImageChoice
key={image.id}
image={image}
shouldClickToClaim={hasUnclaimedAwards}
/>
))}
</div>
<Card className="max-w-4xl">
<CardHeader>
<CardTitle>Pick new awards</CardTitle>
<CardDescription>
Click an image to add to your awards.
</CardDescription>
</CardHeader>
<CardContent>
{" "}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{(allAwardImages ?? []).map((image) => (
<AwardImageChoice
key={image.id}
image={image}
shouldClickToClaim={hasUnclaimedAwards}
/>
))}
</div>
</CardContent>
</Card>
</>
)}

<Textarea
value={imageUrls}
onChange={(e) => setImageUrls(e.target.value)}
/>
<ButtonLoading
onClick={handleAddAwardImages}
isLoading={addAwardImages.isLoading}
>
Add URLs
</ButtonLoading>
</div>
);
}

function AwardCard({ award }: { award: Award }) {
return (
<div className="flex flex-col items-center bg-gray-200">
<p>{award.awardType}</p>
<p>{award.awardValue ?? 0}</p>
{award.word && <p>{award.word.word}</p>}
{award.image && (
<Image
key={award.id}
src={award.image.imageUrl}
alt={"Award image"}
width={256}
height={256}
/>
)}
</div>
);
}

function AwardImageChoice({
image,
shouldClickToClaim,
}: {
image: AwardImage;
shouldClickToClaim: boolean;
}) {
const utils = trpc.useContext();

const addImageIdToAward = trpc.awardRouter.addImageIdToAward.useMutation();

const handleAddImageIdToAward = async (imageId: string) => {
// confirm add
const shouldAdd = confirm(
"Are you sure you want to add this image to the award?"
);
if (!shouldAdd) {
return;
}

await addImageIdToAward.mutateAsync({
imageId,
});

await utils.awardRouter.getAllAwardsForProfile.invalidate();
};

const deleteImage = trpc.awardRouter.deleteImage.useMutation();

const handleDeleteImage = async (imageId: string) => {
await deleteImage.mutateAsync({
imageId,
});

await utils.awardRouter.getAllAwardImages.invalidate();
};

return (
<div>
<Image
key={image.id}
src={image.imageUrl}
alt={"Award image"}
width={256}
height={256}
onClick={() => shouldClickToClaim && handleAddImageIdToAward(image.id)}
/>
<ButtonLoading
onClick={() => handleDeleteImage(image.id)}
isLoading={deleteImage.isLoading}
>
Delete
</ButtonLoading>
</div>
);
}

function AwardList({ awards = [] }: { awards?: Award[] }) {
return (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{awards.map((award) => (
<AwardCard key={award.id} award={award} />
))}
<Card className="max-w-4xl">
<CardHeader>
<CardTitle>Add images to award choices</CardTitle>
<CardDescription>
Paste a set of image URLs here to add to DB
</CardDescription>
</CardHeader>
<CardContent>
<Textarea
value={imageUrls}
onChange={(e) => setImageUrls(e.target.value)}
/>
<ButtonLoading
onClick={handleAddAwardImages}
isLoading={addAwardImages.isLoading}
>
Add URLs
</ButtonLoading>
</CardContent>
</Card>
</div>
);
}
Loading

0 comments on commit 5dc7fe4

Please sign in to comment.