Skip to content

Commit 8b4c207

Browse files
authored
Merge pull request webdevcody#704 from QuickerMaths/699-animation-for-achievement-progress
webdevcody#699 Animation for achievement progress
2 parents f8d3354 + cc74359 commit 8b4c207

File tree

5 files changed

+122
-180
lines changed

5 files changed

+122
-180
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,76 @@
1-
"use client"
1+
"use client";
22

3-
import React, { useEffect, useRef, useState } from "react";
3+
import React from "react";
44
import { ScrollArea } from "@/components/ui/scroll-area";
55
import { Avatar, AvatarImage } from "@/components/ui/avatar";
66
import { BanIcon } from "lucide-react";
77
import { Achievement as PrsimaAchievement } from "@prisma/client";
88
import { Achievement } from "@/types/achievement";
9-
import "../timeline.css";
109

1110
interface Props {
12-
userAchievements: PrsimaAchievement[];
13-
allAchievements: Achievement[];
11+
userAchievements: PrsimaAchievement[];
12+
allAchievements: Achievement[];
1413
}
1514

1615
export default function AchievementProgress({
17-
userAchievements,
18-
allAchievements,
16+
userAchievements,
17+
allAchievements,
1918
}: Props) {
20-
const updatedAllAch = [
21-
...allAchievements,
22-
{
23-
type: "MORE_TO_COME",
24-
image: "/static/first.png",
25-
name: "More to come!",
26-
},
27-
{
28-
type: "MORE_TO_COME_2",
29-
image: "/static/first.png",
30-
name: "More to come!",
31-
},
32-
];
19+
const updatedAllAch = [
20+
...allAchievements,
21+
{
22+
type: "MORE_TO_COME",
23+
image: "/static/first.png",
24+
name: "More to come!",
25+
},
26+
{
27+
type: "MORE_TO_COME_2",
28+
image: "/static/first.png",
29+
name: "More to come!",
30+
},
31+
];
3332

34-
const timelineRef = useRef<HTMLDivElement>(null);
35-
const [animatedItems, setAnimatedItems] = useState<string[]>([]);
33+
return (
34+
<ScrollArea className="rounded-md h-[300px] border-2 border-primary p-4 mt-10">
35+
<h3 className="text-primary font-special">Achievement Progress</h3>
36+
<div className="relative my-[20px] flex flex-col justify-center items-center">
37+
{updatedAllAch.map((achievement) => {
38+
const isUnlocked = userAchievements.some(
39+
(userAchievement) =>
40+
userAchievement.achievementType === achievement.type
41+
);
3642

37-
useEffect(() => {
38-
const observer = new IntersectionObserver(
39-
(entries) => {
40-
const inViewItems = entries
41-
.filter((entry) => entry.isIntersecting)
42-
.map((entry) => entry.target.id);
43+
const itemId = `achievement-${achievement.type}`;
4344

44-
setAnimatedItems((prevItems) => [...prevItems, ...inViewItems]);
45-
},
46-
{
47-
root: null,
48-
rootMargin: "0px",
49-
threshold: 0.5, // Adjust this threshold as per your needs
50-
}
51-
);
52-
53-
const timelineItems = timelineRef.current?.querySelectorAll(".timeline-item");
54-
if (timelineItems) {
55-
timelineItems.forEach((item) => observer.observe(item));
56-
}
57-
58-
return () => {
59-
if (timelineItems) {
60-
timelineItems.forEach((item) => observer.unobserve(item));
61-
}
62-
};
63-
}, []);
64-
65-
66-
return (
67-
<ScrollArea className="rounded-md h-[300px] border-2 border-primary p-4">
68-
<h3 className="text-primary font-special">Achievement Progress</h3>
69-
<div className="timeline" ref={timelineRef}>
70-
{updatedAllAch.map((achievement) => {
71-
const isUnlocked = userAchievements.some(
72-
(userAchievement) => userAchievement.achievementType === achievement.type
73-
);
74-
75-
const itemId = `achievement-${achievement.type}`;
76-
77-
return (
78-
<div
79-
key={achievement.type}
80-
id={itemId}
81-
className={`timeline-item
82-
${isUnlocked ? "unlocked" : "locked"
83-
} shadow-lg shadow-accent opacity-100
84-
${animatedItems.includes(itemId) ? "animate-fade" : ""} hover:animate-bounce hover:cursor-pointer`}
85-
>
86-
<div className="timeline-content">
87-
{isUnlocked ? (
88-
<Avatar>
89-
<AvatarImage src={achievement.image} />
90-
</Avatar>
91-
) : (
92-
<BanIcon className="text-primary" />
93-
)}
94-
<p>{achievement.name}</p>
95-
</div>
96-
</div>
97-
);
98-
})}
45+
return (
46+
<div
47+
key={achievement.type}
48+
id={itemId}
49+
style={
50+
isUnlocked
51+
? {
52+
backgroundImage: "var(--achievement-progress-gradient)",
53+
}
54+
: {}
55+
}
56+
className={`relative h-[62px] p-[3px] mb-[20px] rounded-sm cursor-pointer w-1/2 lg:w-1/4 ${
57+
isUnlocked ? "animate-gradient bg-achievement" : "bg-red-500"
58+
} shadow-lg shadow-accent even:ml-[50%] odd:mr-[50%]`}
59+
>
60+
<div className="flex items-center justify-center text-center w-full h-full bg-accent rounded-md">
61+
{isUnlocked ? (
62+
<Avatar>
63+
<AvatarImage src={achievement.image} />
64+
</Avatar>
65+
) : (
66+
<BanIcon className="text-primary" />
67+
)}
68+
<p className="m-0 pl-[3px]">{achievement.name}</p>
69+
</div>
9970
</div>
100-
</ScrollArea>
101-
);
71+
);
72+
})}
73+
</div>
74+
</ScrollArea>
75+
);
10276
}

packages/app/src/app/dashboard/timeline.css

-67
This file was deleted.

packages/app/src/components/achievement.tsx

+33-26
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,39 @@ export const AchievementCard = ({
1616
};
1717
}) => {
1818
return (
19-
<li className="flex items-center justify-between bg-accent p-3 sm:p-4 md:px-6 md:py-3 rounded-sm">
20-
<div className="flex-1 flex flex-col gap-2">
21-
<h3 className="text-xl font-bold">{achievement.name}</h3>
22-
{achievement.description && (
23-
<p className="text-sm text-muted-foreground">
24-
{achievement.description}
19+
<li
20+
className="p-[5px] rounded-sm cursor-pointer animate-gradient bg-achievement"
21+
style={{
22+
backgroundImage: "var(--achievement-progress-gradient)",
23+
}}
24+
>
25+
<div className="w-full h-full flex items-center justify-between bg-accent p-3 sm:p-4 md:px-6 md:py-3">
26+
<div className="flex-1 flex flex-col gap-2">
27+
<h3 className="text-xl font-bold">{achievement.name}</h3>
28+
{achievement.description && (
29+
<p className="text-sm text-muted-foreground">
30+
{achievement.description}
31+
</p>
32+
)}
33+
<p className="flex items-center text-xs text-accent-foreground">
34+
<Icons.trophy className="w-4 h-4 mr-2" />
35+
<span>
36+
Unlocked:{" "}
37+
<time dateTime={achievement.unlockedAt.toISOString()}>
38+
{achievement.unlockedAt.toLocaleDateString()}
39+
</time>
40+
</span>
2541
</p>
26-
)}
27-
<p className="flex items-center text-xs text-accent-foreground">
28-
<Icons.trophy className="w-4 h-4 mr-2" />
29-
<span>
30-
Unlocked:{" "}
31-
<time dateTime={achievement.unlockedAt.toISOString()}>
32-
{achievement.unlockedAt.toLocaleDateString()}
33-
</time>
34-
</span>
35-
</p>
36-
</div>
37-
<div className="w-13 h-13 md:w-20 md:h-20">
38-
<Image
39-
src={achievement.image}
40-
width={50}
41-
height={50}
42-
alt={`Achievement: ${achievement.name}`}
43-
className="w-full max-w-full object-cover"
44-
/>
42+
</div>
43+
<div className="w-13 h-13 md:w-20 md:h-20">
44+
<Image
45+
src={achievement.image}
46+
width={50}
47+
height={50}
48+
alt={`Achievement: ${achievement.name}`}
49+
className="w-full max-w-full object-cover"
50+
/>
51+
</div>
4552
</div>
4653
</li>
4754
);
@@ -78,7 +85,7 @@ export function unlockAchievement({
7885
}) {
7986
toast({
8087
description: (
81-
<div className="flex flex-col gap-4">
88+
<div className="flex justify-center items-center flex-col gap-4">
8289
<div className="flex items-center gap-2 text-lg md:text-xl">
8390
<UnlockIcon className="text-accent-foreground" />{" "}
8491
<span className="text-muted-foreground">Unlocked:</span>{" "}

packages/app/src/styles/globals.css

+9
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@
4242
--radius: 0.5rem;
4343

4444
--monochrome-color: 0 0% 0%;
45+
46+
--achievement-progress-gradient: linear-gradient(
47+
115deg,
48+
rgb(79, 207, 112),
49+
rgb(250, 214, 72),
50+
rgb(167, 103, 229),
51+
rgb(18, 188, 254),
52+
rgb(68, 206, 123)
53+
);
4554
}
4655

4756
.dark {

packages/app/tailwind.config.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = {
1616
},
1717
},
1818
extend: {
19+
backgroundSize: { achievement: "50% 100%" },
1920
screens: {
2021
xs: "560px",
2122
},
@@ -115,9 +116,26 @@ module.exports = {
115116
"80%": { transform: "scale(0)" },
116117
"100%": { transform: "scale(0)" },
117118
},
119+
gradient: {
120+
"0%": {
121+
backgroundPosition: "0% 0%",
122+
},
123+
"25%": {
124+
backgroundPosition: "25% 50%",
125+
},
126+
"50%": {
127+
backgroundPosition: "50% 75%",
128+
},
129+
"75%": {
130+
backgroundPosition: "75% 100%",
131+
},
132+
"100%": {
133+
backgroundPosition: "100% 0%",
134+
},
135+
},
118136
"half-rotate": {
119-
"0%": {transform: "rotate(0deg)"},
120-
"100%": {transform: "rotate(180deg)"},
137+
"0%": { transform: "rotate(0deg)" },
138+
"100%": { transform: "rotate(180deg)" },
121139
},
122140
},
123141
animation: {
@@ -130,6 +148,7 @@ module.exports = {
130148
dash: "dash linear infinite",
131149
blink: "blink 1.5s ease infinite",
132150
fade: "fade 1s ease-in-out",
151+
gradient: "gradient 0.75s linear infinite",
133152
},
134153
},
135154
},

0 commit comments

Comments
 (0)