Skip to content

Commit

Permalink
feat: adapt frontend to support specialbadges with reward pools
Browse files Browse the repository at this point in the history
  • Loading branch information
juliano-quatrin-nunes committed Jan 20, 2025
1 parent 81d23a3 commit 6009883
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Types
class SpecialBadgeType < Types::BaseObject
field :id, ID, null: false, method: :badge_id
field :display_data, Types::BadgeDisplayDataType, null: false
field :points, Integer, null: false
field :reward_pools, [Types::RewardPoolType], null: false
field :badge_type, String, null: false
field :user_badges, [Types::UserBadgeType], null: false
field :earned_by_current_user, Boolean, null: false
Expand Down
2 changes: 1 addition & 1 deletion apps/govquests-api/rails_app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ type SpecialBadge {
displayData: BadgeDisplayData!
earnedByCurrentUser: Boolean!
id: ID!
points: Int!
rewardPools: [RewardPool!]!
userBadges: [UserBadge!]!
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,69 @@ import { cn, koulen, redactedScript } from "@/lib/utils";
import Image from "next/image";
import { ComponentProps } from "react";
import { useFetchBadge } from "../hooks/useFetchBadge";
import { useFetchSpecialBadge } from "../hooks/useFetchSpecialBadge";

interface BadgeCardProps {
badgeId: string;
badge:
| ReturnType<typeof useFetchBadge>["data"]["badge"]
| ReturnType<typeof useFetchSpecialBadge>["data"]["specialBadge"];
className?: string;
withTitle?: boolean;
header?: string;
revealIncomplete?: boolean;
}

export const NormalBadgeCard = (props: Omit<BadgeCardProps, "badge">) => {
const { data } = useFetchBadge(props.badgeId);

if (props.withTitle)
return (
data && (
<BadgeCardTitle
header={props.header}
badgeableTitle={data?.badge.badgeable.displayData.title}
>
<BadgeCard badgeId={props.badgeId} badge={data.badge} />
</BadgeCardTitle>
)
);
else return data && <BadgeCard badgeId={props.badgeId} badge={data.badge} />;
};

export const SpecialBadgeCard = (props: Omit<BadgeCardProps, "badge">) => {
const { data } = useFetchSpecialBadge(props.badgeId);

if (props.withTitle)
return (
data && (
<BadgeCardTitle header={props.header}>
<BadgeCard badgeId={props.badgeId} badge={data.specialBadge} />
</BadgeCardTitle>
)
);
else
return (
data && <BadgeCard badgeId={props.badgeId} badge={data.specialBadge} />
);
};

export const BadgeCard = ({
badgeId,
className,
withTitle = false,
header,
revealIncomplete = false,
badge,
}: BadgeCardProps) => {
const { data } = useFetchBadge(badgeId);
const revealCard = data?.badge.earnedByCurrentUser || revealIncomplete;
const revealCard = badge.earnedByCurrentUser || revealIncomplete;

const Card = data && (
return (
<div
className={cn(
"relative items-center justify-center col-span-2",
className,
)}
>
<Image
src={data.badge.displayData.imageUrl}
src={badge.displayData.imageUrl}
alt="badge_image"
width={100}
height={100}
Expand All @@ -53,27 +88,16 @@ export const BadgeCard = ({
!revealCard && "scale-x-75",
)}
>
{data.badge.displayData.title}
{badge.displayData.title}
</span>
</div>
</div>
);

if (withTitle)
return (
<BadgeCardTitle
header={header}
badgeableTitle={data?.badge.badgeable.displayData.title}
>
{Card}
</BadgeCardTitle>
);
else return Card;
};

interface BadgeCardTitleProps extends ComponentProps<"div"> {
header: string;
badgeableTitle: string;
badgeableTitle?: string;
}

const BadgeCardTitle = ({
Expand All @@ -85,9 +109,11 @@ const BadgeCardTitle = ({
<div className="my-5 p-2 rounded-lg bg-background/60 transition duration-300 hover:scale-105 flex flex-col">
<div className="px-2 whitespace-nowrap w-full text-start">
<h2 className="text-sm font-bold">{header.toUpperCase()}</h2>
<span className="text-xs font-thin">
{badgeableTitle?.toUpperCase() || ""}
</span>
{badgeableTitle && (
<span className="text-xs font-thin">
{badgeableTitle?.toUpperCase() || ""}
</span>
)}
</div>
<div className="w-full">{children}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
DialogDescription,
DialogHeader,
} from "@/components/ui/dialog";
import { BadgeCard } from "./BadgeCard";
import { NormalBadgeCard, SpecialBadgeCard } from "./BadgeCard";
import { SimpleBadgeContent } from "./SimpleBadgeContent";
import { SpecialBadgeContent } from "./SpecialBadgeContent";

Expand All @@ -20,13 +20,20 @@ export const BadgeDetails = ({
<DialogContent>
<DialogHeader>
<DialogDescription className="flex flex-col gap-8 items-center justify-center text-foreground">
<div className="w-[190px]">
<BadgeCard badgeId={badgeId} revealIncomplete />
</div>
{special ? (
<SpecialBadgeContent badgeId={badgeId} />
<>
<div className="w-[190px]">
<SpecialBadgeCard badgeId={badgeId} revealIncomplete />
</div>
<SpecialBadgeContent badgeId={badgeId} />
</>
) : (
<SimpleBadgeContent badgeId={badgeId} />
<>
<div className="w-[190px]">
<NormalBadgeCard badgeId={badgeId} revealIncomplete />
</div>
<SimpleBadgeContent badgeId={badgeId} />
</>
)}
</DialogDescription>
</DialogHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Dialog, DialogTrigger } from "@/components/ui/dialog";
import { useSearchParams } from "next/navigation";
import { useFetchBadges } from "../hooks/useFetchBadges";
import { BadgeCard } from "./BadgeCard";
import { NormalBadgeCard } from "./BadgeCard";
import { BadgeDetails } from "./BadgeDetails";

export const SimpleBadgesSection: React.FC = () => {
Expand All @@ -24,7 +24,7 @@ export const SimpleBadgesSection: React.FC = () => {
{data.badges.map((badge, index) => (
<Dialog defaultOpen={queryBadgeId == badge.id} key={index}>
<DialogTrigger>
<BadgeCard
<NormalBadgeCard
badgeId={badge.id}
withTitle
header={`${badge.badgeable.__typename} BADGE #${badge.displayData.sequenceNumber}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { useFetchSpecialBadges } from "../hooks/useFetchSpecialBadges";
import { BadgeCard } from "./BadgeCard";
import { SpecialBadgeCard } from "./BadgeCard";
import { BadgeDetails } from "./BadgeDetails";

export const SpecialBadgesSection: React.FC = () => {
Expand Down Expand Up @@ -68,7 +68,7 @@ export const SpecialBadgesSection: React.FC = () => {
>
<Dialog defaultOpen={queryBadgeId == badge.id}>
<DialogTrigger>
<BadgeCard
<SpecialBadgeCard
badgeId={badge.id}
withTitle
header={`SPECIAL BADGE #${index + 1}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ export const SpecialBadgeQuery = graphql(`
query GetSpecialBadge($id: ID!) {
specialBadge(id: $id) {
id
points
rewardPools {
rewardDefinition {
type
amount
}
}
badgeType
earnedByCurrentUser
displayData {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { DividerHeader } from "@/components/ui/DividerHeader";
import { IndicatorPill } from "@/components/IndicatorPill";
import RewardIndicator from "@/components/RewardIndicator";
import { Button } from "@/components/ui/Button";
import { DividerHeader } from "@/components/ui/DividerHeader";
import HtmlRender from "@/components/ui/HtmlRender";
import ActionList from "@/domains/action_tracking/components/ActionList";
import type { Quest } from "@/domains/questing/types/questTypes";
import { ArrowLeft } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
import { NormalBadgeCard } from "../../gamification/components/BadgeCard";
import QuestContentSection from "./QuestContentSection";
import { BadgeCard } from "../../gamification/components/BadgeCard";
import HtmlRender from "@/components/ui/HtmlRender";
import { IndicatorPill } from "@/components/IndicatorPill";
import RewardIndicator from "@/components/RewardIndicator";
import Link from "next/link";

interface QuestDetailsProps {
quest: Quest;
Expand Down Expand Up @@ -59,7 +59,7 @@ const QuestDetails = ({ quest }: QuestDetailsProps) => {
<div className="items-center justify-center flex gap-12 mx-20 pt-8">
{quest.badge.displayData.imageUrl && (
<Link href={`/achievements?badgeId=${quest.badge.id}`}>
<BadgeCard
<NormalBadgeCard
badgeId={quest.badge.id}
className="hover:scale-105 transition-all duration-300 min-w-52 h-60"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from "next/link";
import { BadgeCard } from "../../../gamification/components/BadgeCard";
import { NormalBadgeCard } from "../../../gamification/components/BadgeCard";
import { Tracks } from "../../types/trackTypes";

interface TrackDescriptionProps {
Expand All @@ -19,7 +19,7 @@ export const TrackDescription = ({ track }: TrackDescriptionProps) => {
<div className="items-center justify-center flex gap-12 mx-20">
{track.badge.displayData.imageUrl && (
<Link href={`/achievements?badgeId=${track.badge.id}`}>
<BadgeCard
<NormalBadgeCard
badgeId={track.badge.id}
className="hover:scale-105 transition-all duration-300 min-w-52 h-60"
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/govquests-frontend/src/graphql-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export type introspection_types = {
'SignInWithEthereumPayload': { kind: 'OBJECT'; name: 'SignInWithEthereumPayload'; fields: { 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; 'user': { name: 'user'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; }; };
'SignOutInput': { kind: 'INPUT_OBJECT'; name: 'SignOutInput'; isOneOf: false; inputFields: [{ name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; };
'SignOutPayload': { kind: 'OBJECT'; name: 'SignOutPayload'; fields: { 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'success': { name: 'success'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; };
'SpecialBadge': { kind: 'OBJECT'; name: 'SpecialBadge'; fields: { 'badgeType': { name: 'badgeType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'displayData': { name: 'displayData'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BadgeDisplayData'; ofType: null; }; } }; 'earnedByCurrentUser': { name: 'earnedByCurrentUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'points': { name: 'points'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'userBadges': { name: 'userBadges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UserBadge'; ofType: null; }; }; }; } }; }; };
'SpecialBadge': { kind: 'OBJECT'; name: 'SpecialBadge'; fields: { 'badgeType': { name: 'badgeType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'displayData': { name: 'displayData'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BadgeDisplayData'; ofType: null; }; } }; 'earnedByCurrentUser': { name: 'earnedByCurrentUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'rewardPools': { name: 'rewardPools'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'RewardPool'; ofType: null; }; }; }; } }; 'userBadges': { name: 'userBadges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UserBadge'; ofType: null; }; }; }; } }; }; };
'StartActionExecutionInput': { kind: 'INPUT_OBJECT'; name: 'StartActionExecutionInput'; isOneOf: false; inputFields: [{ name: 'actionId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'actionType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'questId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'sendEmailVerificationInput'; type: { kind: 'INPUT_OBJECT'; name: 'SendEmailVerificationInput'; ofType: null; }; defaultValue: null }]; };
'StartActionExecutionPayload': { kind: 'OBJECT'; name: 'StartActionExecutionPayload'; fields: { 'actionExecution': { name: 'actionExecution'; type: { kind: 'OBJECT'; name: 'ActionExecution'; ofType: null; } }; 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; }; };
'StartDataInterface': { kind: 'INTERFACE'; name: 'StartDataInterface'; fields: { 'actionType': { name: 'actionType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; possibleTypes: 'DiscourseVerificationStartData' | 'EmptyActionStartData' | 'EnsStartData' | 'GitcoinScoreStartData' | 'SendEmailStartData'; };
Expand Down

0 comments on commit 6009883

Please sign in to comment.