From d59155387274ebc0eb6caa554fbfd04fd771c2e7 Mon Sep 17 00:00:00 2001 From: MasterGordon Date: Fri, 13 Jun 2025 17:19:24 +0200 Subject: [PATCH] updated backed --- backend/controller/controller.ts | 3 +- backend/controller/userController.ts | 27 +++++++++++++ backend/database/getDb.ts | 3 +- backend/repositories/collectionRepository.ts | 5 ++- backend/repositories/gameRepository.ts | 30 ++++++++++---- backend/repositories/gemsRepository.ts | 10 +++-- backend/repositories/scoreRepository.ts | 3 +- backend/repositories/userRepository.ts | 13 ++++--- bun.lockb | Bin 247463 -> 247819 bytes package.json | 1 + shared/lootboxes.ts | 2 +- src/main.tsx | 2 + src/views/profile/Profile.tsx | 39 +++++++++++++++++++ 13 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 src/views/profile/Profile.tsx diff --git a/backend/controller/controller.ts b/backend/controller/controller.ts index 1e7bc93..43cdb7e 100644 --- a/backend/controller/controller.ts +++ b/backend/controller/controller.ts @@ -2,10 +2,11 @@ import type { ServerWebSocket } from "bun"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import type { z, ZodType } from "zod"; +import * as schema from "../schema"; interface RequestContext { user?: string; - db: BunSQLiteDatabase; + db: BunSQLiteDatabase; ws: ServerWebSocket; } diff --git a/backend/controller/userController.ts b/backend/controller/userController.ts index 16b858d..78e22c4 100644 --- a/backend/controller/userController.ts +++ b/backend/controller/userController.ts @@ -19,6 +19,9 @@ import { import { getWeight, lootboxes } from "../../shared/lootboxes"; import { weightedPickRandom } from "../../shared/utils"; import { emit } from "../events"; +import { Game } from "../schema"; +import { and, eq, gt } from "drizzle-orm"; +import dayjs from "dayjs"; const secret = process.env.SECRET!; @@ -176,4 +179,28 @@ export const userController = createController({ }); }, ), + getHeatmap: createEndpoint( + z.object({ + id: z.string(), + }), + async ({ id }, { db }) => { + const now = dayjs(); + const firstOfYear = now + .set("day", 0) + .set("month", 0) + .set("hour", 0) + .set("minute", 0); + const gamesOfUser = await db.query.Game.findMany({ + where: and(eq(Game.user, id), gt(Game.finished, firstOfYear.valueOf())), + }); + const heat = Array.from({ + length: now.diff(firstOfYear, "days") + 1, + }).fill(0); + gamesOfUser.forEach((game) => { + const day = dayjs(game.finished).diff(firstOfYear, "days"); + heat[day] += 1; + }); + return heat; + }, + ), }); diff --git a/backend/database/getDb.ts b/backend/database/getDb.ts index f86fcf1..9057e41 100644 --- a/backend/database/getDb.ts +++ b/backend/database/getDb.ts @@ -1,8 +1,9 @@ import { drizzle } from "drizzle-orm/bun-sqlite"; +import * as schema from "../schema"; import { Database } from "bun:sqlite"; export const getDb = (filename: string = "sqlite.db") => { const sqlite = new Database(filename); - const db = drizzle(sqlite); + const db = drizzle(sqlite, { schema }); return db; }; diff --git a/backend/repositories/collectionRepository.ts b/backend/repositories/collectionRepository.ts index bc3bd5c..5137748 100644 --- a/backend/repositories/collectionRepository.ts +++ b/backend/repositories/collectionRepository.ts @@ -3,9 +3,10 @@ import { Collection, type CollectionType } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { decode, encode } from "@msgpack/msgpack"; import type { UserCollection } from "../../shared/gameType"; +import * as schema from "../schema"; export const getCollection = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, ): Promise => { const res = ( @@ -24,7 +25,7 @@ export const getCollection = async ( }; export const upsertCollection = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, collection: UserCollection, ) => { diff --git a/backend/repositories/gameRepository.ts b/backend/repositories/gameRepository.ts index f626410..9c16b4f 100644 --- a/backend/repositories/gameRepository.ts +++ b/backend/repositories/gameRepository.ts @@ -3,12 +3,19 @@ import { Game, type GameType } from "../schema"; import { eq, sql, desc, and, not } from "drizzle-orm"; import type { ServerGame } from "../../shared/game"; import { decode, encode } from "@msgpack/msgpack"; +import * as schema from "../schema"; -export const getGame = async (db: BunSQLiteDatabase, uuid: string) => { +export const getGame = async ( + db: BunSQLiteDatabase, + uuid: string, +) => { return (await db.select().from(Game).where(eq(Game.uuid, uuid)))[0]; }; -export const getGames = async (db: BunSQLiteDatabase, user: string) => { +export const getGames = async ( + db: BunSQLiteDatabase, + user: string, +) => { return await db .select() .from(Game) @@ -16,7 +23,10 @@ export const getGames = async (db: BunSQLiteDatabase, user: string) => { .orderBy(desc(Game.started)); }; -export const getCurrentGame = async (db: BunSQLiteDatabase, user: string) => { +export const getCurrentGame = async ( + db: BunSQLiteDatabase, + user: string, +) => { return ( await db .select() @@ -27,7 +37,10 @@ export const getCurrentGame = async (db: BunSQLiteDatabase, user: string) => { )[0]; }; -export const getGamesCount = async (db: BunSQLiteDatabase, user: string) => { +export const getGamesCount = async ( + db: BunSQLiteDatabase, + user: string, +) => { return ( await db .select({ count: sql`count(*)` }) @@ -36,7 +49,10 @@ export const getGamesCount = async (db: BunSQLiteDatabase, user: string) => { )[0].count; }; -export const upsertGame = async (db: BunSQLiteDatabase, game: GameType) => { +export const upsertGame = async ( + db: BunSQLiteDatabase, + game: GameType, +) => { const { uuid, user, stage, gameState, finished, started } = game; const games = await db.select().from(Game).where(eq(Game.uuid, uuid)); if (games.length > 0) { @@ -62,7 +78,7 @@ export const upsertGame = async (db: BunSQLiteDatabase, game: GameType) => { }; export const upsertGameState = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, game: ServerGame, ) => { const { uuid, user, stage, finished, started } = game; @@ -77,7 +93,7 @@ export const upsertGameState = async ( }; export const getTotalGamesPlayed = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user?: string, ) => { if (user) diff --git a/backend/repositories/gemsRepository.ts b/backend/repositories/gemsRepository.ts index 9d94fb2..43515a9 100644 --- a/backend/repositories/gemsRepository.ts +++ b/backend/repositories/gemsRepository.ts @@ -1,8 +1,12 @@ import { eq } from "drizzle-orm"; import { Gems } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; +import * as schema from "../schema"; -export const getGems = async (db: BunSQLiteDatabase, user: string) => { +export const getGems = async ( + db: BunSQLiteDatabase, + user: string, +) => { const res = (await db.select().from(Gems).where(eq(Gems.user, user)))[0]; const count = res?.count ?? 0; const totalCount = res?.totalCount ?? 0; @@ -10,7 +14,7 @@ export const getGems = async (db: BunSQLiteDatabase, user: string) => { }; export const addGems = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, gems: number, ) => { @@ -28,7 +32,7 @@ export const addGems = async ( }; export const removeGems = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, gems: number, ) => { diff --git a/backend/repositories/scoreRepository.ts b/backend/repositories/scoreRepository.ts index 04d4961..e597ce3 100644 --- a/backend/repositories/scoreRepository.ts +++ b/backend/repositories/scoreRepository.ts @@ -1,8 +1,9 @@ import { eq, sql, not } from "drizzle-orm"; import { Game } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; +import * as schema from "../schema"; -export const getScoreBoard = async (db: BunSQLiteDatabase) => { +export const getScoreBoard = async (db: BunSQLiteDatabase) => { return ( await db .select({ stage: sql`max(${Game.stage})`, user: Game.user }) diff --git a/backend/repositories/userRepository.ts b/backend/repositories/userRepository.ts index cf4ed27..be9e829 100644 --- a/backend/repositories/userRepository.ts +++ b/backend/repositories/userRepository.ts @@ -5,9 +5,10 @@ import { userSettings as userSettingsSchema, type UserSettings as UserSettingsType, } from "../../shared/user-settings"; +import * as schema from "../schema"; export const registerUser = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, name: string, password: string, ) => { @@ -23,7 +24,7 @@ export const registerUser = async ( }; export const loginUser = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, name: string, password: string, ) => { @@ -41,7 +42,7 @@ export const loginUser = async ( }; export const getUser = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, name: string, ): Promise => { const user = await db @@ -52,7 +53,7 @@ export const getUser = async ( }; export const getUserSettings = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, ): Promise => { const userSettings = await db @@ -64,7 +65,7 @@ export const getUserSettings = async ( }; export const upsertUserSettings = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, settings: UserSettingsType, ) => { @@ -87,7 +88,7 @@ export const upsertUserSettings = async ( } }; -export const getUserCount = async (db: BunSQLiteDatabase) => { +export const getUserCount = async (db: BunSQLiteDatabase) => { return (await db.select({ count: sql`count(*)` }).from(User))[0] .count; }; diff --git a/bun.lockb b/bun.lockb index 80697141d7516de801f85011033d542dc121ee95..99f2628a6db91dbb4dd02923948392c2db34eeca 100755 GIT binary patch delta 37044 zcmeIbXM9!F+V;EFBnz_WorHk&5+FbVDGrB4-dy!*E&rYUp5(o}=lgXNg6gjs)^ovrpTsequiN}E`Qsc8`E*~@y^J=w z!?uZKb9fJoiXR$>rZ3A;H6gmP!x7+cB!263IEujG*wx^|us@t$$>akTFN3S1kFvO> z#bqsiRMFw6Li{H%StO=eJloO?L$#4r?3|?B-;V24MgQY^r=*8h0A*Ou*E{9zZmI~YsHhaPu=5Un7K8Gz8 zIfGp^fxrp`aoht-i{`^J6w}FA?4seOy$#NT{TrMgUJVz7UxACkc^f($Za9a<zft)5|#>o2q#FOvB%7?8Z0fcD^d(wGJXwGS z2f@-adEf%@lV%P_Uig8<>2PlBG0{;2#s6#GmKOWN(y}IPO~*pm(law!n`8JZEESD{rDfM(nG?-mY0xQH8c+q6xwF%< z-Il!;7XOTdR%U{;uyoBfxD5OTEESmr%NWK*$BvI4<8VyE&VxN*96o#;<9RU3?9H{XbpK3PdUdd+w}EAt0<8Faa9Qm1 zehx=P*%OWvkOXhTlEGY<{UvcQEaTk{mNi@lmKk3HmJA>EHPc;jnDZxih?#LISp1{o zhR2K;>u^jR=5VlKCia6R|Hc*@@d-FI9cqqWwOEtiwz$5fcN$@iV@o(U8AMn-JbFAK zhodGx3!)E;sa^%s7ND z?TH#Na*Q;5GP+bheS)diq+^R>4~*_VZZPq&R7*bX@RL#|42c;)*EnM0q9>BA!zbSC zmvlHE@uQ-~4ha|*T{3>akf`D042pO7Pcf_g8!XjqKGiJlHn#Yew5ofC>dAy=&KS+!f-UjiB$(-k z#s$Pj#SV8wjTt;DYD`>0^ru8fhPkJi{5a99|1xap9~r}_;cRON%uQmw|V{KEhN${4ZE7 zeR_gMzic+3-Y=r$CiQH49f*&ofTgPX8t84en|#0(sxN9>+J}32{_H-V$$LK#3nF+QhY2d+4hA+ z?_}wIYt3dQz(vr%c*TsbMZDN?Z<^`O!BXw;b*B9pEVT)H%P21)v8EZ24vmW*D?3I& zY}6>OH8@K3H^S21WLP?S;K%`E$Ha_?Edca1rU*lmZ zxHv5SB{xZhSYa~>NC(Hmk}x{1|G1dp10D9&)xPT5S6TZaYF{kvi>Q4yx3A*%RolK= z+gD}lYHVI~*n|eV;Zo>T=hfuns!H+jW973mc?7f(!F6B+q?LSegPK0U9jv? zZ^F_;3t*X)6Zf!X%GeJ_$P2$nfzovyV6hMB&cam^j(0U@MvH@{>IKUt#L2~&k^zUz zxt(^{JeMcIWr-gN%PFLtrB{b5VCRRWI`>E?d-gH7GQ1ux56^}reJPkJpO82-W^4>) zIdUI0t2Z`kR8;@r(T=UyGG#ANL8_N@A#U!0ggo?U4xRpW zuGRfpdR(>ZNG6h$fKe0YX+52Ywv1hCJ)>o~bE6*9%H#Y|Pi^IKH3~M=z;M@?5RP$r zM&}0J1p1MyQx9wz>TVS3aC9@Q`{TpBF@|PFl?!t?`ea%OSgao=f(-UGtmc_<{`Krk z$kLU7)mG0440oR()HKtpKz($>8eJ_^s}io?ZX4l#KiuJnutj&J2H6rgWAxNWk8_)z z9_euvYv^#)*JG=OyW301%rr4fPi^OMUqP|*s2b`jO^}9C{4heT?Yy1u>Z$EL?hbUQ zm73Pi!)j;dg>@0js^Hi#@8=zkmYGq*v07kh`siw5x_?KHR=$xwv}1%bN>A~r%G%nFg z-xe9_YA2Q+Fd^KzK=<$FabDD8x_R7%=?LnM51W9qi=IwQvhLsA<94<(^U9^)uM+BP zp{I8DxCXS5@lWd>?%G1Aoe_FWsJRj9L=Q*kY3;*Z3kh`*&HWvr0cJzGhq_us(jA7r zlF;BxJtre&K4s8^<*hSkCFe@v)vrv74kTYsBSmY#t>Yeu8CsZmSmR zYQ$9`Tui+#aTIFu~(0&o_vCI{5b?WyS&G)bx=mDJ?(9&vA5l)XD z6XkI|-;?U;vC$2@2sGDYqrzRs3AHprg?lHQjkND) zYF=D%+ZkFXLT2m=LRKz>%(OM=cd9~ZqX?PWE<$DsuReB))`VIceoF|MeqRwX{i-sy zR$T}g`8f~kF@rs>uNlc+dhFnESH1qS`AcXip}t1wXF_Id2WDSSLrW!O`sEzRL@>0$ zgkp@)Swd#oTG6)OD})9cu@4AE>lrZ(yapMYG=*&<)ZK{Gn8;=ky$BhZxc3l}CF`vR zt_t%W;&3puVss02505c79K&+{p~npOxYrKjS7UQXy%4wu~&$3*`0{&5t8R2pE(o;ux+^>zWQ%R2JEX$Z! z?tCN7Qvo^FX6|CiR>?{T2zAfG3NcC>T|3mZ7pu9RQ90c09A(bw95Tb5t@YGV9(O95 zSu@(;x{lRUPpcNL`H$9zj*f80j7A_UE`V(8X;>k80GEIaLXylpl=#yp5q5#@!B};% zT(aEU%dNPaI!niW2}`Qxl53c&Qk?8W>~-z|5<<^uT-#P-$<}FlUBaT&mf`M_W9@Qe z-|T@Ujb?zj9&W^v8qxjigEw^laUOTMai%}DUl`_%Aw}mfR`hFFvf7EFT3=dm#!lic zG~V>2r`v|QJ7P)3G{-;GorD#Pm0Pae&J()-1drQyg4wS|gPr5`RJ2qy+0?y_X8&k; zu*Lg_x@t_6ZBd&25}_cxy!61RQ1@o68d%gqu0mMSCAp;Q-JW>Ull_Re(V3PseJz%> zg>+cIU^T`v&MfY*NoH5(Fiwx7u*~vClgnN#8QfelD_u{-)2LPGWV2+Ww$5>S>ST}m zJemwD8uKmZOJ=U-?ihi^4lc8sxCK}*Vo@6g={A-J%c+m<80u`G`%m$>M^3T(H?VuC zbDf?##pB93RW5s6l-ykjNoz>ao9Tlk=_#pKsOt+XjU zOR%_vGGQJN;)+CH7-#21vs`+cG_$b6jGSfl?!%JtVs#itoLFhbo3NpG5Kd^J{m z!)tWgFmDWwYl-=#+qJ^e^^Maa+@q$O8wzKK+M&)py8m>KmScuKbUK%-8B`vBx{2ky z7)z>VRL1#@?mxrht~}FjlnlsFtZ+RbAe=5rpW$(Sulvu$XO>w}W<8A)~id6-!!9 z5BCanAH|X}GOwKZ=h(f%f_HY(V`h8Y3(#b~7#oNCB$njGA%)*ziqAEhY3z9J0a%hV z^PDXq1*@)}#?|>8p$K&IdR$;$_Pu!^R(s;iR_wsaY=!F~R()f?3z~1Y;QouD&JlXd zJdgWbH0iJcdf@a>cLvrAMj4dtoUi-O_qgvTWuEuY2I#5tJzBm+`tA7PVNPm%#G#ZcY%$+5A zwOSFbwoBwHDj6&x)Y^!ab^0|{rnAfEHTp#=;p#@HtuaB?=&7%I+&8hinW^H#yqDUB zZ0tG~CoW39LddM!=-Hv}aAhwxYU-YbWfm)EmlIeK#$nmL%sd`b+tXh>_$oRzro^K*el#!aRr^1S$g)PT;tX=1&yR92+71Enm#MG!CX98{IVK})x#)i9U=Uw_3wt3HX_{FSx;T* zah}rCS9;v_Hkw1smdW{SE>=tOG!~ir99AnV_V370t;Qz(_NoZ^4QjQ=eSDL-kr-{% zyx-P0u8wd$|F+x!$XL_BivTwOR)@R3$_)9Z+1mKb&|yOSwvS)_&BomZLftb%t1?6B znW3O9w%_E;&=Dh)@m2$`t#)KLLfma2yOhk(cbTE;+w9megqj(CJ2OLnWQH1Tw_=?y z>oMy+?qk@}J6!p=FqGV(-(DZ#>a@eS#gf{y3^jev_DjkPeP)CL(i(W}v?GTQYGGuzB{TF(W~kvVJ9Z|aW`^If z%uxQ_w$?c_w9F1UFYD=P9$CeThr?H=bbJ$*YDn}d3_9TCj@m>nMH55(>i4nam+?*mF9K$Vf zp`~Z%9B9u2N!JL7UlYr23d=`W^yWbPS^)WE=RDY4FcRdJwPO^J1Y>|Chy&hWGROs{ z1BssrBz_i1`G;}sNgW0BxjM+*Io87>$_gc() zH1q?oROq0^hhS;{$3Xh^7~^0Bn1cp0p$t9)OTx1jpM$02F92!LMa%vQ7Qf3tGQ0vL z{#zjaH-N<7wD=Y*`u9Nm?oqzs@B=J0`x%JCBa0uy@)4Gd{sdBx7~?{RJ1)ch-gWZjp^0MXiEEezC;tkKS z{Iau1^Y|hB3O~dzNsOmh^abcLaEq;YSXRN?V}<1)EbUrt+0SC>$6Z$Z|2>=K z{|6aR`hQXZ83>sX$E_Cpf6WQm3zV)tMMY#TUxZ~*eGONHHHmzRrOsaH72qmXys(UQ zRm&EZd}>;@u*BCg?V3hFG77XDp2dfX0Hp4aHuVBgNY7S(l-teedjqi}<6%Xgk@Pivh?39TQZemU9h-%Tb##>ty&7mYY7El`3Q?+ zVOXT%mhB5m!G4xrMk4tLOR$2)RV-atg4OvU<M;BP~?*_}Ys6Q;9{s{!6z-UXz&Qic2D?SDm zmsralW!Ymbo(PM7JS;;X`O0Y}36`#349jUF8I}sHgZbwuoyt$C*(R}`Wj(v)%XyWkFe+;TYSvYv$Gs2u2}jr*u0oNBSP~3yM+Ix zc(eS!El65;l@`dU;i1)%{~F6k%ICk9;osyZ4f&0DIcDaP@#d3Y2mTu@bHm%pSXgW~ zECuJW^rtw%h!DrTmScV^VF4?Fu*4UHMJmRR+;AB&{$pnRvu7kVEoWscEOrIU&dze& zscGr|5zBpwXJsHQmd`);HlBHpL%RN-dmHBb{O8`rKle6d*Z=3JQz`pB4bjCGmw)bU822=!Oa8gH@z1>ta|ik7-o}6JR>VK|Hsof9T$cX-d2gdA zE&rE$8#{J?Q?dN>tJgXw^;xxGZtDkWn}__AziaLAO=B;;{N0vaM;w~FL*DDl!p}DR zbl!n_jhh}_7ri05MUghQZ>;Fjx76i^4{|+uZd-^dox|y)E^E#<&S>SI!`WS3%;C(Z zl5!vnR+mJWZ4>v+SH^Kzf(~aPr8)2Ua@yeYWVV4N8 zxe+F--69OhgHSpT!W0#g2f-&V!YL6Fly6>yqasYni!egG5hnT| z1o$AVRq;LuRecdIi?B}l`yyNvA;}kEy}BgAoRSC)OCqGIxg`<8{17rk*re+DA>0xn z#SbA(rHinv6hdSvge@w$6hh0=2#-bBrdpLocql?zX@ni>kq8@}L+Je+!n-QR#l<%YPHZO>XFc=s-r)2L8S_PrkvHF z&s9&Mi)x$DCFQOTeW9X+zErz~zEXv1K$levqZux3^@{QhKsYMGlmLXQ>X--< zYa#^HM7XBnYa&#wg>YGf>&m|t!bK61Y9ZWImqeIT8=+xsgmg8xHig|*SB37VdVxgW z3M4uukmwARF2b@PgvcO-dn!2y#~;*vq5G;;9q323TIhj#B=nQ&SQq+Pr3(F`oWanq zs;AIHwN2=ea)*%Zt`M?~4I$g#)oujmAF5C&^jO6R{izNLJyE`4&hGqCPmKw4c5^z_ zF-)hX%GHB%sCXeSbxz2o{OdzG)ij}8>XMMR3JizbYOYXjbyX;js>k=!oO#tEp?oS` zD8Fjd5GtUOA=NSh{c%HTS4g#rKzJxZS_DE7^$5XPRCV+~#Z;+MhI4^tr2>( zLCDtzAyoBjgW%m3VV?-~l)Ej$E)ineB802mA`FQ{C>@E=P{l+d__RYfC4xu!wnI27 z!jyIhjnpv_CbmZiXphiD#kWVO+5zFR2+fp#2ZW0vBy~V&p)QFqrz1kcjtH&P+>Qug zoe(laXrtz*~-YTXC zf=^F`QzGy2e6hgizgbAu=6oPkugnc5!D|df{T_VKxN0_X3 zi!fvWLg@hrQ&h|V1fPKjr$k6lz5@}CiZEp$!ZdYEgo)7z0nrFERD3i-)j{q^%5RQs4WfH!pDFC$!2@h>A(osDo=gzL(GHo`>_l4c{^RF_1UGY6sJ z9E5Z=cMd|>T!ah}?x=cm5pIc)G8Z93rHim^9zx_ignKG^9+yl`6GHb@tNBDfoKJMx ze4-zyM+kDDcm?`dr3!JNNP>P2t88n1<-FQO6YgBTZq%dtI%T=BgAP!=!x=O zD5nWcjag`(CNSkRu?Whc;)OU(2)UI1Vu;g(P%d=|Qgd`_*HEW+ZZ%g&2wQ@XAwnKi zZwZc^DTMN=bRo_ZuR#S=vQR;FU#O63wG=9>RtptTkAyf)D5#i972-6p3@V{|3i+sQ zLcYqK43$(-LVjwuP$^aDb*Qw85qeG?6e^>9mqTUMXrXfIm{56D?hUAdiWjP=&Iwgg z{wXx$VhS}*N}(B5)Flz-tdNGTK=4;{SKwGpT@|XX>aB!os6|2nDjibGR^c4E3g=oX zc@;v-)d-J82vn_BBRmu#Z8bt2^+<#bYY=*`K?qiTRf#S}oLBJre4oI;KHgRjN?8ozBh9ghKicm5QoWo1I>+f_z&dpSpj*d6WMD z$ICrFW^Aa!jyQjHnk6?%w1g+lofA(w54egMb#d^` zHA)#19XE3L_-M!Koss9Ado@?x?mW#R$*(!3qDwD0y`6P;KK{&k*vnPrC=aUaqz!&X zoZJ3?FoSnyeC1p;MwR{C8Cdv02g#N{T68iwcLTMgy;fT#bktmBc=G>=`4N{wct}eN ziovIly4qeVl2F)^1m@n}+*41r`Q&rwbT^66-4iPpFr?WIn?pp{oIb=1x#)WM+;{a6^tqeyaVFE^0q zo8*q^0dNq=Gjsca49jEi8@LN(NPY$P!4vR1_z^q=^1$>Ja0h$?{s8yDBXAX5)A+{u zcLW}QU%++nJ@^)6fEz&mr1JxO6Z{0y!EH@xowf3ueJXX<$|P*WVZH|AB2MtPz?lt%Ag9U z2&#jspc3$x@0rvfP!q`b4}#cOu&m`<;f{fzFBk@fgMJ_y#DWoEBHlQtN2ik)Upebkunt(9*_Cf@Ka3Ifi1%nVE522L>@^qUI@CBtn2~ZrA1f{`q zzzES{raTJxJCHSd7|1h@x4{E& z2mA6JIz%B3t_zv6$>EJq$=eYZW0U$aDogfczj|81S zXV3*if_6Y2c5ek*19=c${*Ebsvy21s7`{BH9}4OM`Ez(x;18;U8Xy43ld_Y*WbhK0 z0;Yn!V4oNJ+zbLU)uL{iU)NU%js|1E0A}Vu&|kt}Ab1h<1^qx2xIp|T;8SoKoB@}B zY+x(EO0Y`(+)d-Zd?NTNSg0bqYi$yy5R`{f;u!G>U?P|V7^oFcrviDv4kk zc!$Qm3*gK)wJK1BQZAILd{tF@Exe1i6@v2XY0j2+k4r1vn2b zfm7g1@ClG-vOfi#XsUIR;j4&-ZnJ|LYuJ^~*~UZ5-B%h?Y3 zn&*4q4X_9-07*c$>v>?VT=eBMFc?Jd9NkO%HAgYo)2XZRApCS#oTsVyzFMys&WDC6 z(*K*7qZ;{rT~*pO)9T%#2LVimaDCpLa;V18fJfWo!WJ!COGeSO%n=bs)vas`Q%# zSA!_93akKfWR)teROh0o!`3J*Z?U!5YqG^LksPayc$c(7H*#}HA-am{uazkGHbIHm z1Vmm6WVhJ}7K2o?DXG$wDgm2Oq{zHrE7)Q|QrOjz+>D&m>i(KvA-myr=IvGm7ID~Hz%*JcvE2iU`~oACeCg}d;PZj@rOy3sd#R?=e0N=j5JxxZE+pOj#C zggJ)6+3WUnp?^PS`3{lWL2v-vYz+@nY*my7zGy+l~Iqb@pvnPP@U>q0)#)39LD*GHL4N3v& zm^fH28=_0R_)9*6PNT_ z=nDpbD9|5BL!<|#=cKnr0_mAyK)x3*y)gs~1+g;z!wHN4qk$w62gyXna}uy?Y_~+l zTm0m|7LcBJ8O#JSVW)vaAmc6J8DJKW-jhno@Fsy*z-%x_#$Q&?T#R{OK9Ef9jHDpR zcp;FX5?v=SSvoCC7Q@V6PZ0dN8+Aen6j z%fNAP45WZcI3I;S1|NZ{*cCw<;Sa%Ka0rw^I|v_8S7Wpi3Hu3t0Nw!m!24ha*bCNx z4PXy=j$~Wl-QYd&4%iO1fvw(*rJWQDhjujYE_qQx@ z6}%3-3DyFs#A+Z3B%wh3g(Z!I?TjlBmI_Or&nnoC&q~ASW*4QyBm;4fPL|FSts1BT zqzAIc*-X} zPj+8vi7bb5uvGK_Vd-?41$Komp~?J~%7~BXUl5i?RHFRM^=EgBWb!4D^(EbW4M_KY z3$6m04tszM|0?hmkooxyxB|Wgmw{AN3Y0t~EZo=%+db1q3Y8XTZ`ISb_(%_k?$)^H z2y%gR?C-&Ca0mPVGQd4>7v$7b{t;TY+-*f8kFuH?F+zL6r@W-amV20TZ&R*LHDLA4 z2(4m$QRTOjAP@+Gf!v}B0dk{JxHXVFRrP^R?vh(mO+}2;j)*Q3~EkRc`ZlqQ{ zzZ7z76gHpDzpCxH<{+p(AE`~s8#^@e$MK>2Kg)7>NYaud{k;3K-@}u>SdtY}xT1P)f#%~^ zgVFKBW99W+b3ZDtU(fPrp*k+05`)w*$Zs}&mGL{|7=OLWQvZuterakY4k4dd4o!bP z_4||enyk)pcti}dI`Q&nTNiID^8L?QF=Z>MyHW}J{cU;UhnDkPIPg=J$0$|qRVuMe zg+qP^m}V^I#E7~9{d&9s4E3 z4$g8IzVq5*Er*kFzNu?<_%of~5;87YM};iW+G^G6s+mi)YW&e=%Mz^|gM4m@R^8pW zuKDMS`D*`K8oWZSUIXn>i&kjG)br~!FaDGoxC-gEihWJ_PnMwe>!8cs@YRLo%r9ksh7ICj5fWbeivG%?x#XKR4vN#lQ+W^ zr{Zt7c&F*DDtV4d3xkXneyOgm!|%TGY=o?SF>7dbkH&w3IrS~gM=Kh>^!0!3i*ah& z>!f@|Eq|SESgp>8wN0hJu0`@E*3jizGtIk!I{SuJU%mf^)~=NOGP~!Jzxrn4-eTzt zHGM60$=gtse?uE&W>r(|d_(JOkDbxseV^_vtG0iMtZ0vb{f;}|Sv{JRcJ+}-N{gkO zVjk6Jh2~@DZOjY1>6$fgP9sy_VEYYv@Bg~)QT!J@)3Q$ub0_&@*P_(VDh*qn6~km) z$?WL2(ir-`OdKPs+0$ccwBuh|w?pk%MISy<&ehELf{j$E)eM5gX8p`5m^rbmiEeg_ z-IDC{?CFC4I+^~Oj?urjMzd@6%!!>@@L%)EzR;}s_fZq`{U`4SJZKb>a(IsB3}S@^ zvBIn|G*@Ewx;~Y;U)iSS_Iq=l-;2(^m5XKd_cPXaeKl=8)9}BzBs!~FZ;_+9GVH$n zAC`vME&t1!`|=lSqta%VSxeC_$y$rqJLu`b%-#+5bhhf7J=s(J`j=H;?T&xbZ9FgX zAMO|FYWZn8@`38TiLKXu;bYS9h4;MQe5--xtjpD3CPtp-I~Q-#N;$QX&DGYowOd>~ zlhfE?PN-8tSCn%z^rH$EnxtZcW~r3T>~{9M77snF@o>x0j5S_PjuadR_@zdj!+|Gp z?+9h6M%!3g?$)a87M50()++xtu2%LtA1`blvhaiD=@DLRmbGhgO>so2*e#@+q4sZu z)~eNF?NoaqKl=@lzs+w{Vd^KzD52fekyrNivG#AKMvCgYox5^x zsrlQr`@!}*k`~o^A*u4a4eJ$k?(Ay(wL0_VL{oQ-i#r;2A*iTSFGw!zSJk#192$Ff zGtUWeb6fuT&bo>(8TFTV$6)nj2S*_Lbx>b_y+37B(7t-o?PS12vR|EbG*<`J{8sFt zte7`c|Z@&e0Ivx_8Zn%#pcTnw)_bt( zDB4sNl|kWC-_>$D7puU#neiLcO5%g<_hQ{#I`+rao?rH6Woo}8Y>wC4L;4n~KQSxj zs>&c~i2ahVgVz^rC{m)uuUQWEd&-Xfl33=YH@oUtF@8N&gZG%`_KU_s+MaxG_2PFX zWqHg{Bi|!)`*oaQKTK|)zu@e1UThaMtPY3&e&!pd-hJ}@kGV@8uap&2r=R*jGH-{6 zFJ~Z6u8xDo%sDnN%VV&*Pv$(snP(>>_cngq6iLiE_WZ!%ryp=yw97cIJUE1;x?Xg0P z`g1o&+DW^$!sVO97*CEQ+V4Rfbjh>5Ye3lbg3d0)$ZuF`)iJFk#TKI2r}JPF|NW#^ z-P_JZ72L1YRa5t9?`eTURmff~h>Kz5LF(I7O%rOTQub19``x4~Uf$be!{hvKv0y{k zn%NTus&hE-jPV`uv)^60Jh|crk@|Z19f-b>g*{u9d7t@hzwPpc+2tnHd-sdRXunM}uI;d{{hCal zW#nfZu&=6n_?5C>zj1ucy=T>8AeED5x{yvJd-!xl4uuYee)2_U2Rg@VpRqfx$z&}ul?)QhA zznPk8`CbZx?5kNtTWwJbKF~^rJhLT6*ZunfsruK_guJop;Rjk`|rBm_&TGiF>n18F<3Rt~FV&st`P8rV*`qCpv3@oCKm~k6)%~Zamp;-B`LBpvyM4XUYk@WEalc7D{z!Y5FI4ULm`nV>pA6q9=TY|2 z$BE{8-&C&0n%`zUsc2+mG%EKr)!`@$a@jO)wXvby$y?Sp&-6+`Mmr3TUTOwe$dLi=kwg?KUohrNCKvnoS6R7$EuTTkeMSg`xsL`THRw1o>gm-%i+k{ z#{aslnoC{wc-d^#o5#sFT# zk+54;%6_AE-1}WOZPp6hC;PhQy0b1!)3l!sGRl!r^ixCZd$y9EP$P7zTQ%|wnN+hXerx@H?N$MoFy_z1K(#ize zucYnr<)o_BON9;6oQ-+rq97&w`)Su#Me^&9pZ#{*9gDVU`4*mbP_RsGs>J!{u+ez>HqfzMpi|8r7>%H9WU3->{?H33a z@LKV0-=I>7R>sV<>FWMjZ8?ohI!8s5Rn#}o26a%Z9qQoM(0=vvIr6YyTI@6HXz~X= zQmB+DzID{*lQP}eW2M)+sDMvsz5SZzQP01$ z=|Dnwe#?P@oTj31;0rjD#m|2IbJ0e@jmI52^QGlSFC9@^a0s^F8|^AQcGs%q&F%5w z;9N*+pbgj55aX~9>_M|l#Ca8O#(J{YW z(n`2USw~me1ue+We$92U1ry8tdFj;GS$VHh?JrO*`#slrz74gaj4`gQT70U7{zd3Mg~pnZUR+UIs6scGuGTbFUtQGN$y}&$iHrDO;%rmu zb4DwgE2s{8w3)1y@?6nMtD0ZfUFs_}FQJ~?Lf!g>*3QaF`FzPtbG@z-uW~eQ`z7xE z)U+?TZ)cXIZb`gZdM#DwE4!l0?f9IRwdsUewMG|F4Rz+4<}!1!7K02S-FjZ#zif1C z9$6S>AF7D2?fxvTMt-ezG}ld}Nwtku*h|UW2-LzWq%dn|HC$b~qSdpP-x(F~4Wni( z#yU^=?Yym($?Vrr-)SXP%>;hbzDkbfBG#-WWpzoAU-~NZc(Zos_;K-n_A1RRVQ-L* z{#Bj2$}n-u_|{bpjsMfg1X&|v51L&Mz7WlB?i*~s7kucD77r(U6TZioj>fHqf3yDS zQDd(ys-|7zX!MLRNKp50vzG2$(|B>HF{!f66uUk?N;-nCJh2Tu-Cr5sk+Hc=s>;`y z8^(s`tHxeu0$ICWoVt9S{o&tlK}N}u+z;$4v)Y_$#nh`em`Z=Cl=|`pqiB}E<-ZpD zpr8HD_CD>t>73rNY9nj)vZ)x;r??keL9&{HE8jLF`jS2fNOT-$UVYmOKC$i5|zVFE>*6~}MYF?USO1BQ#aq)M4VYdH5xJG&Ia8$D2 z+n#^@$#oso<^D#cgXJ<|zq!5L2ira46aMr!k{Wj@12(D!ceoVBtMzv{36|WXzQXjg z-{?N`$-66l_bE1l1aj5jcZ8X$!1qknXWh;T467Mb+wrVBFJ{cosy9iWd5^?&FzzVH z?;o4+EA^}!HKreTTfX^T^9gy@JuK7XSvRlD816^a%%FwOy6a?m7`K*Wg7E$nyF z>*3#57@G7t|J4Ym7NeoLdaAADDG#syd?%nvzUO;ioC&cJX9l}1!^=7&NtP3@!N$TkD(+s zI)CNx8@=9&mwWc1HG|mbE~x!DgxtlUBo49dLh??Ji)13&4&HmsM+Yw5o}(%(-f$`_ zrp#WI|3@Z!C?1mbo#p#K|LVrt*Rwo2szyIjiJ_{O&~P>GM>e-Z>hObheX~!CNbV-&*eU< zZv3QGbC*17{;s}N`ToqsZjy@pnIRc{%$yRTJ!1ylUtP1N40uqe?B8?LG#vPyYPtCB z!%t@2)VUvg_ua0C#;h~_zEEdy2zh{m-0t06Yrx?Pb7CD?85LlJq}ILx=R0rMvwTlh zOs(VUxnG!dZSjz&Nv528^2vnJXNF{Xu#|q$>Qphl5yv&|8uLAMcl_b rU-(t4QNHNFsQ96AwE}7x9Kc;0{;6`Ka+FgAO6JJ7bBS+`B5D5zp(X|& delta 37290 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 S9W$RO^{ei { const token = localStorage.getItem("loginToken"); @@ -44,6 +45,7 @@ setup().then(() => { + diff --git a/src/views/profile/Profile.tsx b/src/views/profile/Profile.tsx new file mode 100644 index 0000000..85beb85 --- /dev/null +++ b/src/views/profile/Profile.tsx @@ -0,0 +1,39 @@ +import { useMemo } from "react"; +import { useWSQuery } from "../../hooks"; +import dayjs from "dayjs"; + +const Profile: React.FC = () => { + const { data: heatmap } = useWSQuery("user.getHeatmap", { id: "Gordon" }); + const now = useMemo(() => dayjs(), []); + const firstOfYear = useMemo( + () => now.set("day", 0).set("month", 0).set("hour", 0).set("minute", 0), + [now], + ); + const weeks = now.diff(firstOfYear, "weeks") + 1; + const maxHeat = heatmap ? Math.max(...heatmap) : 0; + + return ( +
+ {heatmap && ( +
+ {Array.from({ length: weeks }).map((_, w) => ( +
+ {Array.from({ length: 7 }).map((_, d) => ( +
+
+
+ ))} +
+ ))} +
+ )} +
+ ); +}; + +export default Profile;