Skip to content

Commit

Permalink
fetch donations from db
Browse files Browse the repository at this point in the history
  • Loading branch information
eduardozgz committed Oct 23, 2024
1 parent 0d175a3 commit 2a036da
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 142 deletions.
2 changes: 1 addition & 1 deletion apps/website/src/app/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function NavBar() {
const [t] = useTranslation();
return (
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<nav className="container flex h-14 items-center gap-4 text-sm lg:gap-6">
<nav className="flex h-14 items-center gap-4 px-[18px] text-sm lg:gap-6">
<Link href={Routes.Home} className="mr-auto">
<div className="group flex flex-row items-center">
<BotIcon className="h-9 w-9" />
Expand Down
60 changes: 28 additions & 32 deletions apps/website/src/app/donors/Donor.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
"use client";

import type { DiscordUser } from "@mc/validators/DiscordUser";

import { useTranslation } from "react-i18next";

import {
Dialog,
DialogContent,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@mc/ui/dialog";
} from "@mc/ui/dialog";
import { Separator } from "@mc/ui/separator";

import type { RouterOutputs } from "~/trpc/react";
import { DisplayUsername } from "../components/DisplayUsername";
import { useTranslation } from "react-i18next";
import { Separator } from "@mc/ui/separator";

/* eslint-disable @next/next/no-img-element */
export function Donor({
className,
user,
donations,
donor: { user, donations },
}: {
className: string;
user: DiscordUser;
donations: {
note: string;
amount: number;
currency: string;
anonymous: boolean;
date: Date;
}[];
donor: RouterOutputs["donor"]["geAll"][number];
}) {
const { i18n } = useTranslation();
const { i18n } = useTranslation();

const dateFormatter = Intl.DateTimeFormat(i18n.language, {dateStyle: "short" });
const currencyFormatter = (currency: string) => Intl.NumberFormat(i18n.language, { currency, style: "currency" });
const dateFormatter = Intl.DateTimeFormat(i18n.language, {
dateStyle: "short",
});
const currencyFormatter = (currency: string) =>
Intl.NumberFormat(i18n.language, { currency, style: "currency" });

return (
<Dialog>
Expand All @@ -56,23 +52,23 @@ const currencyFormatter = (currency: string) => Intl.NumberFormat(i18n.language,
<DisplayUsername {...user} />
</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-2 mt-4">
{donations.map((donation,i) => (
<>
<div className="">
<div className="mt-4 flex flex-col gap-2">
{donations.map((donation, i) => (
<>
<div className="">
<div className="flex justify-between text-muted-foreground">
<div>
{dateFormatter.format(donation.date)}
</div>
<div className="">
{currencyFormatter(donation.currency).format(donation.amount)}
</div>
<div>{dateFormatter.format(donation.date)}</div>
<div className="">
{currencyFormatter(donation.currency).format(
donation.amount,
)}
</div>
</div>
<div className="my-2">
{donation.note}
<div className="my-2">{donation.note}</div>
</div>
</div>
{i != donations.length - 1 && <Separator className="my-4 bg-accent-foreground" />}
{i != donations.length - 1 && (
<Separator className="my-4 bg-accent-foreground" />
)}
</>
))}
</div>
Expand Down
148 changes: 60 additions & 88 deletions apps/website/src/app/donors/Donors.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
"use client";

import assert from "assert";
import type { DiscordUser } from "@mc/validators/DiscordUser";
import React, { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";

import type { RouterOutputs } from "~/trpc/react";
import { api } from "~/trpc/react";
import { Donor } from "./Donor";

interface DonorBubble {
donor: RouterOutputs["donor"]["geAll"][number];
radius: number;
x: number;
y: number;
childs: DonorBubble[];
}

export function Donors() {
const [screenSize, setScreenSize] = useState<[number, number]>([
window.innerWidth,
Expand All @@ -18,68 +27,29 @@ export function Donors() {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
interface Donor {
user: DiscordUser;
donations: {
note: string;
amount: number;
currency: string;
anonymous: boolean;
date: Date;
}[];
radius: number;
x: number;
y: number;
childs: Donor[];
childsIsFull: boolean;
}

const donors = useMemo<Donor[]>(() => {
const donors: Donor[] = [];

for (let i = 0; i < 25; i++) {
donors.push({
user: {
id: `user-${i}`,
username: `User ${i}`,
discriminator: `${Math.floor(Math.random() * 10000)}`,
avatar: `https://picsum.photos/seed/${i}/200/300`,
},
donations: [
{
amount: Math.floor(Math.random() * 15),
currency: "USD",
note: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
anonymous: false,
date: new Date(),
},
{
amount: Math.floor(Math.random() * 15),
currency: "EUR",
note: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
anonymous: false,
date: new Date(),
},
],
childs: [],
x: 0,
y: 0,
radius: 0,
childsIsFull: false,
});
}
const donorsQuery = api.donor.geAll.useQuery();

const donors = useMemo<DonorBubble[]>(() => {
const donors: DonorBubble[] = (donorsQuery.data ?? []).map((donor) => ({
donor,
radius: 10,
x: 0,
y: 0,
childs: [],
}));

return donors.sort(
(a, b) =>
b.donations.reduce((a, c) => c.amount + a, 0) -
a.donations.reduce((a, c) => c.amount + a, 0),
b.donor.donations.reduce((a, c) => c.amount + a, 0) -
a.donor.donations.reduce((a, c) => c.amount + a, 0),
);
}, []);
}, [donorsQuery.data]);

const totalDonated = useMemo(
() =>
donors.reduce(
(sum, donor) => sum + donor.donations.reduce((a, c) => c.amount + a, 0),
(sum, { donor }) =>
sum + donor.donations.reduce((a, c) => c.amount + a, 0),
0,
),
[donors],
Expand All @@ -89,8 +59,8 @@ export function Donors() {

const donationBubbleRadius = (amount: number) => {
const radius = (amount / totalDonated) * (totalDonated * 4);
if (radius > 0.25 * screenSize[0]) {
return screenSize[0] * 0.25;
if (radius > 0.15 * screenSize[0]) {
return screenSize[0] * 0.15;
}
if (radius < 0.01 * screenSize[0]) {
return 0.01 * screenSize[0];
Expand All @@ -99,9 +69,9 @@ export function Donors() {
}
};

processedDonors.forEach((donor) => {
donor.radius = donationBubbleRadius(
donor.donations.reduce((a, c) => c.amount + a, 0),
processedDonors.forEach((bubble) => {
bubble.radius = donationBubbleRadius(
bubble.donor.donations.reduce((a, c) => c.amount + a, 0),
);
});

Expand All @@ -117,12 +87,12 @@ export function Donors() {
const rSum = r1 + r2;
const rDiff = Math.abs(r1 - r2);

return d === rSum || d === rDiff || d < rDiff || d < rSum;
return d < rDiff || d < rSum || d === rSum || d === rDiff;
}

const getNewPosition = (
parent: Donor,
child: Donor,
parent: DonorBubble,
child: DonorBubble,
angle: number,
): { x: number; y: number } => {
const distance = parent.radius + child.radius + 1;
Expand All @@ -132,26 +102,31 @@ export function Donors() {
};
};

const findSpaceForDonor = (
newChild: Donor,
): { parent: Donor; x: number; y: number } | undefined => {
const leftLimit = -screenSize[0] / 2;
const rightLimit = screenSize[0] / 2;
const topLimit = -screenSize[1] / 2;
const radianQuarter = (Math.PI * 2) / 4;
const leftLimit = -screenSize[0] / 2;
const rightLimit = screenSize[0] / 2;
const topLimit = -screenSize[1] / 2;
const steps = 18;
const stepSize = (2 * Math.PI) / steps;
let clockwise = true;

const findSpaceForDonor = (
newChild: DonorBubble,
): { parent: DonorBubble; x: number; y: number } | undefined => {
for (const donor of processedDonors) {
if (donor.childsIsFull) continue;
for (let step = 0; step < steps; step++) {
clockwise = !clockwise;

const steps = 25;
let angle = step * stepSize;

if (clockwise) {
angle += Math.PI;
}

for (let step = 0; step < steps; step++) {
const angle = radianQuarter + ((2 * Math.PI) / steps) * step;
const newPosition = getNewPosition(donor, newChild, angle);

if (
newPosition.x - newChild.radius * 2 < leftLimit ||
newPosition.x + newChild.radius * 2 > rightLimit ||
newPosition.x - newChild.radius < leftLimit ||
newPosition.x + newChild.radius > rightLimit ||
newPosition.y - newChild.radius * 2 < topLimit ||
processedDonors.some((donor) =>
circlesIntersect(
Expand All @@ -169,8 +144,6 @@ export function Donors() {

return { parent: donor, x: newPosition.x, y: newPosition.y };
}

donor.childsIsFull = true;
}
};

Expand All @@ -189,28 +162,27 @@ export function Donors() {
return processedDonors;
}, [donors, screenSize, totalDonated]);

// TODO fetch from db, modal to see donors, group donations by donor
return (
<div className="flex grow items-center justify-center">
<div className="relative m-2 transition-all">
{processedDonors.map((donor) => {
{processedDonors.map(({ donor, radius, x, y }) => {
return (
<div
tabIndex={0}
key={donor.user.id}
className="absolute p-1"
style={{
width: `${donor.radius * 2}px`,
height: `${donor.radius * 2}px`,
top: `-${donor.radius}px`,
width: `${radius * 2}px`,
height: `${radius * 2}px`,
top: `-${radius}px`,
borderRadius: `100%`,
left: `-${donor.radius}px`,
transform: `translate(${donor.x}px, ${donor.y}px)`,
left: `-${radius}px`,
transform: `translate(${x}px, ${y}px)`,
}}
>
<Donor
className="flex h-full w-full cursor-pointer flex-col items-center justify-center overflow-hidden rounded-full bg-cover bg-center bg-no-repeat transition-transform hover:scale-[1.03]"
user={donor.user}
donations={donor.donations}
donor={donor}
/>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions apps/website/src/server/api/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
import { botRouter } from "./routers/bot";
import { demoServersRouter } from "./routers/demoServers";
import { discordRouter } from "./routers/discord";
import { donorRouter } from "./routers/donor";
import { guildRouter } from "./routers/guild";
import { sessionRouter } from "./routers/session";
import { userRouter } from "./routers/user";
Expand All @@ -18,6 +19,7 @@ export const appRouter = createTRPCRouter({
discord: discordRouter,
bot: botRouter,
demoServers: demoServersRouter,
donor: donorRouter,
});

// export type definition of API
Expand Down
Loading

0 comments on commit 2a036da

Please sign in to comment.