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/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/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/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 b8d6ade..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) => { @@ -91,7 +76,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.

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 ( -
- {verifiedEmail && ( - - Email verificado - - - )} - - - Contraseña - - - - - - Nombre del complejo - - - - - - Direccion fisica - - - - - - Plan - ( - - )} - /> - {plans.length === 0 && ( -

- Carga planes en la base para continuar con el onboarding. -

- )} - -
- - {errorMessage &&

{errorMessage}

} - {infoMessage &&

{infoMessage}

} - - -
- ); -} 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 ( -
- - Nombre - - - - - - Email - - - - - {errorMessage &&

{errorMessage}

} - {infoMessage &&

{infoMessage}

} - - -
- ); -} 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 ( -
-

- Ingresa el codigo de 6 digitos que enviamos a{' '} - {email ?? 'tu email'}. -

- - -
- Codigo de verificacion - -
- - { - form.setValue('otp', value, { shouldValidate: true }); - }} - > - - - - - - - - - - - - - - -
- -

- Intentos restantes: {remainingAttempts} -

- - {errorMessage &&

{errorMessage}

} - {infoMessage &&

{infoMessage}

} - - -
- ); -} 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} +
+
+ +
+ ))} +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + {currentStep === 'step-5' ? ( + + ) : ( + + + + )} +
+
+ ); +} 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. +

+
+ + + + {setupCourts && ( +
+ + Deporte + + {errors.courtSportId && ( +

+ {String(errors.courtSportId.message ?? '')} +

+ )} +
+ +
+ + Horario desde + + {errors.courtStartTime && ( +

+ {String(errors.courtStartTime.message ?? '')} +

+ )} +
+ + + Horario hasta + + {errors.courtEndTime && ( +

+ {String(errors.courtEndTime.message ?? '')} +

+ )} +
+
+ +
+ +
+ {DAYS_OF_WEEK.map((day) => { + const isSelected = courtDaysOfWeek?.includes(day.value) ?? false; + return ( + + ); + })} +
+ {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 ( +
+
+

Ubicación

+

¿Dónde está ubicado tu complejo?

+
+ + + Dirección + + + + + + Ciudad + + + + +
+ + Provincia / Estado + + + + + + País + + + +
+
+ ); +} 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 ( + + ); + })} +
+ + {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. -

- -
- {user?.email && ( - - Email - - - )} - - - Nombre del complejo - - - - - - Dirección física - - - - - - Ciudad - - - - - - Provincia/Estado - - - - - - País - - - - - - Plan - - {plans.length === 0 && ( -

- Carga planes en la base para continuar. -

- )} - -
- - {errorMessage &&

{errorMessage}

} - - -
-
-
- ); -} 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.'} -

- - - -
-
- -
-
- O -
-
- - {mode === 'login' ? ( -
- - Email - - - - - - Contraseña - - - - - {submitError &&

{submitError}

} - - -
- ) : ( -
- - Nombre completo - - - - - - Email - - - - - - Contraseña - - - - - - Confirmar contraseña - - - - - {submitError &&

{submitError}

} - - -
- )} - -

- {mode === 'login' ? ( - <> - ¿No tenés cuenta?{' '} - - - ) : ( - <> - ¿Ya tenés cuenta?{' '} - - - )} -

-
-
- ); -} 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 ( - - - - - ); -} 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 ( +
+
+

{errorMessage}

+
+
+ ); + } + + 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. -

- -
- - -

- ¿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 new file mode 100644 index 0000000..414f8e3 --- /dev/null +++ b/apps/frontend/src/features/signup/signup-page.tsx @@ -0,0 +1,232 @@ +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 { + const { requiresEmailConfirmation } = await signUp({ + email: values.email, + password: values.password, + fullName: values.fullName, + phone: values.phone || undefined, + }); + + sessionStorage.removeItem(STORAGE_KEY); + + if (requiresEmailConfirmation) { + sessionStorage.setItem('pending-signup-email', values.email); + await navigate({ to: '/onboard/setup' }); + } + } catch { + setSubmitError('No pudimos crear la cuenta. Intenta nuevamente.'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+ +
+ Playzer +
+ + Playzer + + +
+

Crear cuenta

+

+ Creá tu cuenta para comenzar a usar Playzer. +

+ + + +
+
+ +
+
+ O +
+
+ +
+ + Nombre completo + + + + + + Email + + + + + + + Teléfono (opcional) + + + + + + + Contraseña + + + + + + Confirmar contraseña + + + + + {submitError &&

{submitError}

} + + +
+ +

+ ¿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..d8f475e 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, @@ -32,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/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..2de1645 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' @@ -16,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' @@ -29,6 +26,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', @@ -63,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({ @@ -133,13 +115,10 @@ 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 - '/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 @@ -149,14 +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 @@ -170,14 +147,11 @@ 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 - '/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 @@ -193,13 +167,10 @@ export interface FileRouteTypes { | '/onboard' | '/reset-password' | '/select-complex' + | '/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' @@ -209,14 +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' @@ -229,14 +198,11 @@ export interface FileRouteTypes { | '/onboard' | '/reset-password' | '/select-complex' + | '/signup' | '/_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' @@ -251,11 +217,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' @@ -305,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': { @@ -422,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 = @@ -462,6 +400,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/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/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, +}); 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