feat(don): add top donor widget
This commit is contained in:
@@ -6,7 +6,11 @@ import { eq } from "drizzle-orm";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
|
||||
export async function markRunning(id: string) {
|
||||
getPostHogClient().capture({ distinctId: id, event: "donation_widget_state_playing", properties: { donation_id: id } });
|
||||
getPostHogClient().capture({
|
||||
distinctId: id,
|
||||
event: "donation_widget_state_playing",
|
||||
properties: { donation_id: id },
|
||||
});
|
||||
|
||||
await db
|
||||
.update(donations)
|
||||
@@ -15,7 +19,11 @@ export async function markRunning(id: string) {
|
||||
}
|
||||
|
||||
export async function markDone(id: string) {
|
||||
getPostHogClient().capture({ distinctId: id, event: "donation_widget_state_shown", properties: { donation_id: id } });
|
||||
getPostHogClient().capture({
|
||||
distinctId: id,
|
||||
event: "donation_widget_state_shown",
|
||||
properties: { donation_id: id },
|
||||
});
|
||||
|
||||
await db.update(donations).set({ sent: true }).where(eq(donations.id, id));
|
||||
}
|
||||
|
||||
@@ -48,10 +48,15 @@ export default function () {
|
||||
(tts.onerror = () => r(false))
|
||||
),
|
||||
);
|
||||
if (!ttsAvailable) posthog.capture("donation_widget_tts_failed", { amount: data.amount });
|
||||
if (!ttsAvailable)
|
||||
posthog.capture("donation_widget_tts_failed", { amount: data.amount });
|
||||
console.log("Transitioning in");
|
||||
setMounted(true);
|
||||
posthog.capture("donation_widget_displayed", { amount: data.amount, name_length: data.name.length, has_image: !!data.image });
|
||||
posthog.capture("donation_widget_displayed", {
|
||||
amount: data.amount,
|
||||
name_length: data.name.length,
|
||||
has_image: !!data.image,
|
||||
});
|
||||
console.log("Running SFX");
|
||||
if (sfx.current) {
|
||||
const player = sfx.current;
|
||||
@@ -64,7 +69,10 @@ export default function () {
|
||||
if (ttsAvailable) await new Promise((r) => (tts.onended = r));
|
||||
console.log("Done");
|
||||
markDone(data.id);
|
||||
posthog.capture("donation_widget_animation_end", { amount: data.amount, tts_available: ttsAvailable });
|
||||
posthog.capture("donation_widget_animation_end", {
|
||||
amount: data.amount,
|
||||
tts_available: ttsAvailable,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
setMounted(false);
|
||||
await new Promise((r) => setTimeout(r, 1200));
|
||||
@@ -98,7 +106,9 @@ export default function () {
|
||||
} catch {
|
||||
console.error("Heartbeat timed out");
|
||||
setFailed((x) => x + 1);
|
||||
posthog.capture("donation_widget_heartbeat_failure", { fail_count: failed + 1 });
|
||||
posthog.capture("donation_widget_heartbeat_failure", {
|
||||
fail_count: failed + 1,
|
||||
});
|
||||
if (failed > 12) location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
@@ -111,7 +121,7 @@ export default function () {
|
||||
// amount: 1,
|
||||
// message: `เสื้อดำเด้าหน่อย`,
|
||||
// });
|
||||
}, []);
|
||||
}, [failed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
"use server";
|
||||
|
||||
import { db } from "@/lib/db";
|
||||
import { donations } from "@/lib/db/schema";
|
||||
import { asc, desc } from "drizzle-orm";
|
||||
|
||||
export async function getTopDonate() {
|
||||
const [top] = await db
|
||||
.select({
|
||||
name: donations.name,
|
||||
amount: donations.amount,
|
||||
})
|
||||
.from(donations)
|
||||
.limit(1)
|
||||
.orderBy(desc(donations.amount), asc(donations.id));
|
||||
return top;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getTopDonate } from "./api";
|
||||
|
||||
type Data = Awaited<ReturnType<typeof getTopDonate>>;
|
||||
|
||||
export default function TopDonateWidget() {
|
||||
const [top, setTop] = useState<Data>();
|
||||
const textRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function update() {
|
||||
void getTopDonate().then(setTop);
|
||||
}
|
||||
queueMicrotask(update);
|
||||
return sse.donate.subMany({
|
||||
update,
|
||||
ping: update,
|
||||
refresh: () => location.reload(),
|
||||
}).clean;
|
||||
}, []);
|
||||
|
||||
// the most idiomatic way possible but it works
|
||||
useEffect(() => {
|
||||
if (!textRef.current) return;
|
||||
var fontSize = 48,
|
||||
count = 0;
|
||||
const el = textRef.current;
|
||||
const interval = setInterval(() => {
|
||||
console.log(el.scrollWidth, el.clientWidth);
|
||||
fontSize = (fontSize * el.clientWidth) / el.scrollWidth;
|
||||
el.style.fontSize = `${fontSize}px`;
|
||||
count++;
|
||||
if (count > 20) clearInterval(interval);
|
||||
}, 100);
|
||||
return () => clearInterval(interval);
|
||||
}, [textRef, top]);
|
||||
return top ? (
|
||||
<div className="grid grid-cols-[40px_minmax(0,1fr)_max-content] w-94.5 max-w-94.5 h-18.75 *:self-center gap-1 text-5xl font-semibold bg-black/75 rounded-full p-2 px-3 text-[#FFBA00] border-4 border-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="currentColor"
|
||||
className="size-10 shrink-0"
|
||||
>
|
||||
<path d="M200-160v-80h560v80H200Zm0-140-51-321q-2 0-4.5.5t-4.5.5q-25 0-42.5-17.5T80-680q0-25 17.5-42.5T140-740q25 0 42.5 17.5T200-680q0 7-1.5 13t-3.5 11l125 56 125-171q-11-8-18-21t-7-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820q0 15-7 28t-18 21l125 171 125-56q-2-5-3.5-11t-1.5-13q0-25 17.5-42.5T820-740q25 0 42.5 17.5T880-680q0 25-17.5 42.5T820-620q-2 0-4.5-.5t-4.5-.5l-51 321H200Zm68-80h424l26-167-105 46-133-183-133 183-105-46 26 167Zm212 0Z" />
|
||||
</svg>
|
||||
<div ref={textRef} className="min-w-0">
|
||||
{top.name}
|
||||
</div>
|
||||
<div className="text-white">{top.amount}฿</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user