Skip to content

Commit

Permalink
Move award image mgmt to admin page (#98)
Browse files Browse the repository at this point in the history
Cleans up the user facing interface to focus on picking and showing awards
  • Loading branch information
byronwall authored Sep 27, 2023
1 parent f46c0b9 commit 9517de6
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 93 deletions.
85 changes: 85 additions & 0 deletions src/app/admin/awards/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"use client";

import { useState } from "react";

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

export default function AdminAwards() {
const { data: allAwardImages } = trpc.awardRouter.getAllAwardImages.useQuery({
shouldLimitToProfile: false,
});

const utils = trpc.useContext();

const [imageUrls, setImageUrls] = useState<string>("");

const addAwardImages = trpc.awardRouter.addImageUrlsToDb.useMutation();

const handleAddAwardImages = async () => {
const urls = imageUrls.split("\n").filter((url) => url.length > 0);

await addAwardImages.mutateAsync({
imageUrls: urls,
});

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

return (
<section>
<h1>awards</h1>

<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>

<Card className="max-w-4xl">
<CardHeader>
<CardTitle>Manage award images</CardTitle>
<CardDescription>
Use this section to add URLs or delete images. Deleting an image
will prompt the user to choose a new image.
</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={false}
/>
))}
</div>
</CardContent>
</Card>
</section>
);
}
1 change: 1 addition & 0 deletions src/app/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const links: DashLink[] = [
{ href: "/admin", label: "Home", icon: "check" },
{ href: "/admin/sentences", label: "Sentences", icon: "logo" },
{ href: "/admin/words", label: "Words", icon: "pencil" },
{ href: "/admin/awards", label: "Awards", icon: "trophy" },
];

export default function RootLayout({
Expand Down
16 changes: 10 additions & 6 deletions src/app/awards/AwardImageChoice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { type AwardImage } from "./page";
export function AwardImageChoice({
image,
shouldClickToClaim,
shouldShowDelete = false,
}: {
image: AwardImage;
shouldClickToClaim: boolean;
shouldShowDelete?: boolean;
}) {
const utils = trpc.useContext();

Expand Down Expand Up @@ -54,12 +56,14 @@ export function AwardImageChoice({
height={256}
onClick={() => shouldClickToClaim && handleAddImageIdToAward(image.id)}
/>
<ButtonLoading
onClick={() => handleDeleteImage(image.id)}
isLoading={deleteImage.isLoading}
>
Delete
</ButtonLoading>
{shouldShowDelete && (
<ButtonLoading
onClick={() => handleDeleteImage(image.id)}
isLoading={deleteImage.isLoading}
>
Delete
</ButtonLoading>
)}
</div>
);
}
86 changes: 22 additions & 64 deletions src/app/awards/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use client";

import { useState } from "react";

import {
Card,
CardContent,
Expand All @@ -10,8 +8,6 @@ import {
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";

import { AwardImageChoice } from "./AwardImageChoice";
Expand All @@ -32,23 +28,9 @@ export default function AwardsPage() {
const { data: currentSentenceCount } =
trpc.awardRouter.getProfileSentenceCount.useQuery();

const { data: allAwardImages } =
trpc.awardRouter.getAllAwardImages.useQuery();

const [imageUrls, setImageUrls] = useState<string>("");

const addAwardImages = trpc.awardRouter.addImageUrlsToDb.useMutation();

const utils = trpc.useContext();
const handleAddAwardImages = async () => {
const urls = imageUrls.split("\n").filter((url) => url.length > 0);

await addAwardImages.mutateAsync({
imageUrls: urls,
});

await utils.awardRouter.getAllAwardImages.invalidate();
};
const { data: allAwardImages } = trpc.awardRouter.getAllAwardImages.useQuery({
shouldLimitToProfile: true,
});

const wordCountAwards = awards?.filter(
(award) => award.awardType === "WORD_COUNT"
Expand Down Expand Up @@ -115,50 +97,26 @@ export default function AwardsPage() {
</Card>

{hasUnclaimedAwards && (
<>
<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>
</>
<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>
)}

<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>
);
}
2 changes: 2 additions & 0 deletions src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ import {
Baseline,
ArrowLeft,
Star,
Trophy,
} from "lucide-react";

export type Icon = LucideIcon;

export const Icons = {
trophy: Trophy,
star: Star,
circle: Circle,
arrowLeft: ArrowLeft,
Expand Down
50 changes: 27 additions & 23 deletions src/server/api/routers/awardRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,33 +71,37 @@ export const awardRouter = createTRPCRouter({
});
}),

getAllAwardImages: protectedProcedure.query(async ({ ctx }) => {
const profileId = ctx.session.user.activeProfile.id;
getAllAwardImages: protectedProcedure
.input(
z.object({
shouldLimitToProfile: z.boolean().optional().default(true),
})
)
.query(async ({ ctx, input }) => {
const profileId = ctx.session.user.activeProfile.id;
const shouldLimitToProfile = input.shouldLimitToProfile;

const allAwardImages = await prisma.awardImages.findMany();
const allAwardImages = await prisma.awardImages.findMany();

const profileAwardImages = await prisma.profileAward.findMany({
where: {
profileId,
},
select: {
imageId: true,
},
});

// filter out any images that are already assigned to the profile
const filteredAwardImages = allAwardImages.filter(
(awardImage) =>
!profileAwardImages.find(
(profileAwardImage) => profileAwardImage.imageId === awardImage.id
)
);
const profileAwardImages = await prisma.profileAward.findMany({
where: {
profileId: shouldLimitToProfile ? profileId : undefined,
},
select: {
imageId: true,
},
});

// shuffle those
filteredAwardImages.sort(() => Math.random() - 0.5);
// filter out any images that are already assigned to the profile
const filteredAwardImages = allAwardImages.filter(
(awardImage) =>
!profileAwardImages.find(
(profileAwardImage) => profileAwardImage.imageId === awardImage.id
)
);

return filteredAwardImages;
}),
return filteredAwardImages;
}),

addImageIdToAward: protectedProcedure
.input(
Expand Down

0 comments on commit 9517de6

Please sign in to comment.