From 20eb679842e006becc84b7f730106b1640c6c9df Mon Sep 17 00:00:00 2001 From: Shubham Palriwala Date: Tue, 2 Apr 2024 20:16:43 +0530 Subject: [PATCH] feat: use redis for rate limiting & next caching to resolve memory issues (#2078) Co-authored-by: Matthias Nannt --- .github/workflows/kamal.yml | 3 +- apps/web/app/api/internal/cache/client.ts | 27 ++ apps/web/app/api/internal/cache/route.ts | 21 ++ apps/web/app/middleware/rateLimit.ts | 49 ++- apps/web/cache-handler.mjs | 27 ++ apps/web/middleware.ts | 18 +- apps/web/next.config.mjs | 3 + apps/web/package.json | 2 + docker-compose.yml | 3 + docker/docker-compose.yml | 3 + kamal/deploy.yml | 43 +-- packages/lib/constants.ts | 1 + packages/lib/env.ts | 2 + packages/lib/tsconfig.json | 10 +- pnpm-lock.yaml | 407 ++++++++++++++++++---- turbo.json | 1 + 16 files changed, 493 insertions(+), 127 deletions(-) create mode 100644 apps/web/app/api/internal/cache/client.ts create mode 100644 apps/web/app/api/internal/cache/route.ts create mode 100644 apps/web/cache-handler.mjs diff --git a/.github/workflows/kamal.yml b/.github/workflows/kamal.yml index e06d48628a..8e61a7a285 100644 --- a/.github/workflows/kamal.yml +++ b/.github/workflows/kamal.yml @@ -71,6 +71,7 @@ jobs: S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }} OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }} RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }} + REDIS_CLIENT_URL: ${{ vars.REDIS_CLIENT_URL }} KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} steps: @@ -108,7 +109,7 @@ jobs: run: | kamal() { command kamal "$@" -c kamal/deploy.yml; } set +e - DEPLOY_OUTPUT=$(kamal setup 2>&1) + DEPLOY_OUTPUT=$(kamal deploy 2>&1) DEPLOY_EXIT_CODE=$? echo "$DEPLOY_OUTPUT" if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then diff --git a/apps/web/app/api/internal/cache/client.ts b/apps/web/app/api/internal/cache/client.ts new file mode 100644 index 0000000000..7a2883621e --- /dev/null +++ b/apps/web/app/api/internal/cache/client.ts @@ -0,0 +1,27 @@ +import { createClient } from "redis"; + +import { REDIS_CLIENT_URL } from "@formbricks/lib/constants"; + +const client = createClient({ + url: REDIS_CLIENT_URL!, +}); +client.on("error", (err) => console.error("Redis Client Error", err)); +client.connect(); + +type Options = { + interval: number; + allowedPerInterval: number; +}; + +export const redisRateLimiter = (options: Options) => { + return async (token: string) => { + const tokenCount = await client.INCR(token); + if (tokenCount === 1) { + await client.EXPIRE(token, options.interval / 1000); + } + if (tokenCount > options.allowedPerInterval) { + throw new Error("Rate limit exceeded for IP: " + token); + } + return; + }; +}; diff --git a/apps/web/app/api/internal/cache/route.ts b/apps/web/app/api/internal/cache/route.ts new file mode 100644 index 0000000000..12b4459e53 --- /dev/null +++ b/apps/web/app/api/internal/cache/route.ts @@ -0,0 +1,21 @@ +import { redisRateLimiter } from "@/app/api/internal/cache/client"; +import { responses } from "@/app/lib/api/response"; +import { NextRequest } from "next/server"; + +export async function GET(request: NextRequest) { + const token = request.nextUrl.searchParams.get("token"); + const interval = parseInt(request.nextUrl.searchParams.get("interval") ?? "0"); + const allowedPerInterval = parseInt(request.nextUrl.searchParams.get("allowedPerInterval") ?? "0"); + if (!token) { + return responses.notAuthenticatedResponse(); + } + + try { + const rateLimiter = redisRateLimiter({ interval, allowedPerInterval }); + await rateLimiter(token); + + return responses.successResponse({ rateLimitExceeded: false }, true); + } catch (e) { + return responses.successResponse({ rateLimitExceeded: true }, true); + } +} diff --git a/apps/web/app/middleware/rateLimit.ts b/apps/web/app/middleware/rateLimit.ts index d1e882de87..2bc4481df4 100644 --- a/apps/web/app/middleware/rateLimit.ts +++ b/apps/web/app/middleware/rateLimit.ts @@ -1,28 +1,45 @@ import { LRUCache } from "lru-cache"; +import { REDIS_CLIENT_URL, WEBAPP_URL } from "@formbricks/lib/constants"; + type Options = { interval: number; allowedPerInterval: number; }; -export default function rateLimit(options: Options) { - const tokenCache = new LRUCache({ - max: 1000, // Max 1000 unique IP sessions per 15 minutes +const inMemoryRateLimiter = (options: Options) => { + const tokenCache = new LRUCache({ + max: 1000, ttl: options.interval, }); - return { - check: (token: string) => - new Promise((resolve, reject) => { - const tokenCount = (tokenCache.get(token) as number[]) || [0]; - if (tokenCount[0] === 0) { - tokenCache.set(token, tokenCount); - } - tokenCount[0] += 1; - - const currentUsage = tokenCount[0]; - const isRateLimited = currentUsage >= options.allowedPerInterval; - return isRateLimited ? reject() : resolve(); - }), + return async (token: string) => { + const currentUsage = tokenCache.get(token) || 0; + if (currentUsage >= options.allowedPerInterval) { + throw new Error("Rate limit exceeded"); + } + tokenCache.set(token, currentUsage + 1); }; +}; + +const redisRateLimiter = (options: Options) => { + return async (token: string) => { + const tokenCountResponse = await fetch( + `${WEBAPP_URL}/api/internal/cache?token=${token}&interval=${options.interval}&allowedPerInterval=${options.allowedPerInterval}` + ); + const { + data: { rateLimitExceeded }, + } = await tokenCountResponse.json(); + if (!tokenCountResponse.ok || rateLimitExceeded) { + throw new Error("Rate limit exceeded for IP: " + token); + } + }; +}; + +export default function rateLimit(options: Options) { + if (REDIS_CLIENT_URL) { + return redisRateLimiter(options); + } else { + return inMemoryRateLimiter(options); + } } diff --git a/apps/web/cache-handler.mjs b/apps/web/cache-handler.mjs new file mode 100644 index 0000000000..09b843132c --- /dev/null +++ b/apps/web/cache-handler.mjs @@ -0,0 +1,27 @@ +import { CacheHandler } from "@neshca/cache-handler"; +import createLruHandler from "@neshca/cache-handler/local-lru"; +import createRedisHandler from "@neshca/cache-handler/redis-strings"; +import { createClient } from "redis"; + +CacheHandler.onCreation(async () => { + let redisHandler; + if (process.env.REDIS_CLIENT_URL) { + const client = createClient({ + url: process.env.REDIS_CLIENT_URL, + }); + client.on("error", () => {}); + + await client.connect(); + redisHandler = createRedisHandler({ + client, + timeoutMs: 5000, + }); + } + + const localHandler = createLruHandler(); + return { + handlers: [redisHandler, localHandler], + }; +}); + +export default CacheHandler; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index df2edb4f30..5740561f71 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -35,7 +35,6 @@ export async function middleware(request: NextRequest) { return NextResponse.next(); } - const res = NextResponse.next(); let ip = request.ip ?? request.headers.get("x-real-ip"); const forwardedFor = request.headers.get("x-forwarded-for"); if (!ip && forwardedFor) { @@ -45,28 +44,27 @@ export async function middleware(request: NextRequest) { if (ip) { try { if (loginRoute(request.nextUrl.pathname)) { - await loginLimiter.check(ip); + await loginLimiter(`login-${ip}`); } else if (signupRoute(request.nextUrl.pathname)) { - await signUpLimiter.check(ip); + await signUpLimiter(`signup-${ip}`); } else if (clientSideApiRoute(request.nextUrl.pathname)) { - await clientSideApiEndpointsLimiter.check(ip); + await clientSideApiEndpointsLimiter(`client-side-api-${ip}`); const envIdAndUserId = isSyncWithUserIdentificationEndpoint(request.nextUrl.pathname); if (envIdAndUserId) { const { environmentId, userId } = envIdAndUserId; - await syncUserIdentificationLimiter.check(`${environmentId}-${userId}`); + await syncUserIdentificationLimiter(`sync-${environmentId}-${userId}`); } } else if (shareUrlRoute(request.nextUrl.pathname)) { - await shareUrlLimiter.check(ip); + await shareUrlLimiter(`share-${ip}`); } - return res; - } catch (_e) { + return NextResponse.next(); + } catch (e) { console.log(`Rate Limiting IP: ${ip}`); - return NextResponse.json({ error: "Too many requests, Please try after a while!" }, { status: 429 }); } } - return res; + return NextResponse.next(); } export const config = { diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index d8acf9cefe..3c34b900f2 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -1,9 +1,11 @@ import { createId } from "@paralleldrive/cuid2"; import { withSentryConfig } from "@sentry/nextjs"; import createJiti from "jiti"; +import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; const jiti = createJiti(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); jiti("@formbricks/lib/env"); @@ -24,6 +26,7 @@ const nextConfig = { "app/api/packages": ["../../packages/js-core/dist/*", "../../packages/surveys/dist/*"], }, }, + cacheHandler: process.env.VERCEL !== "1" ? require.resolve("./cache-handler.mjs") : undefined, transpilePackages: ["@formbricks/database", "@formbricks/ee", "@formbricks/ui", "@formbricks/lib"], images: { remotePatterns: [ diff --git a/apps/web/package.json b/apps/web/package.json index 4b423e66a9..ef13837817 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -59,6 +59,7 @@ "react-hook-form": "^7.51.0", "react-hot-toast": "^2.4.1", "react-icons": "^5.0.1", + "redis": "^4.6.13", "sharp": "^0.33.2", "ua-parser-js": "^1.0.37", "webpack": "^5.90.3", @@ -66,6 +67,7 @@ }, "devDependencies": { "@formbricks/tsconfig": "workspace:*", + "@neshca/cache-handler": "^1.0.7", "@types/bcryptjs": "^2.4.6", "@types/lodash": "^4.17.0", "@types/markdown-it": "^13.0.7", diff --git a/docker-compose.yml b/docker-compose.yml index c3ed356eab..b386991d89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,8 @@ x-webapp-url: &webapp_url http://localhost:3000 # PostgreSQL DB for Formbricks to connect to x-database-url: &database_url postgresql://postgres:postgres@postgres:5432/formbricks?schema=public +x-redis-url: &redis_url + # NextJS Auth # @see: https://next-auth.js.org/configuration/options#nextauth_secret # You can use: `openssl rand -hex 32` to generate one @@ -113,6 +115,7 @@ services: GOOGLE_CLIENT_ID: *google_client_id GOOGLE_CLIENT_SECRET: *google_client_secret CRON_SECRET: *cron_secret + REDIS_CLIENT_URL: *redis_url volumes: - uploads:/home/nextjs/apps/web/uploads/ diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4a846059d9..aa57fa28be 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -79,6 +79,9 @@ x-environment: &environment # Uncomment and set to 1 to skip onboarding for new users # ONBOARDING_DISABLED: 1 + # The below is used for Rate Limiting & Next Caching (uses In-Memory Next Cache if not provided) + # REDIS_CLIENT_URL: + services: postgres: restart: always diff --git a/kamal/deploy.yml b/kamal/deploy.yml index 1a81770d3c..56f5a9ed23 100644 --- a/kamal/deploy.yml +++ b/kamal/deploy.yml @@ -86,6 +86,7 @@ env: - S3_REGION - S3_BUCKET_NAME - RATE_LIMITING_DISABLED + - REDIS_CLIENT_URL # Use a different ssh user than root ssh: @@ -138,27 +139,27 @@ traefik: - CLOUDFLARE_EMAIL # Use accessory services (secrets come from .env). -# accessories: -# db: -# image: mysql:8.0 -# host: 192.168.0.2 -# port: 3306 -# env: -# clear: -# MYSQL_ROOT_HOST: '%' -# secret: -# - MYSQL_ROOT_PASSWORD -# files: -# - config/mysql/production.cnf:/etc/mysql/my.cnf -# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql -# directories: -# - data:/var/lib/mysql -# redis: -# image: redis:7.0 -# host: 192.168.0.2 -# port: 6379 -# directories: -# - data:/data +accessories: + # db: + # image: mysql:8.0 + # host: 192.168.0.2 + # port: 3306 + # env: + # clear: + # MYSQL_ROOT_HOST: '%' + # secret: + # - MYSQL_ROOT_PASSWORD + # files: + # - config/mysql/production.cnf:/etc/mysql/my.cnf + # - db/production.sql:/docker-entrypoint-initdb.d/setup.sql + # directories: + # - data:/var/lib/mysql + redis: + image: redis:7.0 + host: 18.196.187.144 + port: 6379 + directories: + - data:/data # Configure custom arguments for Traefik # traefik: diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 466c0a9a0c..0c4c26b4b1 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -172,6 +172,7 @@ export const DEBUG = env.DEBUG === "1"; // Enterprise License constant export const ENTERPRISE_LICENSE_KEY = env.ENTERPRISE_LICENSE_KEY; +export const REDIS_CLIENT_URL = env.REDIS_CLIENT_URL; export const RATE_LIMITING_DISABLED = env.RATE_LIMITING_DISABLED === "1"; export const CUSTOMER_IO_SITE_ID = env.CUSTOMER_IO_SITE_ID; diff --git a/packages/lib/env.ts b/packages/lib/env.ts index 1f23b82f9f..8f260e48a0 100644 --- a/packages/lib/env.ts +++ b/packages/lib/env.ts @@ -52,6 +52,7 @@ export const env = createEnv({ OIDC_SIGNING_ALGORITHM: z.string().optional(), OPENTELEMETRY_LISTENER_URL: z.string().optional(), ONBOARDING_DISABLED: z.enum(["1", "0"]).optional(), + REDIS_CLIENT_URL: z.string().optional(), PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(), PRIVACY_URL: z .string() @@ -155,6 +156,7 @@ export const env = createEnv({ OIDC_ISSUER: process.env.OIDC_ISSUER, OIDC_SIGNING_ALGORITHM: process.env.OIDC_SIGNING_ALGORITHM, ONBOARDING_DISABLED: process.env.ONBOARDING_DISABLED, + REDIS_CLIENT_URL: process.env.REDIS_CLIENT_URL, PASSWORD_RESET_DISABLED: process.env.PASSWORD_RESET_DISABLED, PRIVACY_URL: process.env.PRIVACY_URL, RATE_LIMITING_DISABLED: process.env.RATE_LIMITING_DISABLED, diff --git a/packages/lib/tsconfig.json b/packages/lib/tsconfig.json index fa378c95b1..b14ee541ef 100644 --- a/packages/lib/tsconfig.json +++ b/packages/lib/tsconfig.json @@ -6,13 +6,13 @@ "downlevelIteration": true, "baseUrl": ".", "paths": { - "@prisma/client/*": ["@formbricks/database/client/*"], + "@prisma/client/*": ["@formbricks/database/client/*"] }, "plugins": [ { - "name": "next", - }, + "name": "next" + } ], - "strictNullChecks": true, - }, + "strictNullChecks": true + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 816d8777cb..7bc12a0ef1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -344,10 +344,10 @@ importers: version: 2.2.2 '@radix-ui/react-collapsible': specifier: ^1.0.3 - version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.3(react-dom@18.2.0)(react@18.2.0) '@react-email/components': specifier: ^0.0.15 - version: 0.0.15(@types/react@18.2.48)(react-email@2.1.0)(react@18.2.0) + version: 0.0.15(react-email@2.1.0)(react@18.2.0) '@sentry/nextjs': specifier: ^7.106.1 version: 7.106.1(encoding@0.1.13)(next@14.1.3)(react@18.2.0)(webpack@5.90.3) @@ -429,6 +429,9 @@ importers: react-icons: specifier: ^5.0.1 version: 5.0.1(react@18.2.0) + redis: + specifier: ^4.6.13 + version: 4.6.13 sharp: specifier: ^0.33.2 version: 0.33.2 @@ -445,6 +448,9 @@ importers: '@formbricks/tsconfig': specifier: workspace:* version: link:../../packages/tsconfig + '@neshca/cache-handler': + specifier: ^1.0.7 + version: 1.0.7(next@14.1.3)(redis@4.6.13) '@types/bcryptjs': specifier: ^2.4.6 version: 2.4.6 @@ -647,7 +653,7 @@ importers: version: 3.533.0 '@formbricks/api': specifier: '*' - version: link:../api + version: 1.7.0 '@formbricks/database': specifier: '*' version: link:../database @@ -3944,6 +3950,10 @@ packages: resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} dev: false + /@formbricks/api@1.7.0: + resolution: {integrity: sha512-NpsxIMK2B6yUMH6Y7IoDavJoKEO72g3sBZzNVh66g5L+969Fw4gpRB9f8xDb6ei51cirSJzgyMXCAAWUAlxoNQ==} + dev: false + /@grpc/grpc-js@1.10.2: resolution: {integrity: sha512-lSbgu8iayAod8O0YcoXK3+bMFGThY2svtN35Zlm9VepsB3jfyIcoupKknEht7Kh9Q8ITjsp0J4KpYo9l4+FhNg==} engines: {node: '>=12.10.0'} @@ -4723,6 +4733,17 @@ packages: resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} dev: true + /@neshca/cache-handler@1.0.7(next@14.1.3)(redis@4.6.13): + resolution: {integrity: sha512-fn0jpHQ04cchhQf2DBHfvzPLTyKRRETk+BeiwDAbS0lPFX8EP3+iX2zFBw/SR3kMRAfvTrOTRCgdnSGJaJ/fHQ==} + peerDependencies: + next: '>=13.5.1' + redis: '>=4.6' + dependencies: + lru-cache: 10.2.0 + next: 14.1.3(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + redis: 4.6.13 + dev: true + /@next/env@13.5.6: resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} dev: false @@ -4733,7 +4754,6 @@ packages: /@next/env@14.1.3: resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==} - dev: false /@next/eslint-plugin-next@14.1.1: resolution: {integrity: sha512-NP1WoGFnFLpqqCWgGFjnn/sTwUExdPyjeFKRdQP1X/bL/tjAQ/TXDmYqw6vzGaP5NaZ2u6xzg+N/0nd7fOPOGQ==} @@ -4772,7 +4792,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: false optional: true /@next/swc-darwin-x64@14.1.0: @@ -4790,7 +4809,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: false optional: true /@next/swc-linux-arm64-gnu@14.1.0: @@ -4808,7 +4826,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false optional: true /@next/swc-linux-arm64-musl@14.1.0: @@ -4826,7 +4843,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false optional: true /@next/swc-linux-x64-gnu@14.1.0: @@ -4844,7 +4860,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false optional: true /@next/swc-linux-x64-musl@14.1.0: @@ -4862,7 +4877,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false optional: true /@next/swc-win32-arm64-msvc@14.1.0: @@ -4880,7 +4894,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: false optional: true /@next/swc-win32-ia32-msvc@14.1.0: @@ -4898,7 +4911,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: false optional: true /@next/swc-win32-x64-msvc@14.1.0: @@ -4916,7 +4928,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: false optional: true /@noble/hashes@1.3.3: @@ -6246,13 +6257,13 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-collapsible': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -6279,6 +6290,25 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-arrow@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-checkbox@1.0.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==} peerDependencies: @@ -6294,10 +6324,10 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-previous': 1.0.1(react@18.2.0) '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.48)(react@18.2.0) @@ -6333,6 +6363,32 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-collapsible@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} peerDependencies: @@ -6357,6 +6413,28 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-collection@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.48)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -6383,7 +6461,6 @@ packages: '@babel/runtime': 7.23.8 '@types/react': 18.2.61 react: 18.2.0 - dev: true /@radix-ui/react-context@1.0.1(@types/react@18.2.48)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} @@ -6414,16 +6491,16 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-focus-scope': 1.0.4(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) aria-hidden: 1.2.3 react: 18.2.0 @@ -6485,8 +6562,8 @@ packages: dependencies: '@babel/runtime': 7.23.8 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.48)(react@18.2.0) react: 18.2.0 @@ -6508,11 +6585,11 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-menu': 2.0.6(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -6569,8 +6646,8 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.8 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.48)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -6605,7 +6682,7 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.6 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -6625,8 +6702,8 @@ packages: dependencies: '@babel/runtime': 7.23.8 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) @@ -6635,10 +6712,10 @@ packages: '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.48)(react@18.2.0) aria-hidden: 1.2.3 react: 18.2.0 @@ -6696,7 +6773,7 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.48)(react@18.2.0) @@ -6704,9 +6781,9 @@ packages: '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) aria-hidden: 1.2.3 react: 18.2.0 @@ -6759,10 +6836,10 @@ packages: dependencies: '@babel/runtime': 7.23.8 '@floating-ui/react-dom': 2.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.48)(react@18.2.0) @@ -6807,7 +6884,7 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.8 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -6834,6 +6911,26 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-presence@1.0.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -6855,6 +6952,25 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-primitive@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-radio-group@1.1.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==} peerDependencies: @@ -6870,12 +6986,12 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-previous': 1.0.1(react@18.2.0) '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.48)(react@18.2.0) @@ -6912,6 +7028,33 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-roving-focus@1.0.4(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-select@2.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==} peerDependencies: @@ -6928,8 +7071,8 @@ packages: '@babel/runtime': 7.23.6 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) @@ -6938,13 +7081,13 @@ packages: '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-previous': 1.0.1(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(react-dom@18.2.0)(react@18.2.0) aria-hidden: 1.2.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -6967,11 +7110,11 @@ packages: '@babel/runtime': 7.23.6 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-previous': 1.0.1(react@18.2.0) @@ -7008,7 +7151,6 @@ packages: '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@types/react': 18.2.61 react: 18.2.0 - dev: true /@radix-ui/react-switch@1.0.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==} @@ -7025,9 +7167,9 @@ packages: dependencies: '@babel/runtime': 7.23.6 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-use-previous': 1.0.1(react@18.2.0) '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.48)(react@18.2.0) @@ -7132,17 +7274,17 @@ packages: dependencies: '@babel/runtime': 7.23.8 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.48)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -7269,6 +7411,25 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-visually-hidden@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: @@ -7352,6 +7513,38 @@ packages: - react-email dev: false + /@react-email/components@0.0.15(react-email@2.1.0)(react@18.2.0): + resolution: {integrity: sha512-jXfKiuyi94JBYfPVptEUwF57nRCvhEZIfyl2LqbL53fKsMrGlcjlN921iNnx1z41GAJOqZ8LPogeix3Iid23zw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: 18.2.0 + dependencies: + '@react-email/body': 0.0.7(react@18.2.0) + '@react-email/button': 0.0.14(react@18.2.0) + '@react-email/code-block': 0.0.3(react@18.2.0) + '@react-email/code-inline': 0.0.1(react@18.2.0) + '@react-email/column': 0.0.9(react@18.2.0) + '@react-email/container': 0.0.11(react@18.2.0) + '@react-email/font': 0.0.5(react@18.2.0) + '@react-email/head': 0.0.7(react@18.2.0) + '@react-email/heading': 0.0.11(react@18.2.0) + '@react-email/hr': 0.0.7(react@18.2.0) + '@react-email/html': 0.0.7(react@18.2.0) + '@react-email/img': 0.0.7(react@18.2.0) + '@react-email/link': 0.0.7(react@18.2.0) + '@react-email/markdown': 0.0.8(react-email@2.1.0)(react@18.2.0) + '@react-email/preview': 0.0.8(react@18.2.0) + '@react-email/render': 0.0.12 + '@react-email/row': 0.0.7(react@18.2.0) + '@react-email/section': 0.0.11(react@18.2.0) + '@react-email/tailwind': 0.0.14(react@18.2.0) + '@react-email/text': 0.0.7(react@18.2.0) + react: 18.2.0 + transitivePeerDependencies: + - '@types/react' + - react-email + dev: false + /@react-email/container@0.0.11(react@18.2.0): resolution: {integrity: sha512-jzl/EHs0ClXIRFamfH+NR/cqv4GsJJscqRhdYtnWYuRAsWpKBM1muycrrPqIVhWvWi6sFHInWTt07jX+bDc3SQ==} engines: {node: '>=18.0.0'} @@ -7390,6 +7583,18 @@ packages: - '@types/react' dev: false + /@react-email/heading@0.0.11(react@18.2.0): + resolution: {integrity: sha512-EF5ZtRCxhHPw3m+8iibKKg0RAvAeHj1AP68sjU7s6+J+kvRgllr/E972Wi5Y8UvcIGossCvpX1WrSMDzeB4puA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: 18.2.0 + dependencies: + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) + react: 18.2.0 + transitivePeerDependencies: + - '@types/react' + dev: false + /@react-email/hr@0.0.7(react@18.2.0): resolution: {integrity: sha512-8suK0M/deXHt0DBSeKhSC4bnCBCBm37xk6KJh9M0/FIKlvdltQBem52YUiuqVl1XLB87Y6v6tvspn3SZ9fuxEA==} engines: {node: '>=18.0.0'} @@ -7493,6 +7698,49 @@ packages: react: 18.2.0 dev: false + /@redis/bloom@1.2.0(@redis/client@1.5.14): + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.14 + + /@redis/client@1.5.14: + resolution: {integrity: sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==} + engines: {node: '>=14'} + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + /@redis/graph@1.1.1(@redis/client@1.5.14): + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.14 + + /@redis/json@1.0.6(@redis/client@1.5.14): + resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.14 + + /@redis/search@1.1.6(@redis/client@1.5.14): + resolution: {integrity: sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.14 + + /@redis/time-series@1.0.5(@redis/client@1.5.14): + resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.14 + /@resvg/resvg-wasm@2.4.0: resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} engines: {node: '>= 10'} @@ -9034,7 +9282,6 @@ packages: resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} dependencies: tslib: 2.6.2 - dev: false /@swc/types@0.1.5: resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==} @@ -11035,7 +11282,6 @@ packages: engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - dev: false /bytes@3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} @@ -11312,7 +11558,6 @@ packages: /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false /clipboardy@3.0.0: resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} @@ -11358,6 +11603,10 @@ packages: resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} engines: {node: '>=6'} + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + /cmdk@1.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} peerDependencies: @@ -11365,7 +11614,7 @@ packages: react-dom: ^18.0.0 dependencies: '@radix-ui/react-dialog': 1.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -13265,6 +13514,10 @@ packages: - supports-color dev: false + /generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -15980,7 +16233,6 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: false /node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} @@ -16667,7 +16919,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: false /postcss@8.4.35: resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} @@ -17616,6 +17867,16 @@ packages: strip-indent: 3.0.0 dev: false + /redis@4.6.13: + resolution: {integrity: sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==} + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.5.14) + '@redis/client': 1.5.14 + '@redis/graph': 1.1.1(@redis/client@1.5.14) + '@redis/json': 1.0.6(@redis/client@1.5.14) + '@redis/search': 1.1.6(@redis/client@1.5.14) + '@redis/time-series': 1.0.5(@redis/client@1.5.14) + /redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} dependencies: @@ -18551,7 +18812,6 @@ packages: /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: false /string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} @@ -18740,7 +19000,6 @@ packages: dependencies: client-only: 0.0.1 react: 18.2.0 - dev: false /stylis@4.3.0: resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==} diff --git a/turbo.json b/turbo.json index ed978f2978..4f52d79052 100644 --- a/turbo.json +++ b/turbo.json @@ -118,6 +118,7 @@ "PLAYWRIGHT_CI", "PRIVACY_URL", "RATE_LIMITING_DISABLED", + "REDIS_CLIENT_URL", "S3_ACCESS_KEY", "S3_SECRET_KEY", "S3_REGION",