Skip to content

Commit

Permalink
📱 Work on design
Browse files Browse the repository at this point in the history
  • Loading branch information
bal7hazar committed Oct 21, 2024
1 parent 3fa63c0 commit 87d268d
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 44 deletions.
84 changes: 63 additions & 21 deletions packages/profile/src/components/quest/achievement.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,104 @@

import {
cn,
TrackIcon,
CalendarIcon,
SparklesIcon,
StateIconProps,
Separator,
TrophyIcon,
} from "@cartridge/ui-next";
import { useMemo } from "react";
import { useMemo, useState } from "react";

export function Achievement({ Icon, title, description, percentage, earning, timestamp }: { Icon: React.ComponentType<StateIconProps>, title: string, description: string, percentage: number, earning: number, timestamp: number }) {
export function Achievement({ Icon, title, description, percentage, earning, timestamp, completed, last }: { Icon: React.ComponentType<StateIconProps> | undefined, title: string, description: string, percentage: number, earning: number, timestamp: number, completed: boolean, last: boolean }) {
return (
<div className="flex items-center gap-x-px">
<div className="grow flex items-center gap-2 bg-secondary p-2">
<Icon className="text-primary" size="lg" variant="solid" />
<div className="grow flex flex-col">
<div className="flex justify-between items-center">
<p className="text-xs text-secondary-foreground capitalize">{title}</p>
<div className="flex gap-2">
<Earning amount={earning} />
<div className="flex py-1 opacity-30">
<Separator className="grow bg-muted-foreground" orientation="vertical" />
<div className={cn("flex items-center gap-x-px", last && "rounded-b-md overflow-clip")}>
<div className="grow flex-col items-stretch gap-2 bg-secondary p-2">
<div className="flex items-center gap-2">
{!!Icon && <Icon className={cn("min-w-8 min-h-8", completed ? "text-primary" : "text-muted-foreground")} variant="solid" />}
{!Icon && <TrophyIcon className={cn("min-w-8 min-h-8", completed ? "text-primary" : "text-muted-foreground")} variant="solid" />}
<div className="grow flex flex-col">
<div className="flex justify-between items-center">
<div className="flex gap-2">
<Title title={title} completed={completed} />
<Timestamp timestamp={timestamp} />
</div>
<div className={cn("flex gap-2", !completed && "hidden")}>
<Earning amount={earning.toLocaleString()} />
</div>
<Timestamp timestamp={timestamp} />
</div>
<Details percentage={percentage} />
</div>
<p className="text-xs text-secondary-accent">{description.slice(0, 1).toUpperCase() + description.slice(1)}</p>
<p className="text-[0.65rem] text-muted-foreground">{`${percentage}% of players earned`}</p>
</div>
<Description description={description} />
</div>
<Track pinned={false} />
</div>
)
}

function Earning({ amount }: { amount: number }) {
function Title({ title, completed }: { title: string, completed: boolean }) {
return <p className={cn("text-xs text-muted-foreground capitalize", completed && "text-priamry-foreground")}>{title}</p>
}

function Description({ description }: { description: string }) {
const [full, setFull] = useState(false);
const visible = useMemo(() => description.length > 100, [description]);
const content = useMemo(() => {
if (!visible || full) {
return description.slice(0, 1).toUpperCase() + description.slice(1);
} else {
return description.slice(0, 1).toUpperCase() + description.slice(1, 100) + "...";
}
}, [description, full]);

if (description.length === 0) return null;
return (
<p
className="text-xs text-muted-foreground"
>
{content}
{visible && <span className="text-muted-foreground" onClick={() => setFull(!full)}>{full ? " read less" : " read more"}</span>}
</p>
)
}

function Details({ percentage }: { percentage: number }) {
return <p className="text-[0.65rem] text-muted-foreground tracking-wider">{`${percentage}% of players earned`}</p>
}

function Earning({ amount }: { amount: string }) {
return (
<div className="flex items-center gap-1 text-muted-foreground">
<SparklesIcon size="xs" variant="line" />
<SparklesIcon size="xs" variant="solid" />
<p className="text-xs">{amount}</p>
</div>
)
}

function Timestamp({ timestamp }: { timestamp: number }) {
const date = useMemo(() => {
return new Date(timestamp * 1000).toLocaleDateString();
const date = new Date(timestamp * 1000);
const today = new Date();
if (date.getDate() === today.getDate()) {
return 'Today';
} else if (date.getDate() === today.getDate() - 1) {
return 'Yesterday';
} else {
return date.toLocaleDateString(undefined, { month: "numeric", day: "numeric", year: "2-digit" });
}
}, [timestamp]);

return (
<div className="flex items-center gap-1 text-muted-foreground">
<CalendarIcon size="xs" variant="line" />
<p className="text-xs">{date}</p>
<p className="text-[0.65rem]">{date}</p>
</div>
)
}

function Track({ pinned }: { pinned: boolean }) {
return (
<div className="bg-secondary h-full p-2 flex items-center text-muted-foreground">
<div className="bg-secondary h-full p-2 flex items-center text-">
<TrackIcon size="sm" variant={pinned ? "solid" : "line"} />
</div>
)
Expand Down
32 changes: 20 additions & 12 deletions packages/profile/src/components/quest/achievements.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import {
cn,
BookIcon,
DoveIcon,
} from "@cartridge/ui-next";
import { Achievement } from "./achievement";
import { Item } from ".";
import { useMemo } from "react";

export function Achievements() {
export function Achievements({ achievements }: { achievements: Item[] }) {
const { completed, total } = useMemo(() => ({ completed: achievements.filter(item => item.completed).length, total: achievements.length }), [achievements]);

return (
<div className="flex flex-col gap-y-px">
<div className="bg-secondary p-3 rounded-t-md">
<p className="uppercase text-xs text-muted-foreground font-semibold tracking-wider">Progression</p>
</div>
<div className="bg-secondary p-2 flex gap-4">
<div className="bg-secondary py-2 px-3 flex gap-4">
<div className="grow flex flex-col justify-center items-start bg-accent rounded-xl p-1">
<div style={{ width: `${Math.floor(100 * 4 / 9)}%` }} className={cn("grow bg-primary rounded-xl", )} />
<div style={{ width: `${Math.floor(100 * completed / total)}%` }} className={cn("grow bg-primary rounded-xl", )} />
</div>
<p className="text-xs text-muted-foreground">
4 of 9
{`${completed} of ${total}`}
</p>
</div>
<Achievement Icon={BookIcon} title="rogue scholar" description="finish a run without killing any monsters" percentage={12} earning={50} timestamp={1729318800} />
<Achievement Icon={DoveIcon} title="pacifist path" description="lorem ipsum dolor sit amet" percentage={24} earning={10} timestamp={1729328800} />
<Achievement Icon={DoveIcon} title="pacifist path" description="lorem ipsum dolor sit amet" percentage={24} earning={10} timestamp={1729328800} />
<Achievement Icon={DoveIcon} title="pacifist path" description="lorem ipsum dolor sit amet" percentage={24} earning={10} timestamp={1729328800} />
<Achievement Icon={DoveIcon} title="pacifist path" description="lorem ipsum dolor sit amet" percentage={24} earning={10} timestamp={1729328800} />
<Achievement Icon={DoveIcon} title="pacifist path" description="lorem ipsum dolor sit amet" percentage={24} earning={10} timestamp={1729328800} />
{achievements.map((achievement, index) => (
<Achievement
key={index}
Icon={achievement.Icon}
title={achievement.hidden ? achievement.hidden_title : achievement.title}
description={achievement.hidden ? achievement.hidden_description : achievement.description}
percentage={achievement.percentage}
earning={achievement.earning}
timestamp={achievement.timestamp}
completed={achievement.completed}
last={index === achievements.length - 1}
/>
))}
</div>
)
}
137 changes: 129 additions & 8 deletions packages/profile/src/components/quest/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,147 @@ import {
LayoutHeader,
} from "@/components/layout";
import {
BoltIcon,
BookIcon,
DoveIcon,
ScrollArea,
StateIconProps,
} from "@cartridge/ui-next";
import { TrophiesTab, LeaderboardTab } from "./tab";
import { useConnection } from "@/hooks/context";
import { CopyAddress } from "@cartridge/ui-next";
import { Navigation } from "../navigation";
import { useState } from "react";
import { Pinned, Empty } from "./pinned";
import { Achievements } from "./achievements";
import { Pinneds } from "./pinneds";

export interface Item {
title: string;
hidden_title: string;
description: string;
hidden_description: string;
percentage: number;
earning: number;
timestamp: number;
completed: boolean;
hidden: boolean;
pinned: boolean;
Icon: React.ComponentType<StateIconProps> | undefined;
}

export function Quest() {
const { username, address } = useConnection();
const [activeTab, setActiveTab] = useState<"trophies" | "leaderboard">("trophies");

const achivements: Item[] = [
{
title: "pacifist path",
hidden_title: "hidden trophy",
description: "finish a run without killing any monsters",
hidden_description: "",
percentage: 24,
earning: 50,
timestamp: 1729328800,
completed: true,
hidden: false,
pinned: true,
Icon: DoveIcon,
},
{
title: "rogue scholar",
hidden_title: "hidden trophy",
description: "lorem ipsum dolor sit amet",
hidden_description: "",
percentage: 12,
earning: 10000,
timestamp: 1729527062,
completed: true,
hidden: false,
pinned: true,
Icon: BookIcon,
},
{
title: "speed runner",
hidden_title: "hidden trophy",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
hidden_description: "",
percentage: 12,
earning: 1,
timestamp: 1729433462,
completed: true,
hidden: false,
pinned: false,
Icon: undefined,
},
{
title: "Lightning Reflexes",
hidden_title: "",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
hidden_description: "",
percentage: 12,
earning: 100,
timestamp: 0,
completed: false,
hidden: false,
pinned: false,
Icon: BoltIcon,
},
{
title: "",
hidden_title: "hidden trophy",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
hidden_description: "",
percentage: 6,
earning: 100,
timestamp: 0,
completed: false,
hidden: true,
pinned: false,
Icon: undefined,
},
{
title: "",
hidden_title: "hidden trophy",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
hidden_description: "",
percentage: 42,
earning: 100,
timestamp: 0,
completed: false,
hidden: true,
pinned: false,
Icon: undefined,
},
{
title: "",
hidden_title: "hidden trophy",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
hidden_description: "This is an hidden description for an hidden trophy",
percentage: 12,
earning: 100,
timestamp: 0,
completed: false,
hidden: true,
pinned: false,
Icon: undefined,
},
{
title: "",
hidden_title: "hidden trophy",
description: "",
hidden_description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
percentage: 21,
earning: 100,
timestamp: 0,
completed: false,
hidden: true,
pinned: false,
Icon: undefined,
}
];

const pinneds = achivements.filter(item => item.pinned).slice(0, 3);

return (
<LayoutContainer>
<LayoutHeader
Expand All @@ -34,13 +159,9 @@ export function Quest() {
<LeaderboardTab active={activeTab === "leaderboard"} onClick={() => setActiveTab("leaderboard")} />
</div>
<ScrollArea className="overflow-auto">
<div className="flex flex-col h-full flex-1 overflow-y-auto gap-4 pb-4">
<div className="grid grid-cols-3 gap-4">
<Pinned Icon={BookIcon} title={"rogue Scholar"} />
<Pinned Icon={DoveIcon} title={"Lorem ipsum dolor sit amet"} />
<Empty />
</div>
<Achievements />
<div className="flex flex-col h-full flex-1 overflow-y-auto gap-4 mb-4">
<Pinneds achievements={pinneds} />
<Achievements achievements={achivements} />
</div>
</ScrollArea>
</LayoutContent>
Expand Down
8 changes: 5 additions & 3 deletions packages/profile/src/components/quest/pinned.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ import {
cn,
SpiderWebIcon,
StateIconProps,
TrophyIcon,
} from "@cartridge/ui-next";
import {
Card,
CardHeader,
CardTitle,
} from "@cartridge/ui-next";

export function Pinned({ Icon, title, empty }: { Icon: React.ComponentType<StateIconProps>, title: string, empty?: boolean }) {
export function Pinned({ Icon, title, empty }: { Icon: React.ComponentType<StateIconProps> | undefined, title: string, empty?: boolean }) {
return (
<Card>
<CardHeader className={cn(
"flex flex-col justify-between items-center h-full pt-6",
"flex flex-col justify-between items-center h-full py-6",
empty && "bg-background border border-dashed border-secondary"
)}>
<Icon className={cn("min-w-12 min-h-12", empty ? "opacity-10" : "text-primary")} variant="solid" />
{Icon && <Icon className={cn("min-w-12 min-h-12", empty ? "opacity-10" : "text-primary")} variant="solid" />}
{!Icon && <TrophyIcon className={cn("min-w-12 min-h-12", empty ? "opacity-10" : "text-primary")} variant="solid" />}
<CardTitle className={cn(
"grow flex flex-col justify-center items-center capitalize font-normal text-xs",
empty ? "opacity-50" : "text-secondary-foreground"
Expand Down
15 changes: 15 additions & 0 deletions packages/profile/src/components/quest/pinneds.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Item } from "./index";
import { Pinned, Empty } from "./pinned";

export function Pinneds({ achievements }: { achievements: Item[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{achievements.map((achievement, index) => (
<Pinned key={index} Icon={achievement.Icon} title={achievement.title} />
))}
{Array.from({ length: 3 - achievements.length }).map((_, index) => (
<Empty key={index} />
))}
</div>
);
}

0 comments on commit 87d268d

Please sign in to comment.