From b29f846f2982d2bd2363d34e4d91351c729e5daa Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Mon, 9 Sep 2024 23:47:45 +0200 Subject: [PATCH 01/29] feat: add progress bar and polygon --- .../profile/[username]/constants.ts | 19 +++++++ .../(marketing)/profile/[username]/page.tsx | 10 ++++ .../_components/CircularProgressBar.tsx | 55 +++++++++++++++++++ .../profile/_components/Polygon.tsx | 31 +++++++++++ .../profile/_components/ProfileHero.tsx | 39 +++++++++++-- .../profile/_hooks/useExperiences.tsx | 13 +++++ 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 src/app/(marketing)/profile/[username]/constants.ts create mode 100644 src/app/(marketing)/profile/_components/CircularProgressBar.tsx create mode 100644 src/app/(marketing)/profile/_components/Polygon.tsx create mode 100644 src/app/(marketing)/profile/_hooks/useExperiences.tsx diff --git a/src/app/(marketing)/profile/[username]/constants.ts b/src/app/(marketing)/profile/[username]/constants.ts new file mode 100644 index 0000000000..5f0689d3db --- /dev/null +++ b/src/app/(marketing)/profile/[username]/constants.ts @@ -0,0 +1,19 @@ +export const MAX_LEVEL = 100 +export const RANKS = [ + { color: "#78c93d", title: "novice", requiredXp: 0, polygonCount: 20 }, + { color: "#88d525", title: "learner", requiredXp: 1e2, polygonCount: 20 }, + { color: "#f6ca45", title: "knight", requiredXp: 1e3, polygonCount: 4 }, + { color: "#78c93d", title: "veteran", requiredXp: 1e4, polygonCount: 4 }, + { color: "#ec5a53", title: "champion", requiredXp: 1e6, polygonCount: 4 }, + { color: "#53adf0", title: "hero", requiredXp: 1e8, polygonCount: 5 }, + { color: "#c385f8", title: "master", requiredXp: 1e9, polygonCount: 5 }, + { color: "#3e6fc3", title: "grand master", requiredXp: 1e11, polygonCount: 5 }, + { color: "#be4681", title: "legend", requiredXp: 1e12, polygonCount: 6 }, + { color: "#000000", title: "mythic", requiredXp: 1e13, polygonCount: 6 }, + { + color: "linear-gradient(to left top, #00cbfa, #b9f2ff)", + title: "???", + requiredXp: 1e19, + polygonCount: 6, + }, +] as const diff --git a/src/app/(marketing)/profile/[username]/page.tsx b/src/app/(marketing)/profile/[username]/page.tsx index 9254fea7d8..f879d86c94 100644 --- a/src/app/(marketing)/profile/[username]/page.tsx +++ b/src/app/(marketing)/profile/[username]/page.tsx @@ -158,6 +158,14 @@ const fetchPublicProfileData = async ({ : [] const guildsZipped = guildRequests.map(({ pathname }, i) => [pathname, guilds[i]]) const rolesZipped = roleRequests.map(({ pathname }, i) => [pathname, roles[i]]) + const experiencesRequest = new URL(`/v2/profiles/${username}/experiences`, api) + const experiences = await ssrFetcher(experiencesRequest) + const experienceCountRequest = new URL( + `/v2/profiles/${username}/experiences?count=true`, + api + ) + const experienceCount = await ssrFetcher(experiencesRequest) + return { profile, fallback: Object.fromEntries( @@ -167,6 +175,8 @@ const fetchPublicProfileData = async ({ [farcasterProfilesRequest.pathname, farcasterProfiles], [neynarRequest?.href, fcFollowers], [referredUsersRequest.pathname, referredUsers], + [experiencesRequest.pathname, experiences], + [experienceCountRequest.pathname, experienceCount], ...collectionsZipped, ...guildsZipped, ...rolesZipped, diff --git a/src/app/(marketing)/profile/_components/CircularProgressBar.tsx b/src/app/(marketing)/profile/_components/CircularProgressBar.tsx new file mode 100644 index 0000000000..ae5a79cfe1 --- /dev/null +++ b/src/app/(marketing)/profile/_components/CircularProgressBar.tsx @@ -0,0 +1,55 @@ +import { cn } from "@/lib/utils" +import React from "react" + +interface CircularProgressBarProps { + strokeWidth?: number + progress: number + color?: string + bgColor?: string + className?: string +} + +export const CircularProgressBar: React.FC = ({ + strokeWidth = 6, + progress, + color, + bgColor, + className, +}) => { + const size = 128 + const radius = (size - strokeWidth) / 2 + const circumference = 2 * Math.PI * radius + const offset = circumference - progress * circumference + + return ( + + + + + ) +} diff --git a/src/app/(marketing)/profile/_components/Polygon.tsx b/src/app/(marketing)/profile/_components/Polygon.tsx new file mode 100644 index 0000000000..dab503f316 --- /dev/null +++ b/src/app/(marketing)/profile/_components/Polygon.tsx @@ -0,0 +1,31 @@ +import { cn } from "@/lib/utils" +import { PropsWithChildren } from "react" + +type PolygonProps = PropsWithChildren<{ + sides: number + color?: string + className?: string +}> + +export const Polygon = ({ sides, color, className }: PolygonProps) => { + const angleStep = (2 * Math.PI) / sides + const radius = 38 + const points = Array.from({ length: sides }, (_, i) => { + const x = 50 + radius * Math.cos(i * angleStep) + const y = 50 + radius * Math.sin(i * angleStep) + return `${x},${y}` + }).join(" ") + + return ( + + + + ) +} diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 1d7e37e751..12cafc8be4 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -6,16 +6,28 @@ import { Avatar } from "@/components/ui/Avatar" import { Button } from "@/components/ui/Button" import { Card } from "@/components/ui/Card" import { Pencil } from "@phosphor-icons/react" +import { RANKS } from "../[username]/constants" import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" +import { useExperiences } from "../_hooks/useExperiences" import { useProfile } from "../_hooks/useProfile" +import { CircularProgressBar } from "./CircularProgressBar" import { EditProfile } from "./EditProfile/EditProfile" +import { Polygon } from "./Polygon" import { ProfileHeroSkeleton } from "./ProfileSkeleton" import { ProfileSocialCounters } from "./ProfileSocialCounters" export const ProfileHero = () => { const { data: profile } = useProfile() + const { data: experienceCount } = useExperiences({ count: true }) + const rankIndex = RANKS.findIndex( + ({ requiredXp }) => experienceCount >= requiredXp + ) + const [rank, nextRank] = RANKS.slice(rankIndex, rankIndex + 1) as [ + (typeof RANKS)[number], + (typeof RANKS)[number] | undefined, + ] - if (!profile) return + if (!profile || !rank) return return ( @@ -36,12 +48,27 @@ export const ProfileHero = () => { - - + - + + + +
+ + 23 +
+

{profile.name || profile.username} diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.tsx b/src/app/(marketing)/profile/_hooks/useExperiences.tsx new file mode 100644 index 0000000000..e081d9e5b2 --- /dev/null +++ b/src/app/(marketing)/profile/_hooks/useExperiences.tsx @@ -0,0 +1,13 @@ +import useSWRImmutable from "swr/immutable" +import { useProfile } from "./useProfile" + +export const useExperiences = ({ count = false }: { count?: boolean }) => { + const { data: profile } = useProfile() + return useSWRImmutable( + profile + ? [`/v2/profiles/${profile.username}/experiences`, count && `count=${count}`] + .filter(Boolean) + .join("?") + : null + ) +} From 31d88dc1c04c0779b7086440dadc65ebf0cbf115 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Tue, 10 Sep 2024 00:58:51 +0200 Subject: [PATCH 02/29] feat: implement xp ranking, leveling logic --- .../profile/_components/ProfileHero.tsx | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 12cafc8be4..122c455460 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -1,4 +1,5 @@ "use client" + import { CheckMark } from "@/components/CheckMark" import { LayoutContainer } from "@/components/Layout" import { ProfileAvatar } from "@/components/ProfileAvatar" @@ -6,7 +7,7 @@ import { Avatar } from "@/components/ui/Avatar" import { Button } from "@/components/ui/Button" import { Card } from "@/components/ui/Card" import { Pencil } from "@phosphor-icons/react" -import { RANKS } from "../[username]/constants" +import { MAX_LEVEL, RANKS } from "../[username]/constants" import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" import { useExperiences } from "../_hooks/useExperiences" import { useProfile } from "../_hooks/useProfile" @@ -16,16 +17,35 @@ import { Polygon } from "./Polygon" import { ProfileHeroSkeleton } from "./ProfileSkeleton" import { ProfileSocialCounters } from "./ProfileSocialCounters" +const generateExponentialArray = ( + steps: number, + sum: number, + exponent = 2 +): number[] => { + const baseSum = (Math.pow(exponent, steps) - 1) / (exponent - 1) + const scaleFactor = sum / baseSum + return Array.from({ length: steps }, (_, i) => Math.pow(exponent, i) * scaleFactor) +} + export const ProfileHero = () => { const { data: profile } = useProfile() const { data: experienceCount } = useExperiences({ count: true }) - const rankIndex = RANKS.findIndex( - ({ requiredXp }) => experienceCount >= requiredXp + const rankIndex = Math.max( + 0, + RANKS.findIndex(({ requiredXp }) => experienceCount < requiredXp) - 1 ) - const [rank, nextRank] = RANKS.slice(rankIndex, rankIndex + 1) as [ + const [rank, nextRank] = RANKS.slice(rankIndex, rankIndex + 2) as [ (typeof RANKS)[number], (typeof RANKS)[number] | undefined, ] + console.log({ rankIndex }) + const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) + const levels = + nextRank && generateExponentialArray(levelInRank, nextRank.requiredXp) + console.log(levels, generateExponentialArray(5, 100)) + const levelIndex = + levels && Math.max(0, levels.findIndex((level) => experienceCount <= level) - 1) + const level = rankIndex * levelInRank + (levelIndex || 0) if (!profile || !rank) return @@ -50,7 +70,7 @@ export const ProfileHero = () => {
@@ -66,7 +86,7 @@ export const ProfileHero = () => { color={rank.color} className="brightness-75" /> - 23 + {level}

From 8cbeefdf8e9f4a6d7089cfdeb40c34c662651c61 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Tue, 10 Sep 2024 01:04:50 +0200 Subject: [PATCH 03/29] chore: remove console.log --- src/app/(marketing)/profile/_components/ProfileHero.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 122c455460..1198a0010e 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -38,11 +38,9 @@ export const ProfileHero = () => { (typeof RANKS)[number], (typeof RANKS)[number] | undefined, ] - console.log({ rankIndex }) const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) const levels = nextRank && generateExponentialArray(levelInRank, nextRank.requiredXp) - console.log(levels, generateExponentialArray(5, 100)) const levelIndex = levels && Math.max(0, levels.findIndex((level) => experienceCount <= level) - 1) const level = rankIndex * levelInRank + (levelIndex || 0) From 326b10422ae2e9a1eaa7d40a7a5d8ae6f6681935 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Tue, 10 Sep 2024 14:59:38 +0200 Subject: [PATCH 04/29] feat: add xp to activity card, refactor xp system --- .../profile/[username]/constants.ts | 21 ++++----- .../_components/CircularProgressBar.tsx | 5 ++- .../profile/_components/Polygon.tsx | 8 ++-- .../profile/_components/ProfileHero.tsx | 43 +++++++++++-------- .../RecentActivity/ActivityCard.tsx | 6 +++ src/components/[guild]/activity/constants.ts | 1 + 6 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/app/(marketing)/profile/[username]/constants.ts b/src/app/(marketing)/profile/[username]/constants.ts index 5f0689d3db..e188cee1f7 100644 --- a/src/app/(marketing)/profile/[username]/constants.ts +++ b/src/app/(marketing)/profile/[username]/constants.ts @@ -1,15 +1,16 @@ export const MAX_LEVEL = 100 +export const MAX_XP = 11e4 export const RANKS = [ - { color: "#78c93d", title: "novice", requiredXp: 0, polygonCount: 20 }, - { color: "#88d525", title: "learner", requiredXp: 1e2, polygonCount: 20 }, - { color: "#f6ca45", title: "knight", requiredXp: 1e3, polygonCount: 4 }, - { color: "#78c93d", title: "veteran", requiredXp: 1e4, polygonCount: 4 }, - { color: "#ec5a53", title: "champion", requiredXp: 1e6, polygonCount: 4 }, - { color: "#53adf0", title: "hero", requiredXp: 1e8, polygonCount: 5 }, - { color: "#c385f8", title: "master", requiredXp: 1e9, polygonCount: 5 }, - { color: "#3e6fc3", title: "grand master", requiredXp: 1e11, polygonCount: 5 }, - { color: "#be4681", title: "legend", requiredXp: 1e12, polygonCount: 6 }, - { color: "#000000", title: "mythic", requiredXp: 1e13, polygonCount: 6 }, + { color: "#78c93d", title: "novice", polygonCount: 20 }, + { color: "#88d525", title: "learner", polygonCount: 20 }, + { color: "#f6ca45", title: "knight", polygonCount: 4 }, + { color: "#78c93d", 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: "linear-gradient(to left top, #00cbfa, #b9f2ff)", title: "???", diff --git a/src/app/(marketing)/profile/_components/CircularProgressBar.tsx b/src/app/(marketing)/profile/_components/CircularProgressBar.tsx index ae5a79cfe1..eeffaa2d0d 100644 --- a/src/app/(marketing)/profile/_components/CircularProgressBar.tsx +++ b/src/app/(marketing)/profile/_components/CircularProgressBar.tsx @@ -38,7 +38,10 @@ export const CircularProgressBar: React.FC = ({ strokeWidth={strokeWidth} /> { const points = Array.from({ length: sides }, (_, i) => { const x = 50 + radius * Math.cos(i * angleStep) const y = 50 + radius * Math.sin(i * angleStep) - return `${x},${y}` + return [x, y].map((coord) => coord.toFixed(7)).join(",") }).join(" ") return ( diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 1198a0010e..3243644904 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -7,7 +7,7 @@ import { Avatar } from "@/components/ui/Avatar" import { Button } from "@/components/ui/Button" import { Card } from "@/components/ui/Card" import { Pencil } from "@phosphor-icons/react" -import { MAX_LEVEL, RANKS } from "../[username]/constants" +import { MAX_LEVEL, MAX_XP, RANKS } from "../[username]/constants" import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" import { useExperiences } from "../_hooks/useExperiences" import { useProfile } from "../_hooks/useProfile" @@ -20,30 +20,35 @@ import { ProfileSocialCounters } from "./ProfileSocialCounters" const generateExponentialArray = ( steps: number, sum: number, - exponent = 2 + exponent: number ): number[] => { const baseSum = (Math.pow(exponent, steps) - 1) / (exponent - 1) const scaleFactor = sum / baseSum return Array.from({ length: steps }, (_, i) => Math.pow(exponent, i) * scaleFactor) } +export const calculateXpProgression = ({ + experienceCount, +}: { experienceCount: number }) => { + const levels = [...generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03)] + const levelIndex = levels.findIndex((level) => experienceCount < level) + const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) + const level = levels[levelIndex] + const rankIndex = Math.max(0, (levelIndex - 1) % levelInRank) + const rank = RANKS.at(rankIndex) + if (!rank) throw new Error("failed to calculate rank") + const nextLevel = levels.at(levelIndex + 1) + const progress = nextLevel ? experienceCount / nextLevel || 0 : 0 + console.log({ progress, rank, levelIndex, level, experienceCount, levels }) + return { progress, rank, levelIndex } +} + export const ProfileHero = () => { const { data: profile } = useProfile() const { data: experienceCount } = useExperiences({ count: true }) - const rankIndex = Math.max( - 0, - RANKS.findIndex(({ requiredXp }) => experienceCount < requiredXp) - 1 - ) - const [rank, nextRank] = RANKS.slice(rankIndex, rankIndex + 2) as [ - (typeof RANKS)[number], - (typeof RANKS)[number] | undefined, - ] - const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) - const levels = - nextRank && generateExponentialArray(levelInRank, nextRank.requiredXp) - const levelIndex = - levels && Math.max(0, levels.findIndex((level) => experienceCount <= level) - 1) - const level = rankIndex * levelInRank + (levelIndex || 0) + const { rank, progress, levelIndex } = calculateXpProgression({ + experienceCount, + }) if (!profile || !rank) return @@ -68,7 +73,7 @@ export const ProfileHero = () => {
@@ -84,7 +89,9 @@ export const ProfileHero = () => { color={rank.color} className="brightness-75" /> - {level} + + {levelIndex} +

diff --git a/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx b/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx index 8eed66dc4d..f625bf29b9 100644 --- a/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx +++ b/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx @@ -1,6 +1,7 @@ "use client" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar" +import { Badge } from "@/components/ui/Badge" import { Card } from "@/components/ui/Card" import { cn } from "@/lib/utils" import { Guild } from "@guildxyz/types" @@ -61,6 +62,11 @@ export const ActivityCard = ({ activity }: { activity: ActivityLogAction }) => {

+ {activity.xpAmount && ( + + +{activity.xpAmount} XP + + )}

diff --git a/src/components/[guild]/activity/constants.ts b/src/components/[guild]/activity/constants.ts index 09e6218c5d..e78efde1be 100644 --- a/src/components/[guild]/activity/constants.ts +++ b/src/components/[guild]/activity/constants.ts @@ -126,6 +126,7 @@ export type ActivityLogAction = { rolePlatform?: number } children?: Array + xpAmount?: number } export const activityLogActionIcons: Record< From 894ecf2400509b56c50d4c8917fe945c6b70bab9 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Tue, 10 Sep 2024 15:44:40 +0200 Subject: [PATCH 05/29] feat: add xp to activity card, refactor xp system --- package-lock.json | 8 ++-- package.json | 2 +- .../profile/_components/Polygon.tsx | 2 +- .../profile/_components/ProfileHero.tsx | 37 ++----------------- .../_hooks/useExperienceProgression.ts | 34 +++++++++++++++++ .../profile/_hooks/useExperiences.tsx | 5 ++- .../requirementDisplayComponents.ts | 1 + src/requirements/requirementFormComponents.ts | 1 + src/requirements/types.ts | 4 +- 9 files changed, 52 insertions(+), 42 deletions(-) create mode 100644 src/app/(marketing)/profile/_hooks/useExperienceProgression.ts diff --git a/package-lock.json b/package-lock.json index 81a74a5162..e72865ce53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@emotion/styled": "^11.11.0", "@fuels/connectors": "^0.5.0", "@fuels/react": "^0.20.0", - "@guildxyz/types": "^1.10.8", + "@guildxyz/types": "^1.10.10", "@hcaptcha/react-hcaptcha": "^1.4.4", "@hookform/resolvers": "^3.3.4", "@lexical/code": "^0.12.0", @@ -5393,9 +5393,9 @@ } }, "node_modules/@guildxyz/types": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/@guildxyz/types/-/types-1.10.8.tgz", - "integrity": "sha512-oTzLxdFQ3UFXHgsx/xjtIMgb5k9maCsdVlvKZjw/nqY3lYU9UK3DmnPEiiahCVple3zrDE41IgqZqR9McH520A==", + "version": "1.10.10", + "resolved": "https://registry.npmjs.org/@guildxyz/types/-/types-1.10.10.tgz", + "integrity": "sha512-xZjqVSExhp1XHQRsTh4O06GmEf7mGqTejJaNdmFNeNqTKXWIE2jHo0cEeOXavLg6k4BFKx5wC9y4+b6VsAMQBg==", "dependencies": { "zod": "^3.22.4" } diff --git a/package.json b/package.json index 626231b0ef..721238c501 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@emotion/styled": "^11.11.0", "@fuels/connectors": "^0.5.0", "@fuels/react": "^0.20.0", - "@guildxyz/types": "^1.10.8", + "@guildxyz/types": "^1.10.10", "@hcaptcha/react-hcaptcha": "^1.4.4", "@hookform/resolvers": "^3.3.4", "@lexical/code": "^0.12.0", diff --git a/src/app/(marketing)/profile/_components/Polygon.tsx b/src/app/(marketing)/profile/_components/Polygon.tsx index d4240f0634..a6ccff948c 100644 --- a/src/app/(marketing)/profile/_components/Polygon.tsx +++ b/src/app/(marketing)/profile/_components/Polygon.tsx @@ -13,7 +13,7 @@ export const Polygon = ({ sides, color, className }: PolygonProps) => { const points = Array.from({ length: sides }, (_, i) => { const x = 50 + radius * Math.cos(i * angleStep) const y = 50 + radius * Math.sin(i * angleStep) - return [x, y].map((coord) => coord.toFixed(7)).join(",") + return [x, y].map((coord) => coord.toFixed(5)).join(",") }).join(" ") return ( diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 3243644904..b8871a4034 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -7,9 +7,8 @@ import { Avatar } from "@/components/ui/Avatar" import { Button } from "@/components/ui/Button" import { Card } from "@/components/ui/Card" import { Pencil } from "@phosphor-icons/react" -import { MAX_LEVEL, MAX_XP, RANKS } from "../[username]/constants" import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" -import { useExperiences } from "../_hooks/useExperiences" +import { useExperienceProgression } from "../_hooks/useExperienceProgression" import { useProfile } from "../_hooks/useProfile" import { CircularProgressBar } from "./CircularProgressBar" import { EditProfile } from "./EditProfile/EditProfile" @@ -17,40 +16,12 @@ import { Polygon } from "./Polygon" import { ProfileHeroSkeleton } from "./ProfileSkeleton" import { ProfileSocialCounters } from "./ProfileSocialCounters" -const generateExponentialArray = ( - steps: number, - sum: number, - exponent: number -): number[] => { - const baseSum = (Math.pow(exponent, steps) - 1) / (exponent - 1) - const scaleFactor = sum / baseSum - return Array.from({ length: steps }, (_, i) => Math.pow(exponent, i) * scaleFactor) -} - -export const calculateXpProgression = ({ - experienceCount, -}: { experienceCount: number }) => { - const levels = [...generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03)] - const levelIndex = levels.findIndex((level) => experienceCount < level) - const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) - const level = levels[levelIndex] - const rankIndex = Math.max(0, (levelIndex - 1) % levelInRank) - const rank = RANKS.at(rankIndex) - if (!rank) throw new Error("failed to calculate rank") - const nextLevel = levels.at(levelIndex + 1) - const progress = nextLevel ? experienceCount / nextLevel || 0 : 0 - console.log({ progress, rank, levelIndex, level, experienceCount, levels }) - return { progress, rank, levelIndex } -} - export const ProfileHero = () => { const { data: profile } = useProfile() - const { data: experienceCount } = useExperiences({ count: true }) - const { rank, progress, levelIndex } = calculateXpProgression({ - experienceCount, - }) + const progression = useExperienceProgression() - if (!profile || !rank) return + if (!profile || !progression) return + const { rank, levelIndex, progress } = progression return ( diff --git a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts new file mode 100644 index 0000000000..c89724f79f --- /dev/null +++ b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts @@ -0,0 +1,34 @@ +import { MAX_LEVEL, MAX_XP, RANKS } from "../[username]/constants" +import { useExperiences } from "../_hooks/useExperiences" + +const generateExponentialArray = ( + steps: number, + sum: number, + exponent: number +): number[] => { + const baseSum = (Math.pow(exponent, steps) - 1) / (exponent - 1) + const scaleFactor = sum / baseSum + return Array.from({ length: steps }, (_, i) => Math.pow(exponent, i) * scaleFactor) +} + +export const calculateXpProgression = ({ + experienceCount, +}: { experienceCount: number }) => { + const levels = [...generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03)] + const levelIndex = Math.max( + 0, + levels.findIndex((level) => experienceCount < level) + ) + const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) + const rankIndex = Math.max(0, (levelIndex - 1) % levelInRank) + const rank = RANKS.at(rankIndex) + if (!rank) throw new Error("failed to calculate rank") + const nextLevel = levels.at(levelIndex + 1) + const progress = nextLevel ? experienceCount / nextLevel || 0 : 0 + return { progress, rank, levelIndex } +} + +export const useExperienceProgression = () => { + const { data: experienceCount } = useExperiences(true) + return experienceCount && calculateXpProgression({ experienceCount }) +} diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.tsx b/src/app/(marketing)/profile/_hooks/useExperiences.tsx index e081d9e5b2..cad837231c 100644 --- a/src/app/(marketing)/profile/_hooks/useExperiences.tsx +++ b/src/app/(marketing)/profile/_hooks/useExperiences.tsx @@ -1,9 +1,10 @@ +import { Schemas } from "@guildxyz/types" import useSWRImmutable from "swr/immutable" import { useProfile } from "./useProfile" -export const useExperiences = ({ count = false }: { count?: boolean }) => { +export const useExperiences = (count?: T) => { const { data: profile } = useProfile() - return useSWRImmutable( + return useSWRImmutable( profile ? [`/v2/profiles/${profile.username}/experiences`, count && `count=${count}`] .filter(Boolean) diff --git a/src/requirements/requirementDisplayComponents.ts b/src/requirements/requirementDisplayComponents.ts index 9a6f2a952e..cced28d562 100644 --- a/src/requirements/requirementDisplayComponents.ts +++ b/src/requirements/requirementDisplayComponents.ts @@ -374,4 +374,5 @@ export const REQUIREMENT_DISPLAY_COMPONENTS = { PARALLEL_TRAIT: dynamic( () => import("requirements/Parallel/ParallelRequirement") ), + // @ts-ignore: TODO: migrate to backend types to resolve error } as const satisfies Record> diff --git a/src/requirements/requirementFormComponents.ts b/src/requirements/requirementFormComponents.ts index ca398b81fb..da16f76f53 100644 --- a/src/requirements/requirementFormComponents.ts +++ b/src/requirements/requirementFormComponents.ts @@ -362,6 +362,7 @@ export const REQUIREMENT_FORM_COMPONENTS = { PARALLEL_TRAIT: dynamic( () => import("requirements/Parallel/ParallelForm") ), + // @ts-ignore: TODO: migrate to backend types to resolve error } as const satisfies Record< RequirementType, ReturnType> | null diff --git a/src/requirements/types.ts b/src/requirements/types.ts index 18d1d07bfa..742372869f 100644 --- a/src/requirements/types.ts +++ b/src/requirements/types.ts @@ -3,7 +3,9 @@ import { Icon } from "@phosphor-icons/react" import { UseControllerProps } from "react-hook-form" import { Requirement } from "types" -export type RequirementType = Schemas["Requirement"]["type"] +export type RequirementType = + | Schemas["Requirement"]["type"] + | "WORLD_ID_VERIFICATION" export type RequirementFormProps = { baseFieldPath: string From 09576e672021b470d5b5e3010ac24c91fcea4829 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Tue, 10 Sep 2024 15:58:31 +0200 Subject: [PATCH 06/29] fix: cache experiences count on server --- src/app/(marketing)/profile/[username]/page.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/(marketing)/profile/[username]/page.tsx b/src/app/(marketing)/profile/[username]/page.tsx index f879d86c94..e6d4e67207 100644 --- a/src/app/(marketing)/profile/[username]/page.tsx +++ b/src/app/(marketing)/profile/[username]/page.tsx @@ -159,12 +159,12 @@ const fetchPublicProfileData = async ({ const guildsZipped = guildRequests.map(({ pathname }, i) => [pathname, guilds[i]]) const rolesZipped = roleRequests.map(({ pathname }, i) => [pathname, roles[i]]) const experiencesRequest = new URL(`/v2/profiles/${username}/experiences`, api) - const experiences = await ssrFetcher(experiencesRequest) + const experiences = await ssrFetcher(experiencesRequest) const experienceCountRequest = new URL( `/v2/profiles/${username}/experiences?count=true`, api ) - const experienceCount = await ssrFetcher(experiencesRequest) + const experienceCount = await ssrFetcher(experienceCountRequest) return { profile, @@ -176,7 +176,10 @@ const fetchPublicProfileData = async ({ [neynarRequest?.href, fcFollowers], [referredUsersRequest.pathname, referredUsers], [experiencesRequest.pathname, experiences], - [experienceCountRequest.pathname, experienceCount], + [ + experienceCountRequest.pathname + experienceCountRequest.search, + experienceCount, + ], ...collectionsZipped, ...guildsZipped, ...rolesZipped, From e84b62e619df8fa6bb5bf2676448b66d8bff7b99 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Tue, 10 Sep 2024 18:25:59 +0200 Subject: [PATCH 07/29] feat: add xp to Account and AccountModal --- .../profile/_components/ProfileHero.tsx | 4 +- .../_hooks/useExperienceProgression.ts | 14 +++--- .../profile/_hooks/useExperiences.tsx | 7 ++- .../(marketing)/profile/_hooks/useProfile.tsx | 7 ++- src/v2/components/Account/Account.tsx | 43 +++++++++++++++---- .../components/AccountModal/AccountModal.tsx | 42 +++++++++++++++--- .../components}/CircularProgressBar.tsx | 0 .../_components => v2/components}/Polygon.tsx | 0 8 files changed, 91 insertions(+), 26 deletions(-) rename src/{app/(marketing)/profile/_components => v2/components}/CircularProgressBar.tsx (100%) rename src/{app/(marketing)/profile/_components => v2/components}/Polygon.tsx (100%) diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index b8871a4034..5105941e9b 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -1,7 +1,9 @@ "use client" import { CheckMark } from "@/components/CheckMark" +import { CircularProgressBar } from "@/components/CircularProgressBar" import { LayoutContainer } from "@/components/Layout" +import { Polygon } from "@/components/Polygon" import { ProfileAvatar } from "@/components/ProfileAvatar" import { Avatar } from "@/components/ui/Avatar" import { Button } from "@/components/ui/Button" @@ -10,9 +12,7 @@ import { Pencil } from "@phosphor-icons/react" import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" import { useExperienceProgression } from "../_hooks/useExperienceProgression" import { useProfile } from "../_hooks/useProfile" -import { CircularProgressBar } from "./CircularProgressBar" import { EditProfile } from "./EditProfile/EditProfile" -import { Polygon } from "./Polygon" import { ProfileHeroSkeleton } from "./ProfileSkeleton" import { ProfileSocialCounters } from "./ProfileSocialCounters" diff --git a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts index c89724f79f..c507ccf90e 100644 --- a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts +++ b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts @@ -14,21 +14,25 @@ const generateExponentialArray = ( export const calculateXpProgression = ({ experienceCount, }: { experienceCount: number }) => { + experienceCount = Math.min(experienceCount, MAX_XP) const levels = [...generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03)] const levelIndex = Math.max( 0, levels.findIndex((level) => experienceCount < level) ) + const level = levels.at(levelIndex) const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) const rankIndex = Math.max(0, (levelIndex - 1) % levelInRank) const rank = RANKS.at(rankIndex) - if (!rank) throw new Error("failed to calculate rank") + if (!rank || !level) throw new Error("failed to calculate rank") const nextLevel = levels.at(levelIndex + 1) const progress = nextLevel ? experienceCount / nextLevel || 0 : 0 - return { progress, rank, levelIndex } + return { progress, rank, levelIndex, experienceCount, level } } -export const useExperienceProgression = () => { - const { data: experienceCount } = useExperiences(true) - return experienceCount && calculateXpProgression({ experienceCount }) +export const useExperienceProgression = (showOwnProfile?: boolean) => { + const { data: experienceCount } = useExperiences({ showOwnProfile, count: true }) + return typeof experienceCount === "number" + ? calculateXpProgression({ experienceCount }) + : undefined } diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.tsx b/src/app/(marketing)/profile/_hooks/useExperiences.tsx index cad837231c..ad4d0856f4 100644 --- a/src/app/(marketing)/profile/_hooks/useExperiences.tsx +++ b/src/app/(marketing)/profile/_hooks/useExperiences.tsx @@ -2,8 +2,11 @@ import { Schemas } from "@guildxyz/types" import useSWRImmutable from "swr/immutable" import { useProfile } from "./useProfile" -export const useExperiences = (count?: T) => { - const { data: profile } = useProfile() +export const useExperiences = ({ + count, + showOwnProfile, +}: { count?: T; showOwnProfile?: boolean }) => { + const { data: profile } = useProfile(showOwnProfile) return useSWRImmutable( profile ? [`/v2/profiles/${profile.username}/experiences`, count && `count=${count}`] diff --git a/src/app/(marketing)/profile/_hooks/useProfile.tsx b/src/app/(marketing)/profile/_hooks/useProfile.tsx index 5b1634e3fc..a4146c1be6 100644 --- a/src/app/(marketing)/profile/_hooks/useProfile.tsx +++ b/src/app/(marketing)/profile/_hooks/useProfile.tsx @@ -1,10 +1,13 @@ import { Schemas } from "@guildxyz/types" +import useUser from "components/[guild]/hooks/useUser" import { useParams } from "next/navigation" import useSWRImmutable from "swr/immutable" -export const useProfile = () => { +export const useProfile = (showOwnProfile?: boolean) => { const params = useParams<{ username: string }>() + const user = useUser() + const username = showOwnProfile ? user.guildProfile?.username : params?.username return useSWRImmutable( - params?.username ? `/v2/profiles/${params.username}` : null + username ? `/v2/profiles/${username}` : null ) } diff --git a/src/v2/components/Account/Account.tsx b/src/v2/components/Account/Account.tsx index e41649c44f..172e072fa5 100644 --- a/src/v2/components/Account/Account.tsx +++ b/src/v2/components/Account/Account.tsx @@ -1,7 +1,10 @@ "use client" +import { CircularProgressBar } from "@/components/CircularProgressBar" +import { Polygon } from "@/components/Polygon" import { useDisclosure } from "@/hooks/useDisclosure" import { cn } from "@/lib/utils" +import { useExperienceProgression } from "@app/(marketing)/profile/_hooks/useExperienceProgression" import { Bell } from "@phosphor-icons/react" import { SignIn } from "@phosphor-icons/react/dist/ssr" import useUser from "components/[guild]/hooks/useUser" @@ -30,6 +33,7 @@ export const Account = () => { const { addresses, guildProfile, isLoading } = useUser() const linkedAddressesCount = (addresses?.length ?? 1) - 1 const { captureEvent } = usePostHogContext() + const progression = useExperienceProgression(true) if (isLoading || isWeb3Connected === null) { return ( @@ -80,21 +84,44 @@ export const Account = () => { className="rounded-r-2xl rounded-l-none" > {guildProfile ? ( -
- - - +
+
+ {progression && ( + + )} + + + +
{guildProfile.name || guildProfile.username}
- @{guildProfile.username} + {progression + ? `${progression.experienceCount} / ${Math.ceil(progression.level)} XP` + : `@${guildProfile.username}`}
+ {progression && ( +
+ + + {progression.levelIndex} + +
+ )}
) : (
diff --git a/src/v2/components/Account/components/AccountModal/AccountModal.tsx b/src/v2/components/Account/components/AccountModal/AccountModal.tsx index 5a3a04fa3c..95997f9a01 100644 --- a/src/v2/components/Account/components/AccountModal/AccountModal.tsx +++ b/src/v2/components/Account/components/AccountModal/AccountModal.tsx @@ -1,6 +1,8 @@ import { CheckMark } from "@/components/CheckMark" +import { CircularProgressBar } from "@/components/CircularProgressBar" import { CopyableAddress } from "@/components/CopyableAddress" import { GuildAvatar } from "@/components/GuildAvatar" +import { Polygon } from "@/components/Polygon" import { ProfileAvatar } from "@/components/ProfileAvatar" import { accountModalAtom } from "@/components/Providers/atoms" import useConnectorNameAndIcon from "@/components/Web3ConnectionManager/hooks/useConnectorNameAndIcon" @@ -26,6 +28,7 @@ import { import { Separator } from "@/components/ui/Separator" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip" import { useUserPublic } from "@/hooks/useUserPublic" +import { useExperienceProgression } from "@app/(marketing)/profile/_hooks/useExperienceProgression" import { ArrowRight, DotsThreeVertical } from "@phosphor-icons/react" import { SignOut } from "@phosphor-icons/react/dist/ssr" import useUser from "components/[guild]/hooks/useUser" @@ -48,6 +51,7 @@ const AccountModal = () => { const { address: evmAddress, chainId } = useAccount() const domain = useResolveAddress(evmAddress) + const progression = useExperienceProgression(true) const handleLogout = () => { const keysToRemove = Object.keys({ ...window.localStorage }).filter((key) => @@ -81,12 +85,34 @@ const AccountModal = () => { <> {guildProfile ? (
- - - + {progression && ( +
+ + + + +
+ + + {progression.levelIndex} + +
+
+ )}

@@ -95,7 +121,9 @@ const AccountModal = () => {

- @{guildProfile.username} + {progression + ? `${progression.experienceCount} / ${Math.ceil(progression.level)} XP` + : `@${guildProfile.username}`}
Date: Tue, 10 Sep 2024 19:19:45 +0200 Subject: [PATCH 08/29] fix(a11y): add dialog description to modal --- .../Account/components/AccountModal/AccountModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/v2/components/Account/components/AccountModal/AccountModal.tsx b/src/v2/components/Account/components/AccountModal/AccountModal.tsx index 95997f9a01..95b82de599 100644 --- a/src/v2/components/Account/components/AccountModal/AccountModal.tsx +++ b/src/v2/components/Account/components/AccountModal/AccountModal.tsx @@ -15,6 +15,7 @@ import { DialogBody, DialogCloseButton, DialogContent, + DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/Dialog" @@ -63,7 +64,7 @@ const AccountModal = () => { }) deleteKeyPairFromIdb(id) - ?.catch(() => {}) + ?.catch(() => { }) .finally(() => { setIsOpen(false) disconnect() @@ -78,6 +79,7 @@ const AccountModal = () => { Account + From 2b1618900c795d344b79c88ae781b770d76be23e Mon Sep 17 00:00:00 2001 From: valid Date: Tue, 10 Sep 2024 20:20:30 +0200 Subject: [PATCH 09/29] UI(ActivityCard): move XP to the right in gold --- .../RecentActivity/ActivityCard.tsx | 36 ++++++++++--------- src/app/globals.css | 2 +- src/v2/components/ui/Badge.tsx | 1 + 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx b/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx index f625bf29b9..239ed0ef31 100644 --- a/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx +++ b/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx @@ -59,23 +59,27 @@ export const ActivityCard = ({ activity }: { activity: ActivityLogAction }) => {
)} -
-

- - {activity.xpAmount && ( - - +{activity.xpAmount} XP - - )} -

-
- -

- {formatRelativeTimeFromNow(Date.now() - parseInt(activity.timestamp))}{" "} - ago -

-
+
+
+

+ +

+
+ +

+ {formatRelativeTimeFromNow( + Date.now() - parseInt(activity.timestamp) + )}{" "} + ago +

+
+
+ {activity.xpAmount && ( + + +{activity.xpAmount} XP + + )}
) diff --git a/src/app/globals.css b/src/app/globals.css index de1904e8dd..49ea1b38f0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -129,7 +129,7 @@ --gather_town: 232, 66%, 55%; --cyan: 190, 100%, 42%; --blue: 217, 91%, 60%; - --gold: 35, 39%, 60%; + --gold: 40 67% 51%; --gray: 240, 4%, 46%; --sm: 640px; diff --git a/src/v2/components/ui/Badge.tsx b/src/v2/components/ui/Badge.tsx index 47fee88b80..2818a1391e 100644 --- a/src/v2/components/ui/Badge.tsx +++ b/src/v2/components/ui/Badge.tsx @@ -13,6 +13,7 @@ const badgeVariants = cva( colorScheme: { gray: "[--badge-bg:var(--secondary-subtle)] [--badge-color:var(--secondary-subtle-foreground)]", blue: "[--badge-bg:var(--info-subtle)] [--badge-color:var(--info-subtle-foreground)]", + yellow: "[--badge-bg:var(--gold)] [--badge-color:var(--gold)]", }, size: { sm: "text-xs h-5", From 8f0f3b2469e867acb24bf9557a7154ef49fcfe05 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 00:48:03 +0200 Subject: [PATCH 10/29] feat: add activity chart --- .../profile/_components/ActivityChart.tsx | 68 +++++++++++++++++++ .../profile/_components/Profile.tsx | 61 ++++++++++++++++- .../profile/_hooks/useExperiences.tsx | 2 +- 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/app/(marketing)/profile/_components/ActivityChart.tsx diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx new file mode 100644 index 0000000000..f00a9aedb3 --- /dev/null +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -0,0 +1,68 @@ +import { Skeleton } from "@/components/ui/Skeleton" +import { Schemas } from "@guildxyz/types" +import { Group } from "@visx/group" +import { scaleBand, scaleLinear } from "@visx/scale" +import { Bar } from "@visx/shape" +import { useMemo } from "react" +import { useExperiences } from "../_hooks/useExperiences" + +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 +} + +export const ActivityChart = ({ width, height }: BarsProps) => { + const { data } = useExperiences({ count: false }) + if (!data) return + const xMax = width + const yMax = height - verticalMargin + const xScale = useMemo( + () => + scaleBand({ + range: [0, xMax], + round: true, + domain: data.map(getX), + padding: 0.4, + }), + [xMax] + ) + const yScale = useMemo( + () => + scaleLinear({ + range: [yMax, 0], + round: true, + domain: [0, Math.max(...data.map(getY))], + }), + [yMax] + ) + + return width < 10 ? null : ( + + + {data.map((xp) => { + const x = getX(xp) + const barWidth = xScale.bandwidth() + const barHeight = yMax - (yScale(getY(xp)) ?? 0) + const barX = xScale(x) + const barY = yMax - barHeight + return ( + + ) + })} + + + ) +} diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 1aa2edd213..eed61308fe 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -1,15 +1,21 @@ "use client" +import { Polygon } from "@/components/Polygon" import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager" +import { Badge } from "@/components/ui/Badge" import { Card } from "@/components/ui/Card" import { cn } from "@/lib/utils" import { Info } from "@phosphor-icons/react" +import { Progress, ProgressIndicator } from "@radix-ui/react-progress" +import ParentSize from "@visx/responsive/lib/components/ParentSize" import { PropsWithChildren } from "react" import { ContributionCard } from "../_components/ContributionCard" import { EditContributions } from "../_components/EditContributions" import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" import { useContributions } from "../_hooks/useContributions" +import { useExperienceProgression } from "../_hooks/useExperienceProgression" import { useProfile } from "../_hooks/useProfile" import { useReferredUsers } from "../_hooks/useReferredUsers" +import { ActivityChart } from "./ActivityChart" import { ProfileMainSkeleton } from "./ProfileSkeleton" import { RecentActivity } from "./RecentActivity/RecentActivity" import RecentActivityFallback from "./RecentActivity/RecentActivityFallback" @@ -19,11 +25,64 @@ export const Profile = () => { const { data: contributions } = useContributions() const { data: referredUsers } = useReferredUsers() const { isWeb3Connected } = useWeb3ConnectionManager() + const xp = useExperienceProgression() - if (!profile || !contributions || !referredUsers) return + if (!profile || !contributions || !referredUsers || !xp) + return return ( <> +
+ Experience +
+ + + {/**/} +
+
+

{xp.rank.title}

+

+ {`${xp.experienceCount} / ${Math.ceil(xp.level)} XP`} +

+
+ + + + + {/*

+ This is a description that perfectly matches the 80 character + description limit. +

*/} +
+
+ +
+

Engagement this month

+ +72 XP +
+ + {({ width }) => } + +
+
+
Top contributions diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.tsx b/src/app/(marketing)/profile/_hooks/useExperiences.tsx index ad4d0856f4..eb7677791f 100644 --- a/src/app/(marketing)/profile/_hooks/useExperiences.tsx +++ b/src/app/(marketing)/profile/_hooks/useExperiences.tsx @@ -5,7 +5,7 @@ import { useProfile } from "./useProfile" export const useExperiences = ({ count, showOwnProfile, -}: { count?: T; showOwnProfile?: boolean }) => { +}: { count: T; showOwnProfile?: boolean }) => { const { data: profile } = useProfile(showOwnProfile) return useSWRImmutable( profile From 92df919c0047d424b7f3385222de8f999ad0552c Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 01:10:19 +0200 Subject: [PATCH 11/29] feat: add tooltip to chart --- .../profile/_components/ActivityChart.tsx | 101 ++++++++++++++---- .../profile/_components/Profile.tsx | 4 +- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index f00a9aedb3..c4ce3d6411 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -1,11 +1,21 @@ import { Skeleton } from "@/components/ui/Skeleton" import { Schemas } from "@guildxyz/types" +import { localPoint } from "@visx/event" import { Group } from "@visx/group" import { scaleBand, scaleLinear } from "@visx/scale" import { Bar } from "@visx/shape" +import { defaultStyles, useTooltip, useTooltipInPortal } from "@visx/tooltip" import { useMemo } from "react" import { useExperiences } from "../_hooks/useExperiences" +type TooltipData = Schemas["Experience"] +const tooltipStyles = { + ...defaultStyles, + minWidth: 60, + backgroundColor: "rgba(0,0,0,0.9)", + color: "white", +} + const verticalMargin = 0 const getX = (xp: Schemas["Experience"]) => xp.id.toString() @@ -16,6 +26,7 @@ export type BarsProps = { height: number } +let tooltipTimeout: number export const ActivityChart = ({ width, height }: BarsProps) => { const { data } = useExperiences({ count: false }) if (!data) return @@ -41,28 +52,74 @@ export const ActivityChart = ({ width, height }: BarsProps) => { [yMax] ) + const { + tooltipOpen, + tooltipLeft, + tooltipTop, + tooltipData, + hideTooltip, + showTooltip, + } = useTooltip() + + const { containerRef, TooltipInPortal } = useTooltipInPortal({ + // TooltipInPortal is rendered in a separate child of and positioned + // with page coordinates which should be updated on scroll. consider using + // Tooltip or TooltipWithBounds if you don't need to render inside a Portal + scroll: true, + }) + return width < 10 ? null : ( - - - {data.map((xp) => { - const x = getX(xp) - const barWidth = xScale.bandwidth() - const barHeight = yMax - (yScale(getY(xp)) ?? 0) - const barX = xScale(x) - const barY = yMax - barHeight - return ( - - ) - })} - - +
+ + + {data.map((xp) => { + const x = getX(xp) + const barWidth = xScale.bandwidth() + const barHeight = yMax - (yScale(getY(xp)) ?? 0) + const barX = xScale(x) + const barY = yMax - barHeight + return ( + { + tooltipTimeout = window.setTimeout(() => { + hideTooltip() + }, 300) + }} + onMouseMove={(event) => { + if (tooltipTimeout) clearTimeout(tooltipTimeout) + // TooltipInPortal expects coordinates to be relative to containerRef + // localPoint returns coordinates relative to the nearest SVG, which + // is what containerRef is set to in this example. + const eventSvgCoords = localPoint(event) + const left = (barX || 0) + barWidth / 2 + showTooltip({ + tooltipData: xp, + tooltipTop: eventSvgCoords?.y, + tooltipLeft: left, + }) + }} + /> + ) + })} + + + {tooltipOpen && tooltipData && ( + +
+ +{tooltipData.amount} XP +
+
+ {new Date(tooltipData.createdAt).toLocaleDateString()} +
+
+ )} +
) } diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index eed61308fe..f88d10d7b4 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -1,7 +1,6 @@ "use client" import { Polygon } from "@/components/Polygon" import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager" -import { Badge } from "@/components/ui/Badge" import { Card } from "@/components/ui/Card" import { cn } from "@/lib/utils" import { Info } from "@phosphor-icons/react" @@ -75,10 +74,9 @@ export const Profile = () => {

Engagement this month

- +72 XP
- {({ width }) => } + {({ width }) => }
From 6d60d95684e16531ef183966f6b067b45b36e611 Mon Sep 17 00:00:00 2001 From: valid Date: Wed, 11 Sep 2024 01:35:11 +0200 Subject: [PATCH 12/29] UI(ProfileHero): avatar level indicator refinements --- src/app/(marketing)/profile/_components/ProfileHero.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 5105941e9b..4ece33af12 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -42,25 +42,26 @@ export const ProfileHero = () => { -
+
- + -
+
- + {levelIndex}
From 3818ef38bcd96c47c2742aed59feafb681200184 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 01:50:30 +0200 Subject: [PATCH 13/29] feat: style tooltip and group xp entries --- .../profile/_components/ActivityChart.tsx | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index c4ce3d6411..4651d8ab88 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -4,22 +4,15 @@ import { localPoint } from "@visx/event" import { Group } from "@visx/group" import { scaleBand, scaleLinear } from "@visx/scale" import { Bar } from "@visx/shape" -import { defaultStyles, useTooltip, useTooltipInPortal } from "@visx/tooltip" +import { useTooltip, useTooltipInPortal } from "@visx/tooltip" import { useMemo } from "react" import { useExperiences } from "../_hooks/useExperiences" type TooltipData = Schemas["Experience"] -const tooltipStyles = { - ...defaultStyles, - minWidth: 60, - backgroundColor: "rgba(0,0,0,0.9)", - color: "white", -} - const verticalMargin = 0 const getX = (xp: Schemas["Experience"]) => xp.id.toString() -const getY = (xp: Schemas["Experience"]) => xp.amount +const getY = (xp: Schemas["Experience"]) => xp.amount + 4 export type BarsProps = { width: number @@ -28,8 +21,32 @@ export type BarsProps = { let tooltipTimeout: number export const ActivityChart = ({ width, height }: BarsProps) => { - const { data } = useExperiences({ count: false }) - if (!data) return + const { data: rawData } = useExperiences({ count: false }) + if (!rawData) return + const groupedData = new Map() + for (const xp of rawData) { + const createdAt = new Date(xp.createdAt) + const commonDay = new Date( + createdAt.getFullYear(), + createdAt.getMonth(), + createdAt.getDate() + ).valueOf() + groupedData.set(commonDay, [...(groupedData.get(commonDay) ?? []), xp]) + } + const data = [...groupedData.entries()] + .reduce((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( @@ -62,9 +79,6 @@ export const ActivityChart = ({ width, height }: BarsProps) => { } = useTooltip() const { containerRef, TooltipInPortal } = useTooltipInPortal({ - // TooltipInPortal is rendered in a separate child of and positioned - // with page coordinates which should be updated on scroll. consider using - // Tooltip or TooltipWithBounds if you don't need to render inside a Portal scroll: true, }) @@ -94,9 +108,6 @@ export const ActivityChart = ({ width, height }: BarsProps) => { }} onMouseMove={(event) => { if (tooltipTimeout) clearTimeout(tooltipTimeout) - // TooltipInPortal expects coordinates to be relative to containerRef - // localPoint returns coordinates relative to the nearest SVG, which - // is what containerRef is set to in this example. const eventSvgCoords = localPoint(event) const left = (barX || 0) + barWidth / 2 showTooltip({ @@ -111,12 +122,16 @@ export const ActivityChart = ({ width, height }: BarsProps) => { {tooltipOpen && tooltipData && ( - -
- +{tooltipData.amount} XP -
-
- {new Date(tooltipData.createdAt).toLocaleDateString()} + + +{tooltipData.amount} XP +
+ {new Date(tooltipData.createdAt).toLocaleDateString()}
)} From f6e7e0e3380ad126d1e111082b2cb9132b5030c7 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 12:37:32 +0200 Subject: [PATCH 14/29] chore: adjust barchart size --- src/app/(marketing)/profile/_components/ActivityChart.tsx | 6 +++--- src/app/(marketing)/profile/_components/Profile.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index 4651d8ab88..86fc92f6d4 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -12,7 +12,7 @@ type TooltipData = Schemas["Experience"] const verticalMargin = 0 const getX = (xp: Schemas["Experience"]) => xp.id.toString() -const getY = (xp: Schemas["Experience"]) => xp.amount + 4 +const getY = (xp: Schemas["Experience"]) => xp.amount export type BarsProps = { width: number @@ -52,7 +52,7 @@ export const ActivityChart = ({ width, height }: BarsProps) => { const xScale = useMemo( () => scaleBand({ - range: [0, xMax], + range: [0, Math.min(data.length * 18, xMax)], round: true, domain: data.map(getX), padding: 0.4, @@ -94,7 +94,7 @@ export const ActivityChart = ({ width, height }: BarsProps) => { const barY = yMax - barHeight return ( {

Engagement this month

- {({ width }) => } + {({ width }) => }
From 7f45f039956fc7f7d8cd8b01e9764aee37d56219 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 14:37:26 +0200 Subject: [PATCH 15/29] fix: size experience card with badge --- .../(marketing)/profile/[username]/types.ts | 3 ++ .../profile/_components/LevelBadge.tsx | 39 +++++++++++++++++++ .../profile/_components/Profile.tsx | 18 ++------- .../profile/_components/ProfileHero.tsx | 24 +++++------- src/v2/components/Account/Account.tsx | 27 ++++--------- .../components/AccountModal/AccountModal.tsx | 31 +++++++-------- src/v2/components/ui/Progress.tsx | 2 +- 7 files changed, 77 insertions(+), 67 deletions(-) create mode 100644 src/app/(marketing)/profile/[username]/types.ts create mode 100644 src/app/(marketing)/profile/_components/LevelBadge.tsx diff --git a/src/app/(marketing)/profile/[username]/types.ts b/src/app/(marketing)/profile/[username]/types.ts new file mode 100644 index 0000000000..f587d90b1f --- /dev/null +++ b/src/app/(marketing)/profile/[username]/types.ts @@ -0,0 +1,3 @@ +import { RANKS } from "./constants" + +export type Rank = (typeof RANKS)[number] diff --git a/src/app/(marketing)/profile/_components/LevelBadge.tsx b/src/app/(marketing)/profile/_components/LevelBadge.tsx new file mode 100644 index 0000000000..d9e5e325db --- /dev/null +++ b/src/app/(marketing)/profile/_components/LevelBadge.tsx @@ -0,0 +1,39 @@ +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: "text-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 + +export const LevelBadge = ({ + rank, + levelIndex, + size, + className, +}: LevelBadgeProps) => { + return ( +
+ + {levelIndex} +
+ ) +} diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 21c1d42b1e..664ae0a1ed 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -1,5 +1,4 @@ "use client" -import { Polygon } from "@/components/Polygon" import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager" import { Card } from "@/components/ui/Card" import { cn } from "@/lib/utils" @@ -15,6 +14,7 @@ import { useExperienceProgression } from "../_hooks/useExperienceProgression" import { useProfile } from "../_hooks/useProfile" import { useReferredUsers } from "../_hooks/useReferredUsers" import { ActivityChart } from "./ActivityChart" +import { LevelBadge } from "./LevelBadge" import { ProfileMainSkeleton } from "./ProfileSkeleton" import { RecentActivity } from "./RecentActivity/RecentActivity" import RecentActivityFallback from "./RecentActivity/RecentActivityFallback" @@ -35,15 +35,7 @@ export const Profile = () => { Experience
- - {/**/} +

{xp.rank.title}

@@ -64,11 +56,7 @@ export const Profile = () => { }} /> - - {/*

- This is a description that perfectly matches the 80 character - description limit. -

*/} + {/**/}
diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx index 4ece33af12..ac2f012432 100644 --- a/src/app/(marketing)/profile/_components/ProfileHero.tsx +++ b/src/app/(marketing)/profile/_components/ProfileHero.tsx @@ -3,7 +3,6 @@ import { CheckMark } from "@/components/CheckMark" import { CircularProgressBar } from "@/components/CircularProgressBar" import { LayoutContainer } from "@/components/Layout" -import { Polygon } from "@/components/Polygon" import { ProfileAvatar } from "@/components/ProfileAvatar" import { Avatar } from "@/components/ui/Avatar" import { Button } from "@/components/ui/Button" @@ -13,15 +12,16 @@ import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard" import { useExperienceProgression } from "../_hooks/useExperienceProgression" import { useProfile } from "../_hooks/useProfile" import { EditProfile } from "./EditProfile/EditProfile" +import { LevelBadge } from "./LevelBadge" import { ProfileHeroSkeleton } from "./ProfileSkeleton" import { ProfileSocialCounters } from "./ProfileSocialCounters" export const ProfileHero = () => { const { data: profile } = useProfile() - const progression = useExperienceProgression() + const xp = useExperienceProgression() - if (!profile || !progression) return - const { rank, levelIndex, progress } = progression + if (!profile || !xp) return + const { rank, levelIndex, progress } = xp return ( @@ -55,16 +55,12 @@ export const ProfileHero = () => { profileImageUrl={profile.profileImageUrl} /> -
- - - {levelIndex} - -
+

{profile.name || profile.username} diff --git a/src/v2/components/Account/Account.tsx b/src/v2/components/Account/Account.tsx index 172e072fa5..84ed66eb20 100644 --- a/src/v2/components/Account/Account.tsx +++ b/src/v2/components/Account/Account.tsx @@ -1,9 +1,9 @@ "use client" import { CircularProgressBar } from "@/components/CircularProgressBar" -import { Polygon } from "@/components/Polygon" import { useDisclosure } from "@/hooks/useDisclosure" import { cn } from "@/lib/utils" +import { LevelBadge } from "@app/(marketing)/profile/_components/LevelBadge" import { useExperienceProgression } from "@app/(marketing)/profile/_hooks/useExperienceProgression" import { Bell } from "@phosphor-icons/react" import { SignIn } from "@phosphor-icons/react/dist/ssr" @@ -33,7 +33,7 @@ export const Account = () => { const { addresses, guildProfile, isLoading } = useUser() const linkedAddressesCount = (addresses?.length ?? 1) - 1 const { captureEvent } = usePostHogContext() - const progression = useExperienceProgression(true) + const xp = useExperienceProgression(true) if (isLoading || isWeb3Connected === null) { return ( @@ -86,11 +86,11 @@ export const Account = () => { {guildProfile ? (
- {progression && ( + {xp && ( )} @@ -105,23 +105,12 @@ export const Account = () => { {guildProfile.name || guildProfile.username}
- {progression - ? `${progression.experienceCount} / ${Math.ceil(progression.level)} XP` + {xp + ? `${xp.experienceCount} / ${Math.ceil(xp.level)} XP` : `@${guildProfile.username}`}
- {progression && ( -
- - - {progression.levelIndex} - -
- )} + {xp && }

) : (
diff --git a/src/v2/components/Account/components/AccountModal/AccountModal.tsx b/src/v2/components/Account/components/AccountModal/AccountModal.tsx index 95b82de599..f95b41e1a6 100644 --- a/src/v2/components/Account/components/AccountModal/AccountModal.tsx +++ b/src/v2/components/Account/components/AccountModal/AccountModal.tsx @@ -2,7 +2,6 @@ import { CheckMark } from "@/components/CheckMark" import { CircularProgressBar } from "@/components/CircularProgressBar" import { CopyableAddress } from "@/components/CopyableAddress" import { GuildAvatar } from "@/components/GuildAvatar" -import { Polygon } from "@/components/Polygon" import { ProfileAvatar } from "@/components/ProfileAvatar" import { accountModalAtom } from "@/components/Providers/atoms" import useConnectorNameAndIcon from "@/components/Web3ConnectionManager/hooks/useConnectorNameAndIcon" @@ -29,6 +28,7 @@ import { import { Separator } from "@/components/ui/Separator" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip" import { useUserPublic } from "@/hooks/useUserPublic" +import { LevelBadge } from "@app/(marketing)/profile/_components/LevelBadge" import { useExperienceProgression } from "@app/(marketing)/profile/_hooks/useExperienceProgression" import { ArrowRight, DotsThreeVertical } from "@phosphor-icons/react" import { SignOut } from "@phosphor-icons/react/dist/ssr" @@ -52,7 +52,7 @@ const AccountModal = () => { const { address: evmAddress, chainId } = useAccount() const domain = useResolveAddress(evmAddress) - const progression = useExperienceProgression(true) + const xp = useExperienceProgression(true) const handleLogout = () => { const keysToRemove = Object.keys({ ...window.localStorage }).filter((key) => @@ -64,7 +64,7 @@ const AccountModal = () => { }) deleteKeyPairFromIdb(id) - ?.catch(() => { }) + ?.catch(() => {}) .finally(() => { setIsOpen(false) disconnect() @@ -87,11 +87,11 @@ const AccountModal = () => { <> {guildProfile ? (
- {progression && ( + {xp && (
{ profileImageUrl={guildProfile.profileImageUrl} /> -
- - - {progression.levelIndex} - -
+
)}
@@ -123,8 +118,8 @@ const AccountModal = () => {

- {progression - ? `${progression.experienceCount} / ${Math.ceil(progression.level)} XP` + {xp + ? `${xp.experienceCount} / ${Math.ceil(xp.level)} XP` : `@${guildProfile.username}`}
diff --git a/src/v2/components/ui/Progress.tsx b/src/v2/components/ui/Progress.tsx index da5f1c8694..7ba310dd7d 100644 --- a/src/v2/components/ui/Progress.tsx +++ b/src/v2/components/ui/Progress.tsx @@ -19,7 +19,7 @@ const Progress = React.forwardRef< > )) From fdc5f6c5049524aa38b376c8b5c44515e164e9d6 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 14:42:55 +0200 Subject: [PATCH 16/29] refactor: resize RewardBadge --- src/app/(marketing)/profile/_components/Profile.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 664ae0a1ed..ca009f1ba9 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -35,8 +35,13 @@ export const Profile = () => { Experience
- -
+ +

{xp.rank.title}

From b745f7b1cfcaa89ac9825eac484b92f15b0376d0 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 15:14:52 +0200 Subject: [PATCH 17/29] refactor: add progress ui component --- .../profile/_components/Profile.tsx | 19 ++++-------- src/v2/components/ui/Progress.tsx | 29 +++++++++++++------ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index ca009f1ba9..3d72b5daea 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -1,9 +1,9 @@ "use client" import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager" import { Card } from "@/components/ui/Card" +import { ProgressIndicator, ProgressRoot } from "@/components/ui/Progress" import { cn } from "@/lib/utils" import { Info } from "@phosphor-icons/react" -import { Progress, ProgressIndicator } from "@radix-ui/react-progress" import ParentSize from "@visx/responsive/lib/components/ParentSize" import { PropsWithChildren } from "react" import { ContributionCard } from "../_components/ContributionCard" @@ -48,20 +48,13 @@ export const Profile = () => { {`${xp.experienceCount} / ${Math.ceil(xp.level)} XP`}

- + - - {/**/} +
diff --git a/src/v2/components/ui/Progress.tsx b/src/v2/components/ui/Progress.tsx index 7ba310dd7d..1897978e28 100644 --- a/src/v2/components/ui/Progress.tsx +++ b/src/v2/components/ui/Progress.tsx @@ -5,7 +5,7 @@ import * as React from "react" import { cn } from "@/lib/utils" -const Progress = React.forwardRef< +const ProgressRoot = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, value, ...props }, ref) => ( @@ -16,13 +16,24 @@ const Progress = React.forwardRef< className )} {...props} - > - - + /> )) -Progress.displayName = ProgressPrimitive.Root.displayName -export { Progress } +const ProgressIndicator = React.forwardRef< + React.ElementRef< + React.FunctionComponent + >, + ProgressPrimitive.ProgressIndicatorProps & { value: number } +>(({ className, value, style, ...props }, ref) => ( + +)) + +export { ProgressIndicator, ProgressRoot } From 23c4c8c28ca6ef7bad42221355402394c1af87bf Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 17:40:15 +0200 Subject: [PATCH 18/29] fix: assign rank properly on profile --- src/app/(marketing)/profile/[username]/constants.ts | 4 ++-- src/app/(marketing)/profile/_components/Profile.tsx | 1 - .../profile/_hooks/useExperienceProgression.ts | 13 +++++-------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/app/(marketing)/profile/[username]/constants.ts b/src/app/(marketing)/profile/[username]/constants.ts index e188cee1f7..d6b4a3b15c 100644 --- a/src/app/(marketing)/profile/[username]/constants.ts +++ b/src/app/(marketing)/profile/[username]/constants.ts @@ -4,7 +4,7 @@ export const RANKS = [ { color: "#78c93d", title: "novice", polygonCount: 20 }, { color: "#88d525", title: "learner", polygonCount: 20 }, { color: "#f6ca45", title: "knight", polygonCount: 4 }, - { color: "#78c93d", title: "veteran", 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 }, @@ -12,7 +12,7 @@ export const RANKS = [ { color: "#be4681", title: "legend", polygonCount: 6 }, { color: "#000000", title: "mythic", polygonCount: 6 }, { - color: "linear-gradient(to left top, #00cbfa, #b9f2ff)", + color: "#eeeeee", title: "???", requiredXp: 1e19, polygonCount: 6, diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 3d72b5daea..2d0a2a14d5 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -51,7 +51,6 @@ export const Profile = () => { diff --git a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts index c507ccf90e..0f372bcf45 100644 --- a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts +++ b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts @@ -14,19 +14,16 @@ const generateExponentialArray = ( export const calculateXpProgression = ({ experienceCount, }: { experienceCount: number }) => { - experienceCount = Math.min(experienceCount, MAX_XP) - const levels = [...generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03)] - const levelIndex = Math.max( - 0, - levels.findIndex((level) => experienceCount < level) - ) + const levels = generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03) + let levelIndex = levels.findIndex((level) => experienceCount < level) + levelIndex = levelIndex === -1 ? levels.length - 1 : levelIndex const level = levels.at(levelIndex) const levelInRank = Math.floor(MAX_LEVEL / RANKS.length) - const rankIndex = Math.max(0, (levelIndex - 1) % levelInRank) + const rankIndex = Math.max(0, Math.floor((levelIndex - 1) / levelInRank)) const rank = RANKS.at(rankIndex) if (!rank || !level) throw new Error("failed to calculate rank") const nextLevel = levels.at(levelIndex + 1) - const progress = nextLevel ? experienceCount / nextLevel || 0 : 0 + const progress = experienceCount / (nextLevel || experienceCount) return { progress, rank, levelIndex, experienceCount, level } } From 82290e27177b6fe3bd6a4e845304e3304887f595 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 18:09:30 +0200 Subject: [PATCH 19/29] chore: floor levels for simplicity --- src/app/(marketing)/profile/_components/Profile.tsx | 2 +- .../(marketing)/profile/_hooks/useExperienceProgression.ts | 4 +++- src/v2/components/Account/Account.tsx | 2 +- .../Account/components/AccountModal/AccountModal.tsx | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 2d0a2a14d5..d50224b04c 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -45,7 +45,7 @@ export const Profile = () => {

{xp.rank.title}

- {`${xp.experienceCount} / ${Math.ceil(xp.level)} XP`} + {`${xp.experienceCount} / ${xp.level} XP`}

diff --git a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts index 0f372bcf45..20aa51f427 100644 --- a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts +++ b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts @@ -14,7 +14,9 @@ const generateExponentialArray = ( export const calculateXpProgression = ({ experienceCount, }: { experienceCount: number }) => { - const levels = generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03) + const levels = generateExponentialArray(MAX_LEVEL, MAX_XP, 1.03).map((num) => + Math.floor(num) + ) let levelIndex = levels.findIndex((level) => experienceCount < level) levelIndex = levelIndex === -1 ? levels.length - 1 : levelIndex const level = levels.at(levelIndex) diff --git a/src/v2/components/Account/Account.tsx b/src/v2/components/Account/Account.tsx index 84ed66eb20..8f1a4aff18 100644 --- a/src/v2/components/Account/Account.tsx +++ b/src/v2/components/Account/Account.tsx @@ -106,7 +106,7 @@ export const Account = () => {
{xp - ? `${xp.experienceCount} / ${Math.ceil(xp.level)} XP` + ? `${xp.experienceCount} / ${xp.level} XP` : `@${guildProfile.username}`}
diff --git a/src/v2/components/Account/components/AccountModal/AccountModal.tsx b/src/v2/components/Account/components/AccountModal/AccountModal.tsx index f95b41e1a6..ebca4ff998 100644 --- a/src/v2/components/Account/components/AccountModal/AccountModal.tsx +++ b/src/v2/components/Account/components/AccountModal/AccountModal.tsx @@ -119,7 +119,7 @@ const AccountModal = () => {

{xp - ? `${xp.experienceCount} / ${Math.ceil(xp.level)} XP` + ? `${xp.experienceCount} / ${xp.level} XP` : `@${guildProfile.username}`}
From 00d3ffda753c6c993b91b284e165b14604d1d44a Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 18:29:52 +0200 Subject: [PATCH 20/29] chore: revalidate experiences every 20 min --- src/app/(marketing)/profile/[username]/page.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/(marketing)/profile/[username]/page.tsx b/src/app/(marketing)/profile/[username]/page.tsx index e6d4e67207..fe8768b1ae 100644 --- a/src/app/(marketing)/profile/[username]/page.tsx +++ b/src/app/(marketing)/profile/[username]/page.tsx @@ -159,12 +159,20 @@ const fetchPublicProfileData = async ({ const guildsZipped = guildRequests.map(({ pathname }, i) => [pathname, guilds[i]]) const rolesZipped = roleRequests.map(({ pathname }, i) => [pathname, roles[i]]) const experiencesRequest = new URL(`/v2/profiles/${username}/experiences`, api) - const experiences = await ssrFetcher(experiencesRequest) + const experiences = await ssrFetcher(experiencesRequest, { + next: { + revalidate: 1200, + }, + }) const experienceCountRequest = new URL( `/v2/profiles/${username}/experiences?count=true`, api ) - const experienceCount = await ssrFetcher(experienceCountRequest) + const experienceCount = await ssrFetcher(experienceCountRequest, { + next: { + revalidate: 1200, + }, + }) return { profile, From e4a9a7dd32e5d101842b583d8e4bb708b259dc4a Mon Sep 17 00:00:00 2001 From: valid Date: Wed, 11 Sep 2024 20:26:30 +0200 Subject: [PATCH 21/29] =?UTF-8?q?fix:=20Experience=20&=C2=A0Top=20contribu?= =?UTF-8?q?tions=20title=20colors=20in=20light=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(marketing)/profile/_components/Profile.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index d50224b04c..8b716ffb14 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -31,8 +31,10 @@ export const Profile = () => { return ( <> -
- Experience +
+
+ Experience +
{
-
+
Top contributions From 8cd810d5025aacfba36d5f3eaaaeb60e57dbe911 Mon Sep 17 00:00:00 2001 From: valid Date: Wed, 11 Sep 2024 20:29:02 +0200 Subject: [PATCH 22/29] UI: whitespace refinement --- src/app/(marketing)/profile/_components/Profile.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 8b716ffb14..c52b6f9395 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -31,7 +31,7 @@ export const Profile = () => { return ( <> -
+
Experience
@@ -92,7 +92,7 @@ export const Profile = () => { ))}
-
+
Recent activity {isWeb3Connected ? : }
From b74d0dec5b1439b281811e1197a3f968206fac95 Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 20:50:09 +0200 Subject: [PATCH 23/29] chore: add fallback to activity chart --- .../profile/_components/ActivityChart.tsx | 2 ++ .../(marketing)/profile/_hooks/useExperiences.tsx | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index 86fc92f6d4..46744223c6 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -23,6 +23,8 @@ let tooltipTimeout: number export const ActivityChart = ({ width, height }: BarsProps) => { const { data: rawData } = useExperiences({ count: false }) if (!rawData) return + if (rawData.length === 0) + return

There are no activity this month

const groupedData = new Map() for (const xp of rawData) { const createdAt = new Date(xp.createdAt) diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.tsx b/src/app/(marketing)/profile/_hooks/useExperiences.tsx index eb7677791f..8c1851bf06 100644 --- a/src/app/(marketing)/profile/_hooks/useExperiences.tsx +++ b/src/app/(marketing)/profile/_hooks/useExperiences.tsx @@ -5,13 +5,19 @@ import { useProfile } from "./useProfile" export const useExperiences = ({ count, showOwnProfile, -}: { count: T; showOwnProfile?: boolean }) => { + startTime, +}: { count: T; showOwnProfile?: boolean; startTime?: number }) => { const { data: profile } = useProfile(showOwnProfile) + const oneMonthBeforeApprox = startTime && startTime - 86400 * 30 + const params = new URLSearchParams( + [ + ["count", count && count.toString()], + ["startTime", oneMonthBeforeApprox && oneMonthBeforeApprox.toString()], + ].filter(([_, value]) => value) as string[][] + ) return useSWRImmutable( profile - ? [`/v2/profiles/${profile.username}/experiences`, count && `count=${count}`] - .filter(Boolean) - .join("?") + ? `/v2/profiles/${profile.username}/experiences?${params.toString()}` : null ) } From d5adb06a687f34d8cad3c683d3e78b854adbb3a7 Mon Sep 17 00:00:00 2001 From: valid Date: Wed, 11 Sep 2024 20:50:37 +0200 Subject: [PATCH 24/29] UI(experiences): impros/fixes --- .../profile/_components/ActivityChart.tsx | 18 ++++++++++-------- .../profile/_components/LevelBadge.tsx | 4 +++- .../profile/_components/Profile.tsx | 18 +++++++++++------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index 86fc92f6d4..5c82ffc034 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -6,6 +6,7 @@ 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"] @@ -22,6 +23,7 @@ export type BarsProps = { let tooltipTimeout: number export const ActivityChart = ({ width, height }: BarsProps) => { const { data: rawData } = useExperiences({ count: false }) + const xp = useExperienceProgression() if (!rawData) return const groupedData = new Map() for (const xp of rawData) { @@ -86,21 +88,21 @@ export const ActivityChart = ({ width, height }: BarsProps) => {
- {data.map((xp) => { - const x = getX(xp) + {data.map((currentXp) => { + const x = getX(currentXp) const barWidth = xScale.bandwidth() - const barHeight = yMax - (yScale(getY(xp)) ?? 0) + const barHeight = yMax - (yScale(getY(currentXp)) ?? 0) const barX = xScale(x) const barY = yMax - barHeight return ( { tooltipTimeout = window.setTimeout(() => { hideTooltip() @@ -111,7 +113,7 @@ export const ActivityChart = ({ width, height }: BarsProps) => { const eventSvgCoords = localPoint(event) const left = (barX || 0) + barWidth / 2 showTooltip({ - tooltipData: xp, + tooltipData: currentXp, tooltipTop: eventSvgCoords?.y, tooltipLeft: left, }) @@ -127,10 +129,10 @@ export const ActivityChart = ({ width, height }: BarsProps) => { left={tooltipLeft} unstyled applyPositionStyle - className="rounded border bg-card px-2 py-1" + className="rounded border bg-card px-2 py-1 text-sm" > +{tooltipData.amount} XP -
+
{new Date(tooltipData.createdAt).toLocaleDateString()}
diff --git a/src/app/(marketing)/profile/_components/LevelBadge.tsx b/src/app/(marketing)/profile/_components/LevelBadge.tsx index d9e5e325db..6dd5c734c9 100644 --- a/src/app/(marketing)/profile/_components/LevelBadge.tsx +++ b/src/app/(marketing)/profile/_components/LevelBadge.tsx @@ -33,7 +33,9 @@ export const LevelBadge = ({ color={rank.color} className="brightness-75" /> - {levelIndex} + + {levelIndex} +
) } diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index c52b6f9395..52a2c5f664 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -36,15 +36,15 @@ export const Profile = () => { Experience
- + -
-
+
+

{xp.rank.title}

{`${xp.experienceCount} / ${xp.level} XP`} @@ -58,13 +58,17 @@ export const Profile = () => {

- +

Engagement this month

- - {({ width }) => } - +
+ + {({ width, height }) => ( + + )} + +
From 74ef133c283b3345006a988c3ffe3e31eb17720e Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 11 Sep 2024 21:20:33 +0200 Subject: [PATCH 25/29] fix: reorder useEffect calls, adjust startTime --- .../profile/_components/ActivityChart.tsx | 51 +++++++++++-------- .../profile/_hooks/useExperiences.tsx | 3 +- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index 58cdd9e9e0..168aa77320 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -21,21 +21,35 @@ export type BarsProps = { } let tooltipTimeout: number -export const ActivityChart = ({ width, height }: BarsProps) => { - const { data: rawData } = useExperiences({ count: false }) +export const ActivityChartChildren = ({ + width, + height, + rawData, +}: BarsProps & { + rawData: Schemas["Experience"][] +}) => { + const { + tooltipOpen, + tooltipLeft, + tooltipTop, + tooltipData, + hideTooltip, + showTooltip, + } = useTooltip() + + const { containerRef, TooltipInPortal } = useTooltipInPortal({ + scroll: true, + }) const xp = useExperienceProgression() - if (!rawData) return - if (rawData.length === 0) - return

There are no activity this month

const groupedData = new Map() - for (const xp of rawData) { - const createdAt = new Date(xp.createdAt) + 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) ?? []), xp]) + groupedData.set(commonDay, [...(groupedData.get(commonDay) ?? []), rawXp]) } const data = [...groupedData.entries()] .reduce((acc, [_, xpGroup]) => { @@ -73,19 +87,6 @@ export const ActivityChart = ({ width, height }: BarsProps) => { [yMax] ) - const { - tooltipOpen, - tooltipLeft, - tooltipTop, - tooltipData, - hideTooltip, - showTooltip, - } = useTooltip() - - const { containerRef, TooltipInPortal } = useTooltipInPortal({ - scroll: true, - }) - return width < 10 ? null : (
@@ -142,3 +143,11 @@ export const ActivityChart = ({ width, height }: BarsProps) => {
) } + +export const ActivityChart = ({ width, height }: BarsProps) => { + const { data: rawData } = useExperiences({ count: false }) + if (!rawData) return + if (rawData.length === 0) + return

There are no activity this month

+ return +} diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.tsx b/src/app/(marketing)/profile/_hooks/useExperiences.tsx index 8c1851bf06..2a64013e7e 100644 --- a/src/app/(marketing)/profile/_hooks/useExperiences.tsx +++ b/src/app/(marketing)/profile/_hooks/useExperiences.tsx @@ -8,11 +8,10 @@ export const useExperiences = ({ startTime, }: { count: T; showOwnProfile?: boolean; startTime?: number }) => { const { data: profile } = useProfile(showOwnProfile) - const oneMonthBeforeApprox = startTime && startTime - 86400 * 30 const params = new URLSearchParams( [ ["count", count && count.toString()], - ["startTime", oneMonthBeforeApprox && oneMonthBeforeApprox.toString()], + ["startTime", startTime && startTime.toString()], ].filter(([_, value]) => value) as string[][] ) return useSWRImmutable( From 8dc278d157f244787981d20ecbd01aa77029e488 Mon Sep 17 00:00:00 2001 From: valid Date: Thu, 12 Sep 2024 02:04:08 +0200 Subject: [PATCH 26/29] =?UTF-8?q?UI:=20account=20modal=20&=C2=A0progress?= =?UTF-8?q?=20refinements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 1 + src/v2/components/Account/Account.tsx | 4 +--- .../Account/components/AccountModal/AccountModal.tsx | 6 +++--- src/v2/components/CircularProgressBar.tsx | 4 +++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 49ea1b38f0..10896b5ba3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -92,6 +92,7 @@ /* we plan to add the whole alpha color palettes */ --whiteAlpha-800: rgba(255, 255, 255, 0.80); --blackAlpha-800: rgba(0, 0, 0, 0.80); + --blackAlpha-300: rgba(0, 0, 0, 0.30); --discord: 233 78% 63%; --discord-hover: 243 75% 59%; diff --git a/src/v2/components/Account/Account.tsx b/src/v2/components/Account/Account.tsx index 8f1a4aff18..714b4f61db 100644 --- a/src/v2/components/Account/Account.tsx +++ b/src/v2/components/Account/Account.tsx @@ -3,7 +3,6 @@ import { CircularProgressBar } from "@/components/CircularProgressBar" import { useDisclosure } from "@/hooks/useDisclosure" import { cn } from "@/lib/utils" -import { LevelBadge } from "@app/(marketing)/profile/_components/LevelBadge" import { useExperienceProgression } from "@app/(marketing)/profile/_hooks/useExperienceProgression" import { Bell } from "@phosphor-icons/react" import { SignIn } from "@phosphor-icons/react/dist/ssr" @@ -85,7 +84,7 @@ export const Account = () => { > {guildProfile ? (
-
+
{xp && ( { : `@${guildProfile.username}`}
- {xp && }
) : (
diff --git a/src/v2/components/Account/components/AccountModal/AccountModal.tsx b/src/v2/components/Account/components/AccountModal/AccountModal.tsx index ebca4ff998..b98c62c874 100644 --- a/src/v2/components/Account/components/AccountModal/AccountModal.tsx +++ b/src/v2/components/Account/components/AccountModal/AccountModal.tsx @@ -96,7 +96,7 @@ const AccountModal = () => { /> {
)} -
+

{guildProfile.name || guildProfile.username} diff --git a/src/v2/components/CircularProgressBar.tsx b/src/v2/components/CircularProgressBar.tsx index eeffaa2d0d..ca0bda90b0 100644 --- a/src/v2/components/CircularProgressBar.tsx +++ b/src/v2/components/CircularProgressBar.tsx @@ -29,7 +29,9 @@ export const CircularProgressBar: React.FC = ({ className={className} > Date: Thu, 12 Sep 2024 02:04:42 +0200 Subject: [PATCH 27/29] cleanup(LevelBadge): duplicated text style --- src/app/(marketing)/profile/_components/LevelBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(marketing)/profile/_components/LevelBadge.tsx b/src/app/(marketing)/profile/_components/LevelBadge.tsx index 6dd5c734c9..05f733369f 100644 --- a/src/app/(marketing)/profile/_components/LevelBadge.tsx +++ b/src/app/(marketing)/profile/_components/LevelBadge.tsx @@ -5,7 +5,7 @@ import { Rank } from "../[username]/types" const levelBadgeVariants = cva("flex items-center justify-center", { variants: { size: { - md: "text-md size-7 text-xs", + md: "size-7 text-xs", lg: "text-lg md:text-xl size-10 md:size-12", }, }, From 8c76f50a1e86ca76a819dfcce7d87cfb8429fa62 Mon Sep 17 00:00:00 2001 From: valid Date: Thu, 12 Sep 2024 02:09:13 +0200 Subject: [PATCH 28/29] copy(ActivityChart): fallback fix --- src/app/(marketing)/profile/_components/ActivityChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index 168aa77320..2c87eeb6d5 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -148,6 +148,6 @@ export const ActivityChart = ({ width, height }: BarsProps) => { const { data: rawData } = useExperiences({ count: false }) if (!rawData) return if (rawData.length === 0) - return

There are no activity this month

+ return

There's no activity this month

return } From caba6ee800202d7754c0aec3b04eb67458c2fdfa Mon Sep 17 00:00:00 2001 From: valid Date: Thu, 12 Sep 2024 02:16:37 +0200 Subject: [PATCH 29/29] ActivityChart batching / fallback rendering impro --- .../profile/_components/ActivityChart.tsx | 21 +++++++++++++++---- .../profile/_components/Profile.tsx | 9 +------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx index 2c87eeb6d5..b1c2a7bb46 100644 --- a/src/app/(marketing)/profile/_components/ActivityChart.tsx +++ b/src/app/(marketing)/profile/_components/ActivityChart.tsx @@ -2,6 +2,7 @@ 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" @@ -21,7 +22,8 @@ export type BarsProps = { } let tooltipTimeout: number -export const ActivityChartChildren = ({ + +const ActivityChartChildren = ({ width, height, rawData, @@ -144,10 +146,21 @@ export const ActivityChartChildren = ({ ) } -export const ActivityChart = ({ width, height }: BarsProps) => { +export const ActivityChart = () => { const { data: rawData } = useExperiences({ count: false }) - if (!rawData) return + + if (!rawData) return + if (rawData.length === 0) return

There's no activity this month

- return + + return ( +
+ + {({ width, height }) => ( + + )} + +
+ ) } diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx index 52a2c5f664..69f57b5b48 100644 --- a/src/app/(marketing)/profile/_components/Profile.tsx +++ b/src/app/(marketing)/profile/_components/Profile.tsx @@ -4,7 +4,6 @@ import { Card } from "@/components/ui/Card" import { ProgressIndicator, ProgressRoot } from "@/components/ui/Progress" import { cn } from "@/lib/utils" import { Info } from "@phosphor-icons/react" -import ParentSize from "@visx/responsive/lib/components/ParentSize" import { PropsWithChildren } from "react" import { ContributionCard } from "../_components/ContributionCard" import { EditContributions } from "../_components/EditContributions" @@ -62,13 +61,7 @@ export const Profile = () => {

Engagement this month

-
- - {({ width, height }) => ( - - )} - -
+