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
;
+}
+
+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==