From 2aaa91444dd0f24a3073d24af0b298ed6141390e Mon Sep 17 00:00:00 2001
From: Jose Selesan
Date: Mon, 1 Jun 2026 16:43:52 -0300
Subject: [PATCH 1/2] feat: add phone field to User model and update
authentication flow
- Added phone field to User model in Prisma schema and generated types.
- Updated backend authentication to include phone as an additional field.
- Implemented phone input in the signup form with validation.
- Created a new SignupPage component for user registration.
- Updated routing to include signup path and redirect authenticated users.
- Modified login page to reference Playzer and added link to signup.
---
apps/backend/prisma/auth.prisma | 1 +
.../migration.sql | 2 +
.../src/generated/prisma/internal/class.ts | 8 +-
.../prisma/internal/prismaNamespace.ts | 1 +
.../prisma/internal/prismaNamespaceBrowser.ts | 1 +
.../src/generated/prisma/models/User.ts | 42 +++-
apps/backend/src/lib/auth.ts | 21 ++
.../src/features/login/login-page.tsx | 6 +-
.../src/features/signup/signup-page.tsx | 228 ++++++++++++++++++
apps/frontend/src/lib/api-client.ts | 13 +-
apps/frontend/src/lib/auth.tsx | 4 +-
apps/frontend/src/routeTree.gen.ts | 21 ++
apps/frontend/src/routes/__root.tsx | 2 +-
apps/frontend/src/routes/signup.tsx | 11 +
14 files changed, 352 insertions(+), 9 deletions(-)
create mode 100644 apps/backend/prisma/migrations/20260601193348_add_phone_to_user/migration.sql
create mode 100644 apps/frontend/src/features/signup/signup-page.tsx
create mode 100644 apps/frontend/src/routes/signup.tsx
diff --git a/apps/backend/prisma/auth.prisma b/apps/backend/prisma/auth.prisma
index 6fc5926..56c93c9 100644
--- a/apps/backend/prisma/auth.prisma
+++ b/apps/backend/prisma/auth.prisma
@@ -4,6 +4,7 @@ model User {
email String
emailVerified Boolean @default(false)
image String?
+ phone String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
diff --git a/apps/backend/prisma/migrations/20260601193348_add_phone_to_user/migration.sql b/apps/backend/prisma/migrations/20260601193348_add_phone_to_user/migration.sql
new file mode 100644
index 0000000..8850dab
--- /dev/null
+++ b/apps/backend/prisma/migrations/20260601193348_add_phone_to_user/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "users" ADD COLUMN "phone" TEXT;
diff --git a/apps/backend/src/generated/prisma/internal/class.ts b/apps/backend/src/generated/prisma/internal/class.ts
index dd8c51e..268e76f 100644
--- a/apps/backend/src/generated/prisma/internal/class.ts
+++ b/apps/backend/src/generated/prisma/internal/class.ts
@@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = {
"clientVersion": "7.6.0",
"engineVersion": "75cbdc1eb7150937890ad5465d861175c6624711",
"activeProvider": "postgresql",
- "inlineSchema": "model User {\n id String @id\n name String\n email String\n emailVerified Boolean @default(false)\n image String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n sessions Session[]\n accounts Account[]\n role String @default(\"member\")\n complexes ComplexUser[]\n\n @@unique([email])\n @@map(\"users\")\n}\n\nmodel Session {\n id String @id\n expiresAt DateTime\n token String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ipAddress String?\n userAgent String?\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([token])\n @@index([userId])\n @@map(\"sessions\")\n}\n\nmodel Account {\n id String @id\n accountId String\n providerId String\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n accessToken String?\n refreshToken String?\n idToken String?\n accessTokenExpiresAt DateTime?\n refreshTokenExpiresAt DateTime?\n scope String?\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@map(\"accounts\")\n}\n\nmodel Verification {\n id String @id\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([identifier])\n @@map(\"verification\")\n}\n\nmodel Complex {\n id String @id @db.Uuid\n complexName String @map(\"complex_name\")\n physicalAddress String? @map(\"physical_address\") @db.VarChar(200)\n city String? @map(\"city\") @db.VarChar(100)\n state String? @map(\"state\") @db.VarChar(100)\n country String? @map(\"country\") @db.VarChar(100)\n complexSlug String @unique @map(\"complex_slug\")\n adminEmail String @map(\"admin_email\")\n planCode String? @map(\"plan_code\") @db.VarChar(10)\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n plan Plan? @relation(fields: [planCode], references: [code])\n users ComplexUser[]\n invitations ComplexInvitation[]\n courts Court[]\n\n @@index([planCode])\n @@index([complexSlug])\n @@map(\"complexes\")\n}\n\nenum ComplexUserRole {\n ADMIN\n EMPLOYEE\n}\n\nmodel ComplexUser {\n complexId String @map(\"complex_id\") @db.Uuid\n userId String @map(\"user_id\")\n role ComplexUserRole @default(ADMIN)\n createdAt DateTime @default(now()) @map(\"created_at\")\n complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([complexId, userId])\n @@index([userId])\n @@map(\"complex_users\")\n}\n\nmodel ComplexInvitation {\n id String @id @db.Uuid\n complexId String @map(\"complex_id\") @db.Uuid\n email String @db.VarChar(255)\n tokenHash String @unique @map(\"token_hash\")\n expiresAt DateTime @map(\"expires_at\")\n acceptedAt DateTime? @map(\"accepted_at\")\n revokedAt DateTime? @map(\"revoked_at\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)\n\n @@index([complexId, email])\n @@index([email])\n @@map(\"complex_invitations\")\n}\n\nenum DayOfWeek {\n MONDAY\n TUESDAY\n WEDNESDAY\n THURSDAY\n FRIDAY\n SATURDAY\n SUNDAY\n}\n\nenum CourtBookingStatus {\n CONFIRMED\n CANCELLED\n COMPLETED\n NOSHOW\n}\n\nmodel Sport {\n id String @id @db.Uuid\n name String @unique\n slug String @unique\n isActive Boolean @default(true) @map(\"is_active\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n courts Court[]\n\n @@map(\"sports\")\n}\n\nmodel Court {\n id String @id @db.Uuid\n complexId String @map(\"complex_id\") @db.Uuid\n sportId String @map(\"sport_id\") @db.Uuid\n name String\n slotDurationMinutes Int @map(\"slot_duration_minutes\")\n basePrice Decimal @map(\"base_price\") @db.Decimal(19, 2)\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)\n sport Sport @relation(fields: [sportId], references: [id])\n availabilities CourtAvailability[]\n priceRules CourtPriceRule[]\n bookings CourtBooking[]\n\n @@index([complexId])\n @@index([sportId])\n @@map(\"courts\")\n}\n\nmodel CourtAvailability {\n id String @id @db.Uuid\n courtId String @map(\"court_id\") @db.Uuid\n dayOfWeek DayOfWeek @map(\"day_of_week\")\n startTime String @map(\"start_time\") @db.VarChar(5)\n endTime String @map(\"end_time\") @db.VarChar(5)\n createdAt DateTime @default(now()) @map(\"created_at\")\n court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)\n\n @@index([courtId, dayOfWeek])\n @@map(\"court_availabilities\")\n}\n\nmodel CourtPriceRule {\n id String @id @db.Uuid\n courtId String @map(\"court_id\") @db.Uuid\n dayOfWeek DayOfWeek? @map(\"day_of_week\")\n startTime String? @map(\"start_time\") @db.VarChar(5)\n endTime String? @map(\"end_time\") @db.VarChar(5)\n price Decimal @db.Decimal(19, 2)\n isActive Boolean @default(true) @map(\"is_active\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)\n\n @@index([courtId, isActive])\n @@map(\"court_price_rules\")\n}\n\nmodel CourtBooking {\n id String @id @db.Uuid\n bookingCode String @unique @map(\"booking_code\") @db.VarChar(8)\n courtId String @map(\"court_id\") @db.Uuid\n bookingDate DateTime @map(\"booking_date\") @db.Date\n startTime String @map(\"start_time\") @db.VarChar(5)\n endTime String @map(\"end_time\") @db.VarChar(5)\n customerName String @map(\"customer_name\") @db.VarChar(120)\n customerPhone String @map(\"customer_phone\") @db.VarChar(30)\n status CourtBookingStatus @default(CONFIRMED)\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)\n\n @@unique([courtId, bookingDate, startTime])\n @@index([courtId, bookingDate])\n @@index([bookingDate])\n @@map(\"court_bookings\")\n}\n\nmodel CourtBookingLog {\n id String @id @db.Uuid\n bookingCode String @map(\"booking_code\") @db.VarChar(8)\n courtId String @map(\"court_id\") @db.Uuid\n bookingDate DateTime @map(\"booking_date\") @db.Date\n startTime String @map(\"start_time\") @db.VarChar(5)\n endTime String @map(\"end_time\") @db.VarChar(5)\n previousStatus CourtBookingStatus @map(\"previous_status\")\n newStatus CourtBookingStatus @map(\"new_status\")\n customerName String @map(\"customer_name\") @db.VarChar(120)\n customerPhone String @map(\"customer_phone\") @db.VarChar(30)\n changedAt DateTime @default(now()) @map(\"changed_at\")\n\n @@index([courtId, newStatus])\n @@map(\"court_booking_logs\")\n}\n\nmodel OnboardingRequest {\n id String @id @db.Uuid\n fullName String @map(\"full_name\")\n email String\n otpHash String @map(\"otp_hash\")\n otpExpiresAt DateTime @map(\"otp_expires_at\")\n otpAttempts Int @default(0) @map(\"otp_attempts\")\n otpLastSentAt DateTime @map(\"otp_last_sent_at\")\n otpResendCount Int @default(0) @map(\"otp_resend_count\")\n emailVerifiedAt DateTime? @map(\"email_verified_at\")\n completedAt DateTime? @map(\"completed_at\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n @@index([email])\n @@index([otpExpiresAt])\n @@map(\"onboarding_requests\")\n}\n\nmodel PasswordResetRequest {\n id String @id @db.Uuid\n email String\n otpHash String @map(\"otp_hash\")\n otpExpiresAt DateTime @map(\"otp_expires_at\")\n otpAttempts Int @default(0) @map(\"otp_attempts\")\n otpLastSentAt DateTime @map(\"otp_last_sent_at\")\n otpResendCount Int @default(0) @map(\"otp_resend_count\")\n usedAt DateTime? @map(\"used_at\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n @@index([email])\n @@index([otpExpiresAt])\n @@map(\"password_reset_requests\")\n}\n\nmodel Plan {\n code String @id @db.VarChar(10)\n name String @db.VarChar(30)\n price Decimal @db.Decimal(19, 2)\n rules Json\n lastUpdatedAt DateTime @default(now()) @map(\"last_updated_at\")\n complexes Complex[]\n\n @@map(\"plans\")\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../src/generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\n// User model is defined in auth.prisma (Better Auth schema).\n",
+ "inlineSchema": "model User {\n id String @id\n name String\n email String\n emailVerified Boolean @default(false)\n image String?\n phone String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n sessions Session[]\n accounts Account[]\n role String @default(\"member\")\n complexes ComplexUser[]\n\n @@unique([email])\n @@map(\"users\")\n}\n\nmodel Session {\n id String @id\n expiresAt DateTime\n token String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ipAddress String?\n userAgent String?\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([token])\n @@index([userId])\n @@map(\"sessions\")\n}\n\nmodel Account {\n id String @id\n accountId String\n providerId String\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n accessToken String?\n refreshToken String?\n idToken String?\n accessTokenExpiresAt DateTime?\n refreshTokenExpiresAt DateTime?\n scope String?\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@map(\"accounts\")\n}\n\nmodel Verification {\n id String @id\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([identifier])\n @@map(\"verification\")\n}\n\nmodel Complex {\n id String @id @db.Uuid\n complexName String @map(\"complex_name\")\n physicalAddress String? @map(\"physical_address\") @db.VarChar(200)\n city String? @map(\"city\") @db.VarChar(100)\n state String? @map(\"state\") @db.VarChar(100)\n country String? @map(\"country\") @db.VarChar(100)\n complexSlug String @unique @map(\"complex_slug\")\n adminEmail String @map(\"admin_email\")\n planCode String? @map(\"plan_code\") @db.VarChar(10)\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n plan Plan? @relation(fields: [planCode], references: [code])\n users ComplexUser[]\n invitations ComplexInvitation[]\n courts Court[]\n\n @@index([planCode])\n @@index([complexSlug])\n @@map(\"complexes\")\n}\n\nenum ComplexUserRole {\n ADMIN\n EMPLOYEE\n}\n\nmodel ComplexUser {\n complexId String @map(\"complex_id\") @db.Uuid\n userId String @map(\"user_id\")\n role ComplexUserRole @default(ADMIN)\n createdAt DateTime @default(now()) @map(\"created_at\")\n complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([complexId, userId])\n @@index([userId])\n @@map(\"complex_users\")\n}\n\nmodel ComplexInvitation {\n id String @id @db.Uuid\n complexId String @map(\"complex_id\") @db.Uuid\n email String @db.VarChar(255)\n tokenHash String @unique @map(\"token_hash\")\n expiresAt DateTime @map(\"expires_at\")\n acceptedAt DateTime? @map(\"accepted_at\")\n revokedAt DateTime? @map(\"revoked_at\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)\n\n @@index([complexId, email])\n @@index([email])\n @@map(\"complex_invitations\")\n}\n\nenum DayOfWeek {\n MONDAY\n TUESDAY\n WEDNESDAY\n THURSDAY\n FRIDAY\n SATURDAY\n SUNDAY\n}\n\nenum CourtBookingStatus {\n CONFIRMED\n CANCELLED\n COMPLETED\n NOSHOW\n}\n\nmodel Sport {\n id String @id @db.Uuid\n name String @unique\n slug String @unique\n isActive Boolean @default(true) @map(\"is_active\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n courts Court[]\n\n @@map(\"sports\")\n}\n\nmodel Court {\n id String @id @db.Uuid\n complexId String @map(\"complex_id\") @db.Uuid\n sportId String @map(\"sport_id\") @db.Uuid\n name String\n slotDurationMinutes Int @map(\"slot_duration_minutes\")\n basePrice Decimal @map(\"base_price\") @db.Decimal(19, 2)\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)\n sport Sport @relation(fields: [sportId], references: [id])\n availabilities CourtAvailability[]\n priceRules CourtPriceRule[]\n bookings CourtBooking[]\n\n @@index([complexId])\n @@index([sportId])\n @@map(\"courts\")\n}\n\nmodel CourtAvailability {\n id String @id @db.Uuid\n courtId String @map(\"court_id\") @db.Uuid\n dayOfWeek DayOfWeek @map(\"day_of_week\")\n startTime String @map(\"start_time\") @db.VarChar(5)\n endTime String @map(\"end_time\") @db.VarChar(5)\n createdAt DateTime @default(now()) @map(\"created_at\")\n court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)\n\n @@index([courtId, dayOfWeek])\n @@map(\"court_availabilities\")\n}\n\nmodel CourtPriceRule {\n id String @id @db.Uuid\n courtId String @map(\"court_id\") @db.Uuid\n dayOfWeek DayOfWeek? @map(\"day_of_week\")\n startTime String? @map(\"start_time\") @db.VarChar(5)\n endTime String? @map(\"end_time\") @db.VarChar(5)\n price Decimal @db.Decimal(19, 2)\n isActive Boolean @default(true) @map(\"is_active\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)\n\n @@index([courtId, isActive])\n @@map(\"court_price_rules\")\n}\n\nmodel CourtBooking {\n id String @id @db.Uuid\n bookingCode String @unique @map(\"booking_code\") @db.VarChar(8)\n courtId String @map(\"court_id\") @db.Uuid\n bookingDate DateTime @map(\"booking_date\") @db.Date\n startTime String @map(\"start_time\") @db.VarChar(5)\n endTime String @map(\"end_time\") @db.VarChar(5)\n customerName String @map(\"customer_name\") @db.VarChar(120)\n customerPhone String @map(\"customer_phone\") @db.VarChar(30)\n status CourtBookingStatus @default(CONFIRMED)\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)\n\n @@unique([courtId, bookingDate, startTime])\n @@index([courtId, bookingDate])\n @@index([bookingDate])\n @@map(\"court_bookings\")\n}\n\nmodel CourtBookingLog {\n id String @id @db.Uuid\n bookingCode String @map(\"booking_code\") @db.VarChar(8)\n courtId String @map(\"court_id\") @db.Uuid\n bookingDate DateTime @map(\"booking_date\") @db.Date\n startTime String @map(\"start_time\") @db.VarChar(5)\n endTime String @map(\"end_time\") @db.VarChar(5)\n previousStatus CourtBookingStatus @map(\"previous_status\")\n newStatus CourtBookingStatus @map(\"new_status\")\n customerName String @map(\"customer_name\") @db.VarChar(120)\n customerPhone String @map(\"customer_phone\") @db.VarChar(30)\n changedAt DateTime @default(now()) @map(\"changed_at\")\n\n @@index([courtId, newStatus])\n @@map(\"court_booking_logs\")\n}\n\nmodel OnboardingRequest {\n id String @id @db.Uuid\n fullName String @map(\"full_name\")\n email String\n otpHash String @map(\"otp_hash\")\n otpExpiresAt DateTime @map(\"otp_expires_at\")\n otpAttempts Int @default(0) @map(\"otp_attempts\")\n otpLastSentAt DateTime @map(\"otp_last_sent_at\")\n otpResendCount Int @default(0) @map(\"otp_resend_count\")\n emailVerifiedAt DateTime? @map(\"email_verified_at\")\n completedAt DateTime? @map(\"completed_at\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n @@index([email])\n @@index([otpExpiresAt])\n @@map(\"onboarding_requests\")\n}\n\nmodel PasswordResetRequest {\n id String @id @db.Uuid\n email String\n otpHash String @map(\"otp_hash\")\n otpExpiresAt DateTime @map(\"otp_expires_at\")\n otpAttempts Int @default(0) @map(\"otp_attempts\")\n otpLastSentAt DateTime @map(\"otp_last_sent_at\")\n otpResendCount Int @default(0) @map(\"otp_resend_count\")\n usedAt DateTime? @map(\"used_at\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n @@index([email])\n @@index([otpExpiresAt])\n @@map(\"password_reset_requests\")\n}\n\nmodel Plan {\n code String @id @db.VarChar(10)\n name String @db.VarChar(30)\n price Decimal @db.Decimal(19, 2)\n rules Json\n lastUpdatedAt DateTime @default(now()) @map(\"last_updated_at\")\n complexes Complex[]\n\n @@map(\"plans\")\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../src/generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\n// User model is defined in auth.prisma (Better Auth schema).\n",
"runtimeDataModel": {
"models": {},
"enums": {},
@@ -32,10 +32,10 @@ const config: runtime.GetPrismaClientConfig = {
}
}
-config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"accounts\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToUser\"},{\"name\":\"role\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexes\",\"kind\":\"object\",\"type\":\"ComplexUser\",\"relationName\":\"ComplexUserToUser\"}],\"dbName\":\"users\"},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":\"sessions\"},\"Account\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"providerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AccountToUser\"},{\"name\":\"accessToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"refreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"idToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"scope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":\"accounts\"},\"Verification\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":\"verification\"},\"Complex\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_name\"},{\"name\":\"physicalAddress\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"physical_address\"},{\"name\":\"city\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"city\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"state\"},{\"name\":\"country\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"country\"},{\"name\":\"complexSlug\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_slug\"},{\"name\":\"adminEmail\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"admin_email\"},{\"name\":\"planCode\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"plan_code\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"plan\",\"kind\":\"object\",\"type\":\"Plan\",\"relationName\":\"ComplexToPlan\"},{\"name\":\"users\",\"kind\":\"object\",\"type\":\"ComplexUser\",\"relationName\":\"ComplexToComplexUser\"},{\"name\":\"invitations\",\"kind\":\"object\",\"type\":\"ComplexInvitation\",\"relationName\":\"ComplexToComplexInvitation\"},{\"name\":\"courts\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"ComplexToCourt\"}],\"dbName\":\"complexes\"},\"ComplexUser\":{\"fields\":[{\"name\":\"complexId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_id\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"user_id\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"ComplexUserRole\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"complex\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToComplexUser\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ComplexUserToUser\"}],\"dbName\":\"complex_users\"},\"ComplexInvitation\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_id\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tokenHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"token_hash\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"expires_at\"},{\"name\":\"acceptedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"accepted_at\"},{\"name\":\"revokedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"revoked_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"complex\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToComplexInvitation\"}],\"dbName\":\"complex_invitations\"},\"Sport\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"courts\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToSport\"}],\"dbName\":\"sports\"},\"Court\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_id\"},{\"name\":\"sportId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"sport_id\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slotDurationMinutes\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"slot_duration_minutes\"},{\"name\":\"basePrice\",\"kind\":\"scalar\",\"type\":\"Decimal\",\"dbName\":\"base_price\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"complex\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToCourt\"},{\"name\":\"sport\",\"kind\":\"object\",\"type\":\"Sport\",\"relationName\":\"CourtToSport\"},{\"name\":\"availabilities\",\"kind\":\"object\",\"type\":\"CourtAvailability\",\"relationName\":\"CourtToCourtAvailability\"},{\"name\":\"priceRules\",\"kind\":\"object\",\"type\":\"CourtPriceRule\",\"relationName\":\"CourtToCourtPriceRule\"},{\"name\":\"bookings\",\"kind\":\"object\",\"type\":\"CourtBooking\",\"relationName\":\"CourtToCourtBooking\"}],\"dbName\":\"courts\"},\"CourtAvailability\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"dayOfWeek\",\"kind\":\"enum\",\"type\":\"DayOfWeek\",\"dbName\":\"day_of_week\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"court\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToCourtAvailability\"}],\"dbName\":\"court_availabilities\"},\"CourtPriceRule\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"dayOfWeek\",\"kind\":\"enum\",\"type\":\"DayOfWeek\",\"dbName\":\"day_of_week\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Decimal\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"court\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToCourtPriceRule\"}],\"dbName\":\"court_price_rules\"},\"CourtBooking\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"bookingCode\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"booking_code\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"bookingDate\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"booking_date\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"customerName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_name\"},{\"name\":\"customerPhone\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_phone\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"CourtBookingStatus\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"court\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToCourtBooking\"}],\"dbName\":\"court_bookings\"},\"CourtBookingLog\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"bookingCode\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"booking_code\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"bookingDate\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"booking_date\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"previousStatus\",\"kind\":\"enum\",\"type\":\"CourtBookingStatus\",\"dbName\":\"previous_status\"},{\"name\":\"newStatus\",\"kind\":\"enum\",\"type\":\"CourtBookingStatus\",\"dbName\":\"new_status\"},{\"name\":\"customerName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_name\"},{\"name\":\"customerPhone\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_phone\"},{\"name\":\"changedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"changed_at\"}],\"dbName\":\"court_booking_logs\"},\"OnboardingRequest\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fullName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"full_name\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"otpHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"otp_hash\"},{\"name\":\"otpExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_expires_at\"},{\"name\":\"otpAttempts\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_attempts\"},{\"name\":\"otpLastSentAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_last_sent_at\"},{\"name\":\"otpResendCount\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_resend_count\"},{\"name\":\"emailVerifiedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"email_verified_at\"},{\"name\":\"completedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"completed_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"onboarding_requests\"},\"PasswordResetRequest\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"otpHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"otp_hash\"},{\"name\":\"otpExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_expires_at\"},{\"name\":\"otpAttempts\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_attempts\"},{\"name\":\"otpLastSentAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_last_sent_at\"},{\"name\":\"otpResendCount\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_resend_count\"},{\"name\":\"usedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"used_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"password_reset_requests\"},\"Plan\":{\"fields\":[{\"name\":\"code\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Decimal\"},{\"name\":\"rules\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"lastUpdatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"last_updated_at\"},{\"name\":\"complexes\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToPlan\"}],\"dbName\":\"plans\"}},\"enums\":{},\"types\":{}}")
+config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"phone\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"accounts\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToUser\"},{\"name\":\"role\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexes\",\"kind\":\"object\",\"type\":\"ComplexUser\",\"relationName\":\"ComplexUserToUser\"}],\"dbName\":\"users\"},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":\"sessions\"},\"Account\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"providerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AccountToUser\"},{\"name\":\"accessToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"refreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"idToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"scope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":\"accounts\"},\"Verification\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":\"verification\"},\"Complex\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_name\"},{\"name\":\"physicalAddress\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"physical_address\"},{\"name\":\"city\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"city\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"state\"},{\"name\":\"country\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"country\"},{\"name\":\"complexSlug\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_slug\"},{\"name\":\"adminEmail\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"admin_email\"},{\"name\":\"planCode\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"plan_code\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"plan\",\"kind\":\"object\",\"type\":\"Plan\",\"relationName\":\"ComplexToPlan\"},{\"name\":\"users\",\"kind\":\"object\",\"type\":\"ComplexUser\",\"relationName\":\"ComplexToComplexUser\"},{\"name\":\"invitations\",\"kind\":\"object\",\"type\":\"ComplexInvitation\",\"relationName\":\"ComplexToComplexInvitation\"},{\"name\":\"courts\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"ComplexToCourt\"}],\"dbName\":\"complexes\"},\"ComplexUser\":{\"fields\":[{\"name\":\"complexId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_id\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"user_id\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"ComplexUserRole\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"complex\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToComplexUser\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ComplexUserToUser\"}],\"dbName\":\"complex_users\"},\"ComplexInvitation\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_id\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tokenHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"token_hash\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"expires_at\"},{\"name\":\"acceptedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"accepted_at\"},{\"name\":\"revokedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"revoked_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"complex\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToComplexInvitation\"}],\"dbName\":\"complex_invitations\"},\"Sport\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"courts\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToSport\"}],\"dbName\":\"sports\"},\"Court\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"complexId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"complex_id\"},{\"name\":\"sportId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"sport_id\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slotDurationMinutes\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"slot_duration_minutes\"},{\"name\":\"basePrice\",\"kind\":\"scalar\",\"type\":\"Decimal\",\"dbName\":\"base_price\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"complex\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToCourt\"},{\"name\":\"sport\",\"kind\":\"object\",\"type\":\"Sport\",\"relationName\":\"CourtToSport\"},{\"name\":\"availabilities\",\"kind\":\"object\",\"type\":\"CourtAvailability\",\"relationName\":\"CourtToCourtAvailability\"},{\"name\":\"priceRules\",\"kind\":\"object\",\"type\":\"CourtPriceRule\",\"relationName\":\"CourtToCourtPriceRule\"},{\"name\":\"bookings\",\"kind\":\"object\",\"type\":\"CourtBooking\",\"relationName\":\"CourtToCourtBooking\"}],\"dbName\":\"courts\"},\"CourtAvailability\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"dayOfWeek\",\"kind\":\"enum\",\"type\":\"DayOfWeek\",\"dbName\":\"day_of_week\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"court\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToCourtAvailability\"}],\"dbName\":\"court_availabilities\"},\"CourtPriceRule\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"dayOfWeek\",\"kind\":\"enum\",\"type\":\"DayOfWeek\",\"dbName\":\"day_of_week\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Decimal\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"court\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToCourtPriceRule\"}],\"dbName\":\"court_price_rules\"},\"CourtBooking\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"bookingCode\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"booking_code\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"bookingDate\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"booking_date\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"customerName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_name\"},{\"name\":\"customerPhone\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_phone\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"CourtBookingStatus\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"court\",\"kind\":\"object\",\"type\":\"Court\",\"relationName\":\"CourtToCourtBooking\"}],\"dbName\":\"court_bookings\"},\"CourtBookingLog\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"bookingCode\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"booking_code\"},{\"name\":\"courtId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"court_id\"},{\"name\":\"bookingDate\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"booking_date\"},{\"name\":\"startTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"start_time\"},{\"name\":\"endTime\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"end_time\"},{\"name\":\"previousStatus\",\"kind\":\"enum\",\"type\":\"CourtBookingStatus\",\"dbName\":\"previous_status\"},{\"name\":\"newStatus\",\"kind\":\"enum\",\"type\":\"CourtBookingStatus\",\"dbName\":\"new_status\"},{\"name\":\"customerName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_name\"},{\"name\":\"customerPhone\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"customer_phone\"},{\"name\":\"changedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"changed_at\"}],\"dbName\":\"court_booking_logs\"},\"OnboardingRequest\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fullName\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"full_name\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"otpHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"otp_hash\"},{\"name\":\"otpExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_expires_at\"},{\"name\":\"otpAttempts\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_attempts\"},{\"name\":\"otpLastSentAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_last_sent_at\"},{\"name\":\"otpResendCount\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_resend_count\"},{\"name\":\"emailVerifiedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"email_verified_at\"},{\"name\":\"completedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"completed_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"onboarding_requests\"},\"PasswordResetRequest\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"otpHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"otp_hash\"},{\"name\":\"otpExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_expires_at\"},{\"name\":\"otpAttempts\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_attempts\"},{\"name\":\"otpLastSentAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"otp_last_sent_at\"},{\"name\":\"otpResendCount\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"otp_resend_count\"},{\"name\":\"usedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"used_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"password_reset_requests\"},\"Plan\":{\"fields\":[{\"name\":\"code\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Decimal\"},{\"name\":\"rules\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"lastUpdatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"last_updated_at\"},{\"name\":\"complexes\",\"kind\":\"object\",\"type\":\"Complex\",\"relationName\":\"ComplexToPlan\"}],\"dbName\":\"plans\"}},\"enums\":{},\"types\":{}}")
config.parameterizationSchema = {
- strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"sessions\",\"accounts\",\"complexes\",\"_count\",\"plan\",\"users\",\"complex\",\"invitations\",\"courts\",\"sport\",\"court\",\"availabilities\",\"priceRules\",\"bookings\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Session.findUnique\",\"Session.findUniqueOrThrow\",\"Session.findFirst\",\"Session.findFirstOrThrow\",\"Session.findMany\",\"Session.createOne\",\"Session.createMany\",\"Session.createManyAndReturn\",\"Session.updateOne\",\"Session.updateMany\",\"Session.updateManyAndReturn\",\"Session.upsertOne\",\"Session.deleteOne\",\"Session.deleteMany\",\"Session.groupBy\",\"Session.aggregate\",\"Account.findUnique\",\"Account.findUniqueOrThrow\",\"Account.findFirst\",\"Account.findFirstOrThrow\",\"Account.findMany\",\"Account.createOne\",\"Account.createMany\",\"Account.createManyAndReturn\",\"Account.updateOne\",\"Account.updateMany\",\"Account.updateManyAndReturn\",\"Account.upsertOne\",\"Account.deleteOne\",\"Account.deleteMany\",\"Account.groupBy\",\"Account.aggregate\",\"Verification.findUnique\",\"Verification.findUniqueOrThrow\",\"Verification.findFirst\",\"Verification.findFirstOrThrow\",\"Verification.findMany\",\"Verification.createOne\",\"Verification.createMany\",\"Verification.createManyAndReturn\",\"Verification.updateOne\",\"Verification.updateMany\",\"Verification.updateManyAndReturn\",\"Verification.upsertOne\",\"Verification.deleteOne\",\"Verification.deleteMany\",\"Verification.groupBy\",\"Verification.aggregate\",\"Complex.findUnique\",\"Complex.findUniqueOrThrow\",\"Complex.findFirst\",\"Complex.findFirstOrThrow\",\"Complex.findMany\",\"Complex.createOne\",\"Complex.createMany\",\"Complex.createManyAndReturn\",\"Complex.updateOne\",\"Complex.updateMany\",\"Complex.updateManyAndReturn\",\"Complex.upsertOne\",\"Complex.deleteOne\",\"Complex.deleteMany\",\"Complex.groupBy\",\"Complex.aggregate\",\"ComplexUser.findUnique\",\"ComplexUser.findUniqueOrThrow\",\"ComplexUser.findFirst\",\"ComplexUser.findFirstOrThrow\",\"ComplexUser.findMany\",\"ComplexUser.createOne\",\"ComplexUser.createMany\",\"ComplexUser.createManyAndReturn\",\"ComplexUser.updateOne\",\"ComplexUser.updateMany\",\"ComplexUser.updateManyAndReturn\",\"ComplexUser.upsertOne\",\"ComplexUser.deleteOne\",\"ComplexUser.deleteMany\",\"ComplexUser.groupBy\",\"ComplexUser.aggregate\",\"ComplexInvitation.findUnique\",\"ComplexInvitation.findUniqueOrThrow\",\"ComplexInvitation.findFirst\",\"ComplexInvitation.findFirstOrThrow\",\"ComplexInvitation.findMany\",\"ComplexInvitation.createOne\",\"ComplexInvitation.createMany\",\"ComplexInvitation.createManyAndReturn\",\"ComplexInvitation.updateOne\",\"ComplexInvitation.updateMany\",\"ComplexInvitation.updateManyAndReturn\",\"ComplexInvitation.upsertOne\",\"ComplexInvitation.deleteOne\",\"ComplexInvitation.deleteMany\",\"ComplexInvitation.groupBy\",\"ComplexInvitation.aggregate\",\"Sport.findUnique\",\"Sport.findUniqueOrThrow\",\"Sport.findFirst\",\"Sport.findFirstOrThrow\",\"Sport.findMany\",\"Sport.createOne\",\"Sport.createMany\",\"Sport.createManyAndReturn\",\"Sport.updateOne\",\"Sport.updateMany\",\"Sport.updateManyAndReturn\",\"Sport.upsertOne\",\"Sport.deleteOne\",\"Sport.deleteMany\",\"Sport.groupBy\",\"Sport.aggregate\",\"Court.findUnique\",\"Court.findUniqueOrThrow\",\"Court.findFirst\",\"Court.findFirstOrThrow\",\"Court.findMany\",\"Court.createOne\",\"Court.createMany\",\"Court.createManyAndReturn\",\"Court.updateOne\",\"Court.updateMany\",\"Court.updateManyAndReturn\",\"Court.upsertOne\",\"Court.deleteOne\",\"Court.deleteMany\",\"_avg\",\"_sum\",\"Court.groupBy\",\"Court.aggregate\",\"CourtAvailability.findUnique\",\"CourtAvailability.findUniqueOrThrow\",\"CourtAvailability.findFirst\",\"CourtAvailability.findFirstOrThrow\",\"CourtAvailability.findMany\",\"CourtAvailability.createOne\",\"CourtAvailability.createMany\",\"CourtAvailability.createManyAndReturn\",\"CourtAvailability.updateOne\",\"CourtAvailability.updateMany\",\"CourtAvailability.updateManyAndReturn\",\"CourtAvailability.upsertOne\",\"CourtAvailability.deleteOne\",\"CourtAvailability.deleteMany\",\"CourtAvailability.groupBy\",\"CourtAvailability.aggregate\",\"CourtPriceRule.findUnique\",\"CourtPriceRule.findUniqueOrThrow\",\"CourtPriceRule.findFirst\",\"CourtPriceRule.findFirstOrThrow\",\"CourtPriceRule.findMany\",\"CourtPriceRule.createOne\",\"CourtPriceRule.createMany\",\"CourtPriceRule.createManyAndReturn\",\"CourtPriceRule.updateOne\",\"CourtPriceRule.updateMany\",\"CourtPriceRule.updateManyAndReturn\",\"CourtPriceRule.upsertOne\",\"CourtPriceRule.deleteOne\",\"CourtPriceRule.deleteMany\",\"CourtPriceRule.groupBy\",\"CourtPriceRule.aggregate\",\"CourtBooking.findUnique\",\"CourtBooking.findUniqueOrThrow\",\"CourtBooking.findFirst\",\"CourtBooking.findFirstOrThrow\",\"CourtBooking.findMany\",\"CourtBooking.createOne\",\"CourtBooking.createMany\",\"CourtBooking.createManyAndReturn\",\"CourtBooking.updateOne\",\"CourtBooking.updateMany\",\"CourtBooking.updateManyAndReturn\",\"CourtBooking.upsertOne\",\"CourtBooking.deleteOne\",\"CourtBooking.deleteMany\",\"CourtBooking.groupBy\",\"CourtBooking.aggregate\",\"CourtBookingLog.findUnique\",\"CourtBookingLog.findUniqueOrThrow\",\"CourtBookingLog.findFirst\",\"CourtBookingLog.findFirstOrThrow\",\"CourtBookingLog.findMany\",\"CourtBookingLog.createOne\",\"CourtBookingLog.createMany\",\"CourtBookingLog.createManyAndReturn\",\"CourtBookingLog.updateOne\",\"CourtBookingLog.updateMany\",\"CourtBookingLog.updateManyAndReturn\",\"CourtBookingLog.upsertOne\",\"CourtBookingLog.deleteOne\",\"CourtBookingLog.deleteMany\",\"CourtBookingLog.groupBy\",\"CourtBookingLog.aggregate\",\"OnboardingRequest.findUnique\",\"OnboardingRequest.findUniqueOrThrow\",\"OnboardingRequest.findFirst\",\"OnboardingRequest.findFirstOrThrow\",\"OnboardingRequest.findMany\",\"OnboardingRequest.createOne\",\"OnboardingRequest.createMany\",\"OnboardingRequest.createManyAndReturn\",\"OnboardingRequest.updateOne\",\"OnboardingRequest.updateMany\",\"OnboardingRequest.updateManyAndReturn\",\"OnboardingRequest.upsertOne\",\"OnboardingRequest.deleteOne\",\"OnboardingRequest.deleteMany\",\"OnboardingRequest.groupBy\",\"OnboardingRequest.aggregate\",\"PasswordResetRequest.findUnique\",\"PasswordResetRequest.findUniqueOrThrow\",\"PasswordResetRequest.findFirst\",\"PasswordResetRequest.findFirstOrThrow\",\"PasswordResetRequest.findMany\",\"PasswordResetRequest.createOne\",\"PasswordResetRequest.createMany\",\"PasswordResetRequest.createManyAndReturn\",\"PasswordResetRequest.updateOne\",\"PasswordResetRequest.updateMany\",\"PasswordResetRequest.updateManyAndReturn\",\"PasswordResetRequest.upsertOne\",\"PasswordResetRequest.deleteOne\",\"PasswordResetRequest.deleteMany\",\"PasswordResetRequest.groupBy\",\"PasswordResetRequest.aggregate\",\"Plan.findUnique\",\"Plan.findUniqueOrThrow\",\"Plan.findFirst\",\"Plan.findFirstOrThrow\",\"Plan.findMany\",\"Plan.createOne\",\"Plan.createMany\",\"Plan.createManyAndReturn\",\"Plan.updateOne\",\"Plan.updateMany\",\"Plan.updateManyAndReturn\",\"Plan.upsertOne\",\"Plan.deleteOne\",\"Plan.deleteMany\",\"Plan.groupBy\",\"Plan.aggregate\",\"AND\",\"OR\",\"NOT\",\"code\",\"name\",\"price\",\"rules\",\"lastUpdatedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"contains\",\"startsWith\",\"endsWith\",\"every\",\"some\",\"none\",\"id\",\"email\",\"otpHash\",\"otpExpiresAt\",\"otpAttempts\",\"otpLastSentAt\",\"otpResendCount\",\"usedAt\",\"createdAt\",\"updatedAt\",\"fullName\",\"emailVerifiedAt\",\"completedAt\",\"bookingCode\",\"courtId\",\"bookingDate\",\"startTime\",\"endTime\",\"CourtBookingStatus\",\"previousStatus\",\"newStatus\",\"customerName\",\"customerPhone\",\"changedAt\",\"status\",\"DayOfWeek\",\"dayOfWeek\",\"isActive\",\"complexId\",\"sportId\",\"slotDurationMinutes\",\"basePrice\",\"slug\",\"tokenHash\",\"expiresAt\",\"acceptedAt\",\"revokedAt\",\"userId\",\"ComplexUserRole\",\"role\",\"complexName\",\"physicalAddress\",\"city\",\"state\",\"country\",\"complexSlug\",\"adminEmail\",\"planCode\",\"identifier\",\"value\",\"accountId\",\"providerId\",\"accessToken\",\"refreshToken\",\"idToken\",\"accessTokenExpiresAt\",\"refreshTokenExpiresAt\",\"scope\",\"password\",\"token\",\"ipAddress\",\"userAgent\",\"emailVerified\",\"image\",\"courtId_bookingDate_startTime\",\"complexId_userId\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"),
- graph: "zgaQAYACDgQAAPUDACAFAAD2AwAgBgAA9wMAIJoCAADzAwAwmwIAADcAEJwCAADzAwAwngIBALgDACG2AgEAAAABtwIBAAAAAb4CQAC7AwAhvwJAALsDACHdAgEAuAMAIfQCIADmAwAh9QIBAPQDACEBAAAAAQAgDAMAAIwEACCaAgAAjgQAMJsCAAADABCcAgAAjgQAMLYCAQC4AwAhvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh2wIBALgDACHxAgEAuAMAIfICAQD0AwAh8wIBAPQDACEDAwAAjAYAIPICAACGBQAg8wIAAIYFACAMAwAAjAQAIJoCAACOBAAwmwIAAAMAEJwCAACOBAAwtgIBAAAAAb4CQAC7AwAhvwJAALsDACHYAkAAuwMAIdsCAQC4AwAh8QIBAAAAAfICAQD0AwAh8wIBAPQDACEDAAAAAwAgAQAABAAwAgAABQAgEQMAAIwEACCaAgAAjQQAMJsCAAAHABCcAgAAjQQAMLYCAQC4AwAhvgJAALsDACG_AkAAuwMAIdsCAQC4AwAh6AIBALgDACHpAgEAuAMAIeoCAQD0AwAh6wIBAPQDACHsAgEA9AMAIe0CQADKAwAh7gJAAMoDACHvAgEA9AMAIfACAQD0AwAhCAMAAIwGACDqAgAAhgUAIOsCAACGBQAg7AIAAIYFACDtAgAAhgUAIO4CAACGBQAg7wIAAIYFACDwAgAAhgUAIBEDAACMBAAgmgIAAI0EADCbAgAABwAQnAIAAI0EADC2AgEAAAABvgJAALsDACG_AkAAuwMAIdsCAQC4AwAh6AIBALgDACHpAgEAuAMAIeoCAQD0AwAh6wIBAPQDACHsAgEA9AMAIe0CQADKAwAh7gJAAMoDACHvAgEA9AMAIfACAQD0AwAhAwAAAAcAIAEAAAgAMAIAAAkAIAkDAACMBAAgCgAAgAQAIJoCAACKBAAwmwIAAAsAEJwCAACKBAAwvgJAALsDACHSAgEAyAMAIdsCAQC4AwAh3QIAAIsE3QIiAgMAAIwGACAKAACFBgAgCgMAAIwEACAKAACABAAgmgIAAIoEADCbAgAACwAQnAIAAIoEADC-AkAAuwMAIdICAQDIAwAh2wIBALgDACHdAgAAiwTdAiL3AgAAiQQAIAMAAAALACABAAAMADACAAANACAJBgAAvAMAIJoCAAC3AwAwmwIAAA8AEJwCAAC3AwAwnQIBALgDACGeAgEAuAMAIZ8CEAC5AwAhoAIAALoDACChAkAAuwMAIQEAAAAPACASCAAAhwQAIAkAAPcDACALAACIBAAgDAAA5wMAIJoCAACGBAAwmwIAABEAEJwCAACGBAAwtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh3gIBALgDACHfAgEA9AMAIeACAQD0AwAh4QIBAPQDACHiAgEA9AMAIeMCAQC4AwAh5AIBALgDACHlAgEA9AMAIQkIAACKBgAgCQAAgwYAIAsAAIsGACAMAAC6BQAg3wIAAIYFACDgAgAAhgUAIOECAACGBQAg4gIAAIYFACDlAgAAhgUAIBIIAACHBAAgCQAA9wMAIAsAAIgEACAMAADnAwAgmgIAAIYEADCbAgAAEQAQnAIAAIYEADC2AgEAAAABvgJAALsDACG_AkAAuwMAId4CAQC4AwAh3wIBAPQDACHgAgEA9AMAIeECAQD0AwAh4gIBAPQDACHjAgEAAAAB5AIBALgDACHlAgEA9AMAIQMAAAARACABAAASADACAAATACABAAAAEQAgAwAAAAsAIAEAAAwAMAIAAA0AIA0KAACABAAgmgIAAIUEADCbAgAAFwAQnAIAAIUEADC2AgEAyAMAIbcCAQC4AwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh1wIBALgDACHYAkAAuwMAIdkCQADKAwAh2gJAAMoDACEDCgAAhQYAINkCAACGBQAg2gIAAIYFACANCgAAgAQAIJoCAACFBAAwmwIAABcAEJwCAACFBAAwtgIBAAAAAbcCAQC4AwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh1wIBAAAAAdgCQAC7AwAh2QJAAMoDACHaAkAAygMAIQMAAAAXACABAAAYADACAAAZACAQCgAAgAQAIA0AAIEEACAPAACCBAAgEAAAgwQAIBEAAIQEACCaAgAA_wMAMJsCAAAbABCcAgAA_wMAMJ4CAQC4AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHTAgEAyAMAIdQCAgDJAwAh1QIQALkDACEFCgAAhQYAIA0AAIYGACAPAACHBgAgEAAAiAYAIBEAAIkGACAQCgAAgAQAIA0AAIEEACAPAACCBAAgEAAAgwQAIBEAAIQEACCaAgAA_wMAMJsCAAAbABCcAgAA_wMAMJ4CAQC4AwAhtgIBAAAAAb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdMCAQDIAwAh1AICAMkDACHVAhAAuQMAIQMAAAAbACABAAAcADACAAAdACADAAAAGwAgAQAAHAAwAgAAHQAgAQAAABsAIAoOAAD6AwAgmgIAAP0DADCbAgAAIQAQnAIAAP0DADC2AgEAyAMAIb4CQAC7AwAhxAIBAMgDACHGAgEAuAMAIccCAQC4AwAh0AIAAP4D0AIiAQ4AAIQGACAKDgAA-gMAIJoCAAD9AwAwmwIAACEAEJwCAAD9AwAwtgIBAAAAAb4CQAC7AwAhxAIBAMgDACHGAgEAuAMAIccCAQC4AwAh0AIAAP4D0AIiAwAAACEAIAEAACIAMAIAACMAIA0OAAD6AwAgmgIAAPsDADCbAgAAJQAQnAIAAPsDADCfAhAAuQMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcQCAQDIAwAhxgIBAPQDACHHAgEA9AMAIdACAAD8A9ACI9ECIADmAwAhBA4AAIQGACDGAgAAhgUAIMcCAACGBQAg0AIAAIYFACANDgAA-gMAIJoCAAD7AwAwmwIAACUAEJwCAAD7AwAwnwIQALkDACG2AgEAAAABvgJAALsDACG_AkAAuwMAIcQCAQDIAwAhxgIBAPQDACHHAgEA9AMAIdACAAD8A9ACI9ECIADmAwAhAwAAACUAIAEAACYAMAIAACcAIA8OAAD6AwAgmgIAAPkDADCbAgAAKQAQnAIAAPkDADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHDAgEAuAMAIcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhywIBALgDACHMAgEAuAMAIc4CAADTA8kCIgEOAACEBgAgEA4AAPoDACCaAgAA-QMAMJsCAAApABCcAgAA-QMAMLYCAQAAAAG-AkAAuwMAIb8CQAC7AwAhwwIBAAAAAcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhywIBALgDACHMAgEAuAMAIc4CAADTA8kCIvYCAAD4AwAgAwAAACkAIAEAACoAMAIAACsAIAEAAAAhACABAAAAJQAgAQAAACkAIAEAAAALACABAAAAFwAgAQAAABsAIAEAAAADACABAAAABwAgAQAAAAsAIAEAAAABACAOBAAA9QMAIAUAAPYDACAGAAD3AwAgmgIAAPMDADCbAgAANwAQnAIAAPMDADCeAgEAuAMAIbYCAQC4AwAhtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh3QIBALgDACH0AiAA5gMAIfUCAQD0AwAhBAQAAIEGACAFAACCBgAgBgAAgwYAIPUCAACGBQAgAwAAADcAIAEAADgAMAIAAAEAIAMAAAA3ACABAAA4ADACAAABACADAAAANwAgAQAAOAAwAgAAAQAgCwQAAP4FACAFAAD_BQAgBgAAgAYAIJ4CAQAAAAG2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB3QIBAAAAAfQCIAAAAAH1AgEAAAABARcAADwAIAieAgEAAAABtgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAd0CAQAAAAH0AiAAAAAB9QIBAAAAAQEXAAA-ADABFwAAPgAwCwQAANoFACAFAADbBQAgBgAA3AUAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACECAAAAAQAgFwAAQQAgCJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACECAAAANwAgFwAAQwAgAgAAADcAIBcAAEMAIAMAAAABACAeAAA8ACAfAABBACABAAAAAQAgAQAAADcAIAQHAADXBQAgJAAA2QUAICUAANgFACD1AgAAhgUAIAuaAgAA8gMAMJsCAABKABCcAgAA8gMAMJ4CAQCrAwAhtgIBAKsDACG3AgEAqwMAIb4CQACuAwAhvwJAAK4DACHdAgEAqwMAIfQCIADYAwAh9QIBANcDACEDAAAANwAgAQAASQAwIwAASgAgAwAAADcAIAEAADgAMAIAAAEAIAEAAAAFACABAAAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgCQMAANYFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHYAkAAAAAB2wIBAAAAAfECAQAAAAHyAgEAAAAB8wIBAAAAAQEXAABSACAItgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAdsCAQAAAAHxAgEAAAAB8gIBAAAAAfMCAQAAAAEBFwAAVAAwARcAAFQAMAkDAADVBQAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh2AJAAJYEACHbAgEAlAQAIfECAQCUBAAh8gIBAKIEACHzAgEAogQAIQIAAAAFACAXAABXACAItgIBAJQEACG-AkAAlgQAIb8CQACWBAAh2AJAAJYEACHbAgEAlAQAIfECAQCUBAAh8gIBAKIEACHzAgEAogQAIQIAAAADACAXAABZACACAAAAAwAgFwAAWQAgAwAAAAUAIB4AAFIAIB8AAFcAIAEAAAAFACABAAAAAwAgBQcAANIFACAkAADUBQAgJQAA0wUAIPICAACGBQAg8wIAAIYFACALmgIAAPEDADCbAgAAYAAQnAIAAPEDADC2AgEAqwMAIb4CQACuAwAhvwJAAK4DACHYAkAArgMAIdsCAQCrAwAh8QIBAKsDACHyAgEA1wMAIfMCAQDXAwAhAwAAAAMAIAEAAF8AMCMAAGAAIAMAAAADACABAAAEADACAAAFACABAAAACQAgAQAAAAkAIAMAAAAHACABAAAIADACAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIA4DAADRBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAAB2wIBAAAAAegCAQAAAAHpAgEAAAAB6gIBAAAAAesCAQAAAAHsAgEAAAAB7QJAAAAAAe4CQAAAAAHvAgEAAAAB8AIBAAAAAQEXAABoACANtgIBAAAAAb4CQAAAAAG_AkAAAAAB2wIBAAAAAegCAQAAAAHpAgEAAAAB6gIBAAAAAesCAQAAAAHsAgEAAAAB7QJAAAAAAe4CQAAAAAHvAgEAAAAB8AIBAAAAAQEXAABqADABFwAAagAwDgMAANAFACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHbAgEAlAQAIegCAQCUBAAh6QIBAJQEACHqAgEAogQAIesCAQCiBAAh7AIBAKIEACHtAkAA7gQAIe4CQADuBAAh7wIBAKIEACHwAgEAogQAIQIAAAAJACAXAABtACANtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh2wIBAJQEACHoAgEAlAQAIekCAQCUBAAh6gIBAKIEACHrAgEAogQAIewCAQCiBAAh7QJAAO4EACHuAkAA7gQAIe8CAQCiBAAh8AIBAKIEACECAAAABwAgFwAAbwAgAgAAAAcAIBcAAG8AIAMAAAAJACAeAABoACAfAABtACABAAAACQAgAQAAAAcAIAoHAADNBQAgJAAAzwUAICUAAM4FACDqAgAAhgUAIOsCAACGBQAg7AIAAIYFACDtAgAAhgUAIO4CAACGBQAg7wIAAIYFACDwAgAAhgUAIBCaAgAA8AMAMJsCAAB2ABCcAgAA8AMAMLYCAQCrAwAhvgJAAK4DACG_AkAArgMAIdsCAQCrAwAh6AIBAKsDACHpAgEAqwMAIeoCAQDXAwAh6wIBANcDACHsAgEA1wMAIe0CQADAAwAh7gJAAMADACHvAgEA1wMAIfACAQDXAwAhAwAAAAcAIAEAAHUAMCMAAHYAIAMAAAAHACABAAAIADACAAAJACAJmgIAAO8DADCbAgAAfAAQnAIAAO8DADC2AgEAAAABvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh5gIBALgDACHnAgEAuAMAIQEAAAB5ACABAAAAeQAgCZoCAADvAwAwmwIAAHwAEJwCAADvAwAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2AJAALsDACHmAgEAuAMAIecCAQC4AwAhAAMAAAB8ACABAAB9ADACAAB5ACADAAAAfAAgAQAAfQAwAgAAeQAgAwAAAHwAIAEAAH0AMAIAAHkAIAa2AgEAAAABvgJAAAAAAb8CQAAAAAHYAkAAAAAB5gIBAAAAAecCAQAAAAEBFwAAgQEAIAa2AgEAAAABvgJAAAAAAb8CQAAAAAHYAkAAAAAB5gIBAAAAAecCAQAAAAEBFwAAgwEAMAEXAACDAQAwBrYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdgCQACWBAAh5gIBAJQEACHnAgEAlAQAIQIAAAB5ACAXAACGAQAgBrYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdgCQACWBAAh5gIBAJQEACHnAgEAlAQAIQIAAAB8ACAXAACIAQAgAgAAAHwAIBcAAIgBACADAAAAeQAgHgAAgQEAIB8AAIYBACABAAAAeQAgAQAAAHwAIAMHAADKBQAgJAAAzAUAICUAAMsFACAJmgIAAO4DADCbAgAAjwEAEJwCAADuAwAwtgIBAKsDACG-AkAArgMAIb8CQACuAwAh2AJAAK4DACHmAgEAqwMAIecCAQCrAwAhAwAAAHwAIAEAAI4BADAjAACPAQAgAwAAAHwAIAEAAH0AMAIAAHkAIAEAAAATACABAAAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACADAAAAEQAgAQAAEgAwAgAAEwAgDwgAAMkFACAJAACBBQAgCwAAggUAIAwAAIMFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAAB5QIBAAAAAQEXAACXAQAgC7YCAQAAAAG-AkAAAAABvwJAAAAAAd4CAQAAAAHfAgEAAAAB4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQAAAAHlAgEAAAABARcAAJkBADABFwAAmQEAMAEAAAAPACAPCAAAyAUAIAkAAKQEACALAAClBAAgDAAApgQAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAh5QIBAKIEACECAAAAEwAgFwAAnQEAIAu2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhAgAAABEAIBcAAJ8BACACAAAAEQAgFwAAnwEAIAEAAAAPACADAAAAEwAgHgAAlwEAIB8AAJ0BACABAAAAEwAgAQAAABEAIAgHAADFBQAgJAAAxwUAICUAAMYFACDfAgAAhgUAIOACAACGBQAg4QIAAIYFACDiAgAAhgUAIOUCAACGBQAgDpoCAADtAwAwmwIAAKcBABCcAgAA7QMAMLYCAQC-AwAhvgJAAK4DACG_AkAArgMAId4CAQCrAwAh3wIBANcDACHgAgEA1wMAIeECAQDXAwAh4gIBANcDACHjAgEAqwMAIeQCAQCrAwAh5QIBANcDACEDAAAAEQAgAQAApgEAMCMAAKcBACADAAAAEQAgAQAAEgAwAgAAEwAgAQAAAA0AIAEAAAANACADAAAACwAgAQAADAAwAgAADQAgAwAAAAsAIAEAAAwAMAIAAA0AIAMAAAALACABAAAMADACAAANACAGAwAA_wQAIAoAAMQFACC-AkAAAAAB0gIBAAAAAdsCAQAAAAHdAgAAAN0CAgEXAACvAQAgBL4CQAAAAAHSAgEAAAAB2wIBAAAAAd0CAAAA3QICARcAALEBADABFwAAsQEAMAYDAAD9BAAgCgAAwwUAIL4CQACWBAAh0gIBAJQEACHbAgEAlAQAId0CAAD7BN0CIgIAAAANACAXAAC0AQAgBL4CQACWBAAh0gIBAJQEACHbAgEAlAQAId0CAAD7BN0CIgIAAAALACAXAAC2AQAgAgAAAAsAIBcAALYBACADAAAADQAgHgAArwEAIB8AALQBACABAAAADQAgAQAAAAsAIAMHAADABQAgJAAAwgUAICUAAMEFACAHmgIAAOkDADCbAgAAvQEAEJwCAADpAwAwvgJAAK4DACHSAgEAvgMAIdsCAQCrAwAh3QIAAOoD3QIiAwAAAAsAIAEAALwBADAjAAC9AQAgAwAAAAsAIAEAAAwAMAIAAA0AIAEAAAAZACABAAAAGQAgAwAAABcAIAEAABgAMAIAABkAIAMAAAAXACABAAAYADACAAAZACADAAAAFwAgAQAAGAAwAgAAGQAgCgoAAL8FACC2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB0gIBAAAAAdcCAQAAAAHYAkAAAAAB2QJAAAAAAdoCQAAAAAEBFwAAxQEAIAm2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB0gIBAAAAAdcCAQAAAAHYAkAAAAAB2QJAAAAAAdoCQAAAAAEBFwAAxwEAMAEXAADHAQAwCgoAAL4FACC2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh1wIBAJQEACHYAkAAlgQAIdkCQADuBAAh2gJAAO4EACECAAAAGQAgFwAAygEAIAm2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh1wIBAJQEACHYAkAAlgQAIdkCQADuBAAh2gJAAO4EACECAAAAFwAgFwAAzAEAIAIAAAAXACAXAADMAQAgAwAAABkAIB4AAMUBACAfAADKAQAgAQAAABkAIAEAAAAXACAFBwAAuwUAICQAAL0FACAlAAC8BQAg2QIAAIYFACDaAgAAhgUAIAyaAgAA6AMAMJsCAADTAQAQnAIAAOgDADC2AgEAvgMAIbcCAQCrAwAhvgJAAK4DACG_AkAArgMAIdICAQC-AwAh1wIBAKsDACHYAkAArgMAIdkCQADAAwAh2gJAAMADACEDAAAAFwAgAQAA0gEAMCMAANMBACADAAAAFwAgAQAAGAAwAgAAGQAgCgwAAOcDACCaAgAA5QMAMJsCAADZAQAQnAIAAOUDADCeAgEAAAABtgIBAAAAAb4CQAC7AwAhvwJAALsDACHRAiAA5gMAIdYCAQAAAAEBAAAA1gEAIAEAAADWAQAgCgwAAOcDACCaAgAA5QMAMJsCAADZAQAQnAIAAOUDADCeAgEAuAMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIdECIADmAwAh1gIBALgDACEBDAAAugUAIAMAAADZAQAgAQAA2gEAMAIAANYBACADAAAA2QEAIAEAANoBADACAADWAQAgAwAAANkBACABAADaAQAwAgAA1gEAIAcMAAC5BQAgngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdECIAAAAAHWAgEAAAABARcAAN4BACAGngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdECIAAAAAHWAgEAAAABARcAAOABADABFwAA4AEAMAcMAACvBQAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHRAiAAzwQAIdYCAQCUBAAhAgAAANYBACAXAADjAQAgBp4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0QIgAM8EACHWAgEAlAQAIQIAAADZAQAgFwAA5QEAIAIAAADZAQAgFwAA5QEAIAMAAADWAQAgHgAA3gEAIB8AAOMBACABAAAA1gEAIAEAAADZAQAgAwcAAKwFACAkAACuBQAgJQAArQUAIAmaAgAA5AMAMJsCAADsAQAQnAIAAOQDADCeAgEAqwMAIbYCAQC-AwAhvgJAAK4DACG_AkAArgMAIdECIADYAwAh1gIBAKsDACEDAAAA2QEAIAEAAOsBADAjAADsAQAgAwAAANkBACABAADaAQAwAgAA1gEAIAEAAAAdACABAAAAHQAgAwAAABsAIAEAABwAMAIAAB0AIAMAAAAbACABAAAcADACAAAdACADAAAAGwAgAQAAHAAwAgAAHQAgDQoAAKsFACANAADgBAAgDwAA4QQAIBAAAOIEACARAADjBAAgngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHTAgEAAAAB1AICAAAAAdUCEAAAAAEBFwAA9AEAIAieAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0gIBAAAAAdMCAQAAAAHUAgIAAAAB1QIQAAAAAQEXAAD2AQAwARcAAPYBADANCgAAqgUAIA0AALMEACAPAAC0BAAgEAAAtQQAIBEAALYEACCeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhAgAAAB0AIBcAAPkBACAIngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQIAAAAbACAXAAD7AQAgAgAAABsAIBcAAPsBACADAAAAHQAgHgAA9AEAIB8AAPkBACABAAAAHQAgAQAAABsAIAUHAAClBQAgJAAAqAUAICUAAKcFACCmAQAApgUAIKcBAACpBQAgC5oCAADjAwAwmwIAAIICABCcAgAA4wMAMJ4CAQCrAwAhtgIBAL4DACG-AkAArgMAIb8CQACuAwAh0gIBAL4DACHTAgEAvgMAIdQCAgC_AwAh1QIQAKwDACEDAAAAGwAgAQAAgQIAMCMAAIICACADAAAAGwAgAQAAHAAwAgAAHQAgAQAAACMAIAEAAAAjACADAAAAIQAgAQAAIgAwAgAAIwAgAwAAACEAIAEAACIAMAIAACMAIAMAAAAhACABAAAiADACAAAjACAHDgAApAUAILYCAQAAAAG-AkAAAAABxAIBAAAAAcYCAQAAAAHHAgEAAAAB0AIAAADQAgIBFwAAigIAIAa2AgEAAAABvgJAAAAAAcQCAQAAAAHGAgEAAAABxwIBAAAAAdACAAAA0AICARcAAIwCADABFwAAjAIAMAcOAACjBQAgtgIBAJQEACG-AkAAlgQAIcQCAQCUBAAhxgIBAJQEACHHAgEAlAQAIdACAADcBNACIgIAAAAjACAXAACPAgAgBrYCAQCUBAAhvgJAAJYEACHEAgEAlAQAIcYCAQCUBAAhxwIBAJQEACHQAgAA3ATQAiICAAAAIQAgFwAAkQIAIAIAAAAhACAXAACRAgAgAwAAACMAIB4AAIoCACAfAACPAgAgAQAAACMAIAEAAAAhACADBwAAoAUAICQAAKIFACAlAAChBQAgCZoCAADfAwAwmwIAAJgCABCcAgAA3wMAMLYCAQC-AwAhvgJAAK4DACHEAgEAvgMAIcYCAQCrAwAhxwIBAKsDACHQAgAA4APQAiIDAAAAIQAgAQAAlwIAMCMAAJgCACADAAAAIQAgAQAAIgAwAgAAIwAgAQAAACcAIAEAAAAnACADAAAAJQAgAQAAJgAwAgAAJwAgAwAAACUAIAEAACYAMAIAACcAIAMAAAAlACABAAAmADACAAAnACAKDgAAnwUAIJ8CEAAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHEAgEAAAABxgIBAAAAAccCAQAAAAHQAgAAANACA9ECIAAAAAEBFwAAoAIAIAmfAhAAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAABxAIBAAAAAcYCAQAAAAHHAgEAAAAB0AIAAADQAgPRAiAAAAABARcAAKICADABFwAAogIAMAoOAACeBQAgnwIQAJUEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHEAgEAlAQAIcYCAQCiBAAhxwIBAKIEACHQAgAAzgTQAiPRAiAAzwQAIQIAAAAnACAXAAClAgAgCZ8CEACVBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAhxAIBAJQEACHGAgEAogQAIccCAQCiBAAh0AIAAM4E0AIj0QIgAM8EACECAAAAJQAgFwAApwIAIAIAAAAlACAXAACnAgAgAwAAACcAIB4AAKACACAfAAClAgAgAQAAACcAIAEAAAAlACAIBwAAmQUAICQAAJwFACAlAACbBQAgpgEAAJoFACCnAQAAnQUAIMYCAACGBQAgxwIAAIYFACDQAgAAhgUAIAyaAgAA1QMAMJsCAACuAgAQnAIAANUDADCfAhAArAMAIbYCAQC-AwAhvgJAAK4DACG_AkAArgMAIcQCAQC-AwAhxgIBANcDACHHAgEA1wMAIdACAADWA9ACI9ECIADYAwAhAwAAACUAIAEAAK0CADAjAACuAgAgAwAAACUAIAEAACYAMAIAACcAIAEAAAArACABAAAAKwAgAwAAACkAIAEAACoAMAIAACsAIAMAAAApACABAAAqADACAAArACADAAAAKQAgAQAAKgAwAgAAKwAgDA4AAJgFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHDAgEAAAABxAIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAcsCAQAAAAHMAgEAAAABzgIAAADJAgIBFwAAtgIAIAu2AgEAAAABvgJAAAAAAb8CQAAAAAHDAgEAAAABxAIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAcsCAQAAAAHMAgEAAAABzgIAAADJAgIBFwAAuAIAMAEXAAC4AgAwDA4AAJcFACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHDAgEAlAQAIcQCAQCUBAAhxQJAAJYEACHGAgEAlAQAIccCAQCUBAAhywIBAJQEACHMAgEAlAQAIc4CAADBBMkCIgIAAAArACAXAAC7AgAgC7YCAQCUBAAhvgJAAJYEACG_AkAAlgQAIcMCAQCUBAAhxAIBAJQEACHFAkAAlgQAIcYCAQCUBAAhxwIBAJQEACHLAgEAlAQAIcwCAQCUBAAhzgIAAMEEyQIiAgAAACkAIBcAAL0CACACAAAAKQAgFwAAvQIAIAMAAAArACAeAAC2AgAgHwAAuwIAIAEAAAArACABAAAAKQAgAwcAAJQFACAkAACWBQAgJQAAlQUAIA6aAgAA1AMAMJsCAADEAgAQnAIAANQDADC2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHDAgEAqwMAIcQCAQC-AwAhxQJAAK4DACHGAgEAqwMAIccCAQCrAwAhywIBAKsDACHMAgEAqwMAIc4CAADPA8kCIgMAAAApACABAADDAgAwIwAAxAIAIAMAAAApACABAAAqADACAAArACAOmgIAANIDADCbAgAAygIAEJwCAADSAwAwtgIBAAAAAcMCAQC4AwAhxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHJAgAA0wPJAiLKAgAA0wPJAiLLAgEAuAMAIcwCAQC4AwAhzQJAALsDACEBAAAAxwIAIAEAAADHAgAgDpoCAADSAwAwmwIAAMoCABCcAgAA0gMAMLYCAQDIAwAhwwIBALgDACHEAgEAyAMAIcUCQAC7AwAhxgIBALgDACHHAgEAuAMAIckCAADTA8kCIsoCAADTA8kCIssCAQC4AwAhzAIBALgDACHNAkAAuwMAIQADAAAAygIAIAEAAMsCADACAADHAgAgAwAAAMoCACABAADLAgAwAgAAxwIAIAMAAADKAgAgAQAAywIAMAIAAMcCACALtgIBAAAAAcMCAQAAAAHEAgEAAAABxQJAAAAAAcYCAQAAAAHHAgEAAAAByQIAAADJAgLKAgAAAMkCAssCAQAAAAHMAgEAAAABzQJAAAAAAQEXAADPAgAgC7YCAQAAAAHDAgEAAAABxAIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAckCAAAAyQICygIAAADJAgLLAgEAAAABzAIBAAAAAc0CQAAAAAEBFwAA0QIAMAEXAADRAgAwC7YCAQCUBAAhwwIBAJQEACHEAgEAlAQAIcUCQACWBAAhxgIBAJQEACHHAgEAlAQAIckCAADBBMkCIsoCAADBBMkCIssCAQCUBAAhzAIBAJQEACHNAkAAlgQAIQIAAADHAgAgFwAA1AIAIAu2AgEAlAQAIcMCAQCUBAAhxAIBAJQEACHFAkAAlgQAIcYCAQCUBAAhxwIBAJQEACHJAgAAwQTJAiLKAgAAwQTJAiLLAgEAlAQAIcwCAQCUBAAhzQJAAJYEACECAAAAygIAIBcAANYCACACAAAAygIAIBcAANYCACADAAAAxwIAIB4AAM8CACAfAADUAgAgAQAAAMcCACABAAAAygIAIAMHAACRBQAgJAAAkwUAICUAAJIFACAOmgIAAM4DADCbAgAA3QIAEJwCAADOAwAwtgIBAL4DACHDAgEAqwMAIcQCAQC-AwAhxQJAAK4DACHGAgEAqwMAIccCAQCrAwAhyQIAAM8DyQIiygIAAM8DyQIiywIBAKsDACHMAgEAqwMAIc0CQACuAwAhAwAAAMoCACABAADcAgAwIwAA3QIAIAMAAADKAgAgAQAAywIAMAIAAMcCACAPmgIAAM0DADCbAgAA4wIAEJwCAADNAwAwtgIBAAAAAbcCAQC4AwAhuAIBALgDACG5AkAAuwMAIboCAgDJAwAhuwJAALsDACG8AgIAyQMAIb4CQAC7AwAhvwJAALsDACHAAgEAuAMAIcECQADKAwAhwgJAAMoDACEBAAAA4AIAIAEAAADgAgAgD5oCAADNAwAwmwIAAOMCABCcAgAAzQMAMLYCAQDIAwAhtwIBALgDACG4AgEAuAMAIbkCQAC7AwAhugICAMkDACG7AkAAuwMAIbwCAgDJAwAhvgJAALsDACG_AkAAuwMAIcACAQC4AwAhwQJAAMoDACHCAkAAygMAIQLBAgAAhgUAIMICAACGBQAgAwAAAOMCACABAADkAgAwAgAA4AIAIAMAAADjAgAgAQAA5AIAMAIAAOACACADAAAA4wIAIAEAAOQCADACAADgAgAgDLYCAQAAAAG3AgEAAAABuAIBAAAAAbkCQAAAAAG6AgIAAAABuwJAAAAAAbwCAgAAAAG-AkAAAAABvwJAAAAAAcACAQAAAAHBAkAAAAABwgJAAAAAAQEXAADoAgAgDLYCAQAAAAG3AgEAAAABuAIBAAAAAbkCQAAAAAG6AgIAAAABuwJAAAAAAbwCAgAAAAG-AkAAAAABvwJAAAAAAcACAQAAAAHBAkAAAAABwgJAAAAAAQEXAADqAgAwARcAAOoCADAMtgIBAJQEACG3AgEAlAQAIbgCAQCUBAAhuQJAAJYEACG6AgIAsQQAIbsCQACWBAAhvAICALEEACG-AkAAlgQAIb8CQACWBAAhwAIBAJQEACHBAkAA7gQAIcICQADuBAAhAgAAAOACACAXAADtAgAgDLYCAQCUBAAhtwIBAJQEACG4AgEAlAQAIbkCQACWBAAhugICALEEACG7AkAAlgQAIbwCAgCxBAAhvgJAAJYEACG_AkAAlgQAIcACAQCUBAAhwQJAAO4EACHCAkAA7gQAIQIAAADjAgAgFwAA7wIAIAIAAADjAgAgFwAA7wIAIAMAAADgAgAgHgAA6AIAIB8AAO0CACABAAAA4AIAIAEAAADjAgAgBwcAAIwFACAkAACPBQAgJQAAjgUAIKYBAACNBQAgpwEAAJAFACDBAgAAhgUAIMICAACGBQAgD5oCAADMAwAwmwIAAPYCABCcAgAAzAMAMLYCAQC-AwAhtwIBAKsDACG4AgEAqwMAIbkCQACuAwAhugICAL8DACG7AkAArgMAIbwCAgC_AwAhvgJAAK4DACG_AkAArgMAIcACAQCrAwAhwQJAAMADACHCAkAAwAMAIQMAAADjAgAgAQAA9QIAMCMAAPYCACADAAAA4wIAIAEAAOQCADACAADgAgAgDZoCAADHAwAwmwIAAPwCABCcAgAAxwMAMLYCAQAAAAG3AgEAuAMAIbgCAQC4AwAhuQJAALsDACG6AgIAyQMAIbsCQAC7AwAhvAICAMkDACG9AkAAygMAIb4CQAC7AwAhvwJAALsDACEBAAAA-QIAIAEAAAD5AgAgDZoCAADHAwAwmwIAAPwCABCcAgAAxwMAMLYCAQDIAwAhtwIBALgDACG4AgEAuAMAIbkCQAC7AwAhugICAMkDACG7AkAAuwMAIbwCAgDJAwAhvQJAAMoDACG-AkAAuwMAIb8CQAC7AwAhAb0CAACGBQAgAwAAAPwCACABAAD9AgAwAgAA-QIAIAMAAAD8AgAgAQAA_QIAMAIAAPkCACADAAAA_AIAIAEAAP0CADACAAD5AgAgCrYCAQAAAAG3AgEAAAABuAIBAAAAAbkCQAAAAAG6AgIAAAABuwJAAAAAAbwCAgAAAAG9AkAAAAABvgJAAAAAAb8CQAAAAAEBFwAAgQMAIAq2AgEAAAABtwIBAAAAAbgCAQAAAAG5AkAAAAABugICAAAAAbsCQAAAAAG8AgIAAAABvQJAAAAAAb4CQAAAAAG_AkAAAAABARcAAIMDADABFwAAgwMAMAq2AgEAlAQAIbcCAQCUBAAhuAIBAJQEACG5AkAAlgQAIboCAgCxBAAhuwJAAJYEACG8AgIAsQQAIb0CQADuBAAhvgJAAJYEACG_AkAAlgQAIQIAAAD5AgAgFwAAhgMAIAq2AgEAlAQAIbcCAQCUBAAhuAIBAJQEACG5AkAAlgQAIboCAgCxBAAhuwJAAJYEACG8AgIAsQQAIb0CQADuBAAhvgJAAJYEACG_AkAAlgQAIQIAAAD8AgAgFwAAiAMAIAIAAAD8AgAgFwAAiAMAIAMAAAD5AgAgHgAAgQMAIB8AAIYDACABAAAA-QIAIAEAAAD8AgAgBgcAAIcFACAkAACKBQAgJQAAiQUAIKYBAACIBQAgpwEAAIsFACC9AgAAhgUAIA2aAgAAvQMAMJsCAACPAwAQnAIAAL0DADC2AgEAvgMAIbcCAQCrAwAhuAIBAKsDACG5AkAArgMAIboCAgC_AwAhuwJAAK4DACG8AgIAvwMAIb0CQADAAwAhvgJAAK4DACG_AkAArgMAIQMAAAD8AgAgAQAAjgMAMCMAAI8DACADAAAA_AIAIAEAAP0CADACAAD5AgAgCQYAALwDACCaAgAAtwMAMJsCAAAPABCcAgAAtwMAMJ0CAQAAAAGeAgEAuAMAIZ8CEAC5AwAhoAIAALoDACChAkAAuwMAIQEAAACSAwAgAQAAAJIDACABBgAAhQUAIAMAAAAPACABAACVAwAwAgAAkgMAIAMAAAAPACABAACVAwAwAgAAkgMAIAMAAAAPACABAACVAwAwAgAAkgMAIAYGAACEBQAgnQIBAAAAAZ4CAQAAAAGfAhAAAAABoAKAAAAAAaECQAAAAAEBFwAAmQMAIAWdAgEAAAABngIBAAAAAZ8CEAAAAAGgAoAAAAABoQJAAAAAAQEXAACbAwAwARcAAJsDADAGBgAAlwQAIJ0CAQCUBAAhngIBAJQEACGfAhAAlQQAIaACgAAAAAGhAkAAlgQAIQIAAACSAwAgFwAAngMAIAWdAgEAlAQAIZ4CAQCUBAAhnwIQAJUEACGgAoAAAAABoQJAAJYEACECAAAADwAgFwAAoAMAIAIAAAAPACAXAACgAwAgAwAAAJIDACAeAACZAwAgHwAAngMAIAEAAACSAwAgAQAAAA8AIAUHAACPBAAgJAAAkgQAICUAAJEEACCmAQAAkAQAIKcBAACTBAAgCJoCAACqAwAwmwIAAKcDABCcAgAAqgMAMJ0CAQCrAwAhngIBAKsDACGfAhAArAMAIaACAACtAwAgoQJAAK4DACEDAAAADwAgAQAApgMAMCMAAKcDACADAAAADwAgAQAAlQMAMAIAAJIDACAImgIAAKoDADCbAgAApwMAEJwCAACqAwAwnQIBAKsDACGeAgEAqwMAIZ8CEACsAwAhoAIAAK0DACChAkAArgMAIQ4HAACwAwAgJAAAtgMAICUAALYDACCiAgEAAAABowIBAAAABKQCAQAAAASlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBALUDACGwAgEAAAABsQIBAAAAAbICAQAAAAENBwAAsAMAICQAALQDACAlAAC0AwAgpgEAALQDACCnAQAAtAMAIKICEAAAAAGjAhAAAAAEpAIQAAAABKUCEAAAAAGmAhAAAAABpwIQAAAAAagCEAAAAAGpAhAAswMAIQ8HAACwAwAgJAAAsgMAICUAALIDACCiAoAAAAABpQKAAAAAAaYCgAAAAAGnAoAAAAABqAKAAAAAAakCgAAAAAGqAgEAAAABqwIBAAAAAawCAQAAAAGtAoAAAAABrgKAAAAAAa8CgAAAAAELBwAAsAMAICQAALEDACAlAACxAwAgogJAAAAAAaMCQAAAAASkAkAAAAAEpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQACvAwAhCwcAALADACAkAACxAwAgJQAAsQMAIKICQAAAAAGjAkAAAAAEpAJAAAAABKUCQAAAAAGmAkAAAAABpwJAAAAAAagCQAAAAAGpAkAArwMAIQiiAgIAAAABowICAAAABKQCAgAAAASlAgIAAAABpgICAAAAAacCAgAAAAGoAgIAAAABqQICALADACEIogJAAAAAAaMCQAAAAASkAkAAAAAEpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQACxAwAhDKICgAAAAAGlAoAAAAABpgKAAAAAAacCgAAAAAGoAoAAAAABqQKAAAAAAaoCAQAAAAGrAgEAAAABrAIBAAAAAa0CgAAAAAGuAoAAAAABrwKAAAAAAQ0HAACwAwAgJAAAtAMAICUAALQDACCmAQAAtAMAIKcBAAC0AwAgogIQAAAAAaMCEAAAAASkAhAAAAAEpQIQAAAAAaYCEAAAAAGnAhAAAAABqAIQAAAAAakCEACzAwAhCKICEAAAAAGjAhAAAAAEpAIQAAAABKUCEAAAAAGmAhAAAAABpwIQAAAAAagCEAAAAAGpAhAAtAMAIQ4HAACwAwAgJAAAtgMAICUAALYDACCiAgEAAAABowIBAAAABKQCAQAAAASlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBALUDACGwAgEAAAABsQIBAAAAAbICAQAAAAELogIBAAAAAaMCAQAAAASkAgEAAAAEpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQC2AwAhsAIBAAAAAbECAQAAAAGyAgEAAAABCQYAALwDACCaAgAAtwMAMJsCAAAPABCcAgAAtwMAMJ0CAQC4AwAhngIBALgDACGfAhAAuQMAIaACAAC6AwAgoQJAALsDACELogIBAAAAAaMCAQAAAASkAgEAAAAEpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQC2AwAhsAIBAAAAAbECAQAAAAGyAgEAAAABCKICEAAAAAGjAhAAAAAEpAIQAAAABKUCEAAAAAGmAhAAAAABpwIQAAAAAagCEAAAAAGpAhAAtAMAIQyiAoAAAAABpQKAAAAAAaYCgAAAAAGnAoAAAAABqAKAAAAAAakCgAAAAAGqAgEAAAABqwIBAAAAAawCAQAAAAGtAoAAAAABrgKAAAAAAa8CgAAAAAEIogJAAAAAAaMCQAAAAASkAkAAAAAEpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQACxAwAhA7MCAAARACC0AgAAEQAgtQIAABEAIA2aAgAAvQMAMJsCAACPAwAQnAIAAL0DADC2AgEAvgMAIbcCAQCrAwAhuAIBAKsDACG5AkAArgMAIboCAgC_AwAhuwJAAK4DACG8AgIAvwMAIb0CQADAAwAhvgJAAK4DACG_AkAArgMAIQsHAACwAwAgJAAAtgMAICUAALYDACCiAgEAAAABowIBAAAABKQCAQAAAASlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBAMYDACENBwAAsAMAICQAALADACAlAACwAwAgpgEAAMUDACCnAQAAsAMAIKICAgAAAAGjAgIAAAAEpAICAAAABKUCAgAAAAGmAgIAAAABpwICAAAAAagCAgAAAAGpAgIAxAMAIQsHAADCAwAgJAAAwwMAICUAAMMDACCiAkAAAAABowJAAAAABaQCQAAAAAWlAkAAAAABpgJAAAAAAacCQAAAAAGoAkAAAAABqQJAAMEDACELBwAAwgMAICQAAMMDACAlAADDAwAgogJAAAAAAaMCQAAAAAWkAkAAAAAFpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQADBAwAhCKICAgAAAAGjAgIAAAAFpAICAAAABaUCAgAAAAGmAgIAAAABpwICAAAAAagCAgAAAAGpAgIAwgMAIQiiAkAAAAABowJAAAAABaQCQAAAAAWlAkAAAAABpgJAAAAAAacCQAAAAAGoAkAAAAABqQJAAMMDACENBwAAsAMAICQAALADACAlAACwAwAgpgEAAMUDACCnAQAAsAMAIKICAgAAAAGjAgIAAAAEpAICAAAABKUCAgAAAAGmAgIAAAABpwICAAAAAagCAgAAAAGpAgIAxAMAIQiiAggAAAABowIIAAAABKQCCAAAAASlAggAAAABpgIIAAAAAacCCAAAAAGoAggAAAABqQIIAMUDACELBwAAsAMAICQAALYDACAlAAC2AwAgogIBAAAAAaMCAQAAAASkAgEAAAAEpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQDGAwAhDZoCAADHAwAwmwIAAPwCABCcAgAAxwMAMLYCAQDIAwAhtwIBALgDACG4AgEAuAMAIbkCQAC7AwAhugICAMkDACG7AkAAuwMAIbwCAgDJAwAhvQJAAMoDACG-AkAAuwMAIb8CQAC7AwAhCKICAQAAAAGjAgEAAAAEpAIBAAAABKUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEAywMAIQiiAgIAAAABowICAAAABKQCAgAAAASlAgIAAAABpgICAAAAAacCAgAAAAGoAgIAAAABqQICALADACEIogJAAAAAAaMCQAAAAAWkAkAAAAAFpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQADDAwAhCKICAQAAAAGjAgEAAAAEpAIBAAAABKUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEAywMAIQ-aAgAAzAMAMJsCAAD2AgAQnAIAAMwDADC2AgEAvgMAIbcCAQCrAwAhuAIBAKsDACG5AkAArgMAIboCAgC_AwAhuwJAAK4DACG8AgIAvwMAIb4CQACuAwAhvwJAAK4DACHAAgEAqwMAIcECQADAAwAhwgJAAMADACEPmgIAAM0DADCbAgAA4wIAEJwCAADNAwAwtgIBAMgDACG3AgEAuAMAIbgCAQC4AwAhuQJAALsDACG6AgIAyQMAIbsCQAC7AwAhvAICAMkDACG-AkAAuwMAIb8CQAC7AwAhwAIBALgDACHBAkAAygMAIcICQADKAwAhDpoCAADOAwAwmwIAAN0CABCcAgAAzgMAMLYCAQC-AwAhwwIBAKsDACHEAgEAvgMAIcUCQACuAwAhxgIBAKsDACHHAgEAqwMAIckCAADPA8kCIsoCAADPA8kCIssCAQCrAwAhzAIBAKsDACHNAkAArgMAIQcHAACwAwAgJAAA0QMAICUAANEDACCiAgAAAMkCAqMCAAAAyQIIpAIAAADJAgipAgAA0APJAiIHBwAAsAMAICQAANEDACAlAADRAwAgogIAAADJAgKjAgAAAMkCCKQCAAAAyQIIqQIAANADyQIiBKICAAAAyQICowIAAADJAgikAgAAAMkCCKkCAADRA8kCIg6aAgAA0gMAMJsCAADKAgAQnAIAANIDADC2AgEAyAMAIcMCAQC4AwAhxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHJAgAA0wPJAiLKAgAA0wPJAiLLAgEAuAMAIcwCAQC4AwAhzQJAALsDACEEogIAAADJAgKjAgAAAMkCCKQCAAAAyQIIqQIAANEDyQIiDpoCAADUAwAwmwIAAMQCABCcAgAA1AMAMLYCAQC-AwAhvgJAAK4DACG_AkAArgMAIcMCAQCrAwAhxAIBAL4DACHFAkAArgMAIcYCAQCrAwAhxwIBAKsDACHLAgEAqwMAIcwCAQCrAwAhzgIAAM8DyQIiDJoCAADVAwAwmwIAAK4CABCcAgAA1QMAMJ8CEACsAwAhtgIBAL4DACG-AkAArgMAIb8CQACuAwAhxAIBAL4DACHGAgEA1wMAIccCAQDXAwAh0AIAANYD0AIj0QIgANgDACEHBwAAwgMAICQAAN4DACAlAADeAwAgogIAAADQAgOjAgAAANACCaQCAAAA0AIJqQIAAN0D0AIjDgcAAMIDACAkAADcAwAgJQAA3AMAIKICAQAAAAGjAgEAAAAFpAIBAAAABaUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEA2wMAIbACAQAAAAGxAgEAAAABsgIBAAAAAQUHAACwAwAgJAAA2gMAICUAANoDACCiAiAAAAABqQIgANkDACEFBwAAsAMAICQAANoDACAlAADaAwAgogIgAAAAAakCIADZAwAhAqICIAAAAAGpAiAA2gMAIQ4HAADCAwAgJAAA3AMAICUAANwDACCiAgEAAAABowIBAAAABaQCAQAAAAWlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBANsDACGwAgEAAAABsQIBAAAAAbICAQAAAAELogIBAAAAAaMCAQAAAAWkAgEAAAAFpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQDcAwAhsAIBAAAAAbECAQAAAAGyAgEAAAABBwcAAMIDACAkAADeAwAgJQAA3gMAIKICAAAA0AIDowIAAADQAgmkAgAAANACCakCAADdA9ACIwSiAgAAANACA6MCAAAA0AIJpAIAAADQAgmpAgAA3gPQAiMJmgIAAN8DADCbAgAAmAIAEJwCAADfAwAwtgIBAL4DACG-AkAArgMAIcQCAQC-AwAhxgIBAKsDACHHAgEAqwMAIdACAADgA9ACIgcHAACwAwAgJAAA4gMAICUAAOIDACCiAgAAANACAqMCAAAA0AIIpAIAAADQAgipAgAA4QPQAiIHBwAAsAMAICQAAOIDACAlAADiAwAgogIAAADQAgKjAgAAANACCKQCAAAA0AIIqQIAAOED0AIiBKICAAAA0AICowIAAADQAgikAgAAANACCKkCAADiA9ACIguaAgAA4wMAMJsCAACCAgAQnAIAAOMDADCeAgEAqwMAIbYCAQC-AwAhvgJAAK4DACG_AkAArgMAIdICAQC-AwAh0wIBAL4DACHUAgIAvwMAIdUCEACsAwAhCZoCAADkAwAwmwIAAOwBABCcAgAA5AMAMJ4CAQCrAwAhtgIBAL4DACG-AkAArgMAIb8CQACuAwAh0QIgANgDACHWAgEAqwMAIQoMAADnAwAgmgIAAOUDADCbAgAA2QEAEJwCAADlAwAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHRAiAA5gMAIdYCAQC4AwAhAqICIAAAAAGpAiAA2gMAIQOzAgAAGwAgtAIAABsAILUCAAAbACAMmgIAAOgDADCbAgAA0wEAEJwCAADoAwAwtgIBAL4DACG3AgEAqwMAIb4CQACuAwAhvwJAAK4DACHSAgEAvgMAIdcCAQCrAwAh2AJAAK4DACHZAkAAwAMAIdoCQADAAwAhB5oCAADpAwAwmwIAAL0BABCcAgAA6QMAML4CQACuAwAh0gIBAL4DACHbAgEAqwMAId0CAADqA90CIgcHAACwAwAgJAAA7AMAICUAAOwDACCiAgAAAN0CAqMCAAAA3QIIpAIAAADdAgipAgAA6wPdAiIHBwAAsAMAICQAAOwDACAlAADsAwAgogIAAADdAgKjAgAAAN0CCKQCAAAA3QIIqQIAAOsD3QIiBKICAAAA3QICowIAAADdAgikAgAAAN0CCKkCAADsA90CIg6aAgAA7QMAMJsCAACnAQAQnAIAAO0DADC2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHeAgEAqwMAId8CAQDXAwAh4AIBANcDACHhAgEA1wMAIeICAQDXAwAh4wIBAKsDACHkAgEAqwMAIeUCAQDXAwAhCZoCAADuAwAwmwIAAI8BABCcAgAA7gMAMLYCAQCrAwAhvgJAAK4DACG_AkAArgMAIdgCQACuAwAh5gIBAKsDACHnAgEAqwMAIQmaAgAA7wMAMJsCAAB8ABCcAgAA7wMAMLYCAQC4AwAhvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh5gIBALgDACHnAgEAuAMAIRCaAgAA8AMAMJsCAAB2ABCcAgAA8AMAMLYCAQCrAwAhvgJAAK4DACG_AkAArgMAIdsCAQCrAwAh6AIBAKsDACHpAgEAqwMAIeoCAQDXAwAh6wIBANcDACHsAgEA1wMAIe0CQADAAwAh7gJAAMADACHvAgEA1wMAIfACAQDXAwAhC5oCAADxAwAwmwIAAGAAEJwCAADxAwAwtgIBAKsDACG-AkAArgMAIb8CQACuAwAh2AJAAK4DACHbAgEAqwMAIfECAQCrAwAh8gIBANcDACHzAgEA1wMAIQuaAgAA8gMAMJsCAABKABCcAgAA8gMAMJ4CAQCrAwAhtgIBAKsDACG3AgEAqwMAIb4CQACuAwAhvwJAAK4DACHdAgEAqwMAIfQCIADYAwAh9QIBANcDACEOBAAA9QMAIAUAAPYDACAGAAD3AwAgmgIAAPMDADCbAgAANwAQnAIAAPMDADCeAgEAuAMAIbYCAQC4AwAhtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh3QIBALgDACH0AiAA5gMAIfUCAQD0AwAhC6ICAQAAAAGjAgEAAAAFpAIBAAAABaUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEA3AMAIbACAQAAAAGxAgEAAAABsgIBAAAAAQOzAgAAAwAgtAIAAAMAILUCAAADACADswIAAAcAILQCAAAHACC1AgAABwAgA7MCAAALACC0AgAACwAgtQIAAAsAIAPEAgEAAAABxQJAAAAAAcYCAQAAAAEPDgAA-gMAIJoCAAD5AwAwmwIAACkAEJwCAAD5AwAwtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAhwwIBALgDACHEAgEAyAMAIcUCQAC7AwAhxgIBALgDACHHAgEAuAMAIcsCAQC4AwAhzAIBALgDACHOAgAA0wPJAiISCgAAgAQAIA0AAIEEACAPAACCBAAgEAAAgwQAIBEAAIQEACCaAgAA_wMAMJsCAAAbABCcAgAA_wMAMJ4CAQC4AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHTAgEAyAMAIdQCAgDJAwAh1QIQALkDACH4AgAAGwAg-QIAABsAIA0OAAD6AwAgmgIAAPsDADCbAgAAJQAQnAIAAPsDADCfAhAAuQMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcQCAQDIAwAhxgIBAPQDACHHAgEA9AMAIdACAAD8A9ACI9ECIADmAwAhBKICAAAA0AIDowIAAADQAgmkAgAAANACCakCAADeA9ACIwoOAAD6AwAgmgIAAP0DADCbAgAAIQAQnAIAAP0DADC2AgEAyAMAIb4CQAC7AwAhxAIBAMgDACHGAgEAuAMAIccCAQC4AwAh0AIAAP4D0AIiBKICAAAA0AICowIAAADQAgikAgAAANACCKkCAADiA9ACIhAKAACABAAgDQAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIJoCAAD_AwAwmwIAABsAEJwCAAD_AwAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdMCAQDIAwAh1AICAMkDACHVAhAAuQMAIRQIAACHBAAgCQAA9wMAIAsAAIgEACAMAADnAwAgmgIAAIYEADCbAgAAEQAQnAIAAIYEADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHeAgEAuAMAId8CAQD0AwAh4AIBAPQDACHhAgEA9AMAIeICAQD0AwAh4wIBALgDACHkAgEAuAMAIeUCAQD0AwAh-AIAABEAIPkCAAARACAMDAAA5wMAIJoCAADlAwAwmwIAANkBABCcAgAA5QMAMJ4CAQC4AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh0QIgAOYDACHWAgEAuAMAIfgCAADZAQAg-QIAANkBACADswIAACEAILQCAAAhACC1AgAAIQAgA7MCAAAlACC0AgAAJQAgtQIAACUAIAOzAgAAKQAgtAIAACkAILUCAAApACANCgAAgAQAIJoCAACFBAAwmwIAABcAEJwCAACFBAAwtgIBAMgDACG3AgEAuAMAIb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdcCAQC4AwAh2AJAALsDACHZAkAAygMAIdoCQADKAwAhEggAAIcEACAJAAD3AwAgCwAAiAQAIAwAAOcDACCaAgAAhgQAMJsCAAARABCcAgAAhgQAMLYCAQDIAwAhvgJAALsDACG_AkAAuwMAId4CAQC4AwAh3wIBAPQDACHgAgEA9AMAIeECAQD0AwAh4gIBAPQDACHjAgEAuAMAIeQCAQC4AwAh5QIBAPQDACELBgAAvAMAIJoCAAC3AwAwmwIAAA8AEJwCAAC3AwAwnQIBALgDACGeAgEAuAMAIZ8CEAC5AwAhoAIAALoDACChAkAAuwMAIfgCAAAPACD5AgAADwAgA7MCAAAXACC0AgAAFwAgtQIAABcAIALSAgEAAAAB2wIBAAAAAQkDAACMBAAgCgAAgAQAIJoCAACKBAAwmwIAAAsAEJwCAACKBAAwvgJAALsDACHSAgEAyAMAIdsCAQC4AwAh3QIAAIsE3QIiBKICAAAA3QICowIAAADdAgikAgAAAN0CCKkCAADsA90CIhAEAAD1AwAgBQAA9gMAIAYAAPcDACCaAgAA8wMAMJsCAAA3ABCcAgAA8wMAMJ4CAQC4AwAhtgIBALgDACG3AgEAuAMAIb4CQAC7AwAhvwJAALsDACHdAgEAuAMAIfQCIADmAwAh9QIBAPQDACH4AgAANwAg-QIAADcAIBEDAACMBAAgmgIAAI0EADCbAgAABwAQnAIAAI0EADC2AgEAuAMAIb4CQAC7AwAhvwJAALsDACHbAgEAuAMAIegCAQC4AwAh6QIBALgDACHqAgEA9AMAIesCAQD0AwAh7AIBAPQDACHtAkAAygMAIe4CQADKAwAh7wIBAPQDACHwAgEA9AMAIQwDAACMBAAgmgIAAI4EADCbAgAAAwAQnAIAAI4EADC2AgEAuAMAIb4CQAC7AwAhvwJAALsDACHYAkAAuwMAIdsCAQC4AwAh8QIBALgDACHyAgEA9AMAIfMCAQD0AwAhAAAAAAAB_QIBAAAAAQX9AhAAAAABgwMQAAAAAYQDEAAAAAGFAxAAAAABhgMQAAAAAQH9AkAAAAABCx4AAJgEADAfAACdBAAw-gIAAJkEADD7AgAAmgQAMPwCAACbBAAg_QIAAJwEADD-AgAAnAQAMP8CAACcBAAwgAMAAJwEADCBAwAAngQAMIIDAACfBAAwDQkAAIEFACALAACCBQAgDAAAgwUAILYCAQAAAAG-AkAAAAABvwJAAAAAAd4CAQAAAAHfAgEAAAAB4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQAAAAECAAAAEwAgHgAAgAUAIAMAAAATACAeAACABQAgHwAAowQAIAEXAADOBgAwEggAAIcEACAJAAD3AwAgCwAAiAQAIAwAAOcDACCaAgAAhgQAMJsCAAARABCcAgAAhgQAMLYCAQAAAAG-AkAAuwMAIb8CQAC7AwAh3gIBALgDACHfAgEA9AMAIeACAQD0AwAh4QIBAPQDACHiAgEA9AMAIeMCAQAAAAHkAgEAuAMAIeUCAQD0AwAhAgAAABMAIBcAAKMEACACAAAAoAQAIBcAAKEEACAOmgIAAJ8EADCbAgAAoAQAEJwCAACfBAAwtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh3gIBALgDACHfAgEA9AMAIeACAQD0AwAh4QIBAPQDACHiAgEA9AMAIeMCAQC4AwAh5AIBALgDACHlAgEA9AMAIQ6aAgAAnwQAMJsCAACgBAAQnAIAAJ8EADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHeAgEAuAMAId8CAQD0AwAh4AIBAPQDACHhAgEA9AMAIeICAQD0AwAh4wIBALgDACHkAgEAuAMAIeUCAQD0AwAhCrYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAhAf0CAQAAAAENCQAApAQAIAsAAKUEACAMAACmBAAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh3gIBAJQEACHfAgEAogQAIeACAQCiBAAh4QIBAKIEACHiAgEAogQAIeMCAQCUBAAh5AIBAJQEACELHgAA8QQAMB8AAPYEADD6AgAA8gQAMPsCAADzBAAw_AIAAPQEACD9AgAA9QQAMP4CAAD1BAAw_wIAAPUEADCAAwAA9QQAMIEDAAD3BAAwggMAAPgEADALHgAA5AQAMB8AAOkEADD6AgAA5QQAMPsCAADmBAAw_AIAAOcEACD9AgAA6AQAMP4CAADoBAAw_wIAAOgEADCAAwAA6AQAMIEDAADqBAAwggMAAOsEADALHgAApwQAMB8AAKwEADD6AgAAqAQAMPsCAACpBAAw_AIAAKoEACD9AgAAqwQAMP4CAACrBAAw_wIAAKsEADCAAwAAqwQAMIEDAACtBAAwggMAAK4EADALDQAA4AQAIA8AAOEEACAQAADiBAAgEQAA4wQAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHTAgEAAAAB1AICAAAAAdUCEAAAAAECAAAAHQAgHgAA3wQAIAMAAAAdACAeAADfBAAgHwAAsgQAIAEXAADNBgAwEAoAAIAEACANAACBBAAgDwAAggQAIBAAAIMEACARAACEBAAgmgIAAP8DADCbAgAAGwAQnAIAAP8DADCeAgEAuAMAIbYCAQAAAAG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHTAgEAyAMAIdQCAgDJAwAh1QIQALkDACECAAAAHQAgFwAAsgQAIAIAAACvBAAgFwAAsAQAIAuaAgAArgQAMJsCAACvBAAQnAIAAK4EADCeAgEAuAMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh0wIBAMgDACHUAgIAyQMAIdUCEAC5AwAhC5oCAACuBAAwmwIAAK8EABCcAgAArgQAMJ4CAQC4AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHTAgEAyAMAIdQCAgDJAwAh1QIQALkDACEHngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHTAgEAlAQAIdQCAgCxBAAh1QIQAJUEACEF_QICAAAAAYMDAgAAAAGEAwIAAAABhQMCAAAAAYYDAgAAAAELDQAAswQAIA8AALQEACAQAAC1BAAgEQAAtgQAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhBR4AAMUGACAfAADLBgAg-gIAAMYGACD7AgAAygYAIIADAADWAQAgCx4AANIEADAfAADXBAAw-gIAANMEADD7AgAA1AQAMPwCAADVBAAg_QIAANYEADD-AgAA1gQAMP8CAADWBAAwgAMAANYEADCBAwAA2AQAMIIDAADZBAAwCx4AAMQEADAfAADJBAAw-gIAAMUEADD7AgAAxgQAMPwCAADHBAAg_QIAAMgEADD-AgAAyAQAMP8CAADIBAAwgAMAAMgEADCBAwAAygQAMIIDAADLBAAwCx4AALcEADAfAAC8BAAw-gIAALgEADD7AgAAuQQAMPwCAAC6BAAg_QIAALsEADD-AgAAuwQAMP8CAAC7BAAwgAMAALsEADCBAwAAvQQAMIIDAAC-BAAwCrYCAQAAAAG-AkAAAAABvwJAAAAAAcMCAQAAAAHFAkAAAAABxgIBAAAAAccCAQAAAAHLAgEAAAABzAIBAAAAAc4CAAAAyQICAgAAACsAIB4AAMMEACADAAAAKwAgHgAAwwQAIB8AAMIEACABFwAAyQYAMBAOAAD6AwAgmgIAAPkDADCbAgAAKQAQnAIAAPkDADC2AgEAAAABvgJAALsDACG_AkAAuwMAIcMCAQAAAAHEAgEAyAMAIcUCQAC7AwAhxgIBALgDACHHAgEAuAMAIcsCAQC4AwAhzAIBALgDACHOAgAA0wPJAiL2AgAA-AMAIAIAAAArACAXAADCBAAgAgAAAL8EACAXAADABAAgDpoCAAC-BAAwmwIAAL8EABCcAgAAvgQAMLYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcMCAQC4AwAhxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHLAgEAuAMAIcwCAQC4AwAhzgIAANMDyQIiDpoCAAC-BAAwmwIAAL8EABCcAgAAvgQAMLYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcMCAQC4AwAhxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHLAgEAuAMAIcwCAQC4AwAhzgIAANMDyQIiCrYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIcMCAQCUBAAhxQJAAJYEACHGAgEAlAQAIccCAQCUBAAhywIBAJQEACHMAgEAlAQAIc4CAADBBMkCIgH9AgAAAMkCAgq2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHDAgEAlAQAIcUCQACWBAAhxgIBAJQEACHHAgEAlAQAIcsCAQCUBAAhzAIBAJQEACHOAgAAwQTJAiIKtgIBAAAAAb4CQAAAAAG_AkAAAAABwwIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAcsCAQAAAAHMAgEAAAABzgIAAADJAgIInwIQAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAcYCAQAAAAHHAgEAAAAB0AIAAADQAgPRAiAAAAABAgAAACcAIB4AANEEACADAAAAJwAgHgAA0QQAIB8AANAEACABFwAAyAYAMA0OAAD6AwAgmgIAAPsDADCbAgAAJQAQnAIAAPsDADCfAhAAuQMAIbYCAQAAAAG-AkAAuwMAIb8CQAC7AwAhxAIBAMgDACHGAgEA9AMAIccCAQD0AwAh0AIAAPwD0AIj0QIgAOYDACECAAAAJwAgFwAA0AQAIAIAAADMBAAgFwAAzQQAIAyaAgAAywQAMJsCAADMBAAQnAIAAMsEADCfAhAAuQMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcQCAQDIAwAhxgIBAPQDACHHAgEA9AMAIdACAAD8A9ACI9ECIADmAwAhDJoCAADLBAAwmwIAAMwEABCcAgAAywQAMJ8CEAC5AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAhxAIBAMgDACHGAgEA9AMAIccCAQD0AwAh0AIAAPwD0AIj0QIgAOYDACEInwIQAJUEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHGAgEAogQAIccCAQCiBAAh0AIAAM4E0AIj0QIgAM8EACEB_QIAAADQAgMB_QIgAAAAAQifAhAAlQQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIcYCAQCiBAAhxwIBAKIEACHQAgAAzgTQAiPRAiAAzwQAIQifAhAAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACA9ECIAAAAAEFtgIBAAAAAb4CQAAAAAHGAgEAAAABxwIBAAAAAdACAAAA0AICAgAAACMAIB4AAN4EACADAAAAIwAgHgAA3gQAIB8AAN0EACABFwAAxwYAMAoOAAD6AwAgmgIAAP0DADCbAgAAIQAQnAIAAP0DADC2AgEAAAABvgJAALsDACHEAgEAyAMAIcYCAQC4AwAhxwIBALgDACHQAgAA_gPQAiICAAAAIwAgFwAA3QQAIAIAAADaBAAgFwAA2wQAIAmaAgAA2QQAMJsCAADaBAAQnAIAANkEADC2AgEAyAMAIb4CQAC7AwAhxAIBAMgDACHGAgEAuAMAIccCAQC4AwAh0AIAAP4D0AIiCZoCAADZBAAwmwIAANoEABCcAgAA2QQAMLYCAQDIAwAhvgJAALsDACHEAgEAyAMAIcYCAQC4AwAhxwIBALgDACHQAgAA_gPQAiIFtgIBAJQEACG-AkAAlgQAIcYCAQCUBAAhxwIBAJQEACHQAgAA3ATQAiIB_QIAAADQAgIFtgIBAJQEACG-AkAAlgQAIcYCAQCUBAAhxwIBAJQEACHQAgAA3ATQAiIFtgIBAAAAAb4CQAAAAAHGAgEAAAABxwIBAAAAAdACAAAA0AICCw0AAOAEACAPAADhBAAgEAAA4gQAIBEAAOMEACCeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABAx4AAMUGACD6AgAAxgYAIIADAADWAQAgBB4AANIEADD6AgAA0wQAMPwCAADVBAAggAMAANYEADAEHgAAxAQAMPoCAADFBAAw_AIAAMcEACCAAwAAyAQAMAQeAAC3BAAw-gIAALgEADD8AgAAugQAIIADAAC7BAAwCLYCAQAAAAG3AgEAAAABvgJAAAAAAb8CQAAAAAHXAgEAAAAB2AJAAAAAAdkCQAAAAAHaAkAAAAABAgAAABkAIB4AAPAEACADAAAAGQAgHgAA8AQAIB8AAO8EACABFwAAxAYAMA0KAACABAAgmgIAAIUEADCbAgAAFwAQnAIAAIUEADC2AgEAAAABtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHXAgEAAAAB2AJAALsDACHZAkAAygMAIdoCQADKAwAhAgAAABkAIBcAAO8EACACAAAA7AQAIBcAAO0EACAMmgIAAOsEADCbAgAA7AQAEJwCAADrBAAwtgIBAMgDACG3AgEAuAMAIb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdcCAQC4AwAh2AJAALsDACHZAkAAygMAIdoCQADKAwAhDJoCAADrBAAwmwIAAOwEABCcAgAA6wQAMLYCAQDIAwAhtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHXAgEAuAMAIdgCQAC7AwAh2QJAAMoDACHaAkAAygMAIQi2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdcCAQCUBAAh2AJAAJYEACHZAkAA7gQAIdoCQADuBAAhAf0CQAAAAAEItgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHXAgEAlAQAIdgCQACWBAAh2QJAAO4EACHaAkAA7gQAIQi2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB1wIBAAAAAdgCQAAAAAHZAkAAAAAB2gJAAAAAAQQDAAD_BAAgvgJAAAAAAdsCAQAAAAHdAgAAAN0CAgIAAAANACAeAAD-BAAgAwAAAA0AIB4AAP4EACAfAAD8BAAgARcAAMMGADAKAwAAjAQAIAoAAIAEACCaAgAAigQAMJsCAAALABCcAgAAigQAML4CQAC7AwAh0gIBAMgDACHbAgEAuAMAId0CAACLBN0CIvcCAACJBAAgAgAAAA0AIBcAAPwEACACAAAA-QQAIBcAAPoEACAHmgIAAPgEADCbAgAA-QQAEJwCAAD4BAAwvgJAALsDACHSAgEAyAMAIdsCAQC4AwAh3QIAAIsE3QIiB5oCAAD4BAAwmwIAAPkEABCcAgAA-AQAML4CQAC7AwAh0gIBAMgDACHbAgEAuAMAId0CAACLBN0CIgO-AkAAlgQAIdsCAQCUBAAh3QIAAPsE3QIiAf0CAAAA3QICBAMAAP0EACC-AkAAlgQAIdsCAQCUBAAh3QIAAPsE3QIiBR4AAL4GACAfAADBBgAg-gIAAL8GACD7AgAAwAYAIIADAAABACAEAwAA_wQAIL4CQAAAAAHbAgEAAAAB3QIAAADdAgIDHgAAvgYAIPoCAAC_BgAggAMAAAEAIA0JAACBBQAgCwAAggUAIAwAAIMFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAABBB4AAPEEADD6AgAA8gQAMPwCAAD0BAAggAMAAPUEADAEHgAA5AQAMPoCAADlBAAw_AIAAOcEACCAAwAA6AQAMAQeAACnBAAw-gIAAKgEADD8AgAAqgQAIIADAACrBAAwBB4AAJgEADD6AgAAmQQAMPwCAACbBAAggAMAAJwEADAAAAAAAAAAAAAAAAAAAAAAAAAFHgAAuQYAIB8AALwGACD6AgAAugYAIPsCAAC7BgAggAMAAB0AIAMeAAC5BgAg-gIAALoGACCAAwAAHQAgAAAAAAAFHgAAtAYAIB8AALcGACD6AgAAtQYAIPsCAAC2BgAggAMAAB0AIAMeAAC0BgAg-gIAALUGACCAAwAAHQAgAAAABR4AAK8GACAfAACyBgAg-gIAALAGACD7AgAAsQYAIIADAAAdACADHgAArwYAIPoCAACwBgAggAMAAB0AIAAAAAAABR4AAKoGACAfAACtBgAg-gIAAKsGACD7AgAArAYAIIADAAATACADHgAAqgYAIPoCAACrBgAggAMAABMAIAAAAAseAACwBQAwHwAAtAUAMPoCAACxBQAw-wIAALIFADD8AgAAswUAIP0CAACrBAAw_gIAAKsEADD_AgAAqwQAMIADAACrBAAwgQMAALUFADCCAwAArgQAMAsKAACrBQAgDwAA4QQAIBAAAOIEACARAADjBAAgngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHUAgIAAAAB1QIQAAAAAQIAAAAdACAeAAC4BQAgAwAAAB0AIB4AALgFACAfAAC3BQAgARcAAKkGADACAAAAHQAgFwAAtwUAIAIAAACvBAAgFwAAtgUAIAeeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh1AICALEEACHVAhAAlQQAIQsKAACqBQAgDwAAtAQAIBAAALUEACARAAC2BAAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdQCAgCxBAAh1QIQAJUEACELCgAAqwUAIA8AAOEEACAQAADiBAAgEQAA4wQAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHSAgEAAAAB1AICAAAAAdUCEAAAAAEEHgAAsAUAMPoCAACxBQAw_AIAALMFACCAAwAAqwQAMAAAAAAFHgAApAYAIB8AAKcGACD6AgAApQYAIPsCAACmBgAggAMAABMAIAMeAACkBgAg-gIAAKUGACCAAwAAEwAgAAAABR4AAJ8GACAfAACiBgAg-gIAAKAGACD7AgAAoQYAIIADAAATACADHgAAnwYAIPoCAACgBgAggAMAABMAIAAAAAceAACaBgAgHwAAnQYAIPoCAACbBgAg-wIAAJwGACD-AgAADwAg_wIAAA8AIIADAACSAwAgAx4AAJoGACD6AgAAmwYAIIADAACSAwAgAAAAAAAABR4AAJUGACAfAACYBgAg-gIAAJYGACD7AgAAlwYAIIADAAABACADHgAAlQYAIPoCAACWBgAggAMAAAEAIAAAAAUeAACQBgAgHwAAkwYAIPoCAACRBgAg-wIAAJIGACCAAwAAAQAgAx4AAJAGACD6AgAAkQYAIIADAAABACAAAAALHgAA8gUAMB8AAPcFADD6AgAA8wUAMPsCAAD0BQAw_AIAAPUFACD9AgAA9gUAMP4CAAD2BQAw_wIAAPYFADCAAwAA9gUAMIEDAAD4BQAwggMAAPkFADALHgAA5gUAMB8AAOsFADD6AgAA5wUAMPsCAADoBQAw_AIAAOkFACD9AgAA6gUAMP4CAADqBQAw_wIAAOoFADCAAwAA6gUAMIEDAADsBQAwggMAAO0FADALHgAA3QUAMB8AAOEFADD6AgAA3gUAMPsCAADfBQAw_AIAAOAFACD9AgAA9QQAMP4CAAD1BAAw_wIAAPUEADCAAwAA9QQAMIEDAADiBQAwggMAAPgEADAECgAAxAUAIL4CQAAAAAHSAgEAAAAB3QIAAADdAgICAAAADQAgHgAA5QUAIAMAAAANACAeAADlBQAgHwAA5AUAIAEXAACPBgAwAgAAAA0AIBcAAOQFACACAAAA-QQAIBcAAOMFACADvgJAAJYEACHSAgEAlAQAId0CAAD7BN0CIgQKAADDBQAgvgJAAJYEACHSAgEAlAQAId0CAAD7BN0CIgQKAADEBQAgvgJAAAAAAdICAQAAAAHdAgAAAN0CAgy2AgEAAAABvgJAAAAAAb8CQAAAAAHoAgEAAAAB6QIBAAAAAeoCAQAAAAHrAgEAAAAB7AIBAAAAAe0CQAAAAAHuAkAAAAAB7wIBAAAAAfACAQAAAAECAAAACQAgHgAA8QUAIAMAAAAJACAeAADxBQAgHwAA8AUAIAEXAACOBgAwEQMAAIwEACCaAgAAjQQAMJsCAAAHABCcAgAAjQQAMLYCAQAAAAG-AkAAuwMAIb8CQAC7AwAh2wIBALgDACHoAgEAuAMAIekCAQC4AwAh6gIBAPQDACHrAgEA9AMAIewCAQD0AwAh7QJAAMoDACHuAkAAygMAIe8CAQD0AwAh8AIBAPQDACECAAAACQAgFwAA8AUAIAIAAADuBQAgFwAA7wUAIBCaAgAA7QUAMJsCAADuBQAQnAIAAO0FADC2AgEAuAMAIb4CQAC7AwAhvwJAALsDACHbAgEAuAMAIegCAQC4AwAh6QIBALgDACHqAgEA9AMAIesCAQD0AwAh7AIBAPQDACHtAkAAygMAIe4CQADKAwAh7wIBAPQDACHwAgEA9AMAIRCaAgAA7QUAMJsCAADuBQAQnAIAAO0FADC2AgEAuAMAIb4CQAC7AwAhvwJAALsDACHbAgEAuAMAIegCAQC4AwAh6QIBALgDACHqAgEA9AMAIesCAQD0AwAh7AIBAPQDACHtAkAAygMAIe4CQADKAwAh7wIBAPQDACHwAgEA9AMAIQy2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHoAgEAlAQAIekCAQCUBAAh6gIBAKIEACHrAgEAogQAIewCAQCiBAAh7QJAAO4EACHuAkAA7gQAIe8CAQCiBAAh8AIBAKIEACEMtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh6AIBAJQEACHpAgEAlAQAIeoCAQCiBAAh6wIBAKIEACHsAgEAogQAIe0CQADuBAAh7gJAAO4EACHvAgEAogQAIfACAQCiBAAhDLYCAQAAAAG-AkAAAAABvwJAAAAAAegCAQAAAAHpAgEAAAAB6gIBAAAAAesCAQAAAAHsAgEAAAAB7QJAAAAAAe4CQAAAAAHvAgEAAAAB8AIBAAAAAQe2AgEAAAABvgJAAAAAAb8CQAAAAAHYAkAAAAAB8QIBAAAAAfICAQAAAAHzAgEAAAABAgAAAAUAIB4AAP0FACADAAAABQAgHgAA_QUAIB8AAPwFACABFwAAjQYAMAwDAACMBAAgmgIAAI4EADCbAgAAAwAQnAIAAI4EADC2AgEAAAABvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh2wIBALgDACHxAgEAAAAB8gIBAPQDACHzAgEA9AMAIQIAAAAFACAXAAD8BQAgAgAAAPoFACAXAAD7BQAgC5oCAAD5BQAwmwIAAPoFABCcAgAA-QUAMLYCAQC4AwAhvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh2wIBALgDACHxAgEAuAMAIfICAQD0AwAh8wIBAPQDACELmgIAAPkFADCbAgAA-gUAEJwCAAD5BQAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2AJAALsDACHbAgEAuAMAIfECAQC4AwAh8gIBAPQDACHzAgEA9AMAIQe2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHYAkAAlgQAIfECAQCUBAAh8gIBAKIEACHzAgEAogQAIQe2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHYAkAAlgQAIfECAQCUBAAh8gIBAKIEACHzAgEAogQAIQe2AgEAAAABvgJAAAAAAb8CQAAAAAHYAkAAAAAB8QIBAAAAAfICAQAAAAHzAgEAAAABBB4AAPIFADD6AgAA8wUAMPwCAAD1BQAggAMAAPYFADAEHgAA5gUAMPoCAADnBQAw_AIAAOkFACCAAwAA6gUAMAQeAADdBQAw-gIAAN4FADD8AgAA4AUAIIADAAD1BAAwAAAABQoAAIUGACANAACGBgAgDwAAhwYAIBAAAIgGACARAACJBgAgCQgAAIoGACAJAACDBgAgCwAAiwYAIAwAALoFACDfAgAAhgUAIOACAACGBQAg4QIAAIYFACDiAgAAhgUAIOUCAACGBQAgAQwAALoFACAAAAABBgAAhQUAIAAEBAAAgQYAIAUAAIIGACAGAACDBgAg9QIAAIYFACAHtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAfECAQAAAAHyAgEAAAAB8wIBAAAAAQy2AgEAAAABvgJAAAAAAb8CQAAAAAHoAgEAAAAB6QIBAAAAAeoCAQAAAAHrAgEAAAAB7AIBAAAAAe0CQAAAAAHuAkAAAAAB7wIBAAAAAfACAQAAAAEDvgJAAAAAAdICAQAAAAHdAgAAAN0CAgoFAAD_BQAgBgAAgAYAIJ4CAQAAAAG2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB3QIBAAAAAfQCIAAAAAH1AgEAAAABAgAAAAEAIB4AAJAGACADAAAANwAgHgAAkAYAIB8AAJQGACAMAAAANwAgBQAA2wUAIAYAANwFACAXAACUBgAgngIBAJQEACG2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAId0CAQCUBAAh9AIgAM8EACH1AgEAogQAIQoFAADbBQAgBgAA3AUAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACEKBAAA_gUAIAYAAIAGACCeAgEAAAABtgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAd0CAQAAAAH0AiAAAAAB9QIBAAAAAQIAAAABACAeAACVBgAgAwAAADcAIB4AAJUGACAfAACZBgAgDAAAADcAIAQAANoFACAGAADcBQAgFwAAmQYAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACEKBAAA2gUAIAYAANwFACCeAgEAlAQAIbYCAQCUBAAhtwIBAJQEACG-AkAAlgQAIb8CQACWBAAh3QIBAJQEACH0AiAAzwQAIfUCAQCiBAAhBZ0CAQAAAAGeAgEAAAABnwIQAAAAAaACgAAAAAGhAkAAAAABAgAAAJIDACAeAACaBgAgAwAAAA8AIB4AAJoGACAfAACeBgAgBwAAAA8AIBcAAJ4GACCdAgEAlAQAIZ4CAQCUBAAhnwIQAJUEACGgAoAAAAABoQJAAJYEACEFnQIBAJQEACGeAgEAlAQAIZ8CEACVBAAhoAKAAAAAAaECQACWBAAhDggAAMkFACALAACCBQAgDAAAgwUAILYCAQAAAAG-AkAAAAABvwJAAAAAAd4CAQAAAAHfAgEAAAAB4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQAAAAHlAgEAAAABAgAAABMAIB4AAJ8GACADAAAAEQAgHgAAnwYAIB8AAKMGACAQAAAAEQAgCAAAyAUAIAsAAKUEACAMAACmBAAgFwAAowYAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAh5QIBAKIEACEOCAAAyAUAIAsAAKUEACAMAACmBAAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh3gIBAJQEACHfAgEAogQAIeACAQCiBAAh4QIBAKIEACHiAgEAogQAIeMCAQCUBAAh5AIBAJQEACHlAgEAogQAIQ4IAADJBQAgCQAAgQUAIAwAAIMFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAAB5QIBAAAAAQIAAAATACAeAACkBgAgAwAAABEAIB4AAKQGACAfAACoBgAgEAAAABEAIAgAAMgFACAJAACkBAAgDAAApgQAIBcAAKgGACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhDggAAMgFACAJAACkBAAgDAAApgQAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAh5QIBAKIEACEHngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHUAgIAAAAB1QIQAAAAAQ4IAADJBQAgCQAAgQUAIAsAAIIFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAAB5QIBAAAAAQIAAAATACAeAACqBgAgAwAAABEAIB4AAKoGACAfAACuBgAgEAAAABEAIAgAAMgFACAJAACkBAAgCwAApQQAIBcAAK4GACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhDggAAMgFACAJAACkBAAgCwAApQQAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAh5QIBAKIEACEMCgAAqwUAIA0AAOAEACAQAADiBAAgEQAA4wQAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHSAgEAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABAgAAAB0AIB4AAK8GACADAAAAGwAgHgAArwYAIB8AALMGACAOAAAAGwAgCgAAqgUAIA0AALMEACAQAAC1BAAgEQAAtgQAIBcAALMGACCeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhDAoAAKoFACANAACzBAAgEAAAtQQAIBEAALYEACCeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhDAoAAKsFACANAADgBAAgDwAA4QQAIBEAAOMEACCeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0gIBAAAAAdMCAQAAAAHUAgIAAAAB1QIQAAAAAQIAAAAdACAeAAC0BgAgAwAAABsAIB4AALQGACAfAAC4BgAgDgAAABsAIAoAAKoFACANAACzBAAgDwAAtAQAIBEAALYEACAXAAC4BgAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQwKAACqBQAgDQAAswQAIA8AALQEACARAAC2BAAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQwKAACrBQAgDQAA4AQAIA8AAOEEACAQAADiBAAgngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHTAgEAAAAB1AICAAAAAdUCEAAAAAECAAAAHQAgHgAAuQYAIAMAAAAbACAeAAC5BgAgHwAAvQYAIA4AAAAbACAKAACqBQAgDQAAswQAIA8AALQEACAQAAC1BAAgFwAAvQYAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0gIBAJQEACHTAgEAlAQAIdQCAgCxBAAh1QIQAJUEACEMCgAAqgUAIA0AALMEACAPAAC0BAAgEAAAtQQAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0gIBAJQEACHTAgEAlAQAIdQCAgCxBAAh1QIQAJUEACEKBAAA_gUAIAUAAP8FACCeAgEAAAABtgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAd0CAQAAAAH0AiAAAAAB9QIBAAAAAQIAAAABACAeAAC-BgAgAwAAADcAIB4AAL4GACAfAADCBgAgDAAAADcAIAQAANoFACAFAADbBQAgFwAAwgYAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACEKBAAA2gUAIAUAANsFACCeAgEAlAQAIbYCAQCUBAAhtwIBAJQEACG-AkAAlgQAIb8CQACWBAAh3QIBAJQEACH0AiAAzwQAIfUCAQCiBAAhA74CQAAAAAHbAgEAAAAB3QIAAADdAgIItgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAdcCAQAAAAHYAkAAAAAB2QJAAAAAAdoCQAAAAAEGngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdECIAAAAAHWAgEAAAABAgAAANYBACAeAADFBgAgBbYCAQAAAAG-AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACAgifAhAAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACA9ECIAAAAAEKtgIBAAAAAb4CQAAAAAG_AkAAAAABwwIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAcsCAQAAAAHMAgEAAAABzgIAAADJAgIDAAAA2QEAIB4AAMUGACAfAADMBgAgCAAAANkBACAXAADMBgAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHRAiAAzwQAIdYCAQCUBAAhBp4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0QIgAM8EACHWAgEAlAQAIQeeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABCrYCAQAAAAG-AkAAAAABvwJAAAAAAd4CAQAAAAHfAgEAAAAB4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQAAAAEEBAYCBQoDBg4EBwARAQMAAQEDAAECAwABCgAFBQcAEAgQBgkWBAsaCAweCQIGFAUHAAcBBhUAAQoABQYHAA8KAAUNAAoPJAwQKA0RLA4CBwALDB8JAQwgAAEOAAkBDgAJAQ4ACQMPLQAQLgARLwADCTAACzEADDIAAwQzAAU0AAY1AAAAAAMHABYkABclABgAAAADBwAWJAAXJQAYAQMAAQEDAAEDBwAdJAAeJQAfAAAAAwcAHSQAHiUAHwEDAAEBAwABAwcAJCQAJSUAJgAAAAMHACQkACUlACYAAAADBwAsJAAtJQAuAAAAAwcALCQALSUALgEInAEGAQiiAQYDBwAzJAA0JQA1AAAAAwcAMyQANCUANQIDAAEKAAUCAwABCgAFAwcAOiQAOyUAPAAAAAMHADokADslADwBCgAFAQoABQMHAEEkAEIlAEMAAAADBwBBJABCJQBDAAADBwBIJABJJQBKAAAAAwcASCQASSUASgIKAAUNAAoCCgAFDQAKBQcATyQAUiUAU6YBAFCnAQBRAAAAAAAFBwBPJABSJQBTpgEAUKcBAFEBDgAJAQ4ACQMHAFgkAFklAFoAAAADBwBYJABZJQBaAQ4ACQEOAAkFBwBfJABiJQBjpgEAYKcBAGEAAAAAAAUHAF8kAGIlAGOmAQBgpwEAYQEOAAkBDgAJAwcAaCQAaSUAagAAAAMHAGgkAGklAGoAAAADBwBwJABxJQByAAAAAwcAcCQAcSUAcgAAAAUHAHgkAHslAHymAQB5pwEAegAAAAAABQcAeCQAeyUAfKYBAHmnAQB6AAAABQcAggEkAIUBJQCGAaYBAIMBpwEAhAEAAAAAAAUHAIIBJACFASUAhgGmAQCDAacBAIQBAAAFBwCLASQAjgElAI8BpgEAjAGnAQCNAQAAAAAABQcAiwEkAI4BJQCPAaYBAIwBpwEAjQESAgETNgEUOQEVOgEWOwEYPQEZPxIaQBMbQgEcRBIdRRQgRgEhRwEiSBImSxUnTBkoTQIpTgIqTwIrUAIsUQItUwIuVRIvVhowWAIxWhIyWxszXAI0XQI1XhI2YRw3YiA4YwM5ZAM6ZQM7ZgM8ZwM9aQM-axI_bCFAbgNBcBJCcSJDcgNEcwNFdBJGdyNHeCdIeihJeyhKfihLfyhMgAEoTYIBKE6EARJPhQEpUIcBKFGJARJSigEqU4sBKFSMAShVjQESVpABK1eRAS9YkgEFWZMBBVqUAQVblQEFXJYBBV2YAQVemgESX5sBMGCeAQVhoAESYqEBMWOjAQVkpAEFZaUBEmaoATJnqQE2aKoBBGmrAQRqrAEEa60BBGyuAQRtsAEEbrIBEm-zATdwtQEEcbcBEnK4AThzuQEEdLoBBHW7ARJ2vgE5d78BPXjAAQh5wQEIesIBCHvDAQh8xAEIfcYBCH7IARJ_yQE-gAHLAQiBAc0BEoIBzgE_gwHPAQiEAdABCIUB0QEShgHUAUCHAdUBRIgB1wEKiQHYAQqKAdsBCosB3AEKjAHdAQqNAd8BCo4B4QESjwHiAUWQAeQBCpEB5gESkgHnAUaTAegBCpQB6QEKlQHqARKWAe0BR5cB7gFLmAHvAQmZAfABCZoB8QEJmwHyAQmcAfMBCZ0B9QEJngH3ARKfAfgBTKAB-gEJoQH8ARKiAf0BTaMB_gEJpAH_AQmlAYACEqgBgwJOqQGEAlSqAYUCDKsBhgIMrAGHAgytAYgCDK4BiQIMrwGLAgywAY0CErEBjgJVsgGQAgyzAZICErQBkwJWtQGUAgy2AZUCDLcBlgISuAGZAle5AZoCW7oBmwINuwGcAg28AZ0CDb0BngINvgGfAg2_AaECDcABowISwQGkAlzCAaYCDcMBqAISxAGpAl3FAaoCDcYBqwINxwGsAhLIAa8CXskBsAJkygGxAg7LAbICDswBswIOzQG0Ag7OAbUCDs8BtwIO0AG5AhLRAboCZdIBvAIO0wG-AhLUAb8CZtUBwAIO1gHBAg7XAcICEtgBxQJn2QHGAmvaAcgCbNsByQJs3AHMAmzdAc0CbN4BzgJs3wHQAmzgAdICEuEB0wJt4gHVAmzjAdcCEuQB2AJu5QHZAmzmAdoCbOcB2wIS6AHeAm_pAd8Cc-oB4QJ06wHiAnTsAeUCdO0B5gJ07gHnAnTvAekCdPAB6wIS8QHsAnXyAe4CdPMB8AIS9AHxAnb1AfICdPYB8wJ09wH0AhL4AfcCd_kB-AJ9-gH6An77AfsCfvwB_gJ-_QH_An7-AYADfv8BggN-gAKEAxKBAoUDf4IChwN-gwKJAxKEAooDgAGFAosDfoYCjAN-hwKNAxKIApADgQGJApEDhwGKApMDBosClAMGjAKWAwaNApcDBo4CmAMGjwKaAwaQApwDEpECnQOIAZICnwMGkwKhAxKUAqIDiQGVAqMDBpYCpAMGlwKlAxKYAqgDigGZAqkDkAE"
+ strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"sessions\",\"accounts\",\"complexes\",\"_count\",\"plan\",\"users\",\"complex\",\"invitations\",\"courts\",\"sport\",\"court\",\"availabilities\",\"priceRules\",\"bookings\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Session.findUnique\",\"Session.findUniqueOrThrow\",\"Session.findFirst\",\"Session.findFirstOrThrow\",\"Session.findMany\",\"Session.createOne\",\"Session.createMany\",\"Session.createManyAndReturn\",\"Session.updateOne\",\"Session.updateMany\",\"Session.updateManyAndReturn\",\"Session.upsertOne\",\"Session.deleteOne\",\"Session.deleteMany\",\"Session.groupBy\",\"Session.aggregate\",\"Account.findUnique\",\"Account.findUniqueOrThrow\",\"Account.findFirst\",\"Account.findFirstOrThrow\",\"Account.findMany\",\"Account.createOne\",\"Account.createMany\",\"Account.createManyAndReturn\",\"Account.updateOne\",\"Account.updateMany\",\"Account.updateManyAndReturn\",\"Account.upsertOne\",\"Account.deleteOne\",\"Account.deleteMany\",\"Account.groupBy\",\"Account.aggregate\",\"Verification.findUnique\",\"Verification.findUniqueOrThrow\",\"Verification.findFirst\",\"Verification.findFirstOrThrow\",\"Verification.findMany\",\"Verification.createOne\",\"Verification.createMany\",\"Verification.createManyAndReturn\",\"Verification.updateOne\",\"Verification.updateMany\",\"Verification.updateManyAndReturn\",\"Verification.upsertOne\",\"Verification.deleteOne\",\"Verification.deleteMany\",\"Verification.groupBy\",\"Verification.aggregate\",\"Complex.findUnique\",\"Complex.findUniqueOrThrow\",\"Complex.findFirst\",\"Complex.findFirstOrThrow\",\"Complex.findMany\",\"Complex.createOne\",\"Complex.createMany\",\"Complex.createManyAndReturn\",\"Complex.updateOne\",\"Complex.updateMany\",\"Complex.updateManyAndReturn\",\"Complex.upsertOne\",\"Complex.deleteOne\",\"Complex.deleteMany\",\"Complex.groupBy\",\"Complex.aggregate\",\"ComplexUser.findUnique\",\"ComplexUser.findUniqueOrThrow\",\"ComplexUser.findFirst\",\"ComplexUser.findFirstOrThrow\",\"ComplexUser.findMany\",\"ComplexUser.createOne\",\"ComplexUser.createMany\",\"ComplexUser.createManyAndReturn\",\"ComplexUser.updateOne\",\"ComplexUser.updateMany\",\"ComplexUser.updateManyAndReturn\",\"ComplexUser.upsertOne\",\"ComplexUser.deleteOne\",\"ComplexUser.deleteMany\",\"ComplexUser.groupBy\",\"ComplexUser.aggregate\",\"ComplexInvitation.findUnique\",\"ComplexInvitation.findUniqueOrThrow\",\"ComplexInvitation.findFirst\",\"ComplexInvitation.findFirstOrThrow\",\"ComplexInvitation.findMany\",\"ComplexInvitation.createOne\",\"ComplexInvitation.createMany\",\"ComplexInvitation.createManyAndReturn\",\"ComplexInvitation.updateOne\",\"ComplexInvitation.updateMany\",\"ComplexInvitation.updateManyAndReturn\",\"ComplexInvitation.upsertOne\",\"ComplexInvitation.deleteOne\",\"ComplexInvitation.deleteMany\",\"ComplexInvitation.groupBy\",\"ComplexInvitation.aggregate\",\"Sport.findUnique\",\"Sport.findUniqueOrThrow\",\"Sport.findFirst\",\"Sport.findFirstOrThrow\",\"Sport.findMany\",\"Sport.createOne\",\"Sport.createMany\",\"Sport.createManyAndReturn\",\"Sport.updateOne\",\"Sport.updateMany\",\"Sport.updateManyAndReturn\",\"Sport.upsertOne\",\"Sport.deleteOne\",\"Sport.deleteMany\",\"Sport.groupBy\",\"Sport.aggregate\",\"Court.findUnique\",\"Court.findUniqueOrThrow\",\"Court.findFirst\",\"Court.findFirstOrThrow\",\"Court.findMany\",\"Court.createOne\",\"Court.createMany\",\"Court.createManyAndReturn\",\"Court.updateOne\",\"Court.updateMany\",\"Court.updateManyAndReturn\",\"Court.upsertOne\",\"Court.deleteOne\",\"Court.deleteMany\",\"_avg\",\"_sum\",\"Court.groupBy\",\"Court.aggregate\",\"CourtAvailability.findUnique\",\"CourtAvailability.findUniqueOrThrow\",\"CourtAvailability.findFirst\",\"CourtAvailability.findFirstOrThrow\",\"CourtAvailability.findMany\",\"CourtAvailability.createOne\",\"CourtAvailability.createMany\",\"CourtAvailability.createManyAndReturn\",\"CourtAvailability.updateOne\",\"CourtAvailability.updateMany\",\"CourtAvailability.updateManyAndReturn\",\"CourtAvailability.upsertOne\",\"CourtAvailability.deleteOne\",\"CourtAvailability.deleteMany\",\"CourtAvailability.groupBy\",\"CourtAvailability.aggregate\",\"CourtPriceRule.findUnique\",\"CourtPriceRule.findUniqueOrThrow\",\"CourtPriceRule.findFirst\",\"CourtPriceRule.findFirstOrThrow\",\"CourtPriceRule.findMany\",\"CourtPriceRule.createOne\",\"CourtPriceRule.createMany\",\"CourtPriceRule.createManyAndReturn\",\"CourtPriceRule.updateOne\",\"CourtPriceRule.updateMany\",\"CourtPriceRule.updateManyAndReturn\",\"CourtPriceRule.upsertOne\",\"CourtPriceRule.deleteOne\",\"CourtPriceRule.deleteMany\",\"CourtPriceRule.groupBy\",\"CourtPriceRule.aggregate\",\"CourtBooking.findUnique\",\"CourtBooking.findUniqueOrThrow\",\"CourtBooking.findFirst\",\"CourtBooking.findFirstOrThrow\",\"CourtBooking.findMany\",\"CourtBooking.createOne\",\"CourtBooking.createMany\",\"CourtBooking.createManyAndReturn\",\"CourtBooking.updateOne\",\"CourtBooking.updateMany\",\"CourtBooking.updateManyAndReturn\",\"CourtBooking.upsertOne\",\"CourtBooking.deleteOne\",\"CourtBooking.deleteMany\",\"CourtBooking.groupBy\",\"CourtBooking.aggregate\",\"CourtBookingLog.findUnique\",\"CourtBookingLog.findUniqueOrThrow\",\"CourtBookingLog.findFirst\",\"CourtBookingLog.findFirstOrThrow\",\"CourtBookingLog.findMany\",\"CourtBookingLog.createOne\",\"CourtBookingLog.createMany\",\"CourtBookingLog.createManyAndReturn\",\"CourtBookingLog.updateOne\",\"CourtBookingLog.updateMany\",\"CourtBookingLog.updateManyAndReturn\",\"CourtBookingLog.upsertOne\",\"CourtBookingLog.deleteOne\",\"CourtBookingLog.deleteMany\",\"CourtBookingLog.groupBy\",\"CourtBookingLog.aggregate\",\"OnboardingRequest.findUnique\",\"OnboardingRequest.findUniqueOrThrow\",\"OnboardingRequest.findFirst\",\"OnboardingRequest.findFirstOrThrow\",\"OnboardingRequest.findMany\",\"OnboardingRequest.createOne\",\"OnboardingRequest.createMany\",\"OnboardingRequest.createManyAndReturn\",\"OnboardingRequest.updateOne\",\"OnboardingRequest.updateMany\",\"OnboardingRequest.updateManyAndReturn\",\"OnboardingRequest.upsertOne\",\"OnboardingRequest.deleteOne\",\"OnboardingRequest.deleteMany\",\"OnboardingRequest.groupBy\",\"OnboardingRequest.aggregate\",\"PasswordResetRequest.findUnique\",\"PasswordResetRequest.findUniqueOrThrow\",\"PasswordResetRequest.findFirst\",\"PasswordResetRequest.findFirstOrThrow\",\"PasswordResetRequest.findMany\",\"PasswordResetRequest.createOne\",\"PasswordResetRequest.createMany\",\"PasswordResetRequest.createManyAndReturn\",\"PasswordResetRequest.updateOne\",\"PasswordResetRequest.updateMany\",\"PasswordResetRequest.updateManyAndReturn\",\"PasswordResetRequest.upsertOne\",\"PasswordResetRequest.deleteOne\",\"PasswordResetRequest.deleteMany\",\"PasswordResetRequest.groupBy\",\"PasswordResetRequest.aggregate\",\"Plan.findUnique\",\"Plan.findUniqueOrThrow\",\"Plan.findFirst\",\"Plan.findFirstOrThrow\",\"Plan.findMany\",\"Plan.createOne\",\"Plan.createMany\",\"Plan.createManyAndReturn\",\"Plan.updateOne\",\"Plan.updateMany\",\"Plan.updateManyAndReturn\",\"Plan.upsertOne\",\"Plan.deleteOne\",\"Plan.deleteMany\",\"Plan.groupBy\",\"Plan.aggregate\",\"AND\",\"OR\",\"NOT\",\"code\",\"name\",\"price\",\"rules\",\"lastUpdatedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"contains\",\"startsWith\",\"endsWith\",\"every\",\"some\",\"none\",\"id\",\"email\",\"otpHash\",\"otpExpiresAt\",\"otpAttempts\",\"otpLastSentAt\",\"otpResendCount\",\"usedAt\",\"createdAt\",\"updatedAt\",\"fullName\",\"emailVerifiedAt\",\"completedAt\",\"bookingCode\",\"courtId\",\"bookingDate\",\"startTime\",\"endTime\",\"CourtBookingStatus\",\"previousStatus\",\"newStatus\",\"customerName\",\"customerPhone\",\"changedAt\",\"status\",\"DayOfWeek\",\"dayOfWeek\",\"isActive\",\"complexId\",\"sportId\",\"slotDurationMinutes\",\"basePrice\",\"slug\",\"tokenHash\",\"expiresAt\",\"acceptedAt\",\"revokedAt\",\"userId\",\"ComplexUserRole\",\"role\",\"complexName\",\"physicalAddress\",\"city\",\"state\",\"country\",\"complexSlug\",\"adminEmail\",\"planCode\",\"identifier\",\"value\",\"accountId\",\"providerId\",\"accessToken\",\"refreshToken\",\"idToken\",\"accessTokenExpiresAt\",\"refreshTokenExpiresAt\",\"scope\",\"password\",\"token\",\"ipAddress\",\"userAgent\",\"emailVerified\",\"image\",\"phone\",\"courtId_bookingDate_startTime\",\"complexId_userId\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"),
+ graph: "zgaQAYACDwQAAPUDACAFAAD2AwAgBgAA9wMAIJoCAADzAwAwmwIAADcAEJwCAADzAwAwngIBALgDACG2AgEAAAABtwIBAAAAAb4CQAC7AwAhvwJAALsDACHdAgEAuAMAIfQCIADmAwAh9QIBAPQDACH2AgEA9AMAIQEAAAABACAMAwAAjAQAIJoCAACOBAAwmwIAAAMAEJwCAACOBAAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2AJAALsDACHbAgEAuAMAIfECAQC4AwAh8gIBAPQDACHzAgEA9AMAIQMDAACMBgAg8gIAAIYFACDzAgAAhgUAIAwDAACMBAAgmgIAAI4EADCbAgAAAwAQnAIAAI4EADC2AgEAAAABvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh2wIBALgDACHxAgEAAAAB8gIBAPQDACHzAgEA9AMAIQMAAAADACABAAAEADACAAAFACARAwAAjAQAIJoCAACNBAAwmwIAAAcAEJwCAACNBAAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2wIBALgDACHoAgEAuAMAIekCAQC4AwAh6gIBAPQDACHrAgEA9AMAIewCAQD0AwAh7QJAAMoDACHuAkAAygMAIe8CAQD0AwAh8AIBAPQDACEIAwAAjAYAIOoCAACGBQAg6wIAAIYFACDsAgAAhgUAIO0CAACGBQAg7gIAAIYFACDvAgAAhgUAIPACAACGBQAgEQMAAIwEACCaAgAAjQQAMJsCAAAHABCcAgAAjQQAMLYCAQAAAAG-AkAAuwMAIb8CQAC7AwAh2wIBALgDACHoAgEAuAMAIekCAQC4AwAh6gIBAPQDACHrAgEA9AMAIewCAQD0AwAh7QJAAMoDACHuAkAAygMAIe8CAQD0AwAh8AIBAPQDACEDAAAABwAgAQAACAAwAgAACQAgCQMAAIwEACAKAACABAAgmgIAAIoEADCbAgAACwAQnAIAAIoEADC-AkAAuwMAIdICAQDIAwAh2wIBALgDACHdAgAAiwTdAiICAwAAjAYAIAoAAIUGACAKAwAAjAQAIAoAAIAEACCaAgAAigQAMJsCAAALABCcAgAAigQAML4CQAC7AwAh0gIBAMgDACHbAgEAuAMAId0CAACLBN0CIvgCAACJBAAgAwAAAAsAIAEAAAwAMAIAAA0AIAkGAAC8AwAgmgIAALcDADCbAgAADwAQnAIAALcDADCdAgEAuAMAIZ4CAQC4AwAhnwIQALkDACGgAgAAugMAIKECQAC7AwAhAQAAAA8AIBIIAACHBAAgCQAA9wMAIAsAAIgEACAMAADnAwAgmgIAAIYEADCbAgAAEQAQnAIAAIYEADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHeAgEAuAMAId8CAQD0AwAh4AIBAPQDACHhAgEA9AMAIeICAQD0AwAh4wIBALgDACHkAgEAuAMAIeUCAQD0AwAhCQgAAIoGACAJAACDBgAgCwAAiwYAIAwAALoFACDfAgAAhgUAIOACAACGBQAg4QIAAIYFACDiAgAAhgUAIOUCAACGBQAgEggAAIcEACAJAAD3AwAgCwAAiAQAIAwAAOcDACCaAgAAhgQAMJsCAAARABCcAgAAhgQAMLYCAQAAAAG-AkAAuwMAIb8CQAC7AwAh3gIBALgDACHfAgEA9AMAIeACAQD0AwAh4QIBAPQDACHiAgEA9AMAIeMCAQAAAAHkAgEAuAMAIeUCAQD0AwAhAwAAABEAIAEAABIAMAIAABMAIAEAAAARACADAAAACwAgAQAADAAwAgAADQAgDQoAAIAEACCaAgAAhQQAMJsCAAAXABCcAgAAhQQAMLYCAQDIAwAhtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHXAgEAuAMAIdgCQAC7AwAh2QJAAMoDACHaAkAAygMAIQMKAACFBgAg2QIAAIYFACDaAgAAhgUAIA0KAACABAAgmgIAAIUEADCbAgAAFwAQnAIAAIUEADC2AgEAAAABtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHXAgEAAAAB2AJAALsDACHZAkAAygMAIdoCQADKAwAhAwAAABcAIAEAABgAMAIAABkAIBAKAACABAAgDQAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIJoCAAD_AwAwmwIAABsAEJwCAAD_AwAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdMCAQDIAwAh1AICAMkDACHVAhAAuQMAIQUKAACFBgAgDQAAhgYAIA8AAIcGACAQAACIBgAgEQAAiQYAIBAKAACABAAgDQAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIJoCAAD_AwAwmwIAABsAEJwCAAD_AwAwngIBALgDACG2AgEAAAABvgJAALsDACG_AkAAuwMAIdICAQDIAwAh0wIBAMgDACHUAgIAyQMAIdUCEAC5AwAhAwAAABsAIAEAABwAMAIAAB0AIAMAAAAbACABAAAcADACAAAdACABAAAAGwAgCg4AAPoDACCaAgAA_QMAMJsCAAAhABCcAgAA_QMAMLYCAQDIAwAhvgJAALsDACHEAgEAyAMAIcYCAQC4AwAhxwIBALgDACHQAgAA_gPQAiIBDgAAhAYAIAoOAAD6AwAgmgIAAP0DADCbAgAAIQAQnAIAAP0DADC2AgEAAAABvgJAALsDACHEAgEAyAMAIcYCAQC4AwAhxwIBALgDACHQAgAA_gPQAiIDAAAAIQAgAQAAIgAwAgAAIwAgDQ4AAPoDACCaAgAA-wMAMJsCAAAlABCcAgAA-wMAMJ8CEAC5AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAhxAIBAMgDACHGAgEA9AMAIccCAQD0AwAh0AIAAPwD0AIj0QIgAOYDACEEDgAAhAYAIMYCAACGBQAgxwIAAIYFACDQAgAAhgUAIA0OAAD6AwAgmgIAAPsDADCbAgAAJQAQnAIAAPsDADCfAhAAuQMAIbYCAQAAAAG-AkAAuwMAIb8CQAC7AwAhxAIBAMgDACHGAgEA9AMAIccCAQD0AwAh0AIAAPwD0AIj0QIgAOYDACEDAAAAJQAgAQAAJgAwAgAAJwAgDw4AAPoDACCaAgAA-QMAMJsCAAApABCcAgAA-QMAMLYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcMCAQC4AwAhxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHLAgEAuAMAIcwCAQC4AwAhzgIAANMDyQIiAQ4AAIQGACAQDgAA-gMAIJoCAAD5AwAwmwIAACkAEJwCAAD5AwAwtgIBAAAAAb4CQAC7AwAhvwJAALsDACHDAgEAAAABxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHLAgEAuAMAIcwCAQC4AwAhzgIAANMDyQIi9wIAAPgDACADAAAAKQAgAQAAKgAwAgAAKwAgAQAAACEAIAEAAAAlACABAAAAKQAgAQAAAAsAIAEAAAAXACABAAAAGwAgAQAAAAMAIAEAAAAHACABAAAACwAgAQAAAAEAIA8EAAD1AwAgBQAA9gMAIAYAAPcDACCaAgAA8wMAMJsCAAA3ABCcAgAA8wMAMJ4CAQC4AwAhtgIBALgDACG3AgEAuAMAIb4CQAC7AwAhvwJAALsDACHdAgEAuAMAIfQCIADmAwAh9QIBAPQDACH2AgEA9AMAIQUEAACBBgAgBQAAggYAIAYAAIMGACD1AgAAhgUAIPYCAACGBQAgAwAAADcAIAEAADgAMAIAAAEAIAMAAAA3ACABAAA4ADACAAABACADAAAANwAgAQAAOAAwAgAAAQAgDAQAAP4FACAFAAD_BQAgBgAAgAYAIJ4CAQAAAAG2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB3QIBAAAAAfQCIAAAAAH1AgEAAAAB9gIBAAAAAQEXAAA8ACAJngIBAAAAAbYCAQAAAAG3AgEAAAABvgJAAAAAAb8CQAAAAAHdAgEAAAAB9AIgAAAAAfUCAQAAAAH2AgEAAAABARcAAD4AMAEXAAA-ADAMBAAA2gUAIAUAANsFACAGAADcBQAgngIBAJQEACG2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAId0CAQCUBAAh9AIgAM8EACH1AgEAogQAIfYCAQCiBAAhAgAAAAEAIBcAAEEAIAmeAgEAlAQAIbYCAQCUBAAhtwIBAJQEACG-AkAAlgQAIb8CQACWBAAh3QIBAJQEACH0AiAAzwQAIfUCAQCiBAAh9gIBAKIEACECAAAANwAgFwAAQwAgAgAAADcAIBcAAEMAIAMAAAABACAeAAA8ACAfAABBACABAAAAAQAgAQAAADcAIAUHAADXBQAgJAAA2QUAICUAANgFACD1AgAAhgUAIPYCAACGBQAgDJoCAADyAwAwmwIAAEoAEJwCAADyAwAwngIBAKsDACG2AgEAqwMAIbcCAQCrAwAhvgJAAK4DACG_AkAArgMAId0CAQCrAwAh9AIgANgDACH1AgEA1wMAIfYCAQDXAwAhAwAAADcAIAEAAEkAMCMAAEoAIAMAAAA3ACABAAA4ADACAAABACABAAAABQAgAQAAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAkDAADWBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAdsCAQAAAAHxAgEAAAAB8gIBAAAAAfMCAQAAAAEBFwAAUgAgCLYCAQAAAAG-AkAAAAABvwJAAAAAAdgCQAAAAAHbAgEAAAAB8QIBAAAAAfICAQAAAAHzAgEAAAABARcAAFQAMAEXAABUADAJAwAA1QUAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdgCQACWBAAh2wIBAJQEACHxAgEAlAQAIfICAQCiBAAh8wIBAKIEACECAAAABQAgFwAAVwAgCLYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdgCQACWBAAh2wIBAJQEACHxAgEAlAQAIfICAQCiBAAh8wIBAKIEACECAAAAAwAgFwAAWQAgAgAAAAMAIBcAAFkAIAMAAAAFACAeAABSACAfAABXACABAAAABQAgAQAAAAMAIAUHAADSBQAgJAAA1AUAICUAANMFACDyAgAAhgUAIPMCAACGBQAgC5oCAADxAwAwmwIAAGAAEJwCAADxAwAwtgIBAKsDACG-AkAArgMAIb8CQACuAwAh2AJAAK4DACHbAgEAqwMAIfECAQCrAwAh8gIBANcDACHzAgEA1wMAIQMAAAADACABAABfADAjAABgACADAAAAAwAgAQAABAAwAgAABQAgAQAAAAkAIAEAAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAMAAAAHACABAAAIADACAAAJACAOAwAA0QUAILYCAQAAAAG-AkAAAAABvwJAAAAAAdsCAQAAAAHoAgEAAAAB6QIBAAAAAeoCAQAAAAHrAgEAAAAB7AIBAAAAAe0CQAAAAAHuAkAAAAAB7wIBAAAAAfACAQAAAAEBFwAAaAAgDbYCAQAAAAG-AkAAAAABvwJAAAAAAdsCAQAAAAHoAgEAAAAB6QIBAAAAAeoCAQAAAAHrAgEAAAAB7AIBAAAAAe0CQAAAAAHuAkAAAAAB7wIBAAAAAfACAQAAAAEBFwAAagAwARcAAGoAMA4DAADQBQAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh2wIBAJQEACHoAgEAlAQAIekCAQCUBAAh6gIBAKIEACHrAgEAogQAIewCAQCiBAAh7QJAAO4EACHuAkAA7gQAIe8CAQCiBAAh8AIBAKIEACECAAAACQAgFwAAbQAgDbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdsCAQCUBAAh6AIBAJQEACHpAgEAlAQAIeoCAQCiBAAh6wIBAKIEACHsAgEAogQAIe0CQADuBAAh7gJAAO4EACHvAgEAogQAIfACAQCiBAAhAgAAAAcAIBcAAG8AIAIAAAAHACAXAABvACADAAAACQAgHgAAaAAgHwAAbQAgAQAAAAkAIAEAAAAHACAKBwAAzQUAICQAAM8FACAlAADOBQAg6gIAAIYFACDrAgAAhgUAIOwCAACGBQAg7QIAAIYFACDuAgAAhgUAIO8CAACGBQAg8AIAAIYFACAQmgIAAPADADCbAgAAdgAQnAIAAPADADC2AgEAqwMAIb4CQACuAwAhvwJAAK4DACHbAgEAqwMAIegCAQCrAwAh6QIBAKsDACHqAgEA1wMAIesCAQDXAwAh7AIBANcDACHtAkAAwAMAIe4CQADAAwAh7wIBANcDACHwAgEA1wMAIQMAAAAHACABAAB1ADAjAAB2ACADAAAABwAgAQAACAAwAgAACQAgCZoCAADvAwAwmwIAAHwAEJwCAADvAwAwtgIBAAAAAb4CQAC7AwAhvwJAALsDACHYAkAAuwMAIeYCAQC4AwAh5wIBALgDACEBAAAAeQAgAQAAAHkAIAmaAgAA7wMAMJsCAAB8ABCcAgAA7wMAMLYCAQC4AwAhvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh5gIBALgDACHnAgEAuAMAIQADAAAAfAAgAQAAfQAwAgAAeQAgAwAAAHwAIAEAAH0AMAIAAHkAIAMAAAB8ACABAAB9ADACAAB5ACAGtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAeYCAQAAAAHnAgEAAAABARcAAIEBACAGtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAeYCAQAAAAHnAgEAAAABARcAAIMBADABFwAAgwEAMAa2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHYAkAAlgQAIeYCAQCUBAAh5wIBAJQEACECAAAAeQAgFwAAhgEAIAa2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHYAkAAlgQAIeYCAQCUBAAh5wIBAJQEACECAAAAfAAgFwAAiAEAIAIAAAB8ACAXAACIAQAgAwAAAHkAIB4AAIEBACAfAACGAQAgAQAAAHkAIAEAAAB8ACADBwAAygUAICQAAMwFACAlAADLBQAgCZoCAADuAwAwmwIAAI8BABCcAgAA7gMAMLYCAQCrAwAhvgJAAK4DACG_AkAArgMAIdgCQACuAwAh5gIBAKsDACHnAgEAqwMAIQMAAAB8ACABAACOAQAwIwAAjwEAIAMAAAB8ACABAAB9ADACAAB5ACABAAAAEwAgAQAAABMAIAMAAAARACABAAASADACAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIA8IAADJBQAgCQAAgQUAIAsAAIIFACAMAACDBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAAB3gIBAAAAAd8CAQAAAAHgAgEAAAAB4QIBAAAAAeICAQAAAAHjAgEAAAAB5AIBAAAAAeUCAQAAAAEBFwAAlwEAIAu2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAAB5QIBAAAAAQEXAACZAQAwARcAAJkBADABAAAADwAgDwgAAMgFACAJAACkBAAgCwAApQQAIAwAAKYEACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhAgAAABMAIBcAAJ0BACALtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh3gIBAJQEACHfAgEAogQAIeACAQCiBAAh4QIBAKIEACHiAgEAogQAIeMCAQCUBAAh5AIBAJQEACHlAgEAogQAIQIAAAARACAXAACfAQAgAgAAABEAIBcAAJ8BACABAAAADwAgAwAAABMAIB4AAJcBACAfAACdAQAgAQAAABMAIAEAAAARACAIBwAAxQUAICQAAMcFACAlAADGBQAg3wIAAIYFACDgAgAAhgUAIOECAACGBQAg4gIAAIYFACDlAgAAhgUAIA6aAgAA7QMAMJsCAACnAQAQnAIAAO0DADC2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHeAgEAqwMAId8CAQDXAwAh4AIBANcDACHhAgEA1wMAIeICAQDXAwAh4wIBAKsDACHkAgEAqwMAIeUCAQDXAwAhAwAAABEAIAEAAKYBADAjAACnAQAgAwAAABEAIAEAABIAMAIAABMAIAEAAAANACABAAAADQAgAwAAAAsAIAEAAAwAMAIAAA0AIAMAAAALACABAAAMADACAAANACADAAAACwAgAQAADAAwAgAADQAgBgMAAP8EACAKAADEBQAgvgJAAAAAAdICAQAAAAHbAgEAAAAB3QIAAADdAgIBFwAArwEAIAS-AkAAAAAB0gIBAAAAAdsCAQAAAAHdAgAAAN0CAgEXAACxAQAwARcAALEBADAGAwAA_QQAIAoAAMMFACC-AkAAlgQAIdICAQCUBAAh2wIBAJQEACHdAgAA-wTdAiICAAAADQAgFwAAtAEAIAS-AkAAlgQAIdICAQCUBAAh2wIBAJQEACHdAgAA-wTdAiICAAAACwAgFwAAtgEAIAIAAAALACAXAAC2AQAgAwAAAA0AIB4AAK8BACAfAAC0AQAgAQAAAA0AIAEAAAALACADBwAAwAUAICQAAMIFACAlAADBBQAgB5oCAADpAwAwmwIAAL0BABCcAgAA6QMAML4CQACuAwAh0gIBAL4DACHbAgEAqwMAId0CAADqA90CIgMAAAALACABAAC8AQAwIwAAvQEAIAMAAAALACABAAAMADACAAANACABAAAAGQAgAQAAABkAIAMAAAAXACABAAAYADACAAAZACADAAAAFwAgAQAAGAAwAgAAGQAgAwAAABcAIAEAABgAMAIAABkAIAoKAAC_BQAgtgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHXAgEAAAAB2AJAAAAAAdkCQAAAAAHaAkAAAAABARcAAMUBACAJtgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHXAgEAAAAB2AJAAAAAAdkCQAAAAAHaAkAAAAABARcAAMcBADABFwAAxwEAMAoKAAC-BQAgtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdcCAQCUBAAh2AJAAJYEACHZAkAA7gQAIdoCQADuBAAhAgAAABkAIBcAAMoBACAJtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdcCAQCUBAAh2AJAAJYEACHZAkAA7gQAIdoCQADuBAAhAgAAABcAIBcAAMwBACACAAAAFwAgFwAAzAEAIAMAAAAZACAeAADFAQAgHwAAygEAIAEAAAAZACABAAAAFwAgBQcAALsFACAkAAC9BQAgJQAAvAUAINkCAACGBQAg2gIAAIYFACAMmgIAAOgDADCbAgAA0wEAEJwCAADoAwAwtgIBAL4DACG3AgEAqwMAIb4CQACuAwAhvwJAAK4DACHSAgEAvgMAIdcCAQCrAwAh2AJAAK4DACHZAkAAwAMAIdoCQADAAwAhAwAAABcAIAEAANIBADAjAADTAQAgAwAAABcAIAEAABgAMAIAABkAIAoMAADnAwAgmgIAAOUDADCbAgAA2QEAEJwCAADlAwAwngIBAAAAAbYCAQAAAAG-AkAAuwMAIb8CQAC7AwAh0QIgAOYDACHWAgEAAAABAQAAANYBACABAAAA1gEAIAoMAADnAwAgmgIAAOUDADCbAgAA2QEAEJwCAADlAwAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHRAiAA5gMAIdYCAQC4AwAhAQwAALoFACADAAAA2QEAIAEAANoBADACAADWAQAgAwAAANkBACABAADaAQAwAgAA1gEAIAMAAADZAQAgAQAA2gEAMAIAANYBACAHDAAAuQUAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHRAiAAAAAB1gIBAAAAAQEXAADeAQAgBp4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHRAiAAAAAB1gIBAAAAAQEXAADgAQAwARcAAOABADAHDAAArwUAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0QIgAM8EACHWAgEAlAQAIQIAAADWAQAgFwAA4wEAIAaeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdECIADPBAAh1gIBAJQEACECAAAA2QEAIBcAAOUBACACAAAA2QEAIBcAAOUBACADAAAA1gEAIB4AAN4BACAfAADjAQAgAQAAANYBACABAAAA2QEAIAMHAACsBQAgJAAArgUAICUAAK0FACAJmgIAAOQDADCbAgAA7AEAEJwCAADkAwAwngIBAKsDACG2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHRAiAA2AMAIdYCAQCrAwAhAwAAANkBACABAADrAQAwIwAA7AEAIAMAAADZAQAgAQAA2gEAMAIAANYBACABAAAAHQAgAQAAAB0AIAMAAAAbACABAAAcADACAAAdACADAAAAGwAgAQAAHAAwAgAAHQAgAwAAABsAIAEAABwAMAIAAB0AIA0KAACrBQAgDQAA4AQAIA8AAOEEACAQAADiBAAgEQAA4wQAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHSAgEAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABARcAAPQBACAIngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHTAgEAAAAB1AICAAAAAdUCEAAAAAEBFwAA9gEAMAEXAAD2AQAwDQoAAKoFACANAACzBAAgDwAAtAQAIBAAALUEACARAAC2BAAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQIAAAAdACAXAAD5AQAgCJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0gIBAJQEACHTAgEAlAQAIdQCAgCxBAAh1QIQAJUEACECAAAAGwAgFwAA-wEAIAIAAAAbACAXAAD7AQAgAwAAAB0AIB4AAPQBACAfAAD5AQAgAQAAAB0AIAEAAAAbACAFBwAApQUAICQAAKgFACAlAACnBQAgpgEAAKYFACCnAQAAqQUAIAuaAgAA4wMAMJsCAACCAgAQnAIAAOMDADCeAgEAqwMAIbYCAQC-AwAhvgJAAK4DACG_AkAArgMAIdICAQC-AwAh0wIBAL4DACHUAgIAvwMAIdUCEACsAwAhAwAAABsAIAEAAIECADAjAACCAgAgAwAAABsAIAEAABwAMAIAAB0AIAEAAAAjACABAAAAIwAgAwAAACEAIAEAACIAMAIAACMAIAMAAAAhACABAAAiADACAAAjACADAAAAIQAgAQAAIgAwAgAAIwAgBw4AAKQFACC2AgEAAAABvgJAAAAAAcQCAQAAAAHGAgEAAAABxwIBAAAAAdACAAAA0AICARcAAIoCACAGtgIBAAAAAb4CQAAAAAHEAgEAAAABxgIBAAAAAccCAQAAAAHQAgAAANACAgEXAACMAgAwARcAAIwCADAHDgAAowUAILYCAQCUBAAhvgJAAJYEACHEAgEAlAQAIcYCAQCUBAAhxwIBAJQEACHQAgAA3ATQAiICAAAAIwAgFwAAjwIAIAa2AgEAlAQAIb4CQACWBAAhxAIBAJQEACHGAgEAlAQAIccCAQCUBAAh0AIAANwE0AIiAgAAACEAIBcAAJECACACAAAAIQAgFwAAkQIAIAMAAAAjACAeAACKAgAgHwAAjwIAIAEAAAAjACABAAAAIQAgAwcAAKAFACAkAACiBQAgJQAAoQUAIAmaAgAA3wMAMJsCAACYAgAQnAIAAN8DADC2AgEAvgMAIb4CQACuAwAhxAIBAL4DACHGAgEAqwMAIccCAQCrAwAh0AIAAOAD0AIiAwAAACEAIAEAAJcCADAjAACYAgAgAwAAACEAIAEAACIAMAIAACMAIAEAAAAnACABAAAAJwAgAwAAACUAIAEAACYAMAIAACcAIAMAAAAlACABAAAmADACAAAnACADAAAAJQAgAQAAJgAwAgAAJwAgCg4AAJ8FACCfAhAAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAABxAIBAAAAAcYCAQAAAAHHAgEAAAAB0AIAAADQAgPRAiAAAAABARcAAKACACAJnwIQAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAcQCAQAAAAHGAgEAAAABxwIBAAAAAdACAAAA0AID0QIgAAAAAQEXAACiAgAwARcAAKICADAKDgAAngUAIJ8CEACVBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAhxAIBAJQEACHGAgEAogQAIccCAQCiBAAh0AIAAM4E0AIj0QIgAM8EACECAAAAJwAgFwAApQIAIAmfAhAAlQQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIcQCAQCUBAAhxgIBAKIEACHHAgEAogQAIdACAADOBNACI9ECIADPBAAhAgAAACUAIBcAAKcCACACAAAAJQAgFwAApwIAIAMAAAAnACAeAACgAgAgHwAApQIAIAEAAAAnACABAAAAJQAgCAcAAJkFACAkAACcBQAgJQAAmwUAIKYBAACaBQAgpwEAAJ0FACDGAgAAhgUAIMcCAACGBQAg0AIAAIYFACAMmgIAANUDADCbAgAArgIAEJwCAADVAwAwnwIQAKwDACG2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHEAgEAvgMAIcYCAQDXAwAhxwIBANcDACHQAgAA1gPQAiPRAiAA2AMAIQMAAAAlACABAACtAgAwIwAArgIAIAMAAAAlACABAAAmADACAAAnACABAAAAKwAgAQAAACsAIAMAAAApACABAAAqADACAAArACADAAAAKQAgAQAAKgAwAgAAKwAgAwAAACkAIAEAACoAMAIAACsAIAwOAACYBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAABwwIBAAAAAcQCAQAAAAHFAkAAAAABxgIBAAAAAccCAQAAAAHLAgEAAAABzAIBAAAAAc4CAAAAyQICARcAALYCACALtgIBAAAAAb4CQAAAAAG_AkAAAAABwwIBAAAAAcQCAQAAAAHFAkAAAAABxgIBAAAAAccCAQAAAAHLAgEAAAABzAIBAAAAAc4CAAAAyQICARcAALgCADABFwAAuAIAMAwOAACXBQAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAhwwIBAJQEACHEAgEAlAQAIcUCQACWBAAhxgIBAJQEACHHAgEAlAQAIcsCAQCUBAAhzAIBAJQEACHOAgAAwQTJAiICAAAAKwAgFwAAuwIAIAu2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHDAgEAlAQAIcQCAQCUBAAhxQJAAJYEACHGAgEAlAQAIccCAQCUBAAhywIBAJQEACHMAgEAlAQAIc4CAADBBMkCIgIAAAApACAXAAC9AgAgAgAAACkAIBcAAL0CACADAAAAKwAgHgAAtgIAIB8AALsCACABAAAAKwAgAQAAACkAIAMHAACUBQAgJAAAlgUAICUAAJUFACAOmgIAANQDADCbAgAAxAIAEJwCAADUAwAwtgIBAL4DACG-AkAArgMAIb8CQACuAwAhwwIBAKsDACHEAgEAvgMAIcUCQACuAwAhxgIBAKsDACHHAgEAqwMAIcsCAQCrAwAhzAIBAKsDACHOAgAAzwPJAiIDAAAAKQAgAQAAwwIAMCMAAMQCACADAAAAKQAgAQAAKgAwAgAAKwAgDpoCAADSAwAwmwIAAMoCABCcAgAA0gMAMLYCAQAAAAHDAgEAuAMAIcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhyQIAANMDyQIiygIAANMDyQIiywIBALgDACHMAgEAuAMAIc0CQAC7AwAhAQAAAMcCACABAAAAxwIAIA6aAgAA0gMAMJsCAADKAgAQnAIAANIDADC2AgEAyAMAIcMCAQC4AwAhxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHJAgAA0wPJAiLKAgAA0wPJAiLLAgEAuAMAIcwCAQC4AwAhzQJAALsDACEAAwAAAMoCACABAADLAgAwAgAAxwIAIAMAAADKAgAgAQAAywIAMAIAAMcCACADAAAAygIAIAEAAMsCADACAADHAgAgC7YCAQAAAAHDAgEAAAABxAIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAckCAAAAyQICygIAAADJAgLLAgEAAAABzAIBAAAAAc0CQAAAAAEBFwAAzwIAIAu2AgEAAAABwwIBAAAAAcQCAQAAAAHFAkAAAAABxgIBAAAAAccCAQAAAAHJAgAAAMkCAsoCAAAAyQICywIBAAAAAcwCAQAAAAHNAkAAAAABARcAANECADABFwAA0QIAMAu2AgEAlAQAIcMCAQCUBAAhxAIBAJQEACHFAkAAlgQAIcYCAQCUBAAhxwIBAJQEACHJAgAAwQTJAiLKAgAAwQTJAiLLAgEAlAQAIcwCAQCUBAAhzQJAAJYEACECAAAAxwIAIBcAANQCACALtgIBAJQEACHDAgEAlAQAIcQCAQCUBAAhxQJAAJYEACHGAgEAlAQAIccCAQCUBAAhyQIAAMEEyQIiygIAAMEEyQIiywIBAJQEACHMAgEAlAQAIc0CQACWBAAhAgAAAMoCACAXAADWAgAgAgAAAMoCACAXAADWAgAgAwAAAMcCACAeAADPAgAgHwAA1AIAIAEAAADHAgAgAQAAAMoCACADBwAAkQUAICQAAJMFACAlAACSBQAgDpoCAADOAwAwmwIAAN0CABCcAgAAzgMAMLYCAQC-AwAhwwIBAKsDACHEAgEAvgMAIcUCQACuAwAhxgIBAKsDACHHAgEAqwMAIckCAADPA8kCIsoCAADPA8kCIssCAQCrAwAhzAIBAKsDACHNAkAArgMAIQMAAADKAgAgAQAA3AIAMCMAAN0CACADAAAAygIAIAEAAMsCADACAADHAgAgD5oCAADNAwAwmwIAAOMCABCcAgAAzQMAMLYCAQAAAAG3AgEAuAMAIbgCAQC4AwAhuQJAALsDACG6AgIAyQMAIbsCQAC7AwAhvAICAMkDACG-AkAAuwMAIb8CQAC7AwAhwAIBALgDACHBAkAAygMAIcICQADKAwAhAQAAAOACACABAAAA4AIAIA-aAgAAzQMAMJsCAADjAgAQnAIAAM0DADC2AgEAyAMAIbcCAQC4AwAhuAIBALgDACG5AkAAuwMAIboCAgDJAwAhuwJAALsDACG8AgIAyQMAIb4CQAC7AwAhvwJAALsDACHAAgEAuAMAIcECQADKAwAhwgJAAMoDACECwQIAAIYFACDCAgAAhgUAIAMAAADjAgAgAQAA5AIAMAIAAOACACADAAAA4wIAIAEAAOQCADACAADgAgAgAwAAAOMCACABAADkAgAwAgAA4AIAIAy2AgEAAAABtwIBAAAAAbgCAQAAAAG5AkAAAAABugICAAAAAbsCQAAAAAG8AgIAAAABvgJAAAAAAb8CQAAAAAHAAgEAAAABwQJAAAAAAcICQAAAAAEBFwAA6AIAIAy2AgEAAAABtwIBAAAAAbgCAQAAAAG5AkAAAAABugICAAAAAbsCQAAAAAG8AgIAAAABvgJAAAAAAb8CQAAAAAHAAgEAAAABwQJAAAAAAcICQAAAAAEBFwAA6gIAMAEXAADqAgAwDLYCAQCUBAAhtwIBAJQEACG4AgEAlAQAIbkCQACWBAAhugICALEEACG7AkAAlgQAIbwCAgCxBAAhvgJAAJYEACG_AkAAlgQAIcACAQCUBAAhwQJAAO4EACHCAkAA7gQAIQIAAADgAgAgFwAA7QIAIAy2AgEAlAQAIbcCAQCUBAAhuAIBAJQEACG5AkAAlgQAIboCAgCxBAAhuwJAAJYEACG8AgIAsQQAIb4CQACWBAAhvwJAAJYEACHAAgEAlAQAIcECQADuBAAhwgJAAO4EACECAAAA4wIAIBcAAO8CACACAAAA4wIAIBcAAO8CACADAAAA4AIAIB4AAOgCACAfAADtAgAgAQAAAOACACABAAAA4wIAIAcHAACMBQAgJAAAjwUAICUAAI4FACCmAQAAjQUAIKcBAACQBQAgwQIAAIYFACDCAgAAhgUAIA-aAgAAzAMAMJsCAAD2AgAQnAIAAMwDADC2AgEAvgMAIbcCAQCrAwAhuAIBAKsDACG5AkAArgMAIboCAgC_AwAhuwJAAK4DACG8AgIAvwMAIb4CQACuAwAhvwJAAK4DACHAAgEAqwMAIcECQADAAwAhwgJAAMADACEDAAAA4wIAIAEAAPUCADAjAAD2AgAgAwAAAOMCACABAADkAgAwAgAA4AIAIA2aAgAAxwMAMJsCAAD8AgAQnAIAAMcDADC2AgEAAAABtwIBALgDACG4AgEAuAMAIbkCQAC7AwAhugICAMkDACG7AkAAuwMAIbwCAgDJAwAhvQJAAMoDACG-AkAAuwMAIb8CQAC7AwAhAQAAAPkCACABAAAA-QIAIA2aAgAAxwMAMJsCAAD8AgAQnAIAAMcDADC2AgEAyAMAIbcCAQC4AwAhuAIBALgDACG5AkAAuwMAIboCAgDJAwAhuwJAALsDACG8AgIAyQMAIb0CQADKAwAhvgJAALsDACG_AkAAuwMAIQG9AgAAhgUAIAMAAAD8AgAgAQAA_QIAMAIAAPkCACADAAAA_AIAIAEAAP0CADACAAD5AgAgAwAAAPwCACABAAD9AgAwAgAA-QIAIAq2AgEAAAABtwIBAAAAAbgCAQAAAAG5AkAAAAABugICAAAAAbsCQAAAAAG8AgIAAAABvQJAAAAAAb4CQAAAAAG_AkAAAAABARcAAIEDACAKtgIBAAAAAbcCAQAAAAG4AgEAAAABuQJAAAAAAboCAgAAAAG7AkAAAAABvAICAAAAAb0CQAAAAAG-AkAAAAABvwJAAAAAAQEXAACDAwAwARcAAIMDADAKtgIBAJQEACG3AgEAlAQAIbgCAQCUBAAhuQJAAJYEACG6AgIAsQQAIbsCQACWBAAhvAICALEEACG9AkAA7gQAIb4CQACWBAAhvwJAAJYEACECAAAA-QIAIBcAAIYDACAKtgIBAJQEACG3AgEAlAQAIbgCAQCUBAAhuQJAAJYEACG6AgIAsQQAIbsCQACWBAAhvAICALEEACG9AkAA7gQAIb4CQACWBAAhvwJAAJYEACECAAAA_AIAIBcAAIgDACACAAAA_AIAIBcAAIgDACADAAAA-QIAIB4AAIEDACAfAACGAwAgAQAAAPkCACABAAAA_AIAIAYHAACHBQAgJAAAigUAICUAAIkFACCmAQAAiAUAIKcBAACLBQAgvQIAAIYFACANmgIAAL0DADCbAgAAjwMAEJwCAAC9AwAwtgIBAL4DACG3AgEAqwMAIbgCAQCrAwAhuQJAAK4DACG6AgIAvwMAIbsCQACuAwAhvAICAL8DACG9AkAAwAMAIb4CQACuAwAhvwJAAK4DACEDAAAA_AIAIAEAAI4DADAjAACPAwAgAwAAAPwCACABAAD9AgAwAgAA-QIAIAkGAAC8AwAgmgIAALcDADCbAgAADwAQnAIAALcDADCdAgEAAAABngIBALgDACGfAhAAuQMAIaACAAC6AwAgoQJAALsDACEBAAAAkgMAIAEAAACSAwAgAQYAAIUFACADAAAADwAgAQAAlQMAMAIAAJIDACADAAAADwAgAQAAlQMAMAIAAJIDACADAAAADwAgAQAAlQMAMAIAAJIDACAGBgAAhAUAIJ0CAQAAAAGeAgEAAAABnwIQAAAAAaACgAAAAAGhAkAAAAABARcAAJkDACAFnQIBAAAAAZ4CAQAAAAGfAhAAAAABoAKAAAAAAaECQAAAAAEBFwAAmwMAMAEXAACbAwAwBgYAAJcEACCdAgEAlAQAIZ4CAQCUBAAhnwIQAJUEACGgAoAAAAABoQJAAJYEACECAAAAkgMAIBcAAJ4DACAFnQIBAJQEACGeAgEAlAQAIZ8CEACVBAAhoAKAAAAAAaECQACWBAAhAgAAAA8AIBcAAKADACACAAAADwAgFwAAoAMAIAMAAACSAwAgHgAAmQMAIB8AAJ4DACABAAAAkgMAIAEAAAAPACAFBwAAjwQAICQAAJIEACAlAACRBAAgpgEAAJAEACCnAQAAkwQAIAiaAgAAqgMAMJsCAACnAwAQnAIAAKoDADCdAgEAqwMAIZ4CAQCrAwAhnwIQAKwDACGgAgAArQMAIKECQACuAwAhAwAAAA8AIAEAAKYDADAjAACnAwAgAwAAAA8AIAEAAJUDADACAACSAwAgCJoCAACqAwAwmwIAAKcDABCcAgAAqgMAMJ0CAQCrAwAhngIBAKsDACGfAhAArAMAIaACAACtAwAgoQJAAK4DACEOBwAAsAMAICQAALYDACAlAAC2AwAgogIBAAAAAaMCAQAAAASkAgEAAAAEpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQC1AwAhsAIBAAAAAbECAQAAAAGyAgEAAAABDQcAALADACAkAAC0AwAgJQAAtAMAIKYBAAC0AwAgpwEAALQDACCiAhAAAAABowIQAAAABKQCEAAAAASlAhAAAAABpgIQAAAAAacCEAAAAAGoAhAAAAABqQIQALMDACEPBwAAsAMAICQAALIDACAlAACyAwAgogKAAAAAAaUCgAAAAAGmAoAAAAABpwKAAAAAAagCgAAAAAGpAoAAAAABqgIBAAAAAasCAQAAAAGsAgEAAAABrQKAAAAAAa4CgAAAAAGvAoAAAAABCwcAALADACAkAACxAwAgJQAAsQMAIKICQAAAAAGjAkAAAAAEpAJAAAAABKUCQAAAAAGmAkAAAAABpwJAAAAAAagCQAAAAAGpAkAArwMAIQsHAACwAwAgJAAAsQMAICUAALEDACCiAkAAAAABowJAAAAABKQCQAAAAASlAkAAAAABpgJAAAAAAacCQAAAAAGoAkAAAAABqQJAAK8DACEIogICAAAAAaMCAgAAAASkAgIAAAAEpQICAAAAAaYCAgAAAAGnAgIAAAABqAICAAAAAakCAgCwAwAhCKICQAAAAAGjAkAAAAAEpAJAAAAABKUCQAAAAAGmAkAAAAABpwJAAAAAAagCQAAAAAGpAkAAsQMAIQyiAoAAAAABpQKAAAAAAaYCgAAAAAGnAoAAAAABqAKAAAAAAakCgAAAAAGqAgEAAAABqwIBAAAAAawCAQAAAAGtAoAAAAABrgKAAAAAAa8CgAAAAAENBwAAsAMAICQAALQDACAlAAC0AwAgpgEAALQDACCnAQAAtAMAIKICEAAAAAGjAhAAAAAEpAIQAAAABKUCEAAAAAGmAhAAAAABpwIQAAAAAagCEAAAAAGpAhAAswMAIQiiAhAAAAABowIQAAAABKQCEAAAAASlAhAAAAABpgIQAAAAAacCEAAAAAGoAhAAAAABqQIQALQDACEOBwAAsAMAICQAALYDACAlAAC2AwAgogIBAAAAAaMCAQAAAASkAgEAAAAEpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQC1AwAhsAIBAAAAAbECAQAAAAGyAgEAAAABC6ICAQAAAAGjAgEAAAAEpAIBAAAABKUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEAtgMAIbACAQAAAAGxAgEAAAABsgIBAAAAAQkGAAC8AwAgmgIAALcDADCbAgAADwAQnAIAALcDADCdAgEAuAMAIZ4CAQC4AwAhnwIQALkDACGgAgAAugMAIKECQAC7AwAhC6ICAQAAAAGjAgEAAAAEpAIBAAAABKUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEAtgMAIbACAQAAAAGxAgEAAAABsgIBAAAAAQiiAhAAAAABowIQAAAABKQCEAAAAASlAhAAAAABpgIQAAAAAacCEAAAAAGoAhAAAAABqQIQALQDACEMogKAAAAAAaUCgAAAAAGmAoAAAAABpwKAAAAAAagCgAAAAAGpAoAAAAABqgIBAAAAAasCAQAAAAGsAgEAAAABrQKAAAAAAa4CgAAAAAGvAoAAAAABCKICQAAAAAGjAkAAAAAEpAJAAAAABKUCQAAAAAGmAkAAAAABpwJAAAAAAagCQAAAAAGpAkAAsQMAIQOzAgAAEQAgtAIAABEAILUCAAARACANmgIAAL0DADCbAgAAjwMAEJwCAAC9AwAwtgIBAL4DACG3AgEAqwMAIbgCAQCrAwAhuQJAAK4DACG6AgIAvwMAIbsCQACuAwAhvAICAL8DACG9AkAAwAMAIb4CQACuAwAhvwJAAK4DACELBwAAsAMAICQAALYDACAlAAC2AwAgogIBAAAAAaMCAQAAAASkAgEAAAAEpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQDGAwAhDQcAALADACAkAACwAwAgJQAAsAMAIKYBAADFAwAgpwEAALADACCiAgIAAAABowICAAAABKQCAgAAAASlAgIAAAABpgICAAAAAacCAgAAAAGoAgIAAAABqQICAMQDACELBwAAwgMAICQAAMMDACAlAADDAwAgogJAAAAAAaMCQAAAAAWkAkAAAAAFpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQADBAwAhCwcAAMIDACAkAADDAwAgJQAAwwMAIKICQAAAAAGjAkAAAAAFpAJAAAAABaUCQAAAAAGmAkAAAAABpwJAAAAAAagCQAAAAAGpAkAAwQMAIQiiAgIAAAABowICAAAABaQCAgAAAAWlAgIAAAABpgICAAAAAacCAgAAAAGoAgIAAAABqQICAMIDACEIogJAAAAAAaMCQAAAAAWkAkAAAAAFpQJAAAAAAaYCQAAAAAGnAkAAAAABqAJAAAAAAakCQADDAwAhDQcAALADACAkAACwAwAgJQAAsAMAIKYBAADFAwAgpwEAALADACCiAgIAAAABowICAAAABKQCAgAAAASlAgIAAAABpgICAAAAAacCAgAAAAGoAgIAAAABqQICAMQDACEIogIIAAAAAaMCCAAAAASkAggAAAAEpQIIAAAAAaYCCAAAAAGnAggAAAABqAIIAAAAAakCCADFAwAhCwcAALADACAkAAC2AwAgJQAAtgMAIKICAQAAAAGjAgEAAAAEpAIBAAAABKUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEAxgMAIQ2aAgAAxwMAMJsCAAD8AgAQnAIAAMcDADC2AgEAyAMAIbcCAQC4AwAhuAIBALgDACG5AkAAuwMAIboCAgDJAwAhuwJAALsDACG8AgIAyQMAIb0CQADKAwAhvgJAALsDACG_AkAAuwMAIQiiAgEAAAABowIBAAAABKQCAQAAAASlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBAMsDACEIogICAAAAAaMCAgAAAASkAgIAAAAEpQICAAAAAaYCAgAAAAGnAgIAAAABqAICAAAAAakCAgCwAwAhCKICQAAAAAGjAkAAAAAFpAJAAAAABaUCQAAAAAGmAkAAAAABpwJAAAAAAagCQAAAAAGpAkAAwwMAIQiiAgEAAAABowIBAAAABKQCAQAAAASlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBAMsDACEPmgIAAMwDADCbAgAA9gIAEJwCAADMAwAwtgIBAL4DACG3AgEAqwMAIbgCAQCrAwAhuQJAAK4DACG6AgIAvwMAIbsCQACuAwAhvAICAL8DACG-AkAArgMAIb8CQACuAwAhwAIBAKsDACHBAkAAwAMAIcICQADAAwAhD5oCAADNAwAwmwIAAOMCABCcAgAAzQMAMLYCAQDIAwAhtwIBALgDACG4AgEAuAMAIbkCQAC7AwAhugICAMkDACG7AkAAuwMAIbwCAgDJAwAhvgJAALsDACG_AkAAuwMAIcACAQC4AwAhwQJAAMoDACHCAkAAygMAIQ6aAgAAzgMAMJsCAADdAgAQnAIAAM4DADC2AgEAvgMAIcMCAQCrAwAhxAIBAL4DACHFAkAArgMAIcYCAQCrAwAhxwIBAKsDACHJAgAAzwPJAiLKAgAAzwPJAiLLAgEAqwMAIcwCAQCrAwAhzQJAAK4DACEHBwAAsAMAICQAANEDACAlAADRAwAgogIAAADJAgKjAgAAAMkCCKQCAAAAyQIIqQIAANADyQIiBwcAALADACAkAADRAwAgJQAA0QMAIKICAAAAyQICowIAAADJAgikAgAAAMkCCKkCAADQA8kCIgSiAgAAAMkCAqMCAAAAyQIIpAIAAADJAgipAgAA0QPJAiIOmgIAANIDADCbAgAAygIAEJwCAADSAwAwtgIBAMgDACHDAgEAuAMAIcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhyQIAANMDyQIiygIAANMDyQIiywIBALgDACHMAgEAuAMAIc0CQAC7AwAhBKICAAAAyQICowIAAADJAgikAgAAAMkCCKkCAADRA8kCIg6aAgAA1AMAMJsCAADEAgAQnAIAANQDADC2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHDAgEAqwMAIcQCAQC-AwAhxQJAAK4DACHGAgEAqwMAIccCAQCrAwAhywIBAKsDACHMAgEAqwMAIc4CAADPA8kCIgyaAgAA1QMAMJsCAACuAgAQnAIAANUDADCfAhAArAMAIbYCAQC-AwAhvgJAAK4DACG_AkAArgMAIcQCAQC-AwAhxgIBANcDACHHAgEA1wMAIdACAADWA9ACI9ECIADYAwAhBwcAAMIDACAkAADeAwAgJQAA3gMAIKICAAAA0AIDowIAAADQAgmkAgAAANACCakCAADdA9ACIw4HAADCAwAgJAAA3AMAICUAANwDACCiAgEAAAABowIBAAAABaQCAQAAAAWlAgEAAAABpgIBAAAAAacCAQAAAAGoAgEAAAABqQIBANsDACGwAgEAAAABsQIBAAAAAbICAQAAAAEFBwAAsAMAICQAANoDACAlAADaAwAgogIgAAAAAakCIADZAwAhBQcAALADACAkAADaAwAgJQAA2gMAIKICIAAAAAGpAiAA2QMAIQKiAiAAAAABqQIgANoDACEOBwAAwgMAICQAANwDACAlAADcAwAgogIBAAAAAaMCAQAAAAWkAgEAAAAFpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQDbAwAhsAIBAAAAAbECAQAAAAGyAgEAAAABC6ICAQAAAAGjAgEAAAAFpAIBAAAABaUCAQAAAAGmAgEAAAABpwIBAAAAAagCAQAAAAGpAgEA3AMAIbACAQAAAAGxAgEAAAABsgIBAAAAAQcHAADCAwAgJAAA3gMAICUAAN4DACCiAgAAANACA6MCAAAA0AIJpAIAAADQAgmpAgAA3QPQAiMEogIAAADQAgOjAgAAANACCaQCAAAA0AIJqQIAAN4D0AIjCZoCAADfAwAwmwIAAJgCABCcAgAA3wMAMLYCAQC-AwAhvgJAAK4DACHEAgEAvgMAIcYCAQCrAwAhxwIBAKsDACHQAgAA4APQAiIHBwAAsAMAICQAAOIDACAlAADiAwAgogIAAADQAgKjAgAAANACCKQCAAAA0AIIqQIAAOED0AIiBwcAALADACAkAADiAwAgJQAA4gMAIKICAAAA0AICowIAAADQAgikAgAAANACCKkCAADhA9ACIgSiAgAAANACAqMCAAAA0AIIpAIAAADQAgipAgAA4gPQAiILmgIAAOMDADCbAgAAggIAEJwCAADjAwAwngIBAKsDACG2AgEAvgMAIb4CQACuAwAhvwJAAK4DACHSAgEAvgMAIdMCAQC-AwAh1AICAL8DACHVAhAArAMAIQmaAgAA5AMAMJsCAADsAQAQnAIAAOQDADCeAgEAqwMAIbYCAQC-AwAhvgJAAK4DACG_AkAArgMAIdECIADYAwAh1gIBAKsDACEKDAAA5wMAIJoCAADlAwAwmwIAANkBABCcAgAA5QMAMJ4CAQC4AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh0QIgAOYDACHWAgEAuAMAIQKiAiAAAAABqQIgANoDACEDswIAABsAILQCAAAbACC1AgAAGwAgDJoCAADoAwAwmwIAANMBABCcAgAA6AMAMLYCAQC-AwAhtwIBAKsDACG-AkAArgMAIb8CQACuAwAh0gIBAL4DACHXAgEAqwMAIdgCQACuAwAh2QJAAMADACHaAkAAwAMAIQeaAgAA6QMAMJsCAAC9AQAQnAIAAOkDADC-AkAArgMAIdICAQC-AwAh2wIBAKsDACHdAgAA6gPdAiIHBwAAsAMAICQAAOwDACAlAADsAwAgogIAAADdAgKjAgAAAN0CCKQCAAAA3QIIqQIAAOsD3QIiBwcAALADACAkAADsAwAgJQAA7AMAIKICAAAA3QICowIAAADdAgikAgAAAN0CCKkCAADrA90CIgSiAgAAAN0CAqMCAAAA3QIIpAIAAADdAgipAgAA7APdAiIOmgIAAO0DADCbAgAApwEAEJwCAADtAwAwtgIBAL4DACG-AkAArgMAIb8CQACuAwAh3gIBAKsDACHfAgEA1wMAIeACAQDXAwAh4QIBANcDACHiAgEA1wMAIeMCAQCrAwAh5AIBAKsDACHlAgEA1wMAIQmaAgAA7gMAMJsCAACPAQAQnAIAAO4DADC2AgEAqwMAIb4CQACuAwAhvwJAAK4DACHYAkAArgMAIeYCAQCrAwAh5wIBAKsDACEJmgIAAO8DADCbAgAAfAAQnAIAAO8DADC2AgEAuAMAIb4CQAC7AwAhvwJAALsDACHYAkAAuwMAIeYCAQC4AwAh5wIBALgDACEQmgIAAPADADCbAgAAdgAQnAIAAPADADC2AgEAqwMAIb4CQACuAwAhvwJAAK4DACHbAgEAqwMAIegCAQCrAwAh6QIBAKsDACHqAgEA1wMAIesCAQDXAwAh7AIBANcDACHtAkAAwAMAIe4CQADAAwAh7wIBANcDACHwAgEA1wMAIQuaAgAA8QMAMJsCAABgABCcAgAA8QMAMLYCAQCrAwAhvgJAAK4DACG_AkAArgMAIdgCQACuAwAh2wIBAKsDACHxAgEAqwMAIfICAQDXAwAh8wIBANcDACEMmgIAAPIDADCbAgAASgAQnAIAAPIDADCeAgEAqwMAIbYCAQCrAwAhtwIBAKsDACG-AkAArgMAIb8CQACuAwAh3QIBAKsDACH0AiAA2AMAIfUCAQDXAwAh9gIBANcDACEPBAAA9QMAIAUAAPYDACAGAAD3AwAgmgIAAPMDADCbAgAANwAQnAIAAPMDADCeAgEAuAMAIbYCAQC4AwAhtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh3QIBALgDACH0AiAA5gMAIfUCAQD0AwAh9gIBAPQDACELogIBAAAAAaMCAQAAAAWkAgEAAAAFpQIBAAAAAaYCAQAAAAGnAgEAAAABqAIBAAAAAakCAQDcAwAhsAIBAAAAAbECAQAAAAGyAgEAAAABA7MCAAADACC0AgAAAwAgtQIAAAMAIAOzAgAABwAgtAIAAAcAILUCAAAHACADswIAAAsAILQCAAALACC1AgAACwAgA8QCAQAAAAHFAkAAAAABxgIBAAAAAQ8OAAD6AwAgmgIAAPkDADCbAgAAKQAQnAIAAPkDADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHDAgEAuAMAIcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhywIBALgDACHMAgEAuAMAIc4CAADTA8kCIhIKAACABAAgDQAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIJoCAAD_AwAwmwIAABsAEJwCAAD_AwAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdMCAQDIAwAh1AICAMkDACHVAhAAuQMAIfkCAAAbACD6AgAAGwAgDQ4AAPoDACCaAgAA-wMAMJsCAAAlABCcAgAA-wMAMJ8CEAC5AwAhtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAhxAIBAMgDACHGAgEA9AMAIccCAQD0AwAh0AIAAPwD0AIj0QIgAOYDACEEogIAAADQAgOjAgAAANACCaQCAAAA0AIJqQIAAN4D0AIjCg4AAPoDACCaAgAA_QMAMJsCAAAhABCcAgAA_QMAMLYCAQDIAwAhvgJAALsDACHEAgEAyAMAIcYCAQC4AwAhxwIBALgDACHQAgAA_gPQAiIEogIAAADQAgKjAgAAANACCKQCAAAA0AIIqQIAAOID0AIiEAoAAIAEACANAACBBAAgDwAAggQAIBAAAIMEACARAACEBAAgmgIAAP8DADCbAgAAGwAQnAIAAP8DADCeAgEAuAMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh0wIBAMgDACHUAgIAyQMAIdUCEAC5AwAhFAgAAIcEACAJAAD3AwAgCwAAiAQAIAwAAOcDACCaAgAAhgQAMJsCAAARABCcAgAAhgQAMLYCAQDIAwAhvgJAALsDACG_AkAAuwMAId4CAQC4AwAh3wIBAPQDACHgAgEA9AMAIeECAQD0AwAh4gIBAPQDACHjAgEAuAMAIeQCAQC4AwAh5QIBAPQDACH5AgAAEQAg-gIAABEAIAwMAADnAwAgmgIAAOUDADCbAgAA2QEAEJwCAADlAwAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHRAiAA5gMAIdYCAQC4AwAh-QIAANkBACD6AgAA2QEAIAOzAgAAIQAgtAIAACEAILUCAAAhACADswIAACUAILQCAAAlACC1AgAAJQAgA7MCAAApACC0AgAAKQAgtQIAACkAIA0KAACABAAgmgIAAIUEADCbAgAAFwAQnAIAAIUEADC2AgEAyAMAIbcCAQC4AwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh1wIBALgDACHYAkAAuwMAIdkCQADKAwAh2gJAAMoDACESCAAAhwQAIAkAAPcDACALAACIBAAgDAAA5wMAIJoCAACGBAAwmwIAABEAEJwCAACGBAAwtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh3gIBALgDACHfAgEA9AMAIeACAQD0AwAh4QIBAPQDACHiAgEA9AMAIeMCAQC4AwAh5AIBALgDACHlAgEA9AMAIQsGAAC8AwAgmgIAALcDADCbAgAADwAQnAIAALcDADCdAgEAuAMAIZ4CAQC4AwAhnwIQALkDACGgAgAAugMAIKECQAC7AwAh-QIAAA8AIPoCAAAPACADswIAABcAILQCAAAXACC1AgAAFwAgAtICAQAAAAHbAgEAAAABCQMAAIwEACAKAACABAAgmgIAAIoEADCbAgAACwAQnAIAAIoEADC-AkAAuwMAIdICAQDIAwAh2wIBALgDACHdAgAAiwTdAiIEogIAAADdAgKjAgAAAN0CCKQCAAAA3QIIqQIAAOwD3QIiEQQAAPUDACAFAAD2AwAgBgAA9wMAIJoCAADzAwAwmwIAADcAEJwCAADzAwAwngIBALgDACG2AgEAuAMAIbcCAQC4AwAhvgJAALsDACG_AkAAuwMAId0CAQC4AwAh9AIgAOYDACH1AgEA9AMAIfYCAQD0AwAh-QIAADcAIPoCAAA3ACARAwAAjAQAIJoCAACNBAAwmwIAAAcAEJwCAACNBAAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2wIBALgDACHoAgEAuAMAIekCAQC4AwAh6gIBAPQDACHrAgEA9AMAIewCAQD0AwAh7QJAAMoDACHuAkAAygMAIe8CAQD0AwAh8AIBAPQDACEMAwAAjAQAIJoCAACOBAAwmwIAAAMAEJwCAACOBAAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2AJAALsDACHbAgEAuAMAIfECAQC4AwAh8gIBAPQDACHzAgEA9AMAIQAAAAAAAf4CAQAAAAEF_gIQAAAAAYQDEAAAAAGFAxAAAAABhgMQAAAAAYcDEAAAAAEB_gJAAAAAAQseAACYBAAwHwAAnQQAMPsCAACZBAAw_AIAAJoEADD9AgAAmwQAIP4CAACcBAAw_wIAAJwEADCAAwAAnAQAMIEDAACcBAAwggMAAJ4EADCDAwAAnwQAMA0JAACBBQAgCwAAggUAIAwAAIMFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAABAgAAABMAIB4AAIAFACADAAAAEwAgHgAAgAUAIB8AAKMEACABFwAAzgYAMBIIAACHBAAgCQAA9wMAIAsAAIgEACAMAADnAwAgmgIAAIYEADCbAgAAEQAQnAIAAIYEADC2AgEAAAABvgJAALsDACG_AkAAuwMAId4CAQC4AwAh3wIBAPQDACHgAgEA9AMAIeECAQD0AwAh4gIBAPQDACHjAgEAAAAB5AIBALgDACHlAgEA9AMAIQIAAAATACAXAACjBAAgAgAAAKAEACAXAAChBAAgDpoCAACfBAAwmwIAAKAEABCcAgAAnwQAMLYCAQDIAwAhvgJAALsDACG_AkAAuwMAId4CAQC4AwAh3wIBAPQDACHgAgEA9AMAIeECAQD0AwAh4gIBAPQDACHjAgEAuAMAIeQCAQC4AwAh5QIBAPQDACEOmgIAAJ8EADCbAgAAoAQAEJwCAACfBAAwtgIBAMgDACG-AkAAuwMAIb8CQAC7AwAh3gIBALgDACHfAgEA9AMAIeACAQD0AwAh4QIBAPQDACHiAgEA9AMAIeMCAQC4AwAh5AIBALgDACHlAgEA9AMAIQq2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIQH-AgEAAAABDQkAAKQEACALAAClBAAgDAAApgQAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAhCx4AAPEEADAfAAD2BAAw-wIAAPIEADD8AgAA8wQAMP0CAAD0BAAg_gIAAPUEADD_AgAA9QQAMIADAAD1BAAwgQMAAPUEADCCAwAA9wQAMIMDAAD4BAAwCx4AAOQEADAfAADpBAAw-wIAAOUEADD8AgAA5gQAMP0CAADnBAAg_gIAAOgEADD_AgAA6AQAMIADAADoBAAwgQMAAOgEADCCAwAA6gQAMIMDAADrBAAwCx4AAKcEADAfAACsBAAw-wIAAKgEADD8AgAAqQQAMP0CAACqBAAg_gIAAKsEADD_AgAAqwQAMIADAACrBAAwgQMAAKsEADCCAwAArQQAMIMDAACuBAAwCw0AAOAEACAPAADhBAAgEAAA4gQAIBEAAOMEACCeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABAgAAAB0AIB4AAN8EACADAAAAHQAgHgAA3wQAIB8AALIEACABFwAAzQYAMBAKAACABAAgDQAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIJoCAAD_AwAwmwIAABsAEJwCAAD_AwAwngIBALgDACG2AgEAAAABvgJAALsDACG_AkAAuwMAIdICAQDIAwAh0wIBAMgDACHUAgIAyQMAIdUCEAC5AwAhAgAAAB0AIBcAALIEACACAAAArwQAIBcAALAEACALmgIAAK4EADCbAgAArwQAEJwCAACuBAAwngIBALgDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHSAgEAyAMAIdMCAQDIAwAh1AICAMkDACHVAhAAuQMAIQuaAgAArgQAMJsCAACvBAAQnAIAAK4EADCeAgEAuAMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh0wIBAMgDACHUAgIAyQMAIdUCEAC5AwAhB54CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhBf4CAgAAAAGEAwIAAAABhQMCAAAAAYYDAgAAAAGHAwIAAAABCw0AALMEACAPAAC0BAAgEAAAtQQAIBEAALYEACCeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQUeAADFBgAgHwAAywYAIPsCAADGBgAg_AIAAMoGACCBAwAA1gEAIAseAADSBAAwHwAA1wQAMPsCAADTBAAw_AIAANQEADD9AgAA1QQAIP4CAADWBAAw_wIAANYEADCAAwAA1gQAMIEDAADWBAAwggMAANgEADCDAwAA2QQAMAseAADEBAAwHwAAyQQAMPsCAADFBAAw_AIAAMYEADD9AgAAxwQAIP4CAADIBAAw_wIAAMgEADCAAwAAyAQAMIEDAADIBAAwggMAAMoEADCDAwAAywQAMAseAAC3BAAwHwAAvAQAMPsCAAC4BAAw_AIAALkEADD9AgAAugQAIP4CAAC7BAAw_wIAALsEADCAAwAAuwQAMIEDAAC7BAAwggMAAL0EADCDAwAAvgQAMAq2AgEAAAABvgJAAAAAAb8CQAAAAAHDAgEAAAABxQJAAAAAAcYCAQAAAAHHAgEAAAABywIBAAAAAcwCAQAAAAHOAgAAAMkCAgIAAAArACAeAADDBAAgAwAAACsAIB4AAMMEACAfAADCBAAgARcAAMkGADAQDgAA-gMAIJoCAAD5AwAwmwIAACkAEJwCAAD5AwAwtgIBAAAAAb4CQAC7AwAhvwJAALsDACHDAgEAAAABxAIBAMgDACHFAkAAuwMAIcYCAQC4AwAhxwIBALgDACHLAgEAuAMAIcwCAQC4AwAhzgIAANMDyQIi9wIAAPgDACACAAAAKwAgFwAAwgQAIAIAAAC_BAAgFwAAwAQAIA6aAgAAvgQAMJsCAAC_BAAQnAIAAL4EADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHDAgEAuAMAIcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhywIBALgDACHMAgEAuAMAIc4CAADTA8kCIg6aAgAAvgQAMJsCAAC_BAAQnAIAAL4EADC2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHDAgEAuAMAIcQCAQDIAwAhxQJAALsDACHGAgEAuAMAIccCAQC4AwAhywIBALgDACHMAgEAuAMAIc4CAADTA8kCIgq2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHDAgEAlAQAIcUCQACWBAAhxgIBAJQEACHHAgEAlAQAIcsCAQCUBAAhzAIBAJQEACHOAgAAwQTJAiIB_gIAAADJAgIKtgIBAJQEACG-AkAAlgQAIb8CQACWBAAhwwIBAJQEACHFAkAAlgQAIcYCAQCUBAAhxwIBAJQEACHLAgEAlAQAIcwCAQCUBAAhzgIAAMEEyQIiCrYCAQAAAAG-AkAAAAABvwJAAAAAAcMCAQAAAAHFAkAAAAABxgIBAAAAAccCAQAAAAHLAgEAAAABzAIBAAAAAc4CAAAAyQICCJ8CEAAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHGAgEAAAABxwIBAAAAAdACAAAA0AID0QIgAAAAAQIAAAAnACAeAADRBAAgAwAAACcAIB4AANEEACAfAADQBAAgARcAAMgGADANDgAA-gMAIJoCAAD7AwAwmwIAACUAEJwCAAD7AwAwnwIQALkDACG2AgEAAAABvgJAALsDACG_AkAAuwMAIcQCAQDIAwAhxgIBAPQDACHHAgEA9AMAIdACAAD8A9ACI9ECIADmAwAhAgAAACcAIBcAANAEACACAAAAzAQAIBcAAM0EACAMmgIAAMsEADCbAgAAzAQAEJwCAADLBAAwnwIQALkDACG2AgEAyAMAIb4CQAC7AwAhvwJAALsDACHEAgEAyAMAIcYCAQD0AwAhxwIBAPQDACHQAgAA_APQAiPRAiAA5gMAIQyaAgAAywQAMJsCAADMBAAQnAIAAMsEADCfAhAAuQMAIbYCAQDIAwAhvgJAALsDACG_AkAAuwMAIcQCAQDIAwAhxgIBAPQDACHHAgEA9AMAIdACAAD8A9ACI9ECIADmAwAhCJ8CEACVBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAhxgIBAKIEACHHAgEAogQAIdACAADOBNACI9ECIADPBAAhAf4CAAAA0AIDAf4CIAAAAAEInwIQAJUEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHGAgEAogQAIccCAQCiBAAh0AIAAM4E0AIj0QIgAM8EACEInwIQAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAcYCAQAAAAHHAgEAAAAB0AIAAADQAgPRAiAAAAABBbYCAQAAAAG-AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACAgIAAAAjACAeAADeBAAgAwAAACMAIB4AAN4EACAfAADdBAAgARcAAMcGADAKDgAA-gMAIJoCAAD9AwAwmwIAACEAEJwCAAD9AwAwtgIBAAAAAb4CQAC7AwAhxAIBAMgDACHGAgEAuAMAIccCAQC4AwAh0AIAAP4D0AIiAgAAACMAIBcAAN0EACACAAAA2gQAIBcAANsEACAJmgIAANkEADCbAgAA2gQAEJwCAADZBAAwtgIBAMgDACG-AkAAuwMAIcQCAQDIAwAhxgIBALgDACHHAgEAuAMAIdACAAD-A9ACIgmaAgAA2QQAMJsCAADaBAAQnAIAANkEADC2AgEAyAMAIb4CQAC7AwAhxAIBAMgDACHGAgEAuAMAIccCAQC4AwAh0AIAAP4D0AIiBbYCAQCUBAAhvgJAAJYEACHGAgEAlAQAIccCAQCUBAAh0AIAANwE0AIiAf4CAAAA0AICBbYCAQCUBAAhvgJAAJYEACHGAgEAlAQAIccCAQCUBAAh0AIAANwE0AIiBbYCAQAAAAG-AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACAgsNAADgBAAgDwAA4QQAIBAAAOIEACARAADjBAAgngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdMCAQAAAAHUAgIAAAAB1QIQAAAAAQMeAADFBgAg-wIAAMYGACCBAwAA1gEAIAQeAADSBAAw-wIAANMEADD9AgAA1QQAIIEDAADWBAAwBB4AAMQEADD7AgAAxQQAMP0CAADHBAAggQMAAMgEADAEHgAAtwQAMPsCAAC4BAAw_QIAALoEACCBAwAAuwQAMAi2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB1wIBAAAAAdgCQAAAAAHZAkAAAAAB2gJAAAAAAQIAAAAZACAeAADwBAAgAwAAABkAIB4AAPAEACAfAADvBAAgARcAAMQGADANCgAAgAQAIJoCAACFBAAwmwIAABcAEJwCAACFBAAwtgIBAAAAAbcCAQC4AwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh1wIBAAAAAdgCQAC7AwAh2QJAAMoDACHaAkAAygMAIQIAAAAZACAXAADvBAAgAgAAAOwEACAXAADtBAAgDJoCAADrBAAwmwIAAOwEABCcAgAA6wQAMLYCAQDIAwAhtwIBALgDACG-AkAAuwMAIb8CQAC7AwAh0gIBAMgDACHXAgEAuAMAIdgCQAC7AwAh2QJAAMoDACHaAkAAygMAIQyaAgAA6wQAMJsCAADsBAAQnAIAAOsEADC2AgEAyAMAIbcCAQC4AwAhvgJAALsDACG_AkAAuwMAIdICAQDIAwAh1wIBALgDACHYAkAAuwMAIdkCQADKAwAh2gJAAMoDACEItgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHXAgEAlAQAIdgCQACWBAAh2QJAAO4EACHaAkAA7gQAIQH-AkAAAAABCLYCAQCUBAAhtwIBAJQEACG-AkAAlgQAIb8CQACWBAAh1wIBAJQEACHYAkAAlgQAIdkCQADuBAAh2gJAAO4EACEItgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAdcCAQAAAAHYAkAAAAAB2QJAAAAAAdoCQAAAAAEEAwAA_wQAIL4CQAAAAAHbAgEAAAAB3QIAAADdAgICAAAADQAgHgAA_gQAIAMAAAANACAeAAD-BAAgHwAA_AQAIAEXAADDBgAwCgMAAIwEACAKAACABAAgmgIAAIoEADCbAgAACwAQnAIAAIoEADC-AkAAuwMAIdICAQDIAwAh2wIBALgDACHdAgAAiwTdAiL4AgAAiQQAIAIAAAANACAXAAD8BAAgAgAAAPkEACAXAAD6BAAgB5oCAAD4BAAwmwIAAPkEABCcAgAA-AQAML4CQAC7AwAh0gIBAMgDACHbAgEAuAMAId0CAACLBN0CIgeaAgAA-AQAMJsCAAD5BAAQnAIAAPgEADC-AkAAuwMAIdICAQDIAwAh2wIBALgDACHdAgAAiwTdAiIDvgJAAJYEACHbAgEAlAQAId0CAAD7BN0CIgH-AgAAAN0CAgQDAAD9BAAgvgJAAJYEACHbAgEAlAQAId0CAAD7BN0CIgUeAAC-BgAgHwAAwQYAIPsCAAC_BgAg_AIAAMAGACCBAwAAAQAgBAMAAP8EACC-AkAAAAAB2wIBAAAAAd0CAAAA3QICAx4AAL4GACD7AgAAvwYAIIEDAAABACANCQAAgQUAIAsAAIIFACAMAACDBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAAB3gIBAAAAAd8CAQAAAAHgAgEAAAAB4QIBAAAAAeICAQAAAAHjAgEAAAAB5AIBAAAAAQQeAADxBAAw-wIAAPIEADD9AgAA9AQAIIEDAAD1BAAwBB4AAOQEADD7AgAA5QQAMP0CAADnBAAggQMAAOgEADAEHgAApwQAMPsCAACoBAAw_QIAAKoEACCBAwAAqwQAMAQeAACYBAAw-wIAAJkEADD9AgAAmwQAIIEDAACcBAAwAAAAAAAAAAAAAAAAAAAAAAAABR4AALkGACAfAAC8BgAg-wIAALoGACD8AgAAuwYAIIEDAAAdACADHgAAuQYAIPsCAAC6BgAggQMAAB0AIAAAAAAABR4AALQGACAfAAC3BgAg-wIAALUGACD8AgAAtgYAIIEDAAAdACADHgAAtAYAIPsCAAC1BgAggQMAAB0AIAAAAAUeAACvBgAgHwAAsgYAIPsCAACwBgAg_AIAALEGACCBAwAAHQAgAx4AAK8GACD7AgAAsAYAIIEDAAAdACAAAAAAAAUeAACqBgAgHwAArQYAIPsCAACrBgAg_AIAAKwGACCBAwAAEwAgAx4AAKoGACD7AgAAqwYAIIEDAAATACAAAAALHgAAsAUAMB8AALQFADD7AgAAsQUAMPwCAACyBQAw_QIAALMFACD-AgAAqwQAMP8CAACrBAAwgAMAAKsEADCBAwAAqwQAMIIDAAC1BQAwgwMAAK4EADALCgAAqwUAIA8AAOEEACAQAADiBAAgEQAA4wQAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHSAgEAAAAB1AICAAAAAdUCEAAAAAECAAAAHQAgHgAAuAUAIAMAAAAdACAeAAC4BQAgHwAAtwUAIAEXAACpBgAwAgAAAB0AIBcAALcFACACAAAArwQAIBcAALYFACAHngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdQCAgCxBAAh1QIQAJUEACELCgAAqgUAIA8AALQEACAQAAC1BAAgEQAAtgQAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0gIBAJQEACHUAgIAsQQAIdUCEACVBAAhCwoAAKsFACAPAADhBAAgEAAA4gQAIBEAAOMEACCeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0gIBAAAAAdQCAgAAAAHVAhAAAAABBB4AALAFADD7AgAAsQUAMP0CAACzBQAggQMAAKsEADAAAAAABR4AAKQGACAfAACnBgAg-wIAAKUGACD8AgAApgYAIIEDAAATACADHgAApAYAIPsCAAClBgAggQMAABMAIAAAAAUeAACfBgAgHwAAogYAIPsCAACgBgAg_AIAAKEGACCBAwAAEwAgAx4AAJ8GACD7AgAAoAYAIIEDAAATACAAAAAHHgAAmgYAIB8AAJ0GACD7AgAAmwYAIPwCAACcBgAg_wIAAA8AIIADAAAPACCBAwAAkgMAIAMeAACaBgAg-wIAAJsGACCBAwAAkgMAIAAAAAAAAAUeAACVBgAgHwAAmAYAIPsCAACWBgAg_AIAAJcGACCBAwAAAQAgAx4AAJUGACD7AgAAlgYAIIEDAAABACAAAAAFHgAAkAYAIB8AAJMGACD7AgAAkQYAIPwCAACSBgAggQMAAAEAIAMeAACQBgAg-wIAAJEGACCBAwAAAQAgAAAACx4AAPIFADAfAAD3BQAw-wIAAPMFADD8AgAA9AUAMP0CAAD1BQAg_gIAAPYFADD_AgAA9gUAMIADAAD2BQAwgQMAAPYFADCCAwAA-AUAMIMDAAD5BQAwCx4AAOYFADAfAADrBQAw-wIAAOcFADD8AgAA6AUAMP0CAADpBQAg_gIAAOoFADD_AgAA6gUAMIADAADqBQAwgQMAAOoFADCCAwAA7AUAMIMDAADtBQAwCx4AAN0FADAfAADhBQAw-wIAAN4FADD8AgAA3wUAMP0CAADgBQAg_gIAAPUEADD_AgAA9QQAMIADAAD1BAAwgQMAAPUEADCCAwAA4gUAMIMDAAD4BAAwBAoAAMQFACC-AkAAAAAB0gIBAAAAAd0CAAAA3QICAgAAAA0AIB4AAOUFACADAAAADQAgHgAA5QUAIB8AAOQFACABFwAAjwYAMAIAAAANACAXAADkBQAgAgAAAPkEACAXAADjBQAgA74CQACWBAAh0gIBAJQEACHdAgAA-wTdAiIECgAAwwUAIL4CQACWBAAh0gIBAJQEACHdAgAA-wTdAiIECgAAxAUAIL4CQAAAAAHSAgEAAAAB3QIAAADdAgIMtgIBAAAAAb4CQAAAAAG_AkAAAAAB6AIBAAAAAekCAQAAAAHqAgEAAAAB6wIBAAAAAewCAQAAAAHtAkAAAAAB7gJAAAAAAe8CAQAAAAHwAgEAAAABAgAAAAkAIB4AAPEFACADAAAACQAgHgAA8QUAIB8AAPAFACABFwAAjgYAMBEDAACMBAAgmgIAAI0EADCbAgAABwAQnAIAAI0EADC2AgEAAAABvgJAALsDACG_AkAAuwMAIdsCAQC4AwAh6AIBALgDACHpAgEAuAMAIeoCAQD0AwAh6wIBAPQDACHsAgEA9AMAIe0CQADKAwAh7gJAAMoDACHvAgEA9AMAIfACAQD0AwAhAgAAAAkAIBcAAPAFACACAAAA7gUAIBcAAO8FACAQmgIAAO0FADCbAgAA7gUAEJwCAADtBQAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2wIBALgDACHoAgEAuAMAIekCAQC4AwAh6gIBAPQDACHrAgEA9AMAIewCAQD0AwAh7QJAAMoDACHuAkAAygMAIe8CAQD0AwAh8AIBAPQDACEQmgIAAO0FADCbAgAA7gUAEJwCAADtBQAwtgIBALgDACG-AkAAuwMAIb8CQAC7AwAh2wIBALgDACHoAgEAuAMAIekCAQC4AwAh6gIBAPQDACHrAgEA9AMAIewCAQD0AwAh7QJAAMoDACHuAkAAygMAIe8CAQD0AwAh8AIBAPQDACEMtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh6AIBAJQEACHpAgEAlAQAIeoCAQCiBAAh6wIBAKIEACHsAgEAogQAIe0CQADuBAAh7gJAAO4EACHvAgEAogQAIfACAQCiBAAhDLYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIegCAQCUBAAh6QIBAJQEACHqAgEAogQAIesCAQCiBAAh7AIBAKIEACHtAkAA7gQAIe4CQADuBAAh7wIBAKIEACHwAgEAogQAIQy2AgEAAAABvgJAAAAAAb8CQAAAAAHoAgEAAAAB6QIBAAAAAeoCAQAAAAHrAgEAAAAB7AIBAAAAAe0CQAAAAAHuAkAAAAAB7wIBAAAAAfACAQAAAAEHtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAfECAQAAAAHyAgEAAAAB8wIBAAAAAQIAAAAFACAeAAD9BQAgAwAAAAUAIB4AAP0FACAfAAD8BQAgARcAAI0GADAMAwAAjAQAIJoCAACOBAAwmwIAAAMAEJwCAACOBAAwtgIBAAAAAb4CQAC7AwAhvwJAALsDACHYAkAAuwMAIdsCAQC4AwAh8QIBAAAAAfICAQD0AwAh8wIBAPQDACECAAAABQAgFwAA_AUAIAIAAAD6BQAgFwAA-wUAIAuaAgAA-QUAMJsCAAD6BQAQnAIAAPkFADC2AgEAuAMAIb4CQAC7AwAhvwJAALsDACHYAkAAuwMAIdsCAQC4AwAh8QIBALgDACHyAgEA9AMAIfMCAQD0AwAhC5oCAAD5BQAwmwIAAPoFABCcAgAA-QUAMLYCAQC4AwAhvgJAALsDACG_AkAAuwMAIdgCQAC7AwAh2wIBALgDACHxAgEAuAMAIfICAQD0AwAh8wIBAPQDACEHtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh2AJAAJYEACHxAgEAlAQAIfICAQCiBAAh8wIBAKIEACEHtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh2AJAAJYEACHxAgEAlAQAIfICAQCiBAAh8wIBAKIEACEHtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAfECAQAAAAHyAgEAAAAB8wIBAAAAAQQeAADyBQAw-wIAAPMFADD9AgAA9QUAIIEDAAD2BQAwBB4AAOYFADD7AgAA5wUAMP0CAADpBQAggQMAAOoFADAEHgAA3QUAMPsCAADeBQAw_QIAAOAFACCBAwAA9QQAMAAAAAUKAACFBgAgDQAAhgYAIA8AAIcGACAQAACIBgAgEQAAiQYAIAkIAACKBgAgCQAAgwYAIAsAAIsGACAMAAC6BQAg3wIAAIYFACDgAgAAhgUAIOECAACGBQAg4gIAAIYFACDlAgAAhgUAIAEMAAC6BQAgAAAAAQYAAIUFACAABQQAAIEGACAFAACCBgAgBgAAgwYAIPUCAACGBQAg9gIAAIYFACAHtgIBAAAAAb4CQAAAAAG_AkAAAAAB2AJAAAAAAfECAQAAAAHyAgEAAAAB8wIBAAAAAQy2AgEAAAABvgJAAAAAAb8CQAAAAAHoAgEAAAAB6QIBAAAAAeoCAQAAAAHrAgEAAAAB7AIBAAAAAe0CQAAAAAHuAkAAAAAB7wIBAAAAAfACAQAAAAEDvgJAAAAAAdICAQAAAAHdAgAAAN0CAgsFAAD_BQAgBgAAgAYAIJ4CAQAAAAG2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB3QIBAAAAAfQCIAAAAAH1AgEAAAAB9gIBAAAAAQIAAAABACAeAACQBgAgAwAAADcAIB4AAJAGACAfAACUBgAgDQAAADcAIAUAANsFACAGAADcBQAgFwAAlAYAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACH2AgEAogQAIQsFAADbBQAgBgAA3AUAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACH2AgEAogQAIQsEAAD-BQAgBgAAgAYAIJ4CAQAAAAG2AgEAAAABtwIBAAAAAb4CQAAAAAG_AkAAAAAB3QIBAAAAAfQCIAAAAAH1AgEAAAAB9gIBAAAAAQIAAAABACAeAACVBgAgAwAAADcAIB4AAJUGACAfAACZBgAgDQAAADcAIAQAANoFACAGAADcBQAgFwAAmQYAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACH2AgEAogQAIQsEAADaBQAgBgAA3AUAIJ4CAQCUBAAhtgIBAJQEACG3AgEAlAQAIb4CQACWBAAhvwJAAJYEACHdAgEAlAQAIfQCIADPBAAh9QIBAKIEACH2AgEAogQAIQWdAgEAAAABngIBAAAAAZ8CEAAAAAGgAoAAAAABoQJAAAAAAQIAAACSAwAgHgAAmgYAIAMAAAAPACAeAACaBgAgHwAAngYAIAcAAAAPACAXAACeBgAgnQIBAJQEACGeAgEAlAQAIZ8CEACVBAAhoAKAAAAAAaECQACWBAAhBZ0CAQCUBAAhngIBAJQEACGfAhAAlQQAIaACgAAAAAGhAkAAlgQAIQ4IAADJBQAgCwAAggUAIAwAAIMFACC2AgEAAAABvgJAAAAAAb8CQAAAAAHeAgEAAAAB3wIBAAAAAeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAAAAB5QIBAAAAAQIAAAATACAeAACfBgAgAwAAABEAIB4AAJ8GACAfAACjBgAgEAAAABEAIAgAAMgFACALAAClBAAgDAAApgQAIBcAAKMGACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhDggAAMgFACALAAClBAAgDAAApgQAILYCAQCUBAAhvgJAAJYEACG_AkAAlgQAId4CAQCUBAAh3wIBAKIEACHgAgEAogQAIeECAQCiBAAh4gIBAKIEACHjAgEAlAQAIeQCAQCUBAAh5QIBAKIEACEOCAAAyQUAIAkAAIEFACAMAACDBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAAB3gIBAAAAAd8CAQAAAAHgAgEAAAAB4QIBAAAAAeICAQAAAAHjAgEAAAAB5AIBAAAAAeUCAQAAAAECAAAAEwAgHgAApAYAIAMAAAARACAeAACkBgAgHwAAqAYAIBAAAAARACAIAADIBQAgCQAApAQAIAwAAKYEACAXAACoBgAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh3gIBAJQEACHfAgEAogQAIeACAQCiBAAh4QIBAKIEACHiAgEAogQAIeMCAQCUBAAh5AIBAJQEACHlAgEAogQAIQ4IAADIBQAgCQAApAQAIAwAAKYEACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhB54CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHSAgEAAAAB1AICAAAAAdUCEAAAAAEOCAAAyQUAIAkAAIEFACALAACCBQAgtgIBAAAAAb4CQAAAAAG_AkAAAAAB3gIBAAAAAd8CAQAAAAHgAgEAAAAB4QIBAAAAAeICAQAAAAHjAgEAAAAB5AIBAAAAAeUCAQAAAAECAAAAEwAgHgAAqgYAIAMAAAARACAeAACqBgAgHwAArgYAIBAAAAARACAIAADIBQAgCQAApAQAIAsAAKUEACAXAACuBgAgtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh3gIBAJQEACHfAgEAogQAIeACAQCiBAAh4QIBAKIEACHiAgEAogQAIeMCAQCUBAAh5AIBAJQEACHlAgEAogQAIQ4IAADIBQAgCQAApAQAIAsAAKUEACC2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHeAgEAlAQAId8CAQCiBAAh4AIBAKIEACHhAgEAogQAIeICAQCiBAAh4wIBAJQEACHkAgEAlAQAIeUCAQCiBAAhDAoAAKsFACANAADgBAAgEAAA4gQAIBEAAOMEACCeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0gIBAAAAAdMCAQAAAAHUAgIAAAAB1QIQAAAAAQIAAAAdACAeAACvBgAgAwAAABsAIB4AAK8GACAfAACzBgAgDgAAABsAIAoAAKoFACANAACzBAAgEAAAtQQAIBEAALYEACAXAACzBgAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQwKAACqBQAgDQAAswQAIBAAALUEACARAAC2BAAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHSAgEAlAQAIdMCAQCUBAAh1AICALEEACHVAhAAlQQAIQwKAACrBQAgDQAA4AQAIA8AAOEEACARAADjBAAgngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdICAQAAAAHTAgEAAAAB1AICAAAAAdUCEAAAAAECAAAAHQAgHgAAtAYAIAMAAAAbACAeAAC0BgAgHwAAuAYAIA4AAAAbACAKAACqBQAgDQAAswQAIA8AALQEACARAAC2BAAgFwAAuAYAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0gIBAJQEACHTAgEAlAQAIdQCAgCxBAAh1QIQAJUEACEMCgAAqgUAIA0AALMEACAPAAC0BAAgEQAAtgQAIJ4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0gIBAJQEACHTAgEAlAQAIdQCAgCxBAAh1QIQAJUEACEMCgAAqwUAIA0AAOAEACAPAADhBAAgEAAA4gQAIJ4CAQAAAAG2AgEAAAABvgJAAAAAAb8CQAAAAAHSAgEAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABAgAAAB0AIB4AALkGACADAAAAGwAgHgAAuQYAIB8AAL0GACAOAAAAGwAgCgAAqgUAIA0AALMEACAPAAC0BAAgEAAAtQQAIBcAAL0GACCeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhDAoAAKoFACANAACzBAAgDwAAtAQAIBAAALUEACCeAgEAlAQAIbYCAQCUBAAhvgJAAJYEACG_AkAAlgQAIdICAQCUBAAh0wIBAJQEACHUAgIAsQQAIdUCEACVBAAhCwQAAP4FACAFAAD_BQAgngIBAAAAAbYCAQAAAAG3AgEAAAABvgJAAAAAAb8CQAAAAAHdAgEAAAAB9AIgAAAAAfUCAQAAAAH2AgEAAAABAgAAAAEAIB4AAL4GACADAAAANwAgHgAAvgYAIB8AAMIGACANAAAANwAgBAAA2gUAIAUAANsFACAXAADCBgAgngIBAJQEACG2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAId0CAQCUBAAh9AIgAM8EACH1AgEAogQAIfYCAQCiBAAhCwQAANoFACAFAADbBQAgngIBAJQEACG2AgEAlAQAIbcCAQCUBAAhvgJAAJYEACG_AkAAlgQAId0CAQCUBAAh9AIgAM8EACH1AgEAogQAIfYCAQCiBAAhA74CQAAAAAHbAgEAAAAB3QIAAADdAgIItgIBAAAAAbcCAQAAAAG-AkAAAAABvwJAAAAAAdcCAQAAAAHYAkAAAAAB2QJAAAAAAdoCQAAAAAEGngIBAAAAAbYCAQAAAAG-AkAAAAABvwJAAAAAAdECIAAAAAHWAgEAAAABAgAAANYBACAeAADFBgAgBbYCAQAAAAG-AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACAgifAhAAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAABxgIBAAAAAccCAQAAAAHQAgAAANACA9ECIAAAAAEKtgIBAAAAAb4CQAAAAAG_AkAAAAABwwIBAAAAAcUCQAAAAAHGAgEAAAABxwIBAAAAAcsCAQAAAAHMAgEAAAABzgIAAADJAgIDAAAA2QEAIB4AAMUGACAfAADMBgAgCAAAANkBACAXAADMBgAgngIBAJQEACG2AgEAlAQAIb4CQACWBAAhvwJAAJYEACHRAiAAzwQAIdYCAQCUBAAhBp4CAQCUBAAhtgIBAJQEACG-AkAAlgQAIb8CQACWBAAh0QIgAM8EACHWAgEAlAQAIQeeAgEAAAABtgIBAAAAAb4CQAAAAAG_AkAAAAAB0wIBAAAAAdQCAgAAAAHVAhAAAAABCrYCAQAAAAG-AkAAAAABvwJAAAAAAd4CAQAAAAHfAgEAAAAB4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQAAAAEEBAYCBQoDBg4EBwARAQMAAQEDAAECAwABCgAFBQcAEAgQBgkWBAsaCAweCQIGFAUHAAcBBhUAAQoABQYHAA8KAAUNAAoPJAwQKA0RLA4CBwALDB8JAQwgAAEOAAkBDgAJAQ4ACQMPLQAQLgARLwADCTAACzEADDIAAwQzAAU0AAY1AAAAAAMHABYkABclABgAAAADBwAWJAAXJQAYAQMAAQEDAAEDBwAdJAAeJQAfAAAAAwcAHSQAHiUAHwEDAAEBAwABAwcAJCQAJSUAJgAAAAMHACQkACUlACYAAAADBwAsJAAtJQAuAAAAAwcALCQALSUALgEInAEGAQiiAQYDBwAzJAA0JQA1AAAAAwcAMyQANCUANQIDAAEKAAUCAwABCgAFAwcAOiQAOyUAPAAAAAMHADokADslADwBCgAFAQoABQMHAEEkAEIlAEMAAAADBwBBJABCJQBDAAADBwBIJABJJQBKAAAAAwcASCQASSUASgIKAAUNAAoCCgAFDQAKBQcATyQAUiUAU6YBAFCnAQBRAAAAAAAFBwBPJABSJQBTpgEAUKcBAFEBDgAJAQ4ACQMHAFgkAFklAFoAAAADBwBYJABZJQBaAQ4ACQEOAAkFBwBfJABiJQBjpgEAYKcBAGEAAAAAAAUHAF8kAGIlAGOmAQBgpwEAYQEOAAkBDgAJAwcAaCQAaSUAagAAAAMHAGgkAGklAGoAAAADBwBwJABxJQByAAAAAwcAcCQAcSUAcgAAAAUHAHgkAHslAHymAQB5pwEAegAAAAAABQcAeCQAeyUAfKYBAHmnAQB6AAAABQcAggEkAIUBJQCGAaYBAIMBpwEAhAEAAAAAAAUHAIIBJACFASUAhgGmAQCDAacBAIQBAAAFBwCLASQAjgElAI8BpgEAjAGnAQCNAQAAAAAABQcAiwEkAI4BJQCPAaYBAIwBpwEAjQESAgETNgEUOQEVOgEWOwEYPQEZPxIaQBMbQgEcRBIdRRQgRgEhRwEiSBImSxUnTBkoTQIpTgIqTwIrUAIsUQItUwIuVRIvVhowWAIxWhIyWxszXAI0XQI1XhI2YRw3YiA4YwM5ZAM6ZQM7ZgM8ZwM9aQM-axI_bCFAbgNBcBJCcSJDcgNEcwNFdBJGdyNHeCdIeihJeyhKfihLfyhMgAEoTYIBKE6EARJPhQEpUIcBKFGJARJSigEqU4sBKFSMAShVjQESVpABK1eRAS9YkgEFWZMBBVqUAQVblQEFXJYBBV2YAQVemgESX5sBMGCeAQVhoAESYqEBMWOjAQVkpAEFZaUBEmaoATJnqQE2aKoBBGmrAQRqrAEEa60BBGyuAQRtsAEEbrIBEm-zATdwtQEEcbcBEnK4AThzuQEEdLoBBHW7ARJ2vgE5d78BPXjAAQh5wQEIesIBCHvDAQh8xAEIfcYBCH7IARJ_yQE-gAHLAQiBAc0BEoIBzgE_gwHPAQiEAdABCIUB0QEShgHUAUCHAdUBRIgB1wEKiQHYAQqKAdsBCosB3AEKjAHdAQqNAd8BCo4B4QESjwHiAUWQAeQBCpEB5gESkgHnAUaTAegBCpQB6QEKlQHqARKWAe0BR5cB7gFLmAHvAQmZAfABCZoB8QEJmwHyAQmcAfMBCZ0B9QEJngH3ARKfAfgBTKAB-gEJoQH8ARKiAf0BTaMB_gEJpAH_AQmlAYACEqgBgwJOqQGEAlSqAYUCDKsBhgIMrAGHAgytAYgCDK4BiQIMrwGLAgywAY0CErEBjgJVsgGQAgyzAZICErQBkwJWtQGUAgy2AZUCDLcBlgISuAGZAle5AZoCW7oBmwINuwGcAg28AZ0CDb0BngINvgGfAg2_AaECDcABowISwQGkAlzCAaYCDcMBqAISxAGpAl3FAaoCDcYBqwINxwGsAhLIAa8CXskBsAJkygGxAg7LAbICDswBswIOzQG0Ag7OAbUCDs8BtwIO0AG5AhLRAboCZdIBvAIO0wG-AhLUAb8CZtUBwAIO1gHBAg7XAcICEtgBxQJn2QHGAmvaAcgCbNsByQJs3AHMAmzdAc0CbN4BzgJs3wHQAmzgAdICEuEB0wJt4gHVAmzjAdcCEuQB2AJu5QHZAmzmAdoCbOcB2wIS6AHeAm_pAd8Cc-oB4QJ06wHiAnTsAeUCdO0B5gJ07gHnAnTvAekCdPAB6wIS8QHsAnXyAe4CdPMB8AIS9AHxAnb1AfICdPYB8wJ09wH0AhL4AfcCd_kB-AJ9-gH6An77AfsCfvwB_gJ-_QH_An7-AYADfv8BggN-gAKEAxKBAoUDf4IChwN-gwKJAxKEAooDgAGFAosDfoYCjAN-hwKNAxKIApADgQGJApEDhwGKApMDBosClAMGjAKWAwaNApcDBo4CmAMGjwKaAwaQApwDEpECnQOIAZICnwMGkwKhAxKUAqIDiQGVAqMDBpYCpAMGlwKlAxKYAqgDigGZAqkDkAE"
}
async function decodeBase64AsWasm(wasmBase64: string): Promise {
diff --git a/apps/backend/src/generated/prisma/internal/prismaNamespace.ts b/apps/backend/src/generated/prisma/internal/prismaNamespace.ts
index 449443a..fc6691e 100644
--- a/apps/backend/src/generated/prisma/internal/prismaNamespace.ts
+++ b/apps/backend/src/generated/prisma/internal/prismaNamespace.ts
@@ -1648,6 +1648,7 @@ export const UserScalarFieldEnum = {
email: 'email',
emailVerified: 'emailVerified',
image: 'image',
+ phone: 'phone',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
role: 'role'
diff --git a/apps/backend/src/generated/prisma/internal/prismaNamespaceBrowser.ts b/apps/backend/src/generated/prisma/internal/prismaNamespaceBrowser.ts
index 9e67129..659ab1a 100644
--- a/apps/backend/src/generated/prisma/internal/prismaNamespaceBrowser.ts
+++ b/apps/backend/src/generated/prisma/internal/prismaNamespaceBrowser.ts
@@ -91,6 +91,7 @@ export const UserScalarFieldEnum = {
email: 'email',
emailVerified: 'emailVerified',
image: 'image',
+ phone: 'phone',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
role: 'role'
diff --git a/apps/backend/src/generated/prisma/models/User.ts b/apps/backend/src/generated/prisma/models/User.ts
index 93011f2..1787905 100644
--- a/apps/backend/src/generated/prisma/models/User.ts
+++ b/apps/backend/src/generated/prisma/models/User.ts
@@ -30,6 +30,7 @@ export type UserMinAggregateOutputType = {
email: string | null
emailVerified: boolean | null
image: string | null
+ phone: string | null
createdAt: Date | null
updatedAt: Date | null
role: string | null
@@ -41,6 +42,7 @@ export type UserMaxAggregateOutputType = {
email: string | null
emailVerified: boolean | null
image: string | null
+ phone: string | null
createdAt: Date | null
updatedAt: Date | null
role: string | null
@@ -52,6 +54,7 @@ export type UserCountAggregateOutputType = {
email: number
emailVerified: number
image: number
+ phone: number
createdAt: number
updatedAt: number
role: number
@@ -65,6 +68,7 @@ export type UserMinAggregateInputType = {
email?: true
emailVerified?: true
image?: true
+ phone?: true
createdAt?: true
updatedAt?: true
role?: true
@@ -76,6 +80,7 @@ export type UserMaxAggregateInputType = {
email?: true
emailVerified?: true
image?: true
+ phone?: true
createdAt?: true
updatedAt?: true
role?: true
@@ -87,6 +92,7 @@ export type UserCountAggregateInputType = {
email?: true
emailVerified?: true
image?: true
+ phone?: true
createdAt?: true
updatedAt?: true
role?: true
@@ -171,6 +177,7 @@ export type UserGroupByOutputType = {
email: string
emailVerified: boolean
image: string | null
+ phone: string | null
createdAt: Date
updatedAt: Date
role: string
@@ -203,6 +210,7 @@ export type UserWhereInput = {
email?: Prisma.StringFilter<"User"> | string
emailVerified?: Prisma.BoolFilter<"User"> | boolean
image?: Prisma.StringNullableFilter<"User"> | string | null
+ phone?: Prisma.StringNullableFilter<"User"> | string | null
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
role?: Prisma.StringFilter<"User"> | string
@@ -217,6 +225,7 @@ export type UserOrderByWithRelationInput = {
email?: Prisma.SortOrder
emailVerified?: Prisma.SortOrder
image?: Prisma.SortOrderInput | Prisma.SortOrder
+ phone?: Prisma.SortOrderInput | Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
role?: Prisma.SortOrder
@@ -234,6 +243,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
name?: Prisma.StringFilter<"User"> | string
emailVerified?: Prisma.BoolFilter<"User"> | boolean
image?: Prisma.StringNullableFilter<"User"> | string | null
+ phone?: Prisma.StringNullableFilter<"User"> | string | null
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
role?: Prisma.StringFilter<"User"> | string
@@ -248,6 +258,7 @@ export type UserOrderByWithAggregationInput = {
email?: Prisma.SortOrder
emailVerified?: Prisma.SortOrder
image?: Prisma.SortOrderInput | Prisma.SortOrder
+ phone?: Prisma.SortOrderInput | Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
role?: Prisma.SortOrder
@@ -265,6 +276,7 @@ export type UserScalarWhereWithAggregatesInput = {
email?: Prisma.StringWithAggregatesFilter<"User"> | string
emailVerified?: Prisma.BoolWithAggregatesFilter<"User"> | boolean
image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
+ phone?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
role?: Prisma.StringWithAggregatesFilter<"User"> | string
@@ -276,6 +288,7 @@ export type UserCreateInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -290,6 +303,7 @@ export type UserUncheckedCreateInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -304,6 +318,7 @@ export type UserUpdateInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -318,6 +333,7 @@ export type UserUncheckedUpdateInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -332,6 +348,7 @@ export type UserCreateManyInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -343,6 +360,7 @@ export type UserUpdateManyMutationInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -354,6 +372,7 @@ export type UserUncheckedUpdateManyInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -365,6 +384,7 @@ export type UserCountOrderByAggregateInput = {
email?: Prisma.SortOrder
emailVerified?: Prisma.SortOrder
image?: Prisma.SortOrder
+ phone?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
role?: Prisma.SortOrder
@@ -376,6 +396,7 @@ export type UserMaxOrderByAggregateInput = {
email?: Prisma.SortOrder
emailVerified?: Prisma.SortOrder
image?: Prisma.SortOrder
+ phone?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
role?: Prisma.SortOrder
@@ -387,6 +408,7 @@ export type UserMinOrderByAggregateInput = {
email?: Prisma.SortOrder
emailVerified?: Prisma.SortOrder
image?: Prisma.SortOrder
+ phone?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
role?: Prisma.SortOrder
@@ -461,6 +483,7 @@ export type UserCreateWithoutSessionsInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -474,6 +497,7 @@ export type UserUncheckedCreateWithoutSessionsInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -503,6 +527,7 @@ export type UserUpdateWithoutSessionsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -516,6 +541,7 @@ export type UserUncheckedUpdateWithoutSessionsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -529,6 +555,7 @@ export type UserCreateWithoutAccountsInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -542,6 +569,7 @@ export type UserUncheckedCreateWithoutAccountsInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -571,6 +599,7 @@ export type UserUpdateWithoutAccountsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -584,6 +613,7 @@ export type UserUncheckedUpdateWithoutAccountsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -597,6 +627,7 @@ export type UserCreateWithoutComplexesInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -610,6 +641,7 @@ export type UserUncheckedCreateWithoutComplexesInput = {
email: string
emailVerified?: boolean
image?: string | null
+ phone?: string | null
createdAt?: Date | string
updatedAt?: Date | string
role?: string
@@ -639,6 +671,7 @@ export type UserUpdateWithoutComplexesInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -652,6 +685,7 @@ export type UserUncheckedUpdateWithoutComplexesInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
+ phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
role?: Prisma.StringFieldUpdateOperationsInput | string
@@ -714,6 +748,7 @@ export type UserSelect = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "emailVerified" | "image" | "createdAt" | "updatedAt" | "role", ExtArgs["result"]["user"]>
+export type UserOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "emailVerified" | "image" | "phone" | "createdAt" | "updatedAt" | "role", ExtArgs["result"]["user"]>
export type UserInclude = {
sessions?: boolean | Prisma.User$sessionsArgs
accounts?: boolean | Prisma.User$accountsArgs
@@ -779,6 +817,7 @@ export type $UserPayload
readonly emailVerified: Prisma.FieldRef<"User", 'Boolean'>
readonly image: Prisma.FieldRef<"User", 'String'>
+ readonly phone: Prisma.FieldRef<"User", 'String'>
readonly createdAt: Prisma.FieldRef<"User", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'>
readonly role: Prisma.FieldRef<"User", 'String'>
diff --git a/apps/backend/src/lib/auth.ts b/apps/backend/src/lib/auth.ts
index a513895..61b0d83 100644
--- a/apps/backend/src/lib/auth.ts
+++ b/apps/backend/src/lib/auth.ts
@@ -2,6 +2,7 @@ import { dash } from '@better-auth/infra';
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { openAPI } from 'better-auth/plugins';
+import { sendMail } from './mailer';
import { db } from './prisma';
export const auth = betterAuth({
@@ -18,6 +19,26 @@ export const auth = betterAuth({
emailAndPassword: {
enabled: true,
},
+ user: {
+ additionalFields: {
+ phone: {
+ type: 'string',
+ required: false,
+ },
+ },
+ },
+ emailVerification: {
+ sendOnSignUp: true,
+ autoSignInAfterVerification: true,
+ sendVerificationEmail: async ({ user, url }) => {
+ await sendMail({
+ to: user.email,
+ subject: 'Verificá tu email en Playzer',
+ html: `Hacé click para verificar tu email: ${url} `,
+ text: `Hacé click para verificar tu email: ${url}`,
+ });
+ },
+ },
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
diff --git a/apps/frontend/src/features/login/login-page.tsx b/apps/frontend/src/features/login/login-page.tsx
index b8d6ade..c776c51 100644
--- a/apps/frontend/src/features/login/login-page.tsx
+++ b/apps/frontend/src/features/login/login-page.tsx
@@ -91,7 +91,7 @@ export function LoginPage({ redirectTo = '/' }: LoginPageProps) {
Iniciar sesión
- Ingresá con tu cuenta para acceder a Home.
+ Ingresá con tu cuenta para acceder a Playzer.
@@ -161,6 +161,10 @@ export function LoginPage({ redirectTo = '/' }: LoginPageProps) {
¿Olvidaste tu contraseña?
+ |
+
+ Crear cuenta
+
diff --git a/apps/frontend/src/features/signup/signup-page.tsx b/apps/frontend/src/features/signup/signup-page.tsx
new file mode 100644
index 0000000..c3fcf52
--- /dev/null
+++ b/apps/frontend/src/features/signup/signup-page.tsx
@@ -0,0 +1,228 @@
+import PlayzerIcon from '@/assets/playzer-favicon-512-transparent.png';
+import { Button } from '@/components/ui/button';
+import { Field, FieldError, FieldLabel } from '@/components/ui/field';
+import { Input } from '@/components/ui/input';
+import { authClient } from '@/lib/api-client';
+import { useAuth } from '@/lib/auth';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Link, useNavigate } from '@tanstack/react-router';
+import { useEffect, useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+const signupSchema = z
+ .object({
+ fullName: z.string().min(2, 'El nombre debe tener al menos 2 caracteres.'),
+ email: z.string().email('Ingresá un email válido.'),
+ phone: z
+ .string()
+ .optional()
+ .refine(
+ (val) => !val || val.replace(/\D/g, '').length >= 7,
+ 'El teléfono debe tener al menos 7 dígitos.'
+ ),
+ password: z.string().min(6, 'La contraseña debe tener al menos 6 caracteres.'),
+ confirmPassword: z.string(),
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: 'Las contraseñas no coinciden.',
+ path: ['confirmPassword'],
+ });
+
+type SignupForm = z.infer;
+
+const STORAGE_KEY = 'signup-form-pending';
+
+function loadFormState(): Partial {
+ try {
+ const saved = sessionStorage.getItem(STORAGE_KEY);
+ if (saved) return JSON.parse(saved);
+ } catch {}
+ return {};
+}
+
+export function SignupPage() {
+ const navigate = useNavigate();
+ const { signUp } = useAuth();
+ const [submitError, setSubmitError] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isValid },
+ watch,
+ } = useForm({
+ resolver: zodResolver(signupSchema),
+ mode: 'onChange',
+ defaultValues: loadFormState(),
+ });
+
+ useEffect(() => {
+ const sub = watch((values) => {
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(values));
+ });
+ return () => sub.unsubscribe();
+ }, [watch]);
+
+ const handleGoogleSignIn = async () => {
+ setSubmitError(null);
+
+ try {
+ await authClient.signIn.social({
+ provider: 'google',
+ callbackURL: `${window.location.origin}/auth-callback`,
+ });
+ } catch {
+ setSubmitError('No pudimos iniciar sesión con Google.');
+ }
+ };
+
+ const onSubmit = async (values: SignupForm) => {
+ setSubmitError(null);
+ setIsSubmitting(true);
+
+ try {
+ await signUp({
+ email: values.email,
+ password: values.password,
+ fullName: values.fullName,
+ phone: values.phone || undefined,
+ });
+
+ sessionStorage.removeItem(STORAGE_KEY);
+ await navigate({ to: '/' });
+ } catch {
+ setSubmitError('No pudimos crear la cuenta. Intenta nuevamente.');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ Playzer
+
+
+
+ Crear cuenta
+
+ Creá tu cuenta para comenzar a usar Playzer.
+
+
+
+
+
+
+
+
+
+ Continuar con Google
+
+
+
+
+
+
+
+ ¿Ya tenés cuenta?{' '}
+
+ Iniciar sesión
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/lib/api-client.ts b/apps/frontend/src/lib/api-client.ts
index 3bf9493..cd51662 100644
--- a/apps/frontend/src/lib/api-client.ts
+++ b/apps/frontend/src/lib/api-client.ts
@@ -1,10 +1,21 @@
import { sentinelClient } from '@better-auth/infra/client';
+import { inferAdditionalFields } from 'better-auth/client/plugins';
import { createAuthClient } from 'better-auth/react';
import * as api from './api';
const apiBaseUrl = api.apiBaseUrl;
-const plugins = [sentinelClient()];
+const plugins = [
+ sentinelClient(),
+ inferAdditionalFields({
+ user: {
+ phone: {
+ type: 'string',
+ required: false,
+ },
+ },
+ }),
+];
export const authClient = createAuthClient({
baseURL: apiBaseUrl,
diff --git a/apps/frontend/src/lib/auth.tsx b/apps/frontend/src/lib/auth.tsx
index c898dac..d8b21eb 100644
--- a/apps/frontend/src/lib/auth.tsx
+++ b/apps/frontend/src/lib/auth.tsx
@@ -13,6 +13,7 @@ type SignUpParams = {
email: string;
password: string;
fullName: string;
+ phone?: string;
};
type UpdateProfileParams = {
@@ -162,11 +163,12 @@ export function AuthProvider({ children }: PropsWithChildren) {
throw error;
}
},
- signUp: async ({ email, password, fullName }) => {
+ signUp: async ({ email, password, fullName, phone }) => {
const { data: signUpData, error } = await authClient.signUp.email({
email,
password,
name: fullName,
+ phone,
});
if (error) {
diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts
index be74199..8dd2857 100644
--- a/apps/frontend/src/routeTree.gen.ts
+++ b/apps/frontend/src/routeTree.gen.ts
@@ -9,6 +9,7 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
+import { Route as SignupRouteImport } from './routes/signup'
import { Route as SelectComplexRouteImport } from './routes/select-complex'
import { Route as ResetPasswordRouteImport } from './routes/reset-password'
import { Route as OnboardRouteImport } from './routes/onboard'
@@ -29,6 +30,11 @@ import { Route as ComplexSlugBookingIndexRouteImport } from './routes/$complexSl
import { Route as ComplexSlugBookingConfirmedBookingCodeRouteImport } from './routes/$complexSlug/booking/confirmed/$bookingCode'
import { Route as AppAuthenticatedComplexSlugEditRouteImport } from './routes/_app/_authenticated/complex/$slug/edit'
+const SignupRoute = SignupRouteImport.update({
+ id: '/signup',
+ path: '/signup',
+ getParentRoute: () => rootRouteImport,
+} as any)
const SelectComplexRoute = SelectComplexRouteImport.update({
id: '/select-complex',
path: '/select-complex',
@@ -133,6 +139,7 @@ export interface FileRoutesByFullPath {
'/onboard': typeof OnboardRouteWithChildren
'/reset-password': typeof ResetPasswordRoute
'/select-complex': typeof SelectComplexRoute
+ '/signup': typeof SignupRoute
'/$complexSlug/booking': typeof ComplexSlugBookingRouteWithChildren
'/profile': typeof AppProfileRoute
'/onboard/complete': typeof OnboardCompleteRoute
@@ -151,6 +158,7 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/reset-password': typeof ResetPasswordRoute
'/select-complex': typeof SelectComplexRoute
+ '/signup': typeof SignupRoute
'/profile': typeof AppProfileRoute
'/onboard/complete': typeof OnboardCompleteRoute
'/onboard/create-complex': typeof OnboardCreateComplexRoute
@@ -170,6 +178,7 @@ export interface FileRoutesById {
'/onboard': typeof OnboardRouteWithChildren
'/reset-password': typeof ResetPasswordRoute
'/select-complex': typeof SelectComplexRoute
+ '/signup': typeof SignupRoute
'/_app/_authenticated': typeof AppAuthenticatedRouteRouteWithChildren
'/$complexSlug/booking': typeof ComplexSlugBookingRouteWithChildren
'/_app/profile': typeof AppProfileRoute
@@ -193,6 +202,7 @@ export interface FileRouteTypes {
| '/onboard'
| '/reset-password'
| '/select-complex'
+ | '/signup'
| '/$complexSlug/booking'
| '/profile'
| '/onboard/complete'
@@ -211,6 +221,7 @@ export interface FileRouteTypes {
| '/login'
| '/reset-password'
| '/select-complex'
+ | '/signup'
| '/profile'
| '/onboard/complete'
| '/onboard/create-complex'
@@ -229,6 +240,7 @@ export interface FileRouteTypes {
| '/onboard'
| '/reset-password'
| '/select-complex'
+ | '/signup'
| '/_app/_authenticated'
| '/$complexSlug/booking'
| '/_app/profile'
@@ -251,11 +263,19 @@ export interface RootRouteChildren {
OnboardRoute: typeof OnboardRouteWithChildren
ResetPasswordRoute: typeof ResetPasswordRoute
SelectComplexRoute: typeof SelectComplexRoute
+ SignupRoute: typeof SignupRoute
ComplexSlugBookingRoute: typeof ComplexSlugBookingRouteWithChildren
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
+ '/signup': {
+ id: '/signup'
+ path: '/signup'
+ fullPath: '/signup'
+ preLoaderRoute: typeof SignupRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/select-complex': {
id: '/select-complex'
path: '/select-complex'
@@ -462,6 +482,7 @@ const rootRouteChildren: RootRouteChildren = {
OnboardRoute: OnboardRouteWithChildren,
ResetPasswordRoute: ResetPasswordRoute,
SelectComplexRoute: SelectComplexRoute,
+ SignupRoute: SignupRoute,
ComplexSlugBookingRoute: ComplexSlugBookingRouteWithChildren,
}
export const routeTree = rootRouteImport
diff --git a/apps/frontend/src/routes/__root.tsx b/apps/frontend/src/routes/__root.tsx
index ffd1d25..e841216 100644
--- a/apps/frontend/src/routes/__root.tsx
+++ b/apps/frontend/src/routes/__root.tsx
@@ -18,7 +18,7 @@ export const Route = createRootRouteWithContext()({
});
const AUTH_PAGE_REGEX =
- /^\/(login|select-complex|reset-password|onboard|invite|auth-callback)(?:\/|$)/;
+ /^\/(login|signup|select-complex|reset-password|onboard|invite|auth-callback)(?:\/|$)/;
function RootRoute() {
const location = useLocation();
diff --git a/apps/frontend/src/routes/signup.tsx b/apps/frontend/src/routes/signup.tsx
new file mode 100644
index 0000000..cbe3b3c
--- /dev/null
+++ b/apps/frontend/src/routes/signup.tsx
@@ -0,0 +1,11 @@
+import { SignupPage } from '@/features/signup/signup-page';
+import { createFileRoute, redirect } from '@tanstack/react-router';
+
+export const Route = createFileRoute('/signup')({
+ beforeLoad: ({ context }) => {
+ if (context.auth.isAuthenticated) {
+ throw redirect({ to: '/' });
+ }
+ },
+ component: SignupPage,
+});
--
2.49.1
From 4544911f0e5dbf4097684d4f42d12d917a9b3be4 Mon Sep 17 00:00:00 2001
From: Jose Selesan
Date: Tue, 2 Jun 2026 10:02:28 -0300
Subject: [PATCH 2/2] refactor(onboarding): restructure onboarding routes and
components
- Removed obsolete onboarding routes: complete, create-complex, index, verify-email, and verify.
- Introduced new setup stepper page for onboarding process.
- Created a multi-step setup component to handle complex creation with validation.
- Added new schemas for complex creation and plan features.
- Implemented detailed steps for complex name, location, plan selection, and court setup.
- Enhanced error handling and user feedback during the onboarding process.
---
apps/backend/prisma/onboarding.prisma | 18 -
.../handlers/create-complex.handler.ts | 5 +
.../complex/services/complex.service.ts | 71 +++
.../handlers/complete-onboarding.handler.ts | 27 --
.../onboarding/handlers/resend-otp.handler.ts | 25 --
.../handlers/start-onboarding.handler.ts | 10 -
.../onboarding/handlers/verify-otp.handler.ts | 10 -
.../modules/onboarding/onboarding.routes.ts | 35 --
.../onboarding/services/onboarding.service.ts | 415 ------------------
.../plan/handlers/list-plans.handler.ts | 21 +-
apps/backend/src/register-routes.ts | 2 -
apps/frontend/src/features/home/home-page.tsx | 12 +-
.../src/features/login/login-page.tsx | 19 +-
.../onboarding-check-email-step.tsx | 7 -
.../components/onboarding-complete-step.tsx | 119 -----
.../components/onboarding-start-step.tsx | 58 ---
.../components/onboarding-verify-otp-step.tsx | 108 -----
.../components/onboarding-verifying-step.tsx | 5 -
.../onboard/components/setup-stepper.tsx | 161 +++++++
.../components/steps/complex-name-step.tsx | 32 ++
.../components/steps/courts-setup-step.tsx | 158 +++++++
.../components/steps/location-step.tsx | 70 +++
.../components/steps/plan-select-step.tsx | 100 +++++
.../onboard/components/steps/review-step.tsx | 123 ++++++
.../features/onboard/create-complex-page.tsx | 222 ----------
.../onboard/onboard-complete-page.tsx | 119 -----
.../features/onboard/onboard-login-page.tsx | 317 -------------
.../features/onboard/onboard-start-page.tsx | 79 ----
.../features/onboard/onboard-verify-page.tsx | 169 -------
.../features/onboard/onboarding-session.ts | 140 ------
.../src/features/onboard/onboarding.types.ts | 10 -
.../features/onboard/setup-stepper-page.tsx | 114 +++++
.../features/onboard/verify-email-page.tsx | 68 ---
.../src/features/signup/signup-page.tsx | 8 +-
apps/frontend/src/lib/api-client.ts | 1 -
apps/frontend/src/lib/api/index.ts | 1 -
.../src/lib/api/resources/onboarding.ts | 37 --
apps/frontend/src/lib/api/resources/plans.ts | 4 +-
apps/frontend/src/routeTree.gen.ts | 120 +----
apps/frontend/src/routes/auth-callback.tsx | 18 +-
apps/frontend/src/routes/invite.tsx | 16 +-
apps/frontend/src/routes/login.tsx | 13 +-
apps/frontend/src/routes/onboard/complete.tsx | 6 -
apps/frontend/src/routes/onboard/index.tsx | 11 -
.../onboard/{create-complex.tsx => setup.tsx} | 8 +-
.../src/routes/onboard/verify-email.tsx | 18 -
apps/frontend/src/routes/onboard/verify.tsx | 6 -
packages/api-contract/src/complex.ts | 114 +++--
packages/api-contract/src/index.ts | 30 +-
packages/api-contract/src/onboarding.ts | 112 -----
packages/api-contract/src/plan.ts | 17 +
51 files changed, 1006 insertions(+), 2383 deletions(-)
delete mode 100644 apps/backend/prisma/onboarding.prisma
delete mode 100644 apps/backend/src/modules/onboarding/handlers/complete-onboarding.handler.ts
delete mode 100644 apps/backend/src/modules/onboarding/handlers/resend-otp.handler.ts
delete mode 100644 apps/backend/src/modules/onboarding/handlers/start-onboarding.handler.ts
delete mode 100644 apps/backend/src/modules/onboarding/handlers/verify-otp.handler.ts
delete mode 100644 apps/backend/src/modules/onboarding/onboarding.routes.ts
delete mode 100644 apps/backend/src/modules/onboarding/services/onboarding.service.ts
delete mode 100644 apps/frontend/src/features/onboard/components/onboarding-check-email-step.tsx
delete mode 100644 apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx
delete mode 100644 apps/frontend/src/features/onboard/components/onboarding-start-step.tsx
delete mode 100644 apps/frontend/src/features/onboard/components/onboarding-verify-otp-step.tsx
delete mode 100644 apps/frontend/src/features/onboard/components/onboarding-verifying-step.tsx
create mode 100644 apps/frontend/src/features/onboard/components/setup-stepper.tsx
create mode 100644 apps/frontend/src/features/onboard/components/steps/complex-name-step.tsx
create mode 100644 apps/frontend/src/features/onboard/components/steps/courts-setup-step.tsx
create mode 100644 apps/frontend/src/features/onboard/components/steps/location-step.tsx
create mode 100644 apps/frontend/src/features/onboard/components/steps/plan-select-step.tsx
create mode 100644 apps/frontend/src/features/onboard/components/steps/review-step.tsx
delete mode 100644 apps/frontend/src/features/onboard/create-complex-page.tsx
delete mode 100644 apps/frontend/src/features/onboard/onboard-complete-page.tsx
delete mode 100644 apps/frontend/src/features/onboard/onboard-login-page.tsx
delete mode 100644 apps/frontend/src/features/onboard/onboard-start-page.tsx
delete mode 100644 apps/frontend/src/features/onboard/onboard-verify-page.tsx
delete mode 100644 apps/frontend/src/features/onboard/onboarding-session.ts
delete mode 100644 apps/frontend/src/features/onboard/onboarding.types.ts
create mode 100644 apps/frontend/src/features/onboard/setup-stepper-page.tsx
delete mode 100644 apps/frontend/src/features/onboard/verify-email-page.tsx
delete mode 100644 apps/frontend/src/lib/api/resources/onboarding.ts
delete mode 100644 apps/frontend/src/routes/onboard/complete.tsx
delete mode 100644 apps/frontend/src/routes/onboard/index.tsx
rename apps/frontend/src/routes/onboard/{create-complex.tsx => setup.tsx} (58%)
delete mode 100644 apps/frontend/src/routes/onboard/verify-email.tsx
delete mode 100644 apps/frontend/src/routes/onboard/verify.tsx
delete mode 100644 packages/api-contract/src/onboarding.ts
diff --git a/apps/backend/prisma/onboarding.prisma b/apps/backend/prisma/onboarding.prisma
deleted file mode 100644
index 13d6d46..0000000
--- a/apps/backend/prisma/onboarding.prisma
+++ /dev/null
@@ -1,18 +0,0 @@
-model OnboardingRequest {
- id String @id @db.Uuid
- fullName String @map("full_name")
- email String
- otpHash String @map("otp_hash")
- otpExpiresAt DateTime @map("otp_expires_at")
- otpAttempts Int @default(0) @map("otp_attempts")
- otpLastSentAt DateTime @map("otp_last_sent_at")
- otpResendCount Int @default(0) @map("otp_resend_count")
- emailVerifiedAt DateTime? @map("email_verified_at")
- completedAt DateTime? @map("completed_at")
- createdAt DateTime @default(now()) @map("created_at")
- updatedAt DateTime @updatedAt @map("updated_at")
-
- @@index([email])
- @@index([otpExpiresAt])
- @@map("onboarding_requests")
-}
diff --git a/apps/backend/src/modules/complex/handlers/create-complex.handler.ts b/apps/backend/src/modules/complex/handlers/create-complex.handler.ts
index 2e738b6..eaeae6f 100644
--- a/apps/backend/src/modules/complex/handlers/create-complex.handler.ts
+++ b/apps/backend/src/modules/complex/handlers/create-complex.handler.ts
@@ -40,6 +40,11 @@ export async function createComplexHandler(c: AppContext) {
city: payload.city,
state: payload.state,
country: payload.country,
+ setupCourts: payload.setupCourts,
+ courtSportId: payload.courtSportId,
+ courtStartTime: payload.courtStartTime,
+ courtEndTime: payload.courtEndTime,
+ courtDaysOfWeek: payload.courtDaysOfWeek,
});
return c.json(complex, 201);
diff --git a/apps/backend/src/modules/complex/services/complex.service.ts b/apps/backend/src/modules/complex/services/complex.service.ts
index 2f37906..167b8ba 100644
--- a/apps/backend/src/modules/complex/services/complex.service.ts
+++ b/apps/backend/src/modules/complex/services/complex.service.ts
@@ -11,8 +11,47 @@ export type CreateComplexInput = {
city?: string;
state?: string;
country?: string;
+ setupCourts?: boolean;
+ courtSportId?: string;
+ courtStartTime?: string;
+ courtEndTime?: string;
+ courtDaysOfWeek?: string[];
};
+type DayOfWeek = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
+
+function toMinutes(value: string): number {
+ const [hours, minutes] = value.split(':').map((part) => Number(part));
+ return hours * 60 + minutes;
+}
+
+function assertAvailabilityRanges(
+ availability: Array<{
+ dayOfWeek: DayOfWeek;
+ startTime: string;
+ endTime: string;
+ }>
+) {
+ for (const range of availability) {
+ const start = toMinutes(range.startTime);
+ const end = toMinutes(range.endTime);
+ if (start >= end) {
+ throw new Error(`El rango ${range.startTime}-${range.endTime} es inválido.`);
+ }
+ }
+}
+
+async function ensureActiveSport(sportId: string) {
+ const sport = await db.sport.findFirst({
+ where: { id: sportId, isActive: true },
+ select: { id: true, name: true },
+ });
+ if (!sport) {
+ throw new Error('El deporte seleccionado no existe o está inactivo.');
+ }
+ return sport;
+}
+
export type UpdateComplexInput = {
complexName?: string;
physicalAddress?: string | null;
@@ -94,6 +133,38 @@ export async function createComplex(input: CreateComplexInput) {
});
}
+ if (input.setupCourts && input.courtSportId) {
+ const sport = await ensureActiveSport(input.courtSportId);
+ const availability = (input.courtDaysOfWeek ?? []).map((day) => ({
+ dayOfWeek: day as DayOfWeek,
+ startTime: input.courtStartTime ?? '08:00',
+ endTime: input.courtEndTime ?? '22:00',
+ }));
+
+ assertAvailabilityRanges(availability);
+
+ const court = await tx.court.create({
+ data: {
+ id: uuidv7(),
+ complexId: complex.id,
+ sportId: input.courtSportId,
+ name: `${sport.name} 1`,
+ slotDurationMinutes: 60,
+ basePrice: 0,
+ },
+ });
+
+ await tx.courtAvailability.createMany({
+ data: availability.map((avail) => ({
+ id: uuidv7(),
+ courtId: court.id,
+ dayOfWeek: avail.dayOfWeek,
+ startTime: avail.startTime,
+ endTime: avail.endTime,
+ })),
+ });
+ }
+
return complex;
});
}
diff --git a/apps/backend/src/modules/onboarding/handlers/complete-onboarding.handler.ts b/apps/backend/src/modules/onboarding/handlers/complete-onboarding.handler.ts
deleted file mode 100644
index 9b42b21..0000000
--- a/apps/backend/src/modules/onboarding/handlers/complete-onboarding.handler.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import {
- OnboardingError,
- completeOnboarding,
-} from '@/modules/onboarding/services/onboarding.service';
-import type { AppContext } from '@/types/hono';
-import type { OnboardingCompleteInput } from '@repo/api-contract';
-
-export async function completeOnboardingHandler(c: AppContext) {
- const payload = c.req.valid('json' as never) as OnboardingCompleteInput;
-
- try {
- const result = await completeOnboarding(payload);
- return c.json({
- message: 'Onboarding completado correctamente.',
- ...result,
- });
- } catch (error) {
- if (error instanceof OnboardingError) {
- return c.json({ message: error.message }, error.status);
- }
-
- if (error instanceof Error) {
- return c.json({ message: error.message }, 400);
- }
- throw error;
- }
-}
diff --git a/apps/backend/src/modules/onboarding/handlers/resend-otp.handler.ts b/apps/backend/src/modules/onboarding/handlers/resend-otp.handler.ts
deleted file mode 100644
index 98a5376..0000000
--- a/apps/backend/src/modules/onboarding/handlers/resend-otp.handler.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import {
- OnboardingError,
- resendOnboardingOtp,
-} from '@/modules/onboarding/services/onboarding.service';
-import type { AppContext } from '@/types/hono';
-import type { OnboardingResendOtpInput } from '@repo/api-contract';
-
-export async function resendOtpHandler(c: AppContext) {
- const payload = c.req.valid('json' as never) as OnboardingResendOtpInput;
-
- try {
- const result = await resendOnboardingOtp(payload);
- return c.json(result);
- } catch (error) {
- if (error instanceof OnboardingError) {
- return c.json({ message: error.message }, error.status);
- }
-
- if (error instanceof Error) {
- return c.json({ message: error.message }, 400);
- }
-
- throw error;
- }
-}
diff --git a/apps/backend/src/modules/onboarding/handlers/start-onboarding.handler.ts b/apps/backend/src/modules/onboarding/handlers/start-onboarding.handler.ts
deleted file mode 100644
index 598152f..0000000
--- a/apps/backend/src/modules/onboarding/handlers/start-onboarding.handler.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { startOnboarding } from '@/modules/onboarding/services/onboarding.service';
-import type { AppContext } from '@/types/hono';
-import type { OnboardingStartInput } from '@repo/api-contract';
-
-export async function startOnboardingHandler(c: AppContext) {
- const payload = c.req.valid('json' as never) as OnboardingStartInput;
-
- const result = await startOnboarding(payload);
- return c.json(result, 202);
-}
diff --git a/apps/backend/src/modules/onboarding/handlers/verify-otp.handler.ts b/apps/backend/src/modules/onboarding/handlers/verify-otp.handler.ts
deleted file mode 100644
index 285d3c3..0000000
--- a/apps/backend/src/modules/onboarding/handlers/verify-otp.handler.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { verifyOnboardingOtp } from '@/modules/onboarding/services/onboarding.service';
-import type { AppContext } from '@/types/hono';
-import type { OnboardingVerifyOtpInput } from '@repo/api-contract';
-
-export async function verifyOtpHandler(c: AppContext) {
- const payload = c.req.valid('json' as never) as OnboardingVerifyOtpInput;
- const result = await verifyOnboardingOtp(payload);
-
- return c.json(result);
-}
diff --git a/apps/backend/src/modules/onboarding/onboarding.routes.ts b/apps/backend/src/modules/onboarding/onboarding.routes.ts
deleted file mode 100644
index 62c1029..0000000
--- a/apps/backend/src/modules/onboarding/onboarding.routes.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { completeOnboardingHandler } from '@/modules/onboarding/handlers/complete-onboarding.handler';
-import { resendOtpHandler } from '@/modules/onboarding/handlers/resend-otp.handler';
-import { startOnboardingHandler } from '@/modules/onboarding/handlers/start-onboarding.handler';
-import { verifyOtpHandler } from '@/modules/onboarding/handlers/verify-otp.handler';
-import type { AppEnv } from '@/types/hono';
-import { zValidator } from '@hono/zod-validator';
-import {
- onboardingCompleteSchema,
- onboardingResendOtpSchema,
- onboardingStartSchema,
- onboardingVerifyOtpSchema,
-} from '@repo/api-contract';
-import { Hono } from 'hono';
-
-export const onboardingRoutes = new Hono();
-
-onboardingRoutes.post('/start', zValidator('json', onboardingStartSchema), startOnboardingHandler);
-
-onboardingRoutes.post(
- '/verify-otp',
- zValidator('json', onboardingVerifyOtpSchema),
- verifyOtpHandler
-);
-
-onboardingRoutes.post(
- '/resend-otp',
- zValidator('json', onboardingResendOtpSchema),
- resendOtpHandler
-);
-
-onboardingRoutes.post(
- '/complete',
- zValidator('json', onboardingCompleteSchema),
- completeOnboardingHandler
-);
diff --git a/apps/backend/src/modules/onboarding/services/onboarding.service.ts b/apps/backend/src/modules/onboarding/services/onboarding.service.ts
deleted file mode 100644
index 9af4550..0000000
--- a/apps/backend/src/modules/onboarding/services/onboarding.service.ts
+++ /dev/null
@@ -1,415 +0,0 @@
-import { createHash, randomInt } from 'node:crypto';
-import { auth } from '@/lib/auth';
-import { sendMail } from '@/lib/mailer';
-import { db } from '@/lib/prisma';
-import type {
- OnboardingCompleteInput,
- OnboardingResendOtpInput,
- OnboardingStartInput,
- OnboardingVerifyOtpInput,
-} from '@repo/api-contract';
-import { v7 as uuidv7 } from 'uuid';
-
-const OTP_LENGTH = 6;
-const OTP_TTL_MINUTES = Number(Bun.env.ONBOARDING_OTP_TTL_MINUTES ?? 10);
-const OTP_MAX_ATTEMPTS = Number(Bun.env.ONBOARDING_OTP_MAX_ATTEMPTS ?? 5);
-const OTP_RESEND_COOLDOWN_SECONDS = Number(Bun.env.ONBOARDING_OTP_RESEND_COOLDOWN_SECONDS ?? 30);
-
-type VerifyOtpResult = {
- message: string;
- verified: boolean;
- requestId: string;
- email: string | null;
- expiresAt: string | null;
- remainingAttempts: number;
- cooldownSeconds: number;
-};
-
-type ResendOtpResult = {
- message: string;
- requestId: string;
- email: string;
- expiresAt: string;
- cooldownSeconds: number;
- remainingAttempts: number;
-};
-
-type StartOnboardingResult = {
- message: string;
- requestId: string;
- email: string;
- expiresAt: string;
- cooldownSeconds: number;
-};
-
-export class OnboardingError extends Error {
- status: 400 | 404 | 409 | 429;
-
- constructor(message: string, status: 400 | 404 | 409 | 429 = 400) {
- super(message);
- this.name = 'OnboardingError';
- this.status = status;
- }
-}
-
-function nowPlusMinutes(minutes: number): Date {
- const date = new Date();
- date.setMinutes(date.getMinutes() + minutes);
- return date;
-}
-
-function normalizeEmail(email: string): string {
- return email.trim().toLowerCase();
-}
-
-function createOtpCode(): string {
- return randomInt(0, 10 ** OTP_LENGTH)
- .toString()
- .padStart(OTP_LENGTH, '0');
-}
-
-function hashValue(value: string): string {
- return createHash('sha256').update(value).digest('hex');
-}
-
-function getRemainingAttempts(otpAttempts: number): number {
- return Math.max(0, OTP_MAX_ATTEMPTS - otpAttempts);
-}
-
-function getCooldownSeconds(otpLastSentAt: Date): number {
- const elapsedMs = Date.now() - otpLastSentAt.getTime();
- const remainingMs = OTP_RESEND_COOLDOWN_SECONDS * 1000 - elapsedMs;
- return Math.max(0, Math.ceil(remainingMs / 1000));
-}
-
-function slugify(value: string): string {
- return value
- .normalize('NFD')
- .replace(/[\u0300-\u036f]/g, '')
- .toLowerCase()
- .trim()
- .replace(/[^a-z0-9\s-]/g, '')
- .replace(/\s+/g, '-')
- .replace(/-+/g, '-');
-}
-
-async function buildUniqueComplexSlug(complexName: string): Promise {
- const base = slugify(complexName);
- const fallback = base.length > 0 ? base : `complex-${uuidv7().slice(0, 8)}`;
-
- let candidate = fallback;
- let index = 1;
-
- while (true) {
- const existing = await db.complex.findFirst({
- where: { complexSlug: candidate },
- select: { id: true },
- });
-
- if (!existing) {
- return candidate;
- }
-
- index += 1;
- candidate = `${fallback}-${index}`;
- }
-}
-
-async function sendOtpEmail(email: string, otpCode: string) {
- await sendMail({
- to: email,
- subject: 'Codigo OTP para validar tu email',
- text: `Tu codigo OTP es ${otpCode}. Vence en ${OTP_TTL_MINUTES} minutos.`,
- html: `Tu codigo OTP es ${otpCode} .
Vence en ${OTP_TTL_MINUTES} minutos.
`,
- });
-}
-
-export async function startOnboarding(input: OnboardingStartInput): Promise {
- const email = normalizeEmail(input.email);
- const otpCode = createOtpCode();
- const expiresAt = nowPlusMinutes(OTP_TTL_MINUTES);
- const now = new Date();
-
- const request = await db.onboardingRequest.create({
- data: {
- id: uuidv7(),
- fullName: input.fullName.trim(),
- email,
- otpHash: hashValue(otpCode),
- otpExpiresAt: expiresAt,
- otpAttempts: 0,
- otpLastSentAt: now,
- otpResendCount: 0,
- },
- select: {
- id: true,
- email: true,
- otpExpiresAt: true,
- },
- });
-
- await sendOtpEmail(email, otpCode);
-
- return {
- message: 'Te enviamos un codigo OTP para validar tu direccion.',
- requestId: request.id,
- email: request.email,
- expiresAt: request.otpExpiresAt.toISOString(),
- cooldownSeconds: OTP_RESEND_COOLDOWN_SECONDS,
- };
-}
-
-export async function verifyOnboardingOtp(
- input: OnboardingVerifyOtpInput
-): Promise {
- const request = await db.onboardingRequest.findUnique({
- where: { id: input.requestId },
- });
-
- if (!request) {
- return {
- message: 'Solicitud de onboarding invalida o expirada.',
- verified: false,
- requestId: input.requestId,
- email: null,
- expiresAt: null,
- remainingAttempts: 0,
- cooldownSeconds: 0,
- };
- }
-
- if (request.completedAt) {
- return {
- message: 'Este onboarding ya fue completado.',
- verified: true,
- requestId: request.id,
- email: request.email,
- expiresAt: request.otpExpiresAt.toISOString(),
- remainingAttempts: getRemainingAttempts(request.otpAttempts),
- cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
- };
- }
-
- if (request.otpExpiresAt < new Date()) {
- return {
- message: 'El codigo OTP vencio. Solicita un nuevo codigo.',
- verified: false,
- requestId: request.id,
- email: request.email,
- expiresAt: request.otpExpiresAt.toISOString(),
- remainingAttempts: getRemainingAttempts(request.otpAttempts),
- cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
- };
- }
-
- if (request.otpAttempts >= OTP_MAX_ATTEMPTS) {
- return {
- message: 'Se agotaron los intentos de OTP. Solicita un nuevo codigo.',
- verified: false,
- requestId: request.id,
- email: request.email,
- expiresAt: request.otpExpiresAt.toISOString(),
- remainingAttempts: 0,
- cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
- };
- }
-
- const otpMatches = hashValue(input.otp) === request.otpHash;
-
- if (!otpMatches) {
- const updated = await db.onboardingRequest.update({
- where: { id: request.id },
- data: {
- otpAttempts: {
- increment: 1,
- },
- },
- select: {
- id: true,
- email: true,
- otpAttempts: true,
- otpExpiresAt: true,
- otpLastSentAt: true,
- },
- });
-
- const remainingAttempts = getRemainingAttempts(updated.otpAttempts);
-
- return {
- message:
- remainingAttempts > 0
- ? 'Codigo OTP invalido.'
- : 'Se agotaron los intentos de OTP. Solicita un nuevo codigo.',
- verified: false,
- requestId: updated.id,
- email: updated.email,
- expiresAt: updated.otpExpiresAt.toISOString(),
- remainingAttempts,
- cooldownSeconds: getCooldownSeconds(updated.otpLastSentAt),
- };
- }
-
- if (!request.emailVerifiedAt) {
- await db.onboardingRequest.update({
- where: { id: request.id },
- data: { emailVerifiedAt: new Date() },
- });
- }
-
- return {
- message: 'Email verificado correctamente.',
- verified: true,
- requestId: request.id,
- email: request.email,
- expiresAt: request.otpExpiresAt.toISOString(),
- remainingAttempts: getRemainingAttempts(request.otpAttempts),
- cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
- };
-}
-
-export async function resendOnboardingOtp(
- input: OnboardingResendOtpInput
-): Promise {
- const request = await db.onboardingRequest.findUnique({
- where: { id: input.requestId },
- });
-
- if (!request) {
- throw new OnboardingError('Solicitud de onboarding invalida o expirada.', 404);
- }
-
- if (request.completedAt) {
- throw new OnboardingError('Este onboarding ya fue completado.', 400);
- }
-
- if (request.emailVerifiedAt) {
- throw new OnboardingError('El email ya fue verificado.', 400);
- }
-
- const cooldownSeconds = getCooldownSeconds(request.otpLastSentAt);
- if (cooldownSeconds > 0) {
- throw new OnboardingError(`Debes esperar ${cooldownSeconds}s antes de reenviar el OTP.`, 429);
- }
-
- const otpCode = createOtpCode();
- const expiresAt = nowPlusMinutes(OTP_TTL_MINUTES);
- const now = new Date();
-
- const updated = await db.onboardingRequest.update({
- where: { id: request.id },
- data: {
- otpHash: hashValue(otpCode),
- otpExpiresAt: expiresAt,
- otpAttempts: 0,
- otpLastSentAt: now,
- otpResendCount: {
- increment: 1,
- },
- },
- select: {
- id: true,
- email: true,
- otpExpiresAt: true,
- otpAttempts: true,
- },
- });
-
- await sendOtpEmail(updated.email, otpCode);
-
- return {
- message: 'Te enviamos un nuevo codigo OTP.',
- requestId: updated.id,
- email: updated.email,
- expiresAt: updated.otpExpiresAt.toISOString(),
- cooldownSeconds: OTP_RESEND_COOLDOWN_SECONDS,
- remainingAttempts: getRemainingAttempts(updated.otpAttempts),
- };
-}
-
-export async function completeOnboarding(input: OnboardingCompleteInput) {
- const onboardingRequest = await db.onboardingRequest.findUnique({
- where: { id: input.onboardingRequestId },
- });
-
- if (!onboardingRequest) {
- throw new OnboardingError('Solicitud de onboarding inválida o expirada.', 400);
- }
-
- if (onboardingRequest.otpExpiresAt < new Date()) {
- throw new OnboardingError('La sesión de onboarding expiró. Solicita un nuevo OTP.', 400);
- }
-
- if (!onboardingRequest.emailVerifiedAt) {
- throw new OnboardingError('Debes verificar el email antes de continuar.', 400);
- }
-
- if (onboardingRequest.completedAt) {
- throw new OnboardingError('Este onboarding ya fue completado.', 400);
- }
-
- const plan = await db.plan.findUnique({
- where: { code: input.planCode },
- select: { code: true },
- });
-
- if (!plan) {
- throw new OnboardingError('El plan seleccionado no existe.', 400);
- }
-
- const { user } = await auth.api.signUpEmail({
- asResponse: false,
- body: {
- email: onboardingRequest.email,
- password: input.password,
- name: onboardingRequest.fullName,
- },
- });
-
- const complexSlug = await buildUniqueComplexSlug(input.complexName);
-
- const result = await db.$transaction(async (tx) => {
- await tx.user.update({
- where: { id: user.id },
- data: { emailVerified: true },
- });
-
- const complex = await tx.complex.create({
- data: {
- id: uuidv7(),
- complexName: input.complexName.trim(),
- physicalAddress: input.physicalAddress.trim(),
- city: input.city?.trim() || null,
- state: input.state?.trim() || null,
- country: input.country?.trim() || null,
- complexSlug,
- adminEmail: onboardingRequest.email,
- planCode: input.planCode,
- },
- });
-
- await tx.complexUser.create({
- data: {
- complexId: complex.id,
- userId: user.id,
- role: 'ADMIN',
- },
- });
-
- await tx.onboardingRequest.update({
- where: { id: onboardingRequest.id },
- data: {
- completedAt: new Date(),
- },
- });
-
- return {
- userId: user.id,
- complexId: complex.id,
- complexSlug: complex.complexSlug,
- };
- });
-
- return {
- ...result,
- };
-}
diff --git a/apps/backend/src/modules/plan/handlers/list-plans.handler.ts b/apps/backend/src/modules/plan/handlers/list-plans.handler.ts
index ec2f6e9..de5d328 100644
--- a/apps/backend/src/modules/plan/handlers/list-plans.handler.ts
+++ b/apps/backend/src/modules/plan/handlers/list-plans.handler.ts
@@ -1,23 +1,24 @@
import { db } from '@/lib/prisma';
+import { parsePlanRules } from '@/modules/plan/services/plan-rules.service';
import type { AppContext } from '@/types/hono';
export async function listPlansHandler(c: AppContext) {
const plans = await db.plan.findMany({
- select: {
- code: true,
- name: true,
- price: true,
- },
orderBy: {
price: 'asc',
},
});
return c.json(
- plans.map((plan) => ({
- code: plan.code,
- name: plan.name,
- price: Number(plan.price),
- }))
+ plans.map((plan) => {
+ const rules = parsePlanRules(plan.rules);
+ return {
+ code: plan.code,
+ name: plan.name,
+ price: Number(plan.price),
+ features: rules.features,
+ limits: rules.limits,
+ };
+ })
);
}
diff --git a/apps/backend/src/register-routes.ts b/apps/backend/src/register-routes.ts
index c9a34fb..1d7c48d 100644
--- a/apps/backend/src/register-routes.ts
+++ b/apps/backend/src/register-routes.ts
@@ -3,7 +3,6 @@ import { adminBookingRoutes } from '@/modules/admin-booking/admin-booking.routes
import { authRoutes } from '@/modules/auth/auth.routes';
import { complexRoutes } from '@/modules/complex/complex.routes';
import { courtRoutes } from '@/modules/court/court.routes';
-import { onboardingRoutes } from '@/modules/onboarding/onboarding.routes';
import { passwordResetRoutes } from '@/modules/password-reset/password-reset.routes';
import { planRoutes } from '@/modules/plan/plan.routes';
import { publicBookingRoutes } from '@/modules/public-booking/public-booking.routes';
@@ -20,7 +19,6 @@ export function registerRoutes(app: Hono) {
.route('', authRoutes)
.route('/api/user', userRoutes)
.route('/api/plans', planRoutes)
- .route('/api/onboarding', onboardingRoutes)
.route('/api/password-reset', passwordResetRoutes)
.route('/api/complexes', complexRoutes)
.route('/api/sports', sportRoutes)
diff --git a/apps/frontend/src/features/home/home-page.tsx b/apps/frontend/src/features/home/home-page.tsx
index 29b7bac..6527810 100644
--- a/apps/frontend/src/features/home/home-page.tsx
+++ b/apps/frontend/src/features/home/home-page.tsx
@@ -60,13 +60,23 @@ export function HomePage() {
}
}
- if (!currentComplexQuery.isLoading && !currentComplexQuery.data && myComplexesQuery.data) {
+ if (
+ !currentComplexQuery.isLoading &&
+ !currentComplexQuery.data &&
+ myComplexesQuery.data &&
+ !myComplexesQuery.isLoading
+ ) {
+ if (myComplexesQuery.data.length === 0) {
+ navigate({ to: '/onboard/setup' });
+ return;
+ }
autoSelect();
}
}, [
currentComplexQuery.isLoading,
currentComplexQuery.data,
myComplexesQuery.data,
+ myComplexesQuery.isLoading,
navigate,
queryClient,
]);
diff --git a/apps/frontend/src/features/login/login-page.tsx b/apps/frontend/src/features/login/login-page.tsx
index c776c51..cb19214 100644
--- a/apps/frontend/src/features/login/login-page.tsx
+++ b/apps/frontend/src/features/login/login-page.tsx
@@ -6,7 +6,7 @@ import { apiClient, authClient } from '@/lib/api-client';
import { useAuth } from '@/lib/auth';
import { acceptStoredPendingInvite } from '@/lib/invitations';
import { zodResolver } from '@hookform/resolvers/zod';
-import { Link, useNavigate } from '@tanstack/react-router';
+import { Link } from '@tanstack/react-router';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@@ -18,12 +18,7 @@ const loginSchema = z.object({
type LoginForm = z.infer;
-type LoginPageProps = {
- redirectTo?: string;
-};
-
-export function LoginPage({ redirectTo = '/' }: LoginPageProps) {
- const navigate = useNavigate();
+export function LoginPage() {
const { signInWithPassword } = useAuth();
const [submitError, setSubmitError] = useState(null);
@@ -42,16 +37,6 @@ export function LoginPage({ redirectTo = '/' }: LoginPageProps) {
async function handlePostLogin() {
await acceptStoredPendingInvite(apiClient.complexes.acceptPendingInvitations);
- const complexes = await apiClient.complexes.listMine();
-
- if (complexes.length === 0) {
- await navigate({ to: redirectTo });
- } else if (complexes.length === 1) {
- await apiClient.complexes.select({ complexId: complexes[0].id });
- navigate({ to: redirectTo });
- } else {
- navigate({ to: '/select-complex' });
- }
}
const onSubmit = async (values: LoginForm) => {
diff --git a/apps/frontend/src/features/onboard/components/onboarding-check-email-step.tsx b/apps/frontend/src/features/onboard/components/onboarding-check-email-step.tsx
deleted file mode 100644
index 4ada26b..0000000
--- a/apps/frontend/src/features/onboard/components/onboarding-check-email-step.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export function OnboardingCheckEmailStep() {
- return (
-
- Revisa tu correo y abre el link de verificacion para continuar.
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx b/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx
deleted file mode 100644
index 99a2f6d..0000000
--- a/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { Field, FieldError, FieldLabel } from '@/components/ui/field';
-import { Input } from '@/components/ui/input';
-import type { CompleteValues } from '@/features/onboard/onboarding.types';
-import type { PlanSummary } from '@repo/api-contract';
-import { Controller, type UseFormReturn } from 'react-hook-form';
-
-type OnboardingCompleteStepProps = {
- form: UseFormReturn;
- plans: PlanSummary[];
- verifiedEmail: string | null;
- errorMessage: string | null;
- infoMessage: string | null;
- planPlaceholder: string;
- onSubmit: (values: CompleteValues) => Promise;
-};
-
-export function OnboardingCompleteStep({
- form,
- plans,
- verifiedEmail,
- errorMessage,
- infoMessage,
- planPlaceholder,
- onSubmit,
-}: OnboardingCompleteStepProps) {
- return (
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/components/onboarding-start-step.tsx b/apps/frontend/src/features/onboard/components/onboarding-start-step.tsx
deleted file mode 100644
index f8f980c..0000000
--- a/apps/frontend/src/features/onboard/components/onboarding-start-step.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { Field, FieldError, FieldLabel } from '@/components/ui/field';
-import { Input } from '@/components/ui/input';
-import type { StartValues } from '@/features/onboard/onboarding.types';
-import type { UseFormReturn } from 'react-hook-form';
-
-type OnboardingStartStepProps = {
- form: UseFormReturn;
- errorMessage: string | null;
- infoMessage: string | null;
- onSubmit: (values: StartValues) => Promise;
-};
-
-export function OnboardingStartStep({
- form,
- errorMessage,
- infoMessage,
- onSubmit,
-}: OnboardingStartStepProps) {
- return (
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/components/onboarding-verify-otp-step.tsx b/apps/frontend/src/features/onboard/components/onboarding-verify-otp-step.tsx
deleted file mode 100644
index 05a3a2f..0000000
--- a/apps/frontend/src/features/onboard/components/onboarding-verify-otp-step.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { Field, FieldError, FieldLabel } from '@/components/ui/field';
-import {
- InputOTP,
- InputOTPGroup,
- InputOTPSeparator,
- InputOTPSlot,
-} from '@/components/ui/input-otp';
-import type { VerifyOtpValues } from '@/features/onboard/onboarding.types';
-import { RefreshCw } from 'lucide-react';
-import type { UseFormReturn } from 'react-hook-form';
-
-type OnboardingVerifyOtpStepProps = {
- form: UseFormReturn;
- email: string | null;
- errorMessage: string | null;
- infoMessage: string | null;
- remainingAttempts: number;
- resendCooldownSeconds: number;
- isResending: boolean;
- onSubmit: (values: VerifyOtpValues) => Promise;
- onResend: () => Promise;
-};
-
-export function OnboardingVerifyOtpStep({
- form,
- email,
- errorMessage,
- infoMessage,
- remainingAttempts,
- resendCooldownSeconds,
- isResending,
- onSubmit,
- onResend,
-}: OnboardingVerifyOtpStepProps) {
- const resendDisabled = resendCooldownSeconds > 0 || isResending;
-
- return (
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/components/onboarding-verifying-step.tsx b/apps/frontend/src/features/onboard/components/onboarding-verifying-step.tsx
deleted file mode 100644
index c792468..0000000
--- a/apps/frontend/src/features/onboard/components/onboarding-verifying-step.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-export function OnboardingVerifyingStep() {
- return (
- Verificando tu email, espera un momento...
- );
-}
diff --git a/apps/frontend/src/features/onboard/components/setup-stepper.tsx b/apps/frontend/src/features/onboard/components/setup-stepper.tsx
new file mode 100644
index 0000000..31d87d0
--- /dev/null
+++ b/apps/frontend/src/features/onboard/components/setup-stepper.tsx
@@ -0,0 +1,161 @@
+import { Button } from '@/components/ui/button';
+import {
+ Stepper,
+ StepperContent,
+ StepperDescription,
+ StepperIndicator,
+ StepperItem,
+ StepperList,
+ StepperNext,
+ StepperPrev,
+ StepperSeparator,
+ StepperTitle,
+ StepperTrigger,
+} from '@/components/ui/stepper';
+import { ComplexNameStep } from '@/features/onboard/components/steps/complex-name-step';
+import { CourtsSetupStep } from '@/features/onboard/components/steps/courts-setup-step';
+import { LocationStep } from '@/features/onboard/components/steps/location-step';
+import { PlanSelectStep } from '@/features/onboard/components/steps/plan-select-step';
+import { ReviewStep } from '@/features/onboard/components/steps/review-step';
+import type { PlanWithFeatures, Sport } from '@repo/api-contract';
+import { Loader2 } from 'lucide-react';
+import { useCallback, useState } from 'react';
+import type { UseFormReturn } from 'react-hook-form';
+
+type SetupStepperProps = {
+ form: UseFormReturn;
+ plans: PlanWithFeatures[];
+ sports: Sport[];
+ isSubmitting: boolean;
+ onSubmit: () => Promise;
+};
+
+const STEPS = [
+ { value: 'step-1', title: 'Nombre', description: 'Nombre del complejo' },
+ { value: 'step-2', title: 'Ubicación', description: 'Dirección y ciudad' },
+ { value: 'step-3', title: 'Plan', description: 'Elegí tu plan' },
+ { value: 'step-4', title: 'Canchas', description: 'Crear cancha inicial' },
+ { value: 'step-5', title: 'Revisar', description: 'Confirmar datos' },
+];
+
+const VALIDATION_MAP: Record = {
+ 'step-2': ['complexName'],
+ 'step-3': ['physicalAddress'],
+ 'step-4': ['planCode'],
+ 'step-5': ['planCode'],
+};
+
+export function SetupStepper({ form, plans, sports, isSubmitting, onSubmit }: SetupStepperProps) {
+ const [currentStep, setCurrentStep] = useState('step-1');
+ const setupCourts = form.watch('setupCourts') as boolean | undefined;
+
+ const handleValidate = useCallback(
+ async (nextValue: string, direction: string) => {
+ if (direction === 'prev') return true;
+
+ let fields = VALIDATION_MAP[nextValue];
+
+ if (nextValue === 'step-5' && setupCourts) {
+ fields = [
+ ...(fields ?? []),
+ 'courtSportId',
+ 'courtStartTime',
+ 'courtEndTime',
+ 'courtDaysOfWeek',
+ ];
+ }
+
+ if (fields && fields.length > 0) {
+ return form.trigger(fields as never[]);
+ }
+
+ return true;
+ },
+ [form, setupCourts]
+ );
+
+ const selectedPlan = plans.find((p) => p.code === form.watch('planCode'));
+ const courtSportId = form.watch('courtSportId') as string | undefined;
+ const selectedSport = sports.find((s) => s.id === courtSportId);
+
+ return (
+
+
+ {STEPS.map((step) => (
+
+
+
+
+ {step.title}
+ {step.description}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Anterior
+
+
+
+ {currentStep === 'step-5' ? (
+
+ {isSubmitting ? (
+ <>
+
+ Creando complejo...
+ >
+ ) : (
+ 'Confirmar y crear complejo'
+ )}
+
+ ) : (
+
+ Siguiente
+
+ )}
+
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/components/steps/complex-name-step.tsx b/apps/frontend/src/features/onboard/components/steps/complex-name-step.tsx
new file mode 100644
index 0000000..d3c5057
--- /dev/null
+++ b/apps/frontend/src/features/onboard/components/steps/complex-name-step.tsx
@@ -0,0 +1,32 @@
+import { Field, FieldError, FieldLabel } from '@/components/ui/field';
+import { Input } from '@/components/ui/input';
+import type { UseFormReturn } from 'react-hook-form';
+
+type ComplexNameStepProps = {
+ form: UseFormReturn;
+};
+
+export function ComplexNameStep({ form }: ComplexNameStepProps) {
+ const errors = form.formState.errors;
+
+ return (
+
+
+
Nombre del complejo
+
¿Cómo se va a llamar tu complejo?
+
+
+
+ Nombre del complejo
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/components/steps/courts-setup-step.tsx b/apps/frontend/src/features/onboard/components/steps/courts-setup-step.tsx
new file mode 100644
index 0000000..4eae6f9
--- /dev/null
+++ b/apps/frontend/src/features/onboard/components/steps/courts-setup-step.tsx
@@ -0,0 +1,158 @@
+import { Field, FieldLabel } from '@/components/ui/field';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { cn } from '@/lib/utils';
+import type { Sport } from '@repo/api-contract';
+import { Check } from 'lucide-react';
+import type { UseFormReturn } from 'react-hook-form';
+
+type CourtsSetupStepProps = {
+ form: UseFormReturn;
+ sports: Sport[];
+};
+
+const DAYS_OF_WEEK = [
+ { value: 'MONDAY', label: 'Lun' },
+ { value: 'TUESDAY', label: 'Mar' },
+ { value: 'WEDNESDAY', label: 'Mié' },
+ { value: 'THURSDAY', label: 'Jue' },
+ { value: 'FRIDAY', label: 'Vie' },
+ { value: 'SATURDAY', label: 'Sáb' },
+ { value: 'SUNDAY', label: 'Dom' },
+] as const;
+
+export function CourtsSetupStep({ form, sports }: CourtsSetupStepProps) {
+ const setupCourts = form.watch('setupCourts') as boolean | undefined;
+ const courtDaysOfWeek = form.watch('courtDaysOfWeek') as string[] | undefined;
+ const errors = form.formState.errors;
+
+ const toggleDay = (day: string) => {
+ const current = courtDaysOfWeek ?? [];
+ const next = current.includes(day) ? current.filter((d) => d !== day) : [...current, day];
+ form.setValue('courtDaysOfWeek', next, { shouldValidate: true });
+ };
+
+ return (
+
+
+
Crear canchas
+
+ Configurá una cancha para empezar a recibir reservas.
+
+
+
+
+ {
+ form.setValue('setupCourts', e.target.checked, { shouldValidate: true });
+ if (!e.target.checked) {
+ form.setValue('courtSportId', undefined);
+ form.setValue('courtStartTime', undefined);
+ form.setValue('courtEndTime', undefined);
+ form.setValue('courtDaysOfWeek', undefined);
+ }
+ }}
+ />
+
+
Sí, crear una cancha ahora
+
+ Vas a poder agregar más canchas después desde el panel de administración.
+
+
+
+
+ {setupCourts && (
+
+
+ Deporte
+
+ Seleccioná un deporte
+ {sports.map((sport) => (
+
+ {sport.name}
+
+ ))}
+
+ {errors.courtSportId && (
+
+ {String(errors.courtSportId.message ?? '')}
+
+ )}
+
+
+
+
+
+
Días de la semana
+
+ {DAYS_OF_WEEK.map((day) => {
+ const isSelected = courtDaysOfWeek?.includes(day.value) ?? false;
+ return (
+ toggleDay(day.value)}
+ className={cn(
+ 'flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium transition-all',
+ isSelected
+ ? 'border-primary bg-primary/10 text-primary'
+ : 'border-border text-muted-foreground hover:border-muted-foreground/30'
+ )}
+ >
+ {isSelected && }
+ {day.label}
+
+ );
+ })}
+
+ {errors.courtDaysOfWeek && (
+
+ {String(errors.courtDaysOfWeek.message ?? '')}
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/components/steps/location-step.tsx b/apps/frontend/src/features/onboard/components/steps/location-step.tsx
new file mode 100644
index 0000000..90cee97
--- /dev/null
+++ b/apps/frontend/src/features/onboard/components/steps/location-step.tsx
@@ -0,0 +1,70 @@
+import { Field, FieldError, FieldLabel } from '@/components/ui/field';
+import { Input } from '@/components/ui/input';
+import type { UseFormReturn } from 'react-hook-form';
+
+type LocationStepProps = {
+ form: UseFormReturn;
+};
+
+export function LocationStep({ form }: LocationStepProps) {
+ const errors = form.formState.errors;
+
+ return (
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/components/steps/plan-select-step.tsx b/apps/frontend/src/features/onboard/components/steps/plan-select-step.tsx
new file mode 100644
index 0000000..433d5fe
--- /dev/null
+++ b/apps/frontend/src/features/onboard/components/steps/plan-select-step.tsx
@@ -0,0 +1,100 @@
+import { cn } from '@/lib/utils';
+import type { PlanWithFeatures } from '@repo/api-contract';
+import { Check, X } from 'lucide-react';
+import type { UseFormReturn } from 'react-hook-form';
+
+type PlanSelectStepProps = {
+ form: UseFormReturn;
+ plans: PlanWithFeatures[];
+};
+
+const FEATURE_LABELS: Record = {
+ publicBookingPage: 'Página de booking pública',
+ onlinePayments: 'Pagos online',
+ advancedReports: 'Reportes avanzados',
+ whatsappReminders: 'Recordatorios WhatsApp',
+};
+
+export function PlanSelectStep({ form, plans }: PlanSelectStepProps) {
+ const selectedCode = form.watch('planCode') as string | undefined;
+ const errors = form.formState.errors;
+
+ const featuresList = [
+ 'publicBookingPage',
+ 'onlinePayments',
+ 'advancedReports',
+ 'whatsappReminders',
+ ] as const;
+
+ return (
+
+
+
Elegí tu plan
+
+ Seleccioná el plan que mejor se adapte a tu complejo.
+
+
+
+
+ {plans.map((plan) => {
+ const isSelected = selectedCode === plan.code;
+ return (
+
form.setValue('planCode', plan.code, { shouldValidate: true })}
+ className={cn(
+ 'relative flex flex-col rounded-xl border-2 p-5 text-left transition-all',
+ 'hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
+ isSelected ? 'border-primary bg-primary/5 shadow-sm' : 'border-border bg-card'
+ )}
+ >
+ {isSelected && (
+
+
+
+ )}
+
+
+
{plan.name}
+
+ ${plan.price}
+ /mes
+
+
+
+
+
Incluye:
+
Hasta {plan.limits.maxCourts} canchas
+
Hasta {plan.limits.maxBookingsPerDay} reservas/día
+ {plan.limits.maxActiveUsers &&
Hasta {plan.limits.maxActiveUsers} usuarios
}
+
+
+
+ {featuresList.map((feature) => {
+ const enabled = plan.features[feature as keyof typeof plan.features];
+ return (
+
+ {enabled ? (
+
+ ) : (
+
+ )}
+
+ {FEATURE_LABELS[feature]}
+
+
+ );
+ })}
+
+
+ );
+ })}
+
+
+ {errors.planCode && (
+
{String(errors.planCode.message ?? '')}
+ )}
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/components/steps/review-step.tsx b/apps/frontend/src/features/onboard/components/steps/review-step.tsx
new file mode 100644
index 0000000..fb5f636
--- /dev/null
+++ b/apps/frontend/src/features/onboard/components/steps/review-step.tsx
@@ -0,0 +1,123 @@
+import type { PlanWithFeatures } from '@repo/api-contract';
+import { Check, X } from 'lucide-react';
+
+type ReviewStepProps = {
+ data: {
+ complexName: string;
+ physicalAddress: string;
+ city?: string;
+ state?: string;
+ country?: string;
+ planCode: string;
+ setupCourts: boolean;
+ courtSportName?: string;
+ courtStartTime?: string;
+ courtEndTime?: string;
+ courtDaysOfWeek?: string[];
+ };
+ selectedPlan: PlanWithFeatures | undefined;
+};
+
+const DAY_LABELS: Record = {
+ MONDAY: 'Lunes',
+ TUESDAY: 'Martes',
+ WEDNESDAY: 'Miércoles',
+ THURSDAY: 'Jueves',
+ FRIDAY: 'Viernes',
+ SATURDAY: 'Sábado',
+ SUNDAY: 'Domingo',
+};
+
+export function ReviewStep({ data, selectedPlan }: ReviewStepProps) {
+ return (
+
+
+
Revisá tus datos
+
+ Antes de confirmar, revisá que todos los datos sean correctos.
+
+
+
+
+
+
+
+ {data.city &&
}
+ {data.state &&
}
+ {data.country &&
}
+
+
+
+ {selectedPlan ? (
+ <>
+
+
+ {[
+ ['publicBookingPage', 'Página de booking pública'],
+ ['onlinePayments', 'Pagos online'],
+ ['advancedReports', 'Reportes avanzados'],
+ ['whatsappReminders', 'Recordatorios WhatsApp'],
+ ].map(([feature, label]) => {
+ const enabled =
+ selectedPlan.features[feature as keyof typeof selectedPlan.features];
+ return (
+
+ {enabled ? (
+
+ ) : (
+
+ )}
+ {label}
+
+ );
+ })}
+
+ >
+ ) : (
+ No se seleccionó un plan.
+ )}
+
+
+
+ {data.setupCourts ? (
+ <>
+
+
+ DAY_LABELS[d] ?? d).join(', ')}
+ />
+ >
+ ) : (
+
+ No se crearán canchas automáticamente. Podés agregarlas después.
+
+ )}
+
+
+
+ );
+}
+
+function Section({ title, children }: { title: string; children: React.ReactNode }) {
+ return (
+
+
+ {title}
+
+
{children}
+
+ );
+}
+
+function Row({ label, value }: { label: string; value: string }) {
+ return (
+
+ {label}
+ {value}
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/create-complex-page.tsx b/apps/frontend/src/features/onboard/create-complex-page.tsx
deleted file mode 100644
index 15dd229..0000000
--- a/apps/frontend/src/features/onboard/create-complex-page.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { Field, FieldError, FieldLabel } from '@/components/ui/field';
-import { Input } from '@/components/ui/input';
-import { apiClient } from '@/lib/api-client';
-import { useAuth } from '@/lib/auth';
-import { setCurrentComplexSlug } from '@/lib/current-complex';
-import { zodResolver } from '@hookform/resolvers/zod';
-import type { PlanSummary } from '@repo/api-contract';
-import { useNavigate } from '@tanstack/react-router';
-import { useEffect, useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { z } from 'zod';
-
-const createComplexSchema = z.object({
- complexName: z
- .string()
- .trim()
- .min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
- .max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
- physicalAddress: z
- .string()
- .trim()
- .min(5, 'La direccion debe tener al menos 5 caracteres.')
- .max(200, 'La direccion no puede superar los 200 caracteres.'),
- city: z.string().trim().max(100, 'La ciudad no puede superar los 100 caracteres.').optional(),
- state: z
- .string()
- .trim()
- .max(100, 'La provincia/estado no puede superar los 100 caracteres.')
- .optional(),
- country: z.string().trim().max(100, 'El país no puede superar los 100 caracteres.').optional(),
- planCode: z
- .string()
- .trim()
- .min(1, 'El planCode no puede estar vacío.')
- .max(10, 'El planCode no puede superar los 10 caracteres.')
- .optional(),
-});
-
-type CreateComplexForm = z.infer;
-
-export function CreateComplexPage() {
- const navigate = useNavigate();
- const { user } = useAuth();
- const [plans, setPlans] = useState([]);
- const [errorMessage, setErrorMessage] = useState(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- const form = useForm({
- resolver: zodResolver(createComplexSchema),
- mode: 'onChange',
- defaultValues: {
- complexName: '',
- physicalAddress: '',
- city: '',
- state: '',
- country: '',
- planCode: '',
- },
- });
-
- useEffect(() => {
- void (async () => {
- try {
- const result = await apiClient.plans.list();
- setPlans(result);
- } catch {
- setPlans([]);
- }
- })();
- }, []);
-
- const onSubmit = async (values: CreateComplexForm) => {
- setErrorMessage(null);
- setIsSubmitting(true);
-
- try {
- const pendingEmail = sessionStorage.getItem('pending-signup-email');
- const payload: Parameters[0] = {
- complexName: values.complexName,
- physicalAddress: values.physicalAddress,
- city: values.city,
- state: values.state,
- country: values.country,
- planCode: values.planCode,
- };
-
- if (pendingEmail) {
- payload.adminEmail = pendingEmail;
- }
-
- const complex = await apiClient.complexes.create(payload);
-
- sessionStorage.removeItem('pending-signup-email');
- sessionStorage.removeItem('pending-signup-userId');
- setCurrentComplexSlug(complex.complexSlug);
- await navigate({
- to: '/complex/$slug/edit',
- params: { slug: complex.complexSlug },
- });
- } catch {
- setErrorMessage('No pudimos crear el complejo. Intenta nuevamente.');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
-
- Crear tu complejo
-
- Completá los datos de tu complejo para comenzar.
-
-
-
-
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/onboard-complete-page.tsx b/apps/frontend/src/features/onboard/onboard-complete-page.tsx
deleted file mode 100644
index ead1b36..0000000
--- a/apps/frontend/src/features/onboard/onboard-complete-page.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { OnboardingCompleteStep } from '@/features/onboard/components/onboarding-complete-step';
-import { OnboardingLayout } from '@/features/onboard/components/onboarding-layout';
-import {
- clearOnboardingSessionState,
- getOnboardingSessionState,
- updateOnboardingSessionState,
-} from '@/features/onboard/onboarding-session';
-import type { CompleteValues } from '@/features/onboard/onboarding.types';
-import { apiClient } from '@/lib/api-client';
-import { useAuth } from '@/lib/auth';
-import { setCurrentComplexSlug } from '@/lib/current-complex';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { type PlanSummary, onboardingCompleteSchema } from '@repo/api-contract';
-import { useNavigate } from '@tanstack/react-router';
-import { useEffect, useMemo, useState } from 'react';
-import { useForm, useWatch } from 'react-hook-form';
-
-export function OnboardCompletePage() {
- const navigate = useNavigate();
- const { signInWithPassword } = useAuth();
- const initialState = useMemo(() => getOnboardingSessionState(), []);
-
- const [requestId] = useState(initialState.requestId);
- const [verifiedEmail] = useState(initialState.verifiedEmail);
- const [onboardingEmail] = useState(initialState.onboardingEmail);
- const [plans, setPlans] = useState([]);
- const [errorMessage, setErrorMessage] = useState(null);
-
- const form = useForm({
- resolver: zodResolver(onboardingCompleteSchema.omit({ onboardingRequestId: true })),
- mode: 'onChange',
- defaultValues: initialState.completeForm,
- });
- const values = useWatch({ control: form.control });
-
- useEffect(() => {
- if (!requestId) {
- void navigate({ to: '/onboard' });
- return;
- }
-
- if (!verifiedEmail) {
- void navigate({ to: '/onboard/verify' });
- }
- }, [navigate, requestId, verifiedEmail]);
-
- useEffect(() => {
- updateOnboardingSessionState((current) => ({
- ...current,
- completeForm: {
- password: values.password ?? '',
- complexName: values.complexName ?? '',
- physicalAddress: values.physicalAddress ?? '',
- planCode: values.planCode ?? '',
- },
- }));
- }, [values.complexName, values.password, values.physicalAddress, values.planCode]);
-
- useEffect(() => {
- void (async () => {
- try {
- const result = await apiClient.plans.list();
- setPlans(result);
- } catch {
- setPlans([]);
- }
- })();
- }, []);
-
- const onSubmit = async (payload: CompleteValues) => {
- if (!requestId) {
- setErrorMessage('No encontramos una solicitud de onboarding activa.');
- return;
- }
-
- setErrorMessage(null);
-
- try {
- const result = await apiClient.onboarding.complete({
- onboardingRequestId: requestId,
- password: payload.password,
- complexName: payload.complexName,
- physicalAddress: payload.physicalAddress,
- planCode: payload.planCode,
- });
-
- setCurrentComplexSlug(result.complexSlug);
- const emailForSignIn = verifiedEmail ?? onboardingEmail;
- if (emailForSignIn) {
- await signInWithPassword({ email: emailForSignIn, password: payload.password });
- }
-
- clearOnboardingSessionState();
- await navigate({ to: '/complex/$slug/edit', params: { slug: result.complexSlug } });
- } catch {
- setErrorMessage('No pudimos completar el onboarding. Intenta nuevamente.');
- }
- };
-
- const planPlaceholder =
- plans.length === 0 ? 'Codigo del plan (por ejemplo: BASIC)' : 'Selecciona un plan';
-
- return (
-
-
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/onboard-login-page.tsx b/apps/frontend/src/features/onboard/onboard-login-page.tsx
deleted file mode 100644
index 4f2ba0d..0000000
--- a/apps/frontend/src/features/onboard/onboard-login-page.tsx
+++ /dev/null
@@ -1,317 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { Field, FieldError, FieldLabel } from '@/components/ui/field';
-import { Input } from '@/components/ui/input';
-import { apiClient, authClient } from '@/lib/api-client';
-import { useAuth } from '@/lib/auth';
-import { acceptStoredPendingInvite } from '@/lib/invitations';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { useNavigate } from '@tanstack/react-router';
-import { useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { z } from 'zod';
-
-const loginSchema = z.object({
- email: z.string().email('Ingresá un email válido.'),
- password: z.string().min(6, 'La contraseña debe tener al menos 6 caracteres.'),
-});
-
-const signupSchema = z
- .object({
- fullName: z.string().min(2, 'El nombre debe tener al menos 2 caracteres.'),
- email: z.string().email('Ingresá un email válido.'),
- password: z.string().min(6, 'La contraseña debe tener al menos 6 caracteres.'),
- confirmPassword: z.string(),
- })
- .refine((data) => data.password === data.confirmPassword, {
- message: 'Las contraseñas no coinciden.',
- path: ['confirmPassword'],
- });
-
-type LoginForm = z.infer;
-type SignupForm = z.infer;
-
-type OnboardLoginPageProps = {
- variant?: 'onboard' | 'invite';
- inviteEmail?: string | null;
-};
-
-export function OnboardLoginPage({
- variant = 'onboard',
- inviteEmail = null,
-}: OnboardLoginPageProps) {
- const navigate = useNavigate();
- const { signInWithPassword, signUp } = useAuth();
- const [mode, setMode] = useState<'login' | 'signup'>(variant === 'invite' ? 'signup' : 'login');
- const [submitError, setSubmitError] = useState(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- const loginForm = useForm({
- resolver: zodResolver(loginSchema),
- mode: 'onChange',
- defaultValues: { email: inviteEmail ?? '', password: '' },
- });
-
- const signupForm = useForm({
- resolver: zodResolver(signupSchema),
- mode: 'onChange',
- defaultValues: {
- fullName: '',
- email: inviteEmail ?? '',
- password: '',
- confirmPassword: '',
- },
- });
-
- async function handlePostLogin() {
- await acceptStoredPendingInvite(apiClient.complexes.acceptPendingInvitations);
-
- if (variant === 'invite') {
- await navigate({ to: '/' });
- return;
- }
-
- const complexes = await apiClient.complexes.listMine();
-
- if (complexes.length === 0) {
- navigate({ to: '/onboard/create-complex' });
- } else if (complexes.length === 1) {
- await apiClient.complexes.select({ complexId: complexes[0].id });
- navigate({ to: '/' });
- } else {
- navigate({ to: '/select-complex' });
- }
- }
-
- const onLoginSubmit = async (values: LoginForm) => {
- setSubmitError(null);
- setIsSubmitting(true);
-
- try {
- await signInWithPassword(values);
- await handlePostLogin();
- } catch {
- setSubmitError('No pudimos iniciar sesión. Verificá tus credenciales.');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const onSignupSubmit = async (values: SignupForm) => {
- setSubmitError(null);
- setIsSubmitting(true);
-
- try {
- const result = await signUp({
- email: values.email,
- password: values.password,
- fullName: values.fullName,
- });
-
- if (result.requiresEmailConfirmation) {
- sessionStorage.setItem('pending-signup-email', values.email);
- navigate({
- to: '/onboard/verify-email',
- search: { email: values.email },
- });
- } else {
- if (variant === 'invite') {
- await acceptStoredPendingInvite(apiClient.complexes.acceptPendingInvitations);
- await navigate({ to: '/' });
- } else {
- await handlePostLogin();
- }
- }
- } catch {
- setSubmitError('No pudimos crear la cuenta. Intenta nuevamente.');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleGoogleSignIn = async () => {
- setSubmitError(null);
-
- try {
- await authClient.signIn.social({
- provider: 'google',
- callbackURL: `${window.location.origin}/auth-callback`,
- });
- } catch {
- setSubmitError('No pudimos iniciar sesión con Google.');
- }
- };
-
- return (
-
-
-
- {mode === 'login' ? 'Iniciar sesión' : 'Crear cuenta'}
-
-
- {mode === 'login'
- ? variant === 'invite'
- ? 'Ingresá con tu cuenta para aceptar la invitación.'
- : 'Ingresá con tu cuenta para continuar.'
- : variant === 'invite'
- ? 'Creá tu cuenta para aceptar la invitación.'
- : 'Creá tu cuenta para comenzar.'}
-
-
-
-
-
-
-
-
-
- Continuar con Google
-
-
-
-
- {mode === 'login' ? (
-
- ) : (
-
- )}
-
-
- {mode === 'login' ? (
- <>
- ¿No tenés cuenta?{' '}
- setMode('signup')}
- >
- Crear cuenta
-
- >
- ) : (
- <>
- ¿Ya tenés cuenta?{' '}
- setMode('login')}
- >
- Iniciar sesión
-
- >
- )}
-
-
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/onboard-start-page.tsx b/apps/frontend/src/features/onboard/onboard-start-page.tsx
deleted file mode 100644
index 8bd642b..0000000
--- a/apps/frontend/src/features/onboard/onboard-start-page.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { OnboardingLayout } from '@/features/onboard/components/onboarding-layout';
-import { OnboardingStartStep } from '@/features/onboard/components/onboarding-start-step';
-import {
- OTP_MAX_ATTEMPTS,
- defaultCompleteFormValues,
- defaultVerifyOtpFormValues,
- getOnboardingSessionState,
- updateOnboardingSessionState,
-} from '@/features/onboard/onboarding-session';
-import type { StartValues } from '@/features/onboard/onboarding.types';
-import { apiClient } from '@/lib/api-client';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { onboardingStartSchema } from '@repo/api-contract';
-import { useNavigate } from '@tanstack/react-router';
-import { useEffect, useMemo, useState } from 'react';
-import { useForm, useWatch } from 'react-hook-form';
-
-export function OnboardStartPage() {
- const navigate = useNavigate();
- const [errorMessage, setErrorMessage] = useState(null);
- const [infoMessage, setInfoMessage] = useState(null);
- const initialState = useMemo(() => getOnboardingSessionState(), []);
-
- const form = useForm({
- resolver: zodResolver(onboardingStartSchema),
- mode: 'onChange',
- defaultValues: initialState.startForm,
- });
- const values = useWatch({ control: form.control });
-
- useEffect(() => {
- updateOnboardingSessionState((current) => ({
- ...current,
- startForm: {
- fullName: values.fullName ?? '',
- email: values.email ?? '',
- },
- }));
- }, [values.email, values.fullName]);
-
- const onSubmit = async (payload: StartValues) => {
- setErrorMessage(null);
- setInfoMessage(null);
-
- try {
- const result = await apiClient.onboarding.start(payload);
-
- updateOnboardingSessionState((current) => ({
- ...current,
- requestId: result.requestId,
- onboardingEmail: result.email,
- verifiedEmail: null,
- remainingAttempts: OTP_MAX_ATTEMPTS,
- resendCooldownSeconds: result.cooldownSeconds,
- startForm: {
- fullName: payload.fullName,
- email: payload.email,
- },
- verifyOtpForm: defaultVerifyOtpFormValues,
- completeForm: defaultCompleteFormValues,
- }));
-
- await navigate({ to: '/onboard/verify' });
- } catch {
- setErrorMessage('No pudimos iniciar el onboarding. Intenta nuevamente.');
- }
- };
-
- return (
-
-
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/onboard-verify-page.tsx b/apps/frontend/src/features/onboard/onboard-verify-page.tsx
deleted file mode 100644
index 73ae5d2..0000000
--- a/apps/frontend/src/features/onboard/onboard-verify-page.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { OnboardingLayout } from '@/features/onboard/components/onboarding-layout';
-import { OnboardingVerifyOtpStep } from '@/features/onboard/components/onboarding-verify-otp-step';
-import {
- OTP_MAX_ATTEMPTS,
- getOnboardingSessionState,
- updateOnboardingSessionState,
-} from '@/features/onboard/onboarding-session';
-import type { VerifyOtpValues } from '@/features/onboard/onboarding.types';
-import { apiClient } from '@/lib/api-client';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { onboardingVerifyOtpSchema } from '@repo/api-contract';
-import { useNavigate } from '@tanstack/react-router';
-import { useEffect, useMemo, useState } from 'react';
-import { useForm, useWatch } from 'react-hook-form';
-
-export function OnboardVerifyPage() {
- const navigate = useNavigate();
- const initialState = useMemo(() => getOnboardingSessionState(), []);
- const [requestId] = useState(initialState.requestId);
- const [onboardingEmail, setOnboardingEmail] = useState(
- initialState.onboardingEmail
- );
- const [remainingAttempts, setRemainingAttempts] = useState(
- initialState.remainingAttempts || OTP_MAX_ATTEMPTS
- );
- const [resendCooldownSeconds, setResendCooldownSeconds] = useState(
- initialState.resendCooldownSeconds || 0
- );
- const [isResending, setIsResending] = useState(false);
- const [errorMessage, setErrorMessage] = useState(null);
- const [infoMessage, setInfoMessage] = useState(null);
-
- const form = useForm({
- resolver: zodResolver(onboardingVerifyOtpSchema.omit({ requestId: true })),
- mode: 'onChange',
- defaultValues: initialState.verifyOtpForm,
- });
- const values = useWatch({ control: form.control });
-
- useEffect(() => {
- if (requestId && onboardingEmail) return;
- void navigate({ to: '/onboard' });
- }, [navigate, onboardingEmail, requestId]);
-
- useEffect(() => {
- updateOnboardingSessionState((current) => ({
- ...current,
- onboardingEmail,
- remainingAttempts,
- resendCooldownSeconds,
- verifyOtpForm: {
- otp: values.otp ?? '',
- },
- }));
- }, [onboardingEmail, remainingAttempts, resendCooldownSeconds, values.otp]);
-
- useEffect(() => {
- if (resendCooldownSeconds <= 0) return;
-
- const timeout = window.setTimeout(() => {
- setResendCooldownSeconds((current) => Math.max(0, current - 1));
- }, 1000);
-
- return () => {
- window.clearTimeout(timeout);
- };
- }, [resendCooldownSeconds]);
-
- const onSubmit = async (payload: VerifyOtpValues) => {
- if (!requestId) {
- setErrorMessage('No encontramos una solicitud de onboarding activa.');
- return;
- }
-
- setErrorMessage(null);
-
- try {
- const result = await apiClient.onboarding.verifyOtp({
- requestId,
- otp: payload.otp,
- });
-
- setRemainingAttempts(result.remainingAttempts);
- setResendCooldownSeconds(result.cooldownSeconds);
- setInfoMessage(result.message);
-
- updateOnboardingSessionState((current) => ({
- ...current,
- remainingAttempts: result.remainingAttempts,
- resendCooldownSeconds: result.cooldownSeconds,
- }));
-
- if (result.verified) {
- const nextVerifiedEmail = result.email ?? onboardingEmail;
-
- updateOnboardingSessionState((current) => ({
- ...current,
- verifiedEmail: nextVerifiedEmail,
- remainingAttempts: result.remainingAttempts,
- resendCooldownSeconds: result.cooldownSeconds,
- }));
-
- await navigate({ to: '/onboard/complete' });
- }
- } catch {
- setErrorMessage('No pudimos verificar el OTP. Intenta nuevamente.');
- }
- };
-
- const onResend = async () => {
- if (!requestId) {
- setErrorMessage('No encontramos una solicitud de onboarding activa.');
- return;
- }
-
- setIsResending(true);
- setErrorMessage(null);
-
- try {
- const result = await apiClient.onboarding.resendOtp({ requestId });
- setOnboardingEmail(result.email);
- setRemainingAttempts(result.remainingAttempts);
- setResendCooldownSeconds(result.cooldownSeconds);
- form.reset({ otp: '' });
- setInfoMessage(result.message);
-
- updateOnboardingSessionState((current) => ({
- ...current,
- onboardingEmail: result.email,
- remainingAttempts: result.remainingAttempts,
- resendCooldownSeconds: result.cooldownSeconds,
- verifyOtpForm: { otp: '' },
- }));
- } catch {
- setErrorMessage('No pudimos reenviar el OTP. Intenta nuevamente.');
- } finally {
- setIsResending(false);
- }
- };
-
- return (
-
-
- {
- void navigate({ to: '/onboard' });
- }}
- >
- Volver al paso anterior
-
-
- );
-}
diff --git a/apps/frontend/src/features/onboard/onboarding-session.ts b/apps/frontend/src/features/onboard/onboarding-session.ts
deleted file mode 100644
index 453df75..0000000
--- a/apps/frontend/src/features/onboard/onboarding-session.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import type {
- CompleteValues,
- StartValues,
- VerifyOtpValues,
-} from '@/features/onboard/onboarding.types';
-
-const ONBOARDING_SESSION_KEY = 'playzer:onboarding-session';
-export const OTP_MAX_ATTEMPTS = 5;
-
-export const defaultStartFormValues: StartValues = {
- fullName: '',
- email: '',
-};
-
-export const defaultVerifyOtpFormValues: VerifyOtpValues = {
- otp: '',
-};
-
-export const defaultCompleteFormValues: CompleteValues = {
- password: '',
- complexName: '',
- physicalAddress: '',
- planCode: '',
-};
-
-export type OnboardingSessionState = {
- requestId: string | null;
- onboardingEmail: string | null;
- verifiedEmail: string | null;
- remainingAttempts: number;
- resendCooldownSeconds: number;
- startForm: StartValues;
- verifyOtpForm: VerifyOtpValues;
- completeForm: CompleteValues;
-};
-
-const defaultOnboardingSessionState: OnboardingSessionState = {
- requestId: null,
- onboardingEmail: null,
- verifiedEmail: null,
- remainingAttempts: OTP_MAX_ATTEMPTS,
- resendCooldownSeconds: 0,
- startForm: defaultStartFormValues,
- verifyOtpForm: defaultVerifyOtpFormValues,
- completeForm: defaultCompleteFormValues,
-};
-
-function isObject(value: unknown): value is Record {
- return typeof value === 'object' && value !== null;
-}
-
-function normalizeSessionState(value: unknown): OnboardingSessionState {
- if (!isObject(value)) {
- return defaultOnboardingSessionState;
- }
-
- return {
- requestId: typeof value.requestId === 'string' ? value.requestId : null,
- onboardingEmail: typeof value.onboardingEmail === 'string' ? value.onboardingEmail : null,
- verifiedEmail: typeof value.verifiedEmail === 'string' ? value.verifiedEmail : null,
- remainingAttempts:
- typeof value.remainingAttempts === 'number'
- ? value.remainingAttempts
- : defaultOnboardingSessionState.remainingAttempts,
- resendCooldownSeconds:
- typeof value.resendCooldownSeconds === 'number'
- ? value.resendCooldownSeconds
- : defaultOnboardingSessionState.resendCooldownSeconds,
- startForm: {
- fullName:
- isObject(value.startForm) && typeof value.startForm.fullName === 'string'
- ? value.startForm.fullName
- : defaultStartFormValues.fullName,
- email:
- isObject(value.startForm) && typeof value.startForm.email === 'string'
- ? value.startForm.email
- : defaultStartFormValues.email,
- },
- verifyOtpForm: {
- otp:
- isObject(value.verifyOtpForm) && typeof value.verifyOtpForm.otp === 'string'
- ? value.verifyOtpForm.otp
- : defaultVerifyOtpFormValues.otp,
- },
- completeForm: {
- password:
- isObject(value.completeForm) && typeof value.completeForm.password === 'string'
- ? value.completeForm.password
- : defaultCompleteFormValues.password,
- complexName:
- isObject(value.completeForm) && typeof value.completeForm.complexName === 'string'
- ? value.completeForm.complexName
- : defaultCompleteFormValues.complexName,
- physicalAddress:
- isObject(value.completeForm) && typeof value.completeForm.physicalAddress === 'string'
- ? value.completeForm.physicalAddress
- : defaultCompleteFormValues.physicalAddress,
- planCode:
- isObject(value.completeForm) && typeof value.completeForm.planCode === 'string'
- ? value.completeForm.planCode
- : defaultCompleteFormValues.planCode,
- },
- };
-}
-
-export function getOnboardingSessionState(): OnboardingSessionState {
- if (typeof window === 'undefined') {
- return defaultOnboardingSessionState;
- }
-
- const rawState = window.sessionStorage.getItem(ONBOARDING_SESSION_KEY);
- if (!rawState) {
- return defaultOnboardingSessionState;
- }
-
- try {
- return normalizeSessionState(JSON.parse(rawState));
- } catch {
- return defaultOnboardingSessionState;
- }
-}
-
-export function setOnboardingSessionState(state: OnboardingSessionState) {
- if (typeof window === 'undefined') return;
- window.sessionStorage.setItem(ONBOARDING_SESSION_KEY, JSON.stringify(state));
-}
-
-export function updateOnboardingSessionState(
- updater: (current: OnboardingSessionState) => OnboardingSessionState
-): OnboardingSessionState {
- const currentState = getOnboardingSessionState();
- const nextState = updater(currentState);
- setOnboardingSessionState(nextState);
- return nextState;
-}
-
-export function clearOnboardingSessionState() {
- if (typeof window === 'undefined') return;
- window.sessionStorage.removeItem(ONBOARDING_SESSION_KEY);
-}
diff --git a/apps/frontend/src/features/onboard/onboarding.types.ts b/apps/frontend/src/features/onboard/onboarding.types.ts
deleted file mode 100644
index 6810078..0000000
--- a/apps/frontend/src/features/onboard/onboarding.types.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import {
- onboardingCompleteSchema,
- onboardingStartSchema,
- onboardingVerifyOtpSchema,
-} from '@repo/api-contract';
-import { z } from 'zod';
-
-export type StartValues = z.infer;
-export type VerifyOtpValues = Omit, 'requestId'>;
-export type CompleteValues = Omit, 'onboardingRequestId'>;
diff --git a/apps/frontend/src/features/onboard/setup-stepper-page.tsx b/apps/frontend/src/features/onboard/setup-stepper-page.tsx
new file mode 100644
index 0000000..4573765
--- /dev/null
+++ b/apps/frontend/src/features/onboard/setup-stepper-page.tsx
@@ -0,0 +1,114 @@
+import { SetupStepper } from '@/features/onboard/components/setup-stepper';
+import { apiClient } from '@/lib/api-client';
+import { setCurrentComplexSlug } from '@/lib/current-complex';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { createComplexSchema } from '@repo/api-contract';
+import type { PlanWithFeatures, Sport } from '@repo/api-contract';
+import { useNavigate } from '@tanstack/react-router';
+import { useEffect, useState } from 'react';
+import { useForm } from 'react-hook-form';
+
+export function SetupStepperPage() {
+ const navigate = useNavigate();
+ const [plans, setPlans] = useState([]);
+ const [sports, setSports] = useState([]);
+ const [errorMessage, setErrorMessage] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(createComplexSchema),
+ mode: 'onChange',
+ defaultValues: {
+ complexName: '',
+ physicalAddress: '',
+ city: '',
+ state: '',
+ country: '',
+ planCode: '',
+ setupCourts: false,
+ courtSportId: undefined,
+ courtStartTime: '08:00',
+ courtEndTime: '22:00',
+ courtDaysOfWeek: [],
+ },
+ });
+
+ useEffect(() => {
+ void (async () => {
+ try {
+ // Safety: if user already has complexes (e.g., accepted invite between redirects),
+ // redirect to home
+ const myComplexes = await apiClient.complexes.listMine();
+ if (myComplexes.length > 0) {
+ navigate({ to: '/' });
+ return;
+ }
+
+ const [plansResult, sportsResult] = await Promise.all([
+ apiClient.plans.list(),
+ apiClient.sports.list(),
+ ]);
+ setPlans(plansResult);
+ setSports(sportsResult);
+ } catch {
+ setErrorMessage('Error al cargar datos iniciales.');
+ }
+ })();
+ }, [navigate]);
+
+ const onSubmit = async () => {
+ const isValid = await form.trigger();
+ if (!isValid) return;
+
+ setErrorMessage(null);
+ setIsSubmitting(true);
+
+ try {
+ const values = form.getValues();
+ const result = await apiClient.complexes.create(values as any);
+
+ setCurrentComplexSlug(result.complexSlug);
+ await navigate({
+ to: '/complex/$slug/edit',
+ params: { slug: result.complexSlug },
+ });
+ } catch {
+ setErrorMessage('No pudimos crear el complejo. Intenta nuevamente.');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ if (errorMessage && plans.length === 0) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
Configurá tu complejo
+
+ Completá los datos para empezar a recibir reservas.
+
+
+
+
+
+ {errorMessage && {errorMessage}
}
+
+
+ );
+}
diff --git a/apps/frontend/src/features/onboard/verify-email-page.tsx b/apps/frontend/src/features/onboard/verify-email-page.tsx
deleted file mode 100644
index e8fe7fa..0000000
--- a/apps/frontend/src/features/onboard/verify-email-page.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Button } from '@/components/ui/button';
-import { getPendingInvite } from '@/lib/invitations';
-import { Route } from '@/routes/onboard/verify-email';
-import { Link } from '@tanstack/react-router';
-import { useEffect, useState } from 'react';
-
-export function VerifyEmailPage() {
- const search = Route.useSearch();
- const email = search.email || sessionStorage.getItem('pending-signup-email') || '';
- const pendingInvite = getPendingInvite();
- const [resendCooldown, setResendCooldown] = useState(0);
-
- useEffect(() => {
- if (resendCooldown > 0) {
- const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
- return () => clearTimeout(timer);
- }
- }, [resendCooldown]);
-
- const handleResend = async () => {
- if (!email || resendCooldown > 0) return;
-
- setResendCooldown(60);
- };
-
- return (
-
-
- Verificá tu email
-
- Te enviamos un email de verificación a {email} . Hacé click en el link
- para confirmar tu cuenta.
-
-
-
-
0}
- >
- {resendCooldown > 0 ? `Reenviar en ${resendCooldown}s` : 'Reenviar email'}
-
-
-
- ¿Ya verificaste tu email?{' '}
- {pendingInvite.token ? (
-
- Continuar con la invitación
-
- ) : (
-
- Continuar
-
- )}
-
-
-
-
- );
-}
diff --git a/apps/frontend/src/features/signup/signup-page.tsx b/apps/frontend/src/features/signup/signup-page.tsx
index c3fcf52..414f8e3 100644
--- a/apps/frontend/src/features/signup/signup-page.tsx
+++ b/apps/frontend/src/features/signup/signup-page.tsx
@@ -83,7 +83,7 @@ export function SignupPage() {
setIsSubmitting(true);
try {
- await signUp({
+ const { requiresEmailConfirmation } = await signUp({
email: values.email,
password: values.password,
fullName: values.fullName,
@@ -91,7 +91,11 @@ export function SignupPage() {
});
sessionStorage.removeItem(STORAGE_KEY);
- await navigate({ to: '/' });
+
+ if (requiresEmailConfirmation) {
+ sessionStorage.setItem('pending-signup-email', values.email);
+ await navigate({ to: '/onboard/setup' });
+ }
} catch {
setSubmitError('No pudimos crear la cuenta. Intenta nuevamente.');
} finally {
diff --git a/apps/frontend/src/lib/api-client.ts b/apps/frontend/src/lib/api-client.ts
index cd51662..d8f475e 100644
--- a/apps/frontend/src/lib/api-client.ts
+++ b/apps/frontend/src/lib/api-client.ts
@@ -43,7 +43,6 @@ export class ApiClientError extends Error {
}
export const apiClient = {
- onboarding: api.onboarding,
plans: api.plans,
user: api.user,
complexes: api.complexes,
diff --git a/apps/frontend/src/lib/api/index.ts b/apps/frontend/src/lib/api/index.ts
index 861bcd6..292f256 100644
--- a/apps/frontend/src/lib/api/index.ts
+++ b/apps/frontend/src/lib/api/index.ts
@@ -6,7 +6,6 @@ export * as courts from './resources/courts';
export * as user from './resources/user';
export * as sports from './resources/sports';
export * as plans from './resources/plans';
-export * as onboarding from './resources/onboarding';
export {
getAvailability,
diff --git a/apps/frontend/src/lib/api/resources/onboarding.ts b/apps/frontend/src/lib/api/resources/onboarding.ts
deleted file mode 100644
index 02b5d36..0000000
--- a/apps/frontend/src/lib/api/resources/onboarding.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import type {
- OnboardingCompleteInput,
- OnboardingCompleteResponse,
- OnboardingResendOtpInput,
- OnboardingResendOtpResponse,
- OnboardingStartInput,
- OnboardingStartResponse,
- OnboardingVerifyOtpInput,
- OnboardingVerifyOtpResponse,
-} from '@repo/api-contract';
-import { http } from '../http';
-
-export async function start(payload: OnboardingStartInput) {
- const response = await http.post('/api/onboarding/start', payload);
- return response.data;
-}
-
-export async function verifyOtp(payload: OnboardingVerifyOtpInput) {
- const response = await http.post(
- '/api/onboarding/verify-otp',
- payload
- );
- return response.data;
-}
-
-export async function resendOtp(payload: OnboardingResendOtpInput) {
- const response = await http.post(
- '/api/onboarding/resend-otp',
- payload
- );
- return response.data;
-}
-
-export async function complete(payload: OnboardingCompleteInput) {
- const response = await http.post('/api/onboarding/complete', payload);
- return response.data;
-}
diff --git a/apps/frontend/src/lib/api/resources/plans.ts b/apps/frontend/src/lib/api/resources/plans.ts
index ebf115d..2cfa99c 100644
--- a/apps/frontend/src/lib/api/resources/plans.ts
+++ b/apps/frontend/src/lib/api/resources/plans.ts
@@ -1,7 +1,7 @@
-import type { PlanSummary } from '@repo/api-contract';
+import type { PlanWithFeatures } from '@repo/api-contract';
import { http } from '../http';
export async function list() {
- const response = await http.get('/api/plans');
+ const response = await http.get('/api/plans');
return response.data;
}
diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts
index 8dd2857..2de1645 100644
--- a/apps/frontend/src/routeTree.gen.ts
+++ b/apps/frontend/src/routeTree.gen.ts
@@ -17,11 +17,7 @@ import { Route as LoginRouteImport } from './routes/login'
import { Route as InviteRouteImport } from './routes/invite'
import { Route as AuthCallbackRouteImport } from './routes/auth-callback'
import { Route as AppRouteRouteImport } from './routes/_app/route'
-import { Route as OnboardIndexRouteImport } from './routes/onboard/index'
-import { Route as OnboardVerifyEmailRouteImport } from './routes/onboard/verify-email'
-import { Route as OnboardVerifyRouteImport } from './routes/onboard/verify'
-import { Route as OnboardCreateComplexRouteImport } from './routes/onboard/create-complex'
-import { Route as OnboardCompleteRouteImport } from './routes/onboard/complete'
+import { Route as OnboardSetupRouteImport } from './routes/onboard/setup'
import { Route as AppProfileRouteImport } from './routes/_app/profile'
import { Route as ComplexSlugBookingRouteImport } from './routes/$complexSlug/booking'
import { Route as AppAuthenticatedRouteRouteImport } from './routes/_app/_authenticated/route'
@@ -69,29 +65,9 @@ const AppRouteRoute = AppRouteRouteImport.update({
id: '/_app',
getParentRoute: () => rootRouteImport,
} as any)
-const OnboardIndexRoute = OnboardIndexRouteImport.update({
- id: '/',
- path: '/',
- getParentRoute: () => OnboardRoute,
-} as any)
-const OnboardVerifyEmailRoute = OnboardVerifyEmailRouteImport.update({
- id: '/verify-email',
- path: '/verify-email',
- getParentRoute: () => OnboardRoute,
-} as any)
-const OnboardVerifyRoute = OnboardVerifyRouteImport.update({
- id: '/verify',
- path: '/verify',
- getParentRoute: () => OnboardRoute,
-} as any)
-const OnboardCreateComplexRoute = OnboardCreateComplexRouteImport.update({
- id: '/create-complex',
- path: '/create-complex',
- getParentRoute: () => OnboardRoute,
-} as any)
-const OnboardCompleteRoute = OnboardCompleteRouteImport.update({
- id: '/complete',
- path: '/complete',
+const OnboardSetupRoute = OnboardSetupRouteImport.update({
+ id: '/setup',
+ path: '/setup',
getParentRoute: () => OnboardRoute,
} as any)
const AppProfileRoute = AppProfileRouteImport.update({
@@ -142,11 +118,7 @@ export interface FileRoutesByFullPath {
'/signup': typeof SignupRoute
'/$complexSlug/booking': typeof ComplexSlugBookingRouteWithChildren
'/profile': typeof AppProfileRoute
- '/onboard/complete': typeof OnboardCompleteRoute
- '/onboard/create-complex': typeof OnboardCreateComplexRoute
- '/onboard/verify': typeof OnboardVerifyRoute
- '/onboard/verify-email': typeof OnboardVerifyEmailRoute
- '/onboard/': typeof OnboardIndexRoute
+ '/onboard/setup': typeof OnboardSetupRoute
'/$complexSlug/booking/': typeof ComplexSlugBookingIndexRoute
'/$complexSlug/booking/confirmed/$bookingCode': typeof ComplexSlugBookingConfirmedBookingCodeRoute
'/complex/$slug/edit': typeof AppAuthenticatedComplexSlugEditRoute
@@ -156,15 +128,12 @@ export interface FileRoutesByTo {
'/auth-callback': typeof AuthCallbackRoute
'/invite': typeof InviteRoute
'/login': typeof LoginRoute
+ '/onboard': typeof OnboardRouteWithChildren
'/reset-password': typeof ResetPasswordRoute
'/select-complex': typeof SelectComplexRoute
'/signup': typeof SignupRoute
'/profile': typeof AppProfileRoute
- '/onboard/complete': typeof OnboardCompleteRoute
- '/onboard/create-complex': typeof OnboardCreateComplexRoute
- '/onboard/verify': typeof OnboardVerifyRoute
- '/onboard/verify-email': typeof OnboardVerifyEmailRoute
- '/onboard': typeof OnboardIndexRoute
+ '/onboard/setup': typeof OnboardSetupRoute
'/$complexSlug/booking': typeof ComplexSlugBookingIndexRoute
'/$complexSlug/booking/confirmed/$bookingCode': typeof ComplexSlugBookingConfirmedBookingCodeRoute
'/complex/$slug/edit': typeof AppAuthenticatedComplexSlugEditRoute
@@ -182,11 +151,7 @@ export interface FileRoutesById {
'/_app/_authenticated': typeof AppAuthenticatedRouteRouteWithChildren
'/$complexSlug/booking': typeof ComplexSlugBookingRouteWithChildren
'/_app/profile': typeof AppProfileRoute
- '/onboard/complete': typeof OnboardCompleteRoute
- '/onboard/create-complex': typeof OnboardCreateComplexRoute
- '/onboard/verify': typeof OnboardVerifyRoute
- '/onboard/verify-email': typeof OnboardVerifyEmailRoute
- '/onboard/': typeof OnboardIndexRoute
+ '/onboard/setup': typeof OnboardSetupRoute
'/$complexSlug/booking/': typeof ComplexSlugBookingIndexRoute
'/_app/_authenticated/': typeof AppAuthenticatedIndexRoute
'/$complexSlug/booking/confirmed/$bookingCode': typeof ComplexSlugBookingConfirmedBookingCodeRoute
@@ -205,11 +170,7 @@ export interface FileRouteTypes {
| '/signup'
| '/$complexSlug/booking'
| '/profile'
- | '/onboard/complete'
- | '/onboard/create-complex'
- | '/onboard/verify'
- | '/onboard/verify-email'
- | '/onboard/'
+ | '/onboard/setup'
| '/$complexSlug/booking/'
| '/$complexSlug/booking/confirmed/$bookingCode'
| '/complex/$slug/edit'
@@ -219,15 +180,12 @@ export interface FileRouteTypes {
| '/auth-callback'
| '/invite'
| '/login'
+ | '/onboard'
| '/reset-password'
| '/select-complex'
| '/signup'
| '/profile'
- | '/onboard/complete'
- | '/onboard/create-complex'
- | '/onboard/verify'
- | '/onboard/verify-email'
- | '/onboard'
+ | '/onboard/setup'
| '/$complexSlug/booking'
| '/$complexSlug/booking/confirmed/$bookingCode'
| '/complex/$slug/edit'
@@ -244,11 +202,7 @@ export interface FileRouteTypes {
| '/_app/_authenticated'
| '/$complexSlug/booking'
| '/_app/profile'
- | '/onboard/complete'
- | '/onboard/create-complex'
- | '/onboard/verify'
- | '/onboard/verify-email'
- | '/onboard/'
+ | '/onboard/setup'
| '/$complexSlug/booking/'
| '/_app/_authenticated/'
| '/$complexSlug/booking/confirmed/$bookingCode'
@@ -325,39 +279,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppRouteRouteImport
parentRoute: typeof rootRouteImport
}
- '/onboard/': {
- id: '/onboard/'
- path: '/'
- fullPath: '/onboard/'
- preLoaderRoute: typeof OnboardIndexRouteImport
- parentRoute: typeof OnboardRoute
- }
- '/onboard/verify-email': {
- id: '/onboard/verify-email'
- path: '/verify-email'
- fullPath: '/onboard/verify-email'
- preLoaderRoute: typeof OnboardVerifyEmailRouteImport
- parentRoute: typeof OnboardRoute
- }
- '/onboard/verify': {
- id: '/onboard/verify'
- path: '/verify'
- fullPath: '/onboard/verify'
- preLoaderRoute: typeof OnboardVerifyRouteImport
- parentRoute: typeof OnboardRoute
- }
- '/onboard/create-complex': {
- id: '/onboard/create-complex'
- path: '/create-complex'
- fullPath: '/onboard/create-complex'
- preLoaderRoute: typeof OnboardCreateComplexRouteImport
- parentRoute: typeof OnboardRoute
- }
- '/onboard/complete': {
- id: '/onboard/complete'
- path: '/complete'
- fullPath: '/onboard/complete'
- preLoaderRoute: typeof OnboardCompleteRouteImport
+ '/onboard/setup': {
+ id: '/onboard/setup'
+ path: '/setup'
+ fullPath: '/onboard/setup'
+ preLoaderRoute: typeof OnboardSetupRouteImport
parentRoute: typeof OnboardRoute
}
'/_app/profile': {
@@ -442,19 +368,11 @@ const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(
)
interface OnboardRouteChildren {
- OnboardCompleteRoute: typeof OnboardCompleteRoute
- OnboardCreateComplexRoute: typeof OnboardCreateComplexRoute
- OnboardVerifyRoute: typeof OnboardVerifyRoute
- OnboardVerifyEmailRoute: typeof OnboardVerifyEmailRoute
- OnboardIndexRoute: typeof OnboardIndexRoute
+ OnboardSetupRoute: typeof OnboardSetupRoute
}
const OnboardRouteChildren: OnboardRouteChildren = {
- OnboardCompleteRoute: OnboardCompleteRoute,
- OnboardCreateComplexRoute: OnboardCreateComplexRoute,
- OnboardVerifyRoute: OnboardVerifyRoute,
- OnboardVerifyEmailRoute: OnboardVerifyEmailRoute,
- OnboardIndexRoute: OnboardIndexRoute,
+ OnboardSetupRoute: OnboardSetupRoute,
}
const OnboardRouteWithChildren =
diff --git a/apps/frontend/src/routes/auth-callback.tsx b/apps/frontend/src/routes/auth-callback.tsx
index eb04f97..39242f5 100644
--- a/apps/frontend/src/routes/auth-callback.tsx
+++ b/apps/frontend/src/routes/auth-callback.tsx
@@ -17,23 +17,9 @@ function AuthCallbackPage() {
try {
await acceptStoredPendingInvite(apiClient.complexes.acceptPendingInvitations);
- const complexes = await apiClient.complexes.listMine();
-
- if (complexes.length === 1) {
- await apiClient.complexes.select({ complexId: complexes[0].id });
- navigate({ to: '/' });
- } else if (complexes.length > 1) {
- const current = await apiClient.complexes.getCurrent();
- if (current) {
- navigate({ to: '/' });
- } else {
- navigate({ to: '/select-complex' });
- }
- } else {
- navigate({ to: '/onboard/create-complex' });
- }
+ navigate({ to: '/' });
} catch {
- navigate({ to: '/onboard' });
+ navigate({ to: '/login' });
}
}
diff --git a/apps/frontend/src/routes/invite.tsx b/apps/frontend/src/routes/invite.tsx
index 8814638..44eebad 100644
--- a/apps/frontend/src/routes/invite.tsx
+++ b/apps/frontend/src/routes/invite.tsx
@@ -1,4 +1,3 @@
-import { OnboardLoginPage } from '@/features/onboard/onboard-login-page';
import { apiClient } from '@/lib/api-client';
import { useAuth } from '@/lib/auth';
import { acceptStoredPendingInvite, setPendingInvite } from '@/lib/invitations';
@@ -25,6 +24,7 @@ function InvitePage() {
useEffect(() => {
if (!isAuthenticated) {
+ navigate({ to: '/login' });
return;
}
@@ -34,15 +34,11 @@ function InvitePage() {
})();
}, [isAuthenticated, navigate]);
- if (isAuthenticated) {
- return (
-
- Aceptando invitación...
-
- );
- }
-
- return ;
+ return (
+
+ Aceptando invitación...
+
+ );
}
export const Route = createFileRoute('/invite')({
diff --git a/apps/frontend/src/routes/login.tsx b/apps/frontend/src/routes/login.tsx
index 45ee78d..50573ed 100644
--- a/apps/frontend/src/routes/login.tsx
+++ b/apps/frontend/src/routes/login.tsx
@@ -1,22 +1,11 @@
import { LoginPage } from '@/features/login/login-page';
import { createFileRoute, redirect } from '@tanstack/react-router';
-import { z } from 'zod';
-
-const loginSearchSchema = z.object({
- redirect: z.string().optional(),
-});
export const Route = createFileRoute('/login')({
- validateSearch: loginSearchSchema,
beforeLoad: ({ context }) => {
if (context.auth.isAuthenticated) {
throw redirect({ to: '/' });
}
},
- component: LoginRouteComponent,
+ component: LoginPage,
});
-
-function LoginRouteComponent() {
- const search = Route.useSearch();
- return ;
-}
diff --git a/apps/frontend/src/routes/onboard/complete.tsx b/apps/frontend/src/routes/onboard/complete.tsx
deleted file mode 100644
index c12e9a9..0000000
--- a/apps/frontend/src/routes/onboard/complete.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { OnboardCompletePage } from '@/features/onboard/onboard-complete-page';
-import { createFileRoute } from '@tanstack/react-router';
-
-export const Route = createFileRoute('/onboard/complete')({
- component: OnboardCompletePage,
-});
diff --git a/apps/frontend/src/routes/onboard/index.tsx b/apps/frontend/src/routes/onboard/index.tsx
deleted file mode 100644
index 003a135..0000000
--- a/apps/frontend/src/routes/onboard/index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { OnboardLoginPage } from '@/features/onboard/onboard-login-page';
-import { createFileRoute, redirect } from '@tanstack/react-router';
-
-export const Route = createFileRoute('/onboard/')({
- beforeLoad: ({ context }) => {
- if (!context.auth.isAuthenticated) {
- throw redirect({ to: '/login', search: { redirect: '/onboard/create-complex' } });
- }
- },
- component: OnboardLoginPage,
-});
diff --git a/apps/frontend/src/routes/onboard/create-complex.tsx b/apps/frontend/src/routes/onboard/setup.tsx
similarity index 58%
rename from apps/frontend/src/routes/onboard/create-complex.tsx
rename to apps/frontend/src/routes/onboard/setup.tsx
index 05b9bd5..4459962 100644
--- a/apps/frontend/src/routes/onboard/create-complex.tsx
+++ b/apps/frontend/src/routes/onboard/setup.tsx
@@ -1,14 +1,14 @@
-import { CreateComplexPage } from '@/features/onboard/create-complex-page';
+import { SetupStepperPage } from '@/features/onboard/setup-stepper-page';
import { createFileRoute, redirect } from '@tanstack/react-router';
-export const Route = createFileRoute('/onboard/create-complex')({
+export const Route = createFileRoute('/onboard/setup')({
beforeLoad: ({ context }) => {
const hasSession = context.auth.isAuthenticated;
const hasPendingSignup = Boolean(sessionStorage.getItem('pending-signup-email'));
if (!hasSession && !hasPendingSignup) {
- throw redirect({ to: '/onboard' });
+ throw redirect({ to: '/login' });
}
},
- component: CreateComplexPage,
+ component: SetupStepperPage,
});
diff --git a/apps/frontend/src/routes/onboard/verify-email.tsx b/apps/frontend/src/routes/onboard/verify-email.tsx
deleted file mode 100644
index 94c51a6..0000000
--- a/apps/frontend/src/routes/onboard/verify-email.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { VerifyEmailPage } from '@/features/onboard/verify-email-page';
-import { createFileRoute, redirect } from '@tanstack/react-router';
-import { z } from 'zod';
-
-const verifyEmailSearchSchema = z.object({
- email: z.string().email().optional(),
-});
-
-export const Route = createFileRoute('/onboard/verify-email')({
- validateSearch: verifyEmailSearchSchema,
- beforeLoad: () => {
- const hasPendingSignup = Boolean(sessionStorage.getItem('pending-signup-email'));
- if (!hasPendingSignup) {
- throw redirect({ to: '/onboard' });
- }
- },
- component: VerifyEmailPage,
-});
diff --git a/apps/frontend/src/routes/onboard/verify.tsx b/apps/frontend/src/routes/onboard/verify.tsx
deleted file mode 100644
index 72e512e..0000000
--- a/apps/frontend/src/routes/onboard/verify.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { OnboardVerifyPage } from '@/features/onboard/onboard-verify-page';
-import { createFileRoute } from '@tanstack/react-router';
-
-export const Route = createFileRoute('/onboard/verify')({
- component: OnboardVerifyPage,
-});
diff --git a/packages/api-contract/src/complex.ts b/packages/api-contract/src/complex.ts
index ec2bd1c..15c8bf9 100644
--- a/packages/api-contract/src/complex.ts
+++ b/packages/api-contract/src/complex.ts
@@ -1,5 +1,15 @@
import { z } from 'zod'
+const dayOfWeekEnum = z.enum([
+ 'MONDAY',
+ 'TUESDAY',
+ 'WEDNESDAY',
+ 'THURSDAY',
+ 'FRIDAY',
+ 'SATURDAY',
+ 'SUNDAY',
+])
+
export const complexSchema = z.object({
id: z.uuid(),
complexName: z.string(),
@@ -14,39 +24,77 @@ export const complexSchema = z.object({
updatedAt: z.string().datetime(),
})
-export const createComplexSchema = z.object({
- complexName: z
- .string()
- .trim()
- .min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
- .max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
- physicalAddress: z
- .string()
- .trim()
- .min(5, 'La direccion debe tener al menos 5 caracteres.')
- .max(200, 'La direccion no puede superar los 200 caracteres.'),
- city: z
- .string()
- .trim()
- .max(100, 'La ciudad no puede superar los 100 caracteres.')
- .optional(),
- state: z
- .string()
- .trim()
- .max(100, 'La provincia/estado no puede superar los 100 caracteres.')
- .optional(),
- country: z
- .string()
- .trim()
- .max(100, 'El país no puede superar los 100 caracteres.')
- .optional(),
- planCode: z
- .string()
- .trim()
- .min(1, 'El planCode no puede estar vacío.')
- .max(10, 'El planCode no puede superar los 10 caracteres.')
- .optional(),
-})
+export const createComplexSchema = z
+ .object({
+ complexName: z
+ .string()
+ .trim()
+ .min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
+ .max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
+ physicalAddress: z
+ .string()
+ .trim()
+ .min(5, 'La direccion debe tener al menos 5 caracteres.')
+ .max(200, 'La direccion no puede superar los 200 caracteres.'),
+ city: z
+ .string()
+ .trim()
+ .max(100, 'La ciudad no puede superar los 100 caracteres.')
+ .optional(),
+ state: z
+ .string()
+ .trim()
+ .max(100, 'La provincia/estado no puede superar los 100 caracteres.')
+ .optional(),
+ country: z
+ .string()
+ .trim()
+ .max(100, 'El país no puede superar los 100 caracteres.')
+ .optional(),
+ planCode: z
+ .string()
+ .trim()
+ .min(1, 'El planCode no puede estar vacío.')
+ .max(10, 'El planCode no puede superar los 10 caracteres.')
+ .optional(),
+ setupCourts: z.boolean().default(false),
+ courtSportId: z.string().uuid('El deporte seleccionado no es válido.').optional(),
+ courtStartTime: z.string().optional(),
+ courtEndTime: z.string().optional(),
+ courtDaysOfWeek: z.array(dayOfWeekEnum).optional(),
+ })
+ .superRefine((data, ctx) => {
+ if (data.setupCourts) {
+ if (!data.courtSportId) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ path: ['courtSportId'],
+ message: 'Debes seleccionar un deporte.',
+ })
+ }
+ if (!data.courtStartTime || !/^\d{2}:\d{2}$/.test(data.courtStartTime)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ path: ['courtStartTime'],
+ message: 'La hora de inicio debe tener formato HH:mm.',
+ })
+ }
+ if (!data.courtEndTime || !/^\d{2}:\d{2}$/.test(data.courtEndTime)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ path: ['courtEndTime'],
+ message: 'La hora de fin debe tener formato HH:mm.',
+ })
+ }
+ if (!data.courtDaysOfWeek || data.courtDaysOfWeek.length === 0) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ path: ['courtDaysOfWeek'],
+ message: 'Debes seleccionar al menos un día.',
+ })
+ }
+ }
+ })
export const updateComplexSchema = z
.object({
diff --git a/packages/api-contract/src/index.ts b/packages/api-contract/src/index.ts
index e7ea440..1696f37 100644
--- a/packages/api-contract/src/index.ts
+++ b/packages/api-contract/src/index.ts
@@ -4,28 +4,18 @@ export {
planRulesV1Schema,
planSchema,
planSummarySchema,
+ planWithFeaturesSchema,
+ planLimitsSchema,
} from './plan'
-export type { Plan, PlanFeatureFlags, PlanRules, PlanRulesV1, PlanSummary } from './plan'
-export {
- onboardingCompleteResponseSchema,
- onboardingCompleteSchema,
- onboardingResendOtpResponseSchema,
- onboardingResendOtpSchema,
- onboardingStartResponseSchema,
- onboardingStartSchema,
- onboardingVerifyOtpResponseSchema,
- onboardingVerifyOtpSchema,
-} from './onboarding'
export type {
- OnboardingCompleteInput,
- OnboardingCompleteResponse,
- OnboardingResendOtpInput,
- OnboardingResendOtpResponse,
- OnboardingStartInput,
- OnboardingStartResponse,
- OnboardingVerifyOtpInput,
- OnboardingVerifyOtpResponse,
-} from './onboarding'
+ Plan,
+ PlanFeatureFlags,
+ PlanRules,
+ PlanRulesV1,
+ PlanSummary,
+ PlanWithFeatures,
+ PlanLimits,
+} from './plan'
export {
acceptComplexInvitationsSchema,
complexInvitationSchema,
diff --git a/packages/api-contract/src/onboarding.ts b/packages/api-contract/src/onboarding.ts
deleted file mode 100644
index 628bc37..0000000
--- a/packages/api-contract/src/onboarding.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { z } from 'zod'
-
-const otpSchema = z
- .string()
- .trim()
- .regex(/^\d{6}$/, 'El OTP debe tener 6 digitos numericos.')
-
-export const onboardingStartSchema = z.object({
- fullName: z
- .string()
- .trim()
- .min(2, 'El nombre debe tener al menos 2 caracteres.')
- .max(80, 'El nombre no puede superar los 80 caracteres.'),
- email: z.email('Ingresa un email valido.'),
-})
-
-export const onboardingVerifyOtpSchema = z.object({
- requestId: z.uuid('El requestId de onboarding no es valido.'),
- otp: otpSchema,
-})
-
-export const onboardingResendOtpSchema = z.object({
- requestId: z.uuid('El requestId de onboarding no es valido.'),
-})
-
-export const onboardingCompleteSchema = z.object({
- onboardingRequestId: z.uuid('El onboardingRequestId no es valido.'),
- password: z
- .string()
- .min(8, 'La contrasena debe tener al menos 8 caracteres.')
- .max(72, 'La contrasena no puede superar los 72 caracteres.'),
- complexName: z
- .string()
- .trim()
- .min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
- .max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
- physicalAddress: z
- .string()
- .trim()
- .min(5, 'La direccion debe tener al menos 5 caracteres.')
- .max(200, 'La direccion no puede superar los 200 caracteres.'),
- city: z
- .string()
- .trim()
- .max(100, 'La ciudad no puede superar los 100 caracteres.')
- .optional(),
- state: z
- .string()
- .trim()
- .max(100, 'La provincia/estado no puede superar los 100 caracteres.')
- .optional(),
- country: z
- .string()
- .trim()
- .max(100, 'El país no puede superar los 100 caracteres.')
- .optional(),
- planCode: z
- .string()
- .trim()
- .min(1, 'Debes seleccionar un plan.')
- .max(10, 'El plan seleccionado no es valido.'),
-})
-
-export const onboardingStartResponseSchema = z.object({
- message: z.string(),
- requestId: z.uuid(),
- email: z.email(),
- expiresAt: z.string().datetime(),
- cooldownSeconds: z.int().nonnegative(),
-})
-
-export const onboardingVerifyOtpResponseSchema = z.object({
- message: z.string(),
- verified: z.boolean(),
- requestId: z.uuid(),
- email: z.email().nullable(),
- expiresAt: z.string().datetime().nullable(),
- remainingAttempts: z.int().nonnegative(),
- cooldownSeconds: z.int().nonnegative(),
-})
-
-export const onboardingResendOtpResponseSchema = z.object({
- message: z.string(),
- requestId: z.uuid(),
- email: z.email(),
- expiresAt: z.string().datetime(),
- cooldownSeconds: z.int().nonnegative(),
- remainingAttempts: z.int().nonnegative(),
-})
-
-export const onboardingCompleteResponseSchema = z.object({
- message: z.string(),
- userId: z.uuid(),
- complexId: z.uuid(),
- complexSlug: z.string(),
-})
-
-export type OnboardingStartInput = z.infer
-export type OnboardingVerifyOtpInput = z.infer
-export type OnboardingResendOtpInput = z.infer
-export type OnboardingCompleteInput = z.infer
-
-export type OnboardingStartResponse = z.infer
-export type OnboardingVerifyOtpResponse = z.infer<
- typeof onboardingVerifyOtpResponseSchema
->
-export type OnboardingResendOtpResponse = z.infer<
- typeof onboardingResendOtpResponseSchema
->
-export type OnboardingCompleteResponse = z.infer<
- typeof onboardingCompleteResponseSchema
->
diff --git a/packages/api-contract/src/plan.ts b/packages/api-contract/src/plan.ts
index 51a1b89..27104ed 100644
--- a/packages/api-contract/src/plan.ts
+++ b/packages/api-contract/src/plan.ts
@@ -49,8 +49,25 @@ export const planSummarySchema = z.object({
price: z.number().nonnegative(),
})
+export const planLimitsSchema = z.object({
+ maxCourts: positiveInt,
+ maxBookingsPerDay: positiveInt,
+ maxActiveUsers: positiveInt.optional(),
+ maxConcurrentBookingsPerSlot: positiveInt.optional(),
+})
+
+export const planWithFeaturesSchema = z.object({
+ code: z.string().trim().min(1).max(10),
+ name: z.string().trim().min(1).max(30),
+ price: z.number().nonnegative(),
+ features: planFeatureFlagsSchema,
+ limits: planLimitsSchema,
+})
+
export type PlanFeatureFlags = z.infer
export type PlanRulesV1 = z.infer
export type PlanRules = z.infer
export type Plan = z.infer
export type PlanSummary = z.infer
+export type PlanWithFeatures = z.infer
+export type PlanLimits = z.infer
--
2.49.1