-
Notifications
You must be signed in to change notification settings - Fork 439
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add progress bar and polygon * feat: implement xp ranking, leveling logic * chore: remove console.log * feat: add xp to activity card, refactor xp system * feat: add xp to activity card, refactor xp system * fix: cache experiences count on server * feat: add xp to Account and AccountModal * fix(a11y): add dialog description to modal * UI(ActivityCard): move XP to the right in gold * feat: add activity chart * feat: add tooltip to chart * UI(ProfileHero): avatar level indicator refinements * feat: style tooltip and group xp entries * chore: adjust barchart size * fix: size experience card with badge * refactor: resize RewardBadge * refactor: add progress ui component * fix: assign rank properly on profile * chore: floor levels for simplicity * chore: revalidate experiences every 20 min * fix: Experience & Top contributions title colors in light mode * UI: whitespace refinement * chore: add fallback to activity chart * UI(experiences): impros/fixes * fix: reorder useEffect calls, adjust startTime * UI: account modal & progress refinements * cleanup(LevelBadge): duplicated text style * copy(ActivityChart): fallback fix * ActivityChart batching / fallback rendering impro --------- Co-authored-by: valid <[email protected]>
- Loading branch information
1 parent
853db20
commit 32510d7
Showing
25 changed files
with
592 additions
and
60 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const MAX_LEVEL = 100 | ||
export const MAX_XP = 11e4 | ||
export const RANKS = [ | ||
{ color: "#78c93d", title: "novice", polygonCount: 20 }, | ||
{ color: "#88d525", title: "learner", polygonCount: 20 }, | ||
{ color: "#f6ca45", title: "knight", polygonCount: 4 }, | ||
{ color: "#f19b38", title: "veteran", polygonCount: 4 }, | ||
{ color: "#ec5a53", title: "champion", polygonCount: 4 }, | ||
{ color: "#53adf0", title: "hero", polygonCount: 5 }, | ||
{ color: "#c385f8", title: "master", polygonCount: 5 }, | ||
{ color: "#3e6fc3", title: "grand master", polygonCount: 5 }, | ||
{ color: "#be4681", title: "legend", polygonCount: 6 }, | ||
{ color: "#000000", title: "mythic", polygonCount: 6 }, | ||
{ | ||
color: "#eeeeee", | ||
title: "???", | ||
requiredXp: 1e19, | ||
polygonCount: 6, | ||
}, | ||
] as const |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { RANKS } from "./constants" | ||
|
||
export type Rank = (typeof RANKS)[number] |
166 changes: 166 additions & 0 deletions
166
src/app/(marketing)/profile/_components/ActivityChart.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { Skeleton } from "@/components/ui/Skeleton" | ||
import { Schemas } from "@guildxyz/types" | ||
import { localPoint } from "@visx/event" | ||
import { Group } from "@visx/group" | ||
import ParentSize from "@visx/responsive/lib/components/ParentSize" | ||
import { scaleBand, scaleLinear } from "@visx/scale" | ||
import { Bar } from "@visx/shape" | ||
import { useTooltip, useTooltipInPortal } from "@visx/tooltip" | ||
import { useMemo } from "react" | ||
import { useExperienceProgression } from "../_hooks/useExperienceProgression" | ||
import { useExperiences } from "../_hooks/useExperiences" | ||
|
||
type TooltipData = Schemas["Experience"] | ||
const verticalMargin = 0 | ||
|
||
const getX = (xp: Schemas["Experience"]) => xp.id.toString() | ||
const getY = (xp: Schemas["Experience"]) => xp.amount | ||
|
||
export type BarsProps = { | ||
width: number | ||
height: number | ||
} | ||
|
||
let tooltipTimeout: number | ||
|
||
const ActivityChartChildren = ({ | ||
width, | ||
height, | ||
rawData, | ||
}: BarsProps & { | ||
rawData: Schemas["Experience"][] | ||
}) => { | ||
const { | ||
tooltipOpen, | ||
tooltipLeft, | ||
tooltipTop, | ||
tooltipData, | ||
hideTooltip, | ||
showTooltip, | ||
} = useTooltip<TooltipData>() | ||
|
||
const { containerRef, TooltipInPortal } = useTooltipInPortal({ | ||
scroll: true, | ||
}) | ||
const xp = useExperienceProgression() | ||
const groupedData = new Map<number, Schemas["Experience"][]>() | ||
for (const rawXp of rawData) { | ||
const createdAt = new Date(rawXp.createdAt) | ||
const commonDay = new Date( | ||
createdAt.getFullYear(), | ||
createdAt.getMonth(), | ||
createdAt.getDate() | ||
).valueOf() | ||
groupedData.set(commonDay, [...(groupedData.get(commonDay) ?? []), rawXp]) | ||
} | ||
const data = [...groupedData.entries()] | ||
.reduce<Schemas["Experience"][]>((acc, [_, xpGroup]) => { | ||
return [ | ||
...acc, | ||
{ | ||
...xpGroup[0], | ||
amount: xpGroup.reduce((sumAcc, xp) => sumAcc + xp.amount, 0), | ||
}, | ||
] | ||
}, []) | ||
.sort( | ||
(a, b) => new Date(a.createdAt).valueOf() - new Date(b.createdAt).valueOf() | ||
) | ||
|
||
const xMax = width | ||
const yMax = height - verticalMargin | ||
const xScale = useMemo( | ||
() => | ||
scaleBand<string>({ | ||
range: [0, Math.min(data.length * 18, xMax)], | ||
round: true, | ||
domain: data.map(getX), | ||
padding: 0.4, | ||
}), | ||
[xMax] | ||
) | ||
const yScale = useMemo( | ||
() => | ||
scaleLinear<number>({ | ||
range: [yMax, 0], | ||
round: true, | ||
domain: [0, Math.max(...data.map(getY))], | ||
}), | ||
[yMax] | ||
) | ||
|
||
return width < 10 ? null : ( | ||
<div className="relative"> | ||
<svg width={width} height={height} ref={containerRef}> | ||
<Group top={verticalMargin / 2}> | ||
{data.map((currentXp) => { | ||
const x = getX(currentXp) | ||
const barWidth = xScale.bandwidth() | ||
const barHeight = yMax - (yScale(getY(currentXp)) ?? 0) | ||
const barX = xScale(x) | ||
const barY = yMax - barHeight | ||
return ( | ||
<Bar | ||
ry={4} | ||
key={currentXp.id} | ||
x={barX} | ||
y={barY} | ||
width={barWidth} | ||
height={barHeight} | ||
fill={xp?.rank.color} | ||
onMouseLeave={() => { | ||
tooltipTimeout = window.setTimeout(() => { | ||
hideTooltip() | ||
}, 300) | ||
}} | ||
onMouseMove={(event) => { | ||
if (tooltipTimeout) clearTimeout(tooltipTimeout) | ||
const eventSvgCoords = localPoint(event) | ||
const left = (barX || 0) + barWidth / 2 | ||
showTooltip({ | ||
tooltipData: currentXp, | ||
tooltipTop: eventSvgCoords?.y, | ||
tooltipLeft: left, | ||
}) | ||
}} | ||
/> | ||
) | ||
})} | ||
</Group> | ||
</svg> | ||
{tooltipOpen && tooltipData && ( | ||
<TooltipInPortal | ||
top={tooltipTop} | ||
left={tooltipLeft} | ||
unstyled | ||
applyPositionStyle | ||
className="rounded border bg-card px-2 py-1 text-sm" | ||
> | ||
<strong>+{tooltipData.amount} XP</strong> | ||
<div className="text-muted-foreground"> | ||
{new Date(tooltipData.createdAt).toLocaleDateString()} | ||
</div> | ||
</TooltipInPortal> | ||
)} | ||
</div> | ||
) | ||
} | ||
|
||
export const ActivityChart = () => { | ||
const { data: rawData } = useExperiences({ count: false }) | ||
|
||
if (!rawData) return <Skeleton className="h-7 w-full" /> | ||
|
||
if (rawData.length === 0) | ||
return <p className="text-muted-foreground">There's no activity this month</p> | ||
|
||
return ( | ||
<div className="h-7"> | ||
<ParentSize> | ||
{({ width, height }) => ( | ||
<ActivityChartChildren height={height} width={width} rawData={rawData} /> | ||
)} | ||
</ParentSize> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Polygon } from "@/components/Polygon" | ||
import { VariantProps, cva } from "class-variance-authority" | ||
import { Rank } from "../[username]/types" | ||
|
||
const levelBadgeVariants = cva("flex items-center justify-center", { | ||
variants: { | ||
size: { | ||
md: "size-7 text-xs", | ||
lg: "text-lg md:text-xl size-10 md:size-12", | ||
}, | ||
}, | ||
defaultVariants: { | ||
size: "md", | ||
}, | ||
}) | ||
|
||
type LevelBadgeProps = { | ||
levelIndex: number | ||
rank: Rank | ||
className?: string | ||
} & VariantProps<typeof levelBadgeVariants> | ||
|
||
export const LevelBadge = ({ | ||
rank, | ||
levelIndex, | ||
size, | ||
className, | ||
}: LevelBadgeProps) => { | ||
return ( | ||
<div className={levelBadgeVariants({ size, className })}> | ||
<Polygon | ||
sides={rank.polygonCount} | ||
color={rank.color} | ||
className="brightness-75" | ||
/> | ||
<span className="-mt-0.5 absolute font-bold font-display text-white"> | ||
{levelIndex} | ||
</span> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.