From ac60d08ee5e71e1347d4261bbdaf005c7d7db8ea Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 28 Jul 2024 19:51:14 -0700 Subject: [PATCH] Sessions page. --- next.config.js | 2 +- package.json | 2 + .../websites/[websiteId]/WebsiteHeader.tsx | 12 +- .../[websiteId]/sessions/SessionsTable.tsx | 13 +- .../[sessionId]/SessionDetailsPage.module.css | 3 + .../[sessionId]/SessionDetailsPage.tsx | 27 +++ .../sessions/[sessionId]/SessionInfo.tsx | 17 ++ .../[websiteId]/sessions/[sessionId]/page.tsx | 10 + src/components/common/Profile.tsx | 41 ++++ src/components/hooks/index.ts | 1 + src/components/hooks/queries/useSession.ts | 14 ++ src/components/hooks/queries/useSessions.ts | 2 +- .../[websiteId]/sessions/[sessionId].ts | 42 ++++ .../{sessions.ts => sessions/index.ts} | 0 src/queries/analytics/sessions/getSession.ts | 41 +++- src/queries/analytics/sessions/getSessions.ts | 8 +- yarn.lock | 195 +++++++++++++++++- 17 files changed, 414 insertions(+), 16 deletions(-) create mode 100644 src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.module.css create mode 100644 src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx create mode 100644 src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx create mode 100644 src/app/(main)/websites/[websiteId]/sessions/[sessionId]/page.tsx create mode 100644 src/components/common/Profile.tsx create mode 100644 src/components/hooks/queries/useSession.ts create mode 100644 src/pages/api/websites/[websiteId]/sessions/[sessionId].ts rename src/pages/api/websites/[websiteId]/{sessions.ts => sessions/index.ts} (100%) diff --git a/next.config.js b/next.config.js index acb83a92..b610eb2c 100644 --- a/next.config.js +++ b/next.config.js @@ -17,7 +17,7 @@ const trackerScriptName = process.env.TRACKER_SCRIPT_NAME; const contentSecurityPolicy = [ `default-src 'self'`, - `img-src *`, + `img-src * data:`, `script-src 'self' 'unsafe-eval' 'unsafe-inline'`, `style-src 'self' 'unsafe-inline'`, `connect-src 'self' api.umami.is cloud.umami.is`, diff --git a/package.json b/package.json index f14e552d..f9513c43 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,8 @@ ], "dependencies": { "@clickhouse/client": "^1.3.0", + "@dicebear/collection": "^9.2.1", + "@dicebear/core": "^9.2.1", "@fontsource/inter": "^4.5.15", "@prisma/client": "5.16.2", "@prisma/extension-read-replicas": "^0.3.0", diff --git a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx index 36578336..bdf0ba7d 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx @@ -45,11 +45,11 @@ export function WebsiteHeader({ icon: , path: '/reports', }, - // { - // label: formatMessage(labels.sessions), - // icon: , - // path: '/sessions', - // }, + { + label: formatMessage(labels.sessions), + icon: , + path: '/sessions', + }, { label: formatMessage(labels.events), icon: , @@ -69,7 +69,7 @@ export function WebsiteHeader({
{links.map(({ label, icon, path }) => { const selected = path - ? pathname.endsWith(path) + ? pathname.includes(path) : pathname.match(/^\/websites\/[\w-]+$/); return ( diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx index d5ca439a..7bae8b68 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx @@ -1,6 +1,8 @@ +import Link from 'next/link'; import { GridColumn, GridTable, useBreakpoint } from 'react-basics'; import { useFormat, useMessages } from 'components/hooks'; import { formatDistanceToNow } from 'date-fns'; +import Profile from 'components/common/Profile'; export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) { const { formatMessage, labels } = useMessages(); @@ -9,7 +11,16 @@ export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean return ( - + + {row => } + + + {row => ( + + {row.id} ({row.firstAt !== row.lastAt ? 'YES' : 'NO'}) + + )} + {row => formatValue(row.country, 'country')} diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.module.css b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.module.css new file mode 100644 index 00000000..2f71e9a8 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.module.css @@ -0,0 +1,3 @@ +.page { + display: grid; +} diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx new file mode 100644 index 00000000..fc6945ce --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx @@ -0,0 +1,27 @@ +'use client'; +import WebsiteHeader from '../../WebsiteHeader'; +import SessionInfo from './SessionInfo'; +import { useSession } from 'components/hooks'; +import { Loading } from 'react-basics'; +import styles from './SessionDetailsPage.module.css'; + +export default function SessionDetailsPage({ + websiteId, + sessionId, +}: { + websiteId: string; + sessionId: string; +}) { + const { data, isLoading } = useSession(websiteId, sessionId); + + if (isLoading) { + return ; + } + + return ( +
+ + +
+ ); +} diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx new file mode 100644 index 00000000..051703a3 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx @@ -0,0 +1,17 @@ +import Profile from 'components/common/Profile'; + +export default function SessionInfo({ data }) { + return ( +

+ +
+
ID
+
{data?.id}
+
Country
+
{data?.country}
+
City
+
{data?.city}
+
+

+ ); +} diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/page.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/page.tsx new file mode 100644 index 00000000..952e8cc5 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/page.tsx @@ -0,0 +1,10 @@ +import SessionDetailsPage from './SessionDetailsPage'; +import { Metadata } from 'next'; + +export default function WebsitePage({ params: { websiteId, sessionId } }) { + return ; +} + +export const metadata: Metadata = { + title: 'Websites', +}; diff --git a/src/components/common/Profile.tsx b/src/components/common/Profile.tsx new file mode 100644 index 00000000..63a59c16 --- /dev/null +++ b/src/components/common/Profile.tsx @@ -0,0 +1,41 @@ +import { useMemo } from 'react'; +import { createAvatar } from '@dicebear/core'; +import { lorelei } from '@dicebear/collection'; +import md5 from 'md5'; + +const lib = lorelei; + +function convertToPastel(hexColor: string, pastelFactor: number = 0.5) { + // Remove the # if present + hexColor = hexColor.replace(/^#/, ''); + + // Convert hex to RGB + let r = parseInt(hexColor.substr(0, 2), 16); + let g = parseInt(hexColor.substr(2, 2), 16); + let b = parseInt(hexColor.substr(4, 2), 16); + + // Calculate pastel version (mix with white) + //const pastelFactor = 0.5; // Adjust this value to control pastel intensity + + r = Math.floor((r + 255 * pastelFactor) / (1 + pastelFactor)); + g = Math.floor((g + 255 * pastelFactor) / (1 + pastelFactor)); + b = Math.floor((b + 255 * pastelFactor) / (1 + pastelFactor)); + + // Convert back to hex + return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`; +} + +function Profile({ seed, size = 128, ...props }: { seed: string; size?: number }) { + const avatar = useMemo(() => { + return createAvatar(lib, { + ...props, + seed, + size, + backgroundColor: [convertToPastel(md5(seed).substring(0, 6), 2).replace(/^#/, '')], + }).toDataUri(); + }, []); + + return Avatar; +} + +export default Profile; diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts index 86abdd84..b0271ff2 100644 --- a/src/components/hooks/index.ts +++ b/src/components/hooks/index.ts @@ -5,6 +5,7 @@ export * from './queries/useLogin'; export * from './queries/useRealtime'; export * from './queries/useReport'; export * from './queries/useReports'; +export * from './queries/useSession'; export * from './queries/useSessions'; export * from './queries/useShareToken'; export * from './queries/useTeam'; diff --git a/src/components/hooks/queries/useSession.ts b/src/components/hooks/queries/useSession.ts new file mode 100644 index 00000000..f65efcf4 --- /dev/null +++ b/src/components/hooks/queries/useSession.ts @@ -0,0 +1,14 @@ +import { useApi } from './useApi'; + +export function useSession(websiteId: string, sessionId: string) { + const { get, useQuery } = useApi(); + + return useQuery({ + queryKey: ['session', { websiteId, sessionId }], + queryFn: () => { + return get(`/websites/${websiteId}/sessions/${sessionId}`); + }, + }); +} + +export default useSession; diff --git a/src/components/hooks/queries/useSessions.ts b/src/components/hooks/queries/useSessions.ts index c54c3acd..91f4ea4c 100644 --- a/src/components/hooks/queries/useSessions.ts +++ b/src/components/hooks/queries/useSessions.ts @@ -4,7 +4,7 @@ import useModified from '../useModified'; export function useSessions(websiteId: string, params?: { [key: string]: string | number }) { const { get } = useApi(); - const { modified } = useModified(`websites`); + const { modified } = useModified(`sessions`); return useFilterQuery({ queryKey: ['sessions', { websiteId, modified, ...params }], diff --git a/src/pages/api/websites/[websiteId]/sessions/[sessionId].ts b/src/pages/api/websites/[websiteId]/sessions/[sessionId].ts new file mode 100644 index 00000000..57e99ea3 --- /dev/null +++ b/src/pages/api/websites/[websiteId]/sessions/[sessionId].ts @@ -0,0 +1,42 @@ +import * as yup from 'yup'; +import { canViewWebsite } from 'lib/auth'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody, PageParams } from 'lib/types'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { getSession } from 'queries'; + +export interface ReportsRequestQuery extends PageParams { + websiteId: string; + sessionId: string; +} + +const schema = { + GET: yup.object().shape({ + websiteId: yup.string().uuid().required(), + sessionId: yup.string().uuid().required(), + }), +}; + +export default async ( + req: NextApiRequestQueryBody, + res: NextApiResponse, +) => { + await useCors(req, res); + await useAuth(req, res); + await useValidate(schema, req, res); + + const { websiteId, sessionId } = req.query; + + if (req.method === 'GET') { + if (!(await canViewWebsite(req.auth, websiteId))) { + return unauthorized(res); + } + + const data = await getSession(websiteId, sessionId); + + return ok(res, data); + } + + return methodNotAllowed(res); +}; diff --git a/src/pages/api/websites/[websiteId]/sessions.ts b/src/pages/api/websites/[websiteId]/sessions/index.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions.ts rename to src/pages/api/websites/[websiteId]/sessions/index.ts diff --git a/src/queries/analytics/sessions/getSession.ts b/src/queries/analytics/sessions/getSession.ts index 256ada4c..2c008e30 100644 --- a/src/queries/analytics/sessions/getSession.ts +++ b/src/queries/analytics/sessions/getSession.ts @@ -1,9 +1,46 @@ import prisma from 'lib/prisma'; +import clickhouse from 'lib/clickhouse'; +import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db'; -export async function getSession(id: string) { +export async function getSession(...args: [websiteId: string, sessionId: string]) { + return runQuery({ + [PRISMA]: () => relationalQuery(...args), + [CLICKHOUSE]: () => clickhouseQuery(...args), + }); +} + +async function relationalQuery(websiteId: string, sessionId: string) { return prisma.client.session.findUnique({ where: { - id, + id: sessionId, }, }); } + +async function clickhouseQuery(websiteId: string, sessionId: string) { + const { rawQuery } = clickhouse; + + return rawQuery( + ` + select + session_id as id, + website_id as websiteId, + min(created_at) as firstAt, + max(created_at) as lastAt, + hostname, + browser, + os, + device, + screen, + language, + country, + subdivision1, + city + from website_event + where website_id = {websiteId:UUID} + and session_id = {sessionId:UUID} + group by session_id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, city + `, + { websiteId, sessionId }, + ).then(result => result?.[0]); +} diff --git a/src/queries/analytics/sessions/getSessions.ts b/src/queries/analytics/sessions/getSessions.ts index 538133ba..e7c95963 100644 --- a/src/queries/analytics/sessions/getSessions.ts +++ b/src/queries/analytics/sessions/getSessions.ts @@ -24,7 +24,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar } async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { - const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse; + const { pagedQuery, parseFilters } = clickhouse; const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters); return pagedQuery( @@ -32,7 +32,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar select session_id as id, website_id as websiteId, - ${getDateStringSQL('created_at', 'second', filters.timezone)} as createdAt, + min(created_at) as createdAt, hostname, browser, os, @@ -41,13 +41,13 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar language, country, subdivision1, - subdivision2, city from website_event where website_id = {websiteId:UUID} ${dateQuery} ${filterQuery} - order by created_at desc + group by session_id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, city + order by createdAt desc `, params, pageParams, diff --git a/yarn.lock b/yarn.lock index 1fe40135..470cad8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1381,6 +1381,199 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@dicebear/adventurer-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/adventurer-neutral/-/adventurer-neutral-9.2.1.tgz#0416ff71d0dd3ff391db9c9fda2acb166b074c5f" + integrity sha512-iP6Tc6CgrJt63j08i/hlyNiGEbDNgP9Ws6WKT9n/0oTU9X/DKLncGStV3uhgYPIOVQE/tw9a/GjbGjrwBlN8CQ== + +"@dicebear/adventurer@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/adventurer/-/adventurer-9.2.1.tgz#3d522d2aaabe17d172ea2302bfdf62d601ebcd64" + integrity sha512-utJr8oEOwPy9y+7rIOnB7mls+2XQrc3Kdlx/ay9KBY/HEUMnwMoN/GJhg4HcyGnV+DS7VhN6JSrnwwD9+SQyBw== + +"@dicebear/avataaars-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/avataaars-neutral/-/avataaars-neutral-9.2.1.tgz#61d3894f4d08d9ee3722f3a64c36725729c6aaf3" + integrity sha512-yceQMVBLimAHgZDL8VKCDGNs5JQ8BERaUMNIJXXRKEYZXlofoXZpYtcWPKQY9lmRJJznO1GX7ZK12ILnZjRPBQ== + +"@dicebear/avataaars@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/avataaars/-/avataaars-9.2.1.tgz#2460fb0d7d364a12e546b40b6e56c220db71c851" + integrity sha512-WIZL7CWSsmzLswY/4ZrgtE/7EvnaNrYreLyT8hjiGyVb9J4cQaVZXSMuDIGFa5wT062AW/4/i82kh/7nh0oL+w== + +"@dicebear/big-ears-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/big-ears-neutral/-/big-ears-neutral-9.2.1.tgz#dfee12787f8a0efa7b3552fca5e9f24c9102fc44" + integrity sha512-98qOCFEhbqCHeyO7ZXBAMMov8bquZt8vhtjj0YeHjGjI/OEWbA2gxq2ryv1BHSehVc/vTrd1KbHag7yYoeCDuw== + +"@dicebear/big-ears@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/big-ears/-/big-ears-9.2.1.tgz#4a7f2f3d987c11d76602b6ee8b398f9dcd2ce486" + integrity sha512-BUVTonwSYiGKcnk8wdwUHZ1b34GhfzRpG1kguK4kWAKlayBq7Q+iDJlmk4Bch0XdDQc2bqFf1GQCCj+xXWRHyg== + +"@dicebear/big-smile@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/big-smile/-/big-smile-9.2.1.tgz#d6cf95d34d65a393901925df370a1afbd0b0ae76" + integrity sha512-bspur+wtnlv/Z4QDvRWg9rs3snf+iuBkamkgw4nZOUFKMlZdPQGqNoh1DkycRcLXNX1Q61KM172K6bS60ZlKxw== + +"@dicebear/bottts-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/bottts-neutral/-/bottts-neutral-9.2.1.tgz#cc89b1ed834b7e8b7163dee2928dc1aa74077f75" + integrity sha512-uwd+xcbRQUIHKQ1iEiLjf5RwCaVzOfBgIu2WRE+6MUaahYi6cJ0eJAs0h1q+zpgYyvqPDPDAi9j7AUwjmig0GA== + +"@dicebear/bottts@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/bottts/-/bottts-9.2.1.tgz#922ccdba942d8c2c15655be0b6f25f4e9691cb80" + integrity sha512-AQQ/WKd54G9sa+TkQptcu6c+Tjfc9hitgB70uA5GqJe+w6Bal+gwY6kPm5sJ1CY2mk/UBh1rXBuauQZ25bgTcQ== + +"@dicebear/collection@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/collection/-/collection-9.2.1.tgz#b2de84ef654ac1550458a17049b02a0013213b92" + integrity sha512-Su1eygO8llKuJ68N+xhBCzBN2Lqrsx9ZNdlvfZeH/s70RjL0raNQaI6/hRABDmlbLYwW4AjRh2lOgDdGfCp5DQ== + dependencies: + "@dicebear/adventurer" "9.2.1" + "@dicebear/adventurer-neutral" "9.2.1" + "@dicebear/avataaars" "9.2.1" + "@dicebear/avataaars-neutral" "9.2.1" + "@dicebear/big-ears" "9.2.1" + "@dicebear/big-ears-neutral" "9.2.1" + "@dicebear/big-smile" "9.2.1" + "@dicebear/bottts" "9.2.1" + "@dicebear/bottts-neutral" "9.2.1" + "@dicebear/croodles" "9.2.1" + "@dicebear/croodles-neutral" "9.2.1" + "@dicebear/dylan" "9.2.1" + "@dicebear/fun-emoji" "9.2.1" + "@dicebear/glass" "9.2.1" + "@dicebear/icons" "9.2.1" + "@dicebear/identicon" "9.2.1" + "@dicebear/initials" "9.2.1" + "@dicebear/lorelei" "9.2.1" + "@dicebear/lorelei-neutral" "9.2.1" + "@dicebear/micah" "9.2.1" + "@dicebear/miniavs" "9.2.1" + "@dicebear/notionists" "9.2.1" + "@dicebear/notionists-neutral" "9.2.1" + "@dicebear/open-peeps" "9.2.1" + "@dicebear/personas" "9.2.1" + "@dicebear/pixel-art" "9.2.1" + "@dicebear/pixel-art-neutral" "9.2.1" + "@dicebear/rings" "9.2.1" + "@dicebear/shapes" "9.2.1" + "@dicebear/thumbs" "9.2.1" + +"@dicebear/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/core/-/core-9.2.1.tgz#b93800ac7e21ae955cceaa2370e7dc033a3cc557" + integrity sha512-Y3E59+3xO2UWKdf3Zt/rwMdy9r7uccTgM89Kv8aXN1vmdrnA4YYmr4jslRRRqPLVpenuT4105nkboC4rMnDgHw== + dependencies: + "@types/json-schema" "^7.0.11" + +"@dicebear/croodles-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/croodles-neutral/-/croodles-neutral-9.2.1.tgz#379646cf53ba4d61b6c94c13b3ec3386359e3541" + integrity sha512-2iyr+B/y795P7cSIpFg4RjxUu6kljesKjtepvMzfeBR9xKyI84exBNHRoCTEVwOCFePmlPJX1qtw/YWM0sAPJw== + +"@dicebear/croodles@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/croodles/-/croodles-9.2.1.tgz#74fa6aa8c0ee0dc303c0ad82fd78f5d6cd66610d" + integrity sha512-V7+m21BizYTGgLgxmh5dxHHADeD3gkeuPYkhKqP8Uu8jZFBgh5wKFqqfVI/XSQkx/+lRla5c6l55mymgjt4k8Q== + +"@dicebear/dylan@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/dylan/-/dylan-9.2.1.tgz#997aff74b4bf112e4895463eeea0adc2456d332f" + integrity sha512-UeKz3Gxprh4bJ73Q2DjDpmjt854G3xfakc5KfeBmPV25EP+al7HCsM/HE+ZgKTSh+PPz5/mVtZQYU40pTzJEyg== + +"@dicebear/fun-emoji@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/fun-emoji/-/fun-emoji-9.2.1.tgz#e88c8c2db7927d732ef2d0af3a24c4d4152a7f02" + integrity sha512-F08p+Ggdxo4Ryji+3aCJXAKnjx4rM4UMtrJU4eA2t8lAkpwFNgfGK6mpMYPnxmKULYljGOgySmw7AyWcbX8s2Q== + +"@dicebear/glass@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/glass/-/glass-9.2.1.tgz#61bc231a5b0bd8d15cfa94c76ac608ab8fe98ac1" + integrity sha512-UoErQwg7/qkEKWyEDTyt8FYhw/aZryP0Tr7cwBEuxMXZ585NUTvEel0K5j9aDkBrimJVEM+jKzOFIIMAGLlR0g== + +"@dicebear/icons@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/icons/-/icons-9.2.1.tgz#0ef78e8ff742bd9985a3c36cd9cbd2705449e789" + integrity sha512-0VuWohGMiv4n1nxwehYi6w+PIT9OBRlV721yNoewQWgQCrnMKBvM0cFRX9Dtg+MvwLMslQCIU3pEauEZ5FNmFA== + +"@dicebear/identicon@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/identicon/-/identicon-9.2.1.tgz#aebd7b692de9a6601746ab47711359f81471841c" + integrity sha512-Dlqpn3tzqimR8KPIRkSJCKd5XwKgTLVXzT5KiY+2ysMZZQh4uJvBjVfY5SLrHDHC2a42W6EdwQxU6tFTRiKQuQ== + +"@dicebear/initials@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/initials/-/initials-9.2.1.tgz#d4e435c18e48837f97086ba5210d4742594ba181" + integrity sha512-d6Shnt1LiCf9yAEck3y/w4pXG4bWYVjBFCeI43l0BAR39Mk2Dq05UEFZH5Dtj2kyfNozMjh6vG1cQyBigtamug== + +"@dicebear/lorelei-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/lorelei-neutral/-/lorelei-neutral-9.2.1.tgz#5d5d6400c0e2232081f58f8d82efff00ee307b81" + integrity sha512-4YkkR697qXAYxN5N/zVsRe955QLhw0yLib2CzeBga1QXXMIkywq2nRFa3fr4toSRPl45kl1eF8J5HC17CU9inw== + +"@dicebear/lorelei@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/lorelei/-/lorelei-9.2.1.tgz#b23ddb02b098578ef1bcb121c7a638795f3de89e" + integrity sha512-DNjZpUpe/CxKK8Byn1meBvRz/NJWtBizcoS2DzIIyqPYOwA5cLIa2g/qKkESvXzU9naEMkiHfMZb1RYYzN2FAA== + +"@dicebear/micah@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/micah/-/micah-9.2.1.tgz#2e65fdb15b6ee6338123329c92e5afb98f694718" + integrity sha512-FK91igiVpPNhGCsfGpOgwYFKRP+FNR1V45Z4Tg/f82ux9TBdTmeoIfkgwrfhcXmCgagoYg2EAY+L72stUVapcA== + +"@dicebear/miniavs@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/miniavs/-/miniavs-9.2.1.tgz#de1e571663775b4b2a30944d8033d6a8b972aca3" + integrity sha512-r0TcaSrKJDPMqMYIiXNArq9i//cZzA1yuiXJw46iTloBDTh7yL1tpnL84CDxMpQ+OZLeMiRA6jVBx0coer4vmg== + +"@dicebear/notionists-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/notionists-neutral/-/notionists-neutral-9.2.1.tgz#2e77ae7330201628c4b3e4789b1aa83ffc684b82" + integrity sha512-Vi/FwMXzc1m/U2TjBnY9NHedoLbPc3BBsNQL8jPU27wdkXoyJHuXBevcUtsF0Zf8OuRbNpZKPbfYy6OYBr9qvw== + +"@dicebear/notionists@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/notionists/-/notionists-9.2.1.tgz#70aa14e8846a49096648f891a625a92b04513c75" + integrity sha512-oAyvPlp3xfFnDpW3nXhdAPGVm5WYj6VW6RgdzLAHoRO2EOYDNkQruIXd+d8JYo1DMTLUbgp3onr5AF9UU2OBzw== + +"@dicebear/open-peeps@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/open-peeps/-/open-peeps-9.2.1.tgz#dd9be6721ff7226e3a0286aec699fd6b0e0ad2b2" + integrity sha512-oPA/ljbPtuj2cdM0QtyJu2i24AaEMTIIk/FJbnrBK765WPnQcCZh84w+ZuInTMIfF9gYszNY34gaRD8Z6UiYxQ== + +"@dicebear/personas@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/personas/-/personas-9.2.1.tgz#dfa8291ebba098e8cebdeb04a4fba9e4384c4496" + integrity sha512-OgtyT9dnY8U60sUo0SLKCFVt0+dIr3a4vR0bDs/zwK4Qb/yTdB1VPdfxq0Fwk2q1vfn9YgbDrb0YYRgMRl60qQ== + +"@dicebear/pixel-art-neutral@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/pixel-art-neutral/-/pixel-art-neutral-9.2.1.tgz#457b4e038c9516e75d41c6c1efd9ff9d4099162a" + integrity sha512-GUtxJYX7/9XDgSZhkx24PB+yLcKkLHblDldvRr5xGlGxhgAovTBQFHLgCJxmUJgIaNW7pvSWCw7txguyxbBN9A== + +"@dicebear/pixel-art@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/pixel-art/-/pixel-art-9.2.1.tgz#e50bcc300a43c16ad3f69de1b6bf3dad92528274" + integrity sha512-ftKPKCvnS1cJ2OvuQLmtEIwdb9PzF5C2ofWBdVI/RFvhH1BhYc3OsdQ28o90+ZJQO4fivKwfsh8MPUTaqToQ7Q== + +"@dicebear/rings@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/rings/-/rings-9.2.1.tgz#ed6e930c02b09ebbd774fc104cffc5c23ffa6b4c" + integrity sha512-BlFYCaKB+wdpWWS28ZnQ/MvHeuNSRvkvWRoiw7pgS653LXx4kz/erVMmeVMSAr82y4BV+K8He2Rl2dMjuLyrXw== + +"@dicebear/shapes@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/shapes/-/shapes-9.2.1.tgz#e076b9aabd67bf611e9bbf7149919000e3d74197" + integrity sha512-cQzTcYimtuiAun55uPdIIhK53QTyjWqF/YN7LqEBGBqrJuGqHZBm1HXCcj7wPpoQ3zSy/2u8Rp0Etv7+5XFzyw== + +"@dicebear/thumbs@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@dicebear/thumbs/-/thumbs-9.2.1.tgz#174be721256e5ff97e19586a54bde8fd25468e74" + integrity sha512-ziX5HFmhiApO2k7QKj41+dGXbMdmQUUgFBYPyzTwnubhkDldJk7tpRoa5u2OsyTVDQCcPMv5mFSQpbANfmFwMg== + "@esbuild/android-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" @@ -2600,7 +2793,7 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.12": +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.12": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==