Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions apps/frontend/src/components/GrowingGoal/GrowingGoal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
.goal-container {
aspect-ratio: 7 / 10;
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: 3%;
border: 2% solid #cecece;
background: #f2f2f2;
text-align: center;
justify-content: start;
container-type: inline-size;
gap: 4%;
}

.description-label {
border-top-left-radius: 5%;
border-top-right-radius: 5%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: #fff;
font-family: Helvetica;
font-weight: 700;
white-space: nowrap;
height: 12%;
padding: 2%;
background-color: #3d3e6e;
font-size: 5cqw;
}

.growth-container {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
}

.growth-container-solid-grey {
position: relative;
width: 70%;
aspect-ratio: 1 / 1;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: #cecece;
}

.total-donation-label {
color: #000;
text-align: center;
font-family: Helvetica;
font-size: 6cqw;
font-weight: 400;
}

.sample-donor-container {
width: 100%;
aspect-ratio: 5 / 1;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
align-self: center;
}

.sample-donor-profile {
display: flex;
width: 12%;
border-radius: 50%;
aspect-ratio: 1 / 1;
justify-content: center;
align-items: center;
background: #d9d9d9;
}

.sample-donor-label {
width: 70%;
overflow: hidden;
border-radius: 4px;
padding: 2%;
background: #3d3e6e;
display: flex;
align-items: center;
justify-content: start;
gap: 6%;
padding: 4%;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

padding defined twice in here

}

.sample-donor-amount {
color: #fff;
font-family: Helvetica;
font-size: 4cqw;
overflow: hidden;
}

.growth-container-solid-white {
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
width: 90%;
aspect-ratio: 1 / 1;
border-radius: 50%;
background: #ffffff;
}

.growth-container-solid-grey-inner {
z-index: 11;
display: flex;
justify-content: center;
align-items: center;
width: 90%;
aspect-ratio: 1 / 1;
border-radius: 50%;
background: #55565a;
}

.growth-container-solid-teal {
position: relative;
aspect-ratio: 1 / 1;
width: 90%;
border-radius: 50%;
background: #0c7962;
}

.growth-container-gradient {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
border-radius: 50%;
background: conic-gradient(
from 180deg at 50% 50%,
#c6be3b 0deg,
#863b27 90deg,
#650d77 180deg,
#0c7962 270deg,
#c6be3b 360deg,
transparent 180deg 360deg
);
}

.soil-base {
background-color: #211504;
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 50%;
}

.soil-top {
position: absolute;
background-color: #332308;
width: 90%;
left: 5%;
height: 18%;
border-radius: 50%;
bottom: 20%;
}

.progress-bar-handle {
position: absolute;
width: 5%;
aspect-ratio: 1 / 1;
border-radius: 50%;
background-color: #ffffff;
}
157 changes: 157 additions & 0 deletions apps/frontend/src/components/GrowingGoal/GrowingGoal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { useEffect, useRef, useState } from 'react';
import styles from './GrowingGoal.module.css';
import Plant from './Plant';

export type SampleDonation = {
name: string;
amount: number;
profile?: string; //base64 string
};

export type GrowingGoalProps = {
message: string;
total: number;
goal: number;
sampleDonation?: SampleDonation;
};

export const GrowingGoal = (props: GrowingGoalProps) => {
const { message, total, goal } = props;
const growthContainerRef = useRef<HTMLDivElement>(null);
const [radius, setRadius] = useState(0);
const [endHandle, setEndHandle] = useState({
top: 0,
left: 0,
});
const progress = Math.floor((total / goal) * 360);

// calculate gradient color of growth container handles
const getGradientColor = (degree: number): string => {
const gradientAngle = (degree + 180) % 360;

const stops = [
{ angle: 0, color: { r: 198, g: 190, b: 59 } },
{ angle: 90, color: { r: 134, g: 59, b: 39 } },
{ angle: 180, color: { r: 101, g: 13, b: 119 } },
{ angle: 270, color: { r: 12, g: 121, b: 98 } },
{ angle: 360, color: { r: 198, g: 190, b: 59 } },
];

let startStop = stops[stops.length - 1];
let endStop = stops[0];

for (let i = 0; i < stops.length - 1; i++) {
if (
gradientAngle >= stops[i].angle &&
gradientAngle <= stops[i + 1].angle
) {
startStop = stops[i];
endStop = stops[i + 1];
break;
}
}
const range = endStop.angle - startStop.angle;
const factor = (gradientAngle - startStop.angle) / range;
const r = Math.round(
startStop.color.r + (endStop.color.r - startStop.color.r) * factor,
);
const g = Math.round(
startStop.color.g + (endStop.color.g - startStop.color.g) * factor,
);
const b = Math.round(
startStop.color.b + (endStop.color.b - startStop.color.b) * factor,
);

return `rgb(${r}, ${g}, ${b})`;
};

// calculate growth container end handle position
useEffect(() => {
if (growthContainerRef.current) {
setRadius(growthContainerRef.current.offsetWidth / 2);
const handleSize = radius * 0.05;
const progressRadians = ((90 - progress) * Math.PI) / 180;
const topOffset = (radius - handleSize) * Math.sin(progressRadians);
const leftOffset = (radius - handleSize) * Math.cos(progressRadians);

const top = radius - topOffset - handleSize;
const left = radius + leftOffset - handleSize;

setEndHandle({
top: top,
left: left,
});
}
}, [growthContainerRef, progress, radius]);

const startHandleStyle: React.CSSProperties = {
top: '0%',
left: `47.5%`,
};

const endHandleStyle: React.CSSProperties = {
top: `${endHandle.top}px`,
left: `${endHandle.left}px`,
};

return (
<div className={styles['goal-container']}>
<div className={styles['description-label']}>{message}</div>
<div className={styles['growth-container']}>
<div
ref={growthContainerRef}
className={styles['growth-container-solid-grey']}
>
<div
className={styles['growth-container-gradient']}
style={{
mask: `conic-gradient(black 0deg ${progress}deg, transparent ${progress}deg 360deg)`,
}}
></div>
<Plant />
<div
style={{ ...startHandleStyle, backgroundColor: '#650d77' }}
className={styles['progress-bar-handle']}
></div>
<div
style={{
...endHandleStyle,
backgroundColor: getGradientColor(progress),
}}
className={styles['progress-bar-handle']}
></div>
</div>
</div>
<div className={styles['total-donation-label']}>
<span style={{ fontWeight: '700' }}>${total.toFixed(0)}</span>{' '}
<span style={{ fontSize: '4cqw' }}>raised of</span> ${goal.toFixed(0)}
</div>
<div className={styles['sample-donor-container']}>
{props.sampleDonation && (
<div className={styles['sample-donor-label']}>
{props.sampleDonation.profile ? (
<div
style={{
backgroundImage: `url("${props.sampleDonation.profile}")`,
backgroundSize: 'cover',
}}
className={styles['sample-donor-profile']}
></div>
) : (
<div className={styles['sample-donor-profile']}></div>
)}
<div className={styles['sample-donor-amount']}>
<b>
{props.sampleDonation.name.length > 8
? props.sampleDonation.name.slice(0,8) + '...'
: props.sampleDonation.name}
</b>
{' donated $'}
<b>{props.sampleDonation.amount.toFixed(2)}</b>!
</div>
</div>
)}
</div>
</div>
);
};
Loading