Skip to content

Commit

Permalink
Merge pull request #111 from game-node-app/dev
Browse files Browse the repository at this point in the history
- Achievement admin screen
  • Loading branch information
Lamarcke authored Aug 15, 2024
2 parents 31b8b8d + adffc92 commit 7d5b69b
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 12 deletions.
22 changes: 19 additions & 3 deletions src/components/achievement/AchievementsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import AchievementItem from "@/components/achievement/AchievementItem";
import UserLevelInfo from "@/components/user-level/UserLevelInfo";
import CenteredLoading from "@/components/general/CenteredLoading";
import UserAvatarWithLevelInfo from "@/components/general/avatar/UserAvatarWithLevelInfo";
import { useMutation } from "@tanstack/react-query";
import RedeemAchievementCodeModal from "@/components/achievement/RedeemAchievementCodeModal";
import { useDisclosure } from "@mantine/hooks";

interface Props {
targetUserId: string;
Expand All @@ -31,7 +34,11 @@ const AchievementsScreen = ({ targetUserId }: Props) => {
});
const achievements = useAchievements(paginationData);
const isOwnUserId = userId != undefined && userId === targetUserId;

const [redeemCodeModalOpened, redeemCodeModalUtils] = useDisclosure();

if (!targetUserId) return null;

return (
<Paper className={"w-full h-full"}>
<Stack w={"100%"} py={"3rem"} px={"2rem"}>
Expand All @@ -44,9 +51,18 @@ const AchievementsScreen = ({ targetUserId }: Props) => {
</Box>

{isOwnUserId && (
<Button className={""} disabled>
Redeem a code
</Button>
<>
<RedeemAchievementCodeModal
opened={redeemCodeModalOpened}
onClose={redeemCodeModalUtils.close}
/>
<Button
className={""}
onClick={redeemCodeModalUtils.open}
>
Redeem a code
</Button>
</>
)}
</Group>

Expand Down
68 changes: 68 additions & 0 deletions src/components/achievement/RedeemAchievementCodeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from "react";
import { BaseModalProps } from "@/util/types/modal-props";
import { Button, Modal, Stack, TextInput } from "@mantine/core";
import { useMutation } from "@tanstack/react-query";
import { AchievementsCodeService } from "@/wrapper/server";
import { notifications } from "@mantine/notifications";

interface Props extends BaseModalProps {}

const RedeemAchievementCodeModal = ({ opened, onClose }: Props) => {
const [achievementCode, setAchievementCode] = useState<string | undefined>(
undefined,
);

const redeemMutation = useMutation({
mutationFn: async () => {
if (!achievementCode) {
throw new Error("An achievement code must be provided!");
}
await AchievementsCodeService.achievementsCodeControllerConsume(
achievementCode,
);
},
onError: (err) => {
notifications.show({
message: err.message,
color: "red",
});
},
onSuccess: () => {
notifications.show({
message: "Successfully redeemed your code. Enjoy!",
color: "green",
});
onClose();
},
});

return (
<Modal
opened={opened}
onClose={onClose}
title={"Redeem a achievement with a code"}
>
<Stack>
<TextInput
value={achievementCode}
onChange={(v) => setAchievementCode(v.currentTarget.value)}
label={"Your achievement code"}
description={"It may contain letters and numbers"}
/>
<Button
disabled={
achievementCode == undefined ||
achievementCode.length === 0
}
onClick={() => {
redeemMutation.mutate();
}}
>
Redeem
</Button>
</Stack>
</Modal>
);
};

export default RedeemAchievementCodeModal;
7 changes: 1 addition & 6 deletions src/components/admin/AdminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ const AdminLayout = ({ children }: PropsWithChildren) => {
<SessionAuthWithRoles roles={[EUserRoles.MOD, EUserRoles.ADMIN]}>
<Stack className={"w-full flex-wrap justify-start gap-1"}>
<AdminLayoutTabs />
<Group className={"w-full h-full items-start lg:flex-nowrap"}>
<Box className={"w-full lg:w-3/12"}>
<ModerationSidebar />
</Box>
<Box className={"w-full lg:w-9/12 mt-4"}>{children}</Box>
</Group>
{children}
</Stack>
</SessionAuthWithRoles>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useCallback, useState } from "react";
import {
Button,
ComboboxItem,
Paper,
Select,
Stack,
Text,
} from "@mantine/core";
import { DetailsBox } from "@/components/general/DetailsBox";
import { useAchievements } from "@/components/achievement/hooks/useAchievements";
import AchievementItem from "@/components/achievement/AchievementItem";
import useUserId from "@/components/auth/hooks/useUserId";
import { useMutation } from "@tanstack/react-query";
import { AchievementsCodeService } from "@/wrapper/server";
import { notifications } from "@mantine/notifications";

const AchievementsGenerateCodeView = () => {
const userId = useUserId();

const [generatedAchievementCode, setGeneratedAchievementCode] = useState<
string | undefined
>(undefined);

const [selectedAchievementId, setSelectedAchievementId] = useState<
string | undefined
>(undefined);
const achievementsQuery = useAchievements({});

const buildAchievementsOptions = useCallback((): ComboboxItem[] => {
if (!achievementsQuery.data) return [];

return achievementsQuery.data.data.map((achievement): ComboboxItem => {
return {
value: achievement.id,
label: achievement.name,
};
});
}, [achievementsQuery.data]);

const getSelectedAchievement = useCallback(() => {
if (!achievementsQuery.data || !selectedAchievementId) return undefined;

return achievementsQuery.data?.data.find(
(achievement) => achievement.id === selectedAchievementId,
);
}, [achievementsQuery.data, selectedAchievementId]);

const generateCodeMutation = useMutation({
mutationFn: async () => {
if (!selectedAchievementId) {
throw new Error("A achievement must be selected first.");
}

const threeDaysFromNow = new Date();
threeDaysFromNow.setDate(threeDaysFromNow.getDate() + 3);

const resp =
await AchievementsCodeService.achievementsCodeControllerGenerate(
{
achievementId: selectedAchievementId,
expiresAt: threeDaysFromNow.toISOString(),
},
);

console.log("Generated achievement code: ", resp);

setGeneratedAchievementCode(resp.code);

return resp;
},

onError: (err) => {
notifications.show({
message: err.message,
color: "red",
});
},

onSuccess: (data) => {
notifications.show({
message:
"Successfully generated code! Make sure to write it down, as it will not be shown again.",
color: "green",
autoClose: 15000,
});
},
});

return (
<DetailsBox
title={"Generate achievement code"}
description={
"This code can be used by users to automatically receive an achievement"
}
>
<Stack className={"w-full h-full items-center"}>
<Select
value={selectedAchievementId}
onChange={(v) => {
if (v) {
setSelectedAchievementId(v);
}
}}
label={"Select a achievement"}
description={
"You can search by typing a achievement's name"
}
className={"w-full"}
data={buildAchievementsOptions()}
searchable
clearable={false}
withCheckIcon
/>
{userId && getSelectedAchievement() && (
<AchievementItem
targetUserId={userId}
achievement={getSelectedAchievement()}
/>
)}

{getSelectedAchievement() && (
<Button
className={"my-4"}
onClick={() => {
generateCodeMutation.mutate();
}}
disabled={generateCodeMutation.isPending}
>
Generate code
</Button>
)}

{generatedAchievementCode && (
<Stack className={"w-full"}>
<Paper className={"bg-[#111111] py-6 px-12 w-full"}>
<Text className={"text-center text-xl"}>
{generatedAchievementCode}
</Text>
</Paper>
<Text className={"text-dimmed text-sm text-center"}>
Make sure to write it down, as it will not be shown
again. This code will expire in 3 days.
</Text>
</Stack>
)}
</Stack>
</DetailsBox>
);
};

export default AchievementsGenerateCodeView;
Loading

0 comments on commit 7d5b69b

Please sign in to comment.