commit 50bf3fe210ca8abe10bc4e766cb4ffe23cf136e1 Author: Sidney Date: Wed Jun 3 22:00:00 2026 -0300 feat: integracao asaas gateway e painel admin diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1399a05 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +.next +.git +*.md +.env* +docker-compose*.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f763411 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Dependencies +node_modules/ +/.pnp +.pnp.js + +# Testing +/coverage + +# Next.js +.next/ +/out/ + +# Production build +/build + +# Debug logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# Local env files +.env*.local +.env +.env.development +.env.production + +# Vercel +.vercel + +# IDE/OS files +.DS_Store +*.pem +Thumbs.db +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..613d01f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM node:20-alpine AS base + +# --- Dependencies --- +FROM base AS deps +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci --only=production + +# --- Builder --- +FROM base AS builder +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci +COPY . . +RUN npm run build + +# --- Runner --- +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" +CMD ["node", "server.js"] diff --git a/admin/Dockerfile b/admin/Dockerfile new file mode 100644 index 0000000..a5d3abe --- /dev/null +++ b/admin/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine AS deps +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci --omit=dev + +FROM node:20-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/publi[c] ./public/ +USER nextjs +EXPOSE 3001 +ENV PORT=3001 +CMD ["node", "server.js"] diff --git a/admin/next-env.d.ts b/admin/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/admin/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/admin/next.config.ts b/admin/next.config.ts new file mode 100644 index 0000000..a994298 --- /dev/null +++ b/admin/next.config.ts @@ -0,0 +1,3 @@ +import type { NextConfig } from "next"; +const nextConfig: NextConfig = { output: "standalone" }; +export default nextConfig; diff --git a/admin/package-lock.json b/admin/package-lock.json new file mode 100644 index 0000000..70c0e5d --- /dev/null +++ b/admin/package-lock.json @@ -0,0 +1,1112 @@ +{ + "name": "agendapro-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agendapro-admin", + "version": "1.0.0", + "dependencies": { + "@types/node": "^22.15.21", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "lucide-react": "^0.511.0", + "next": "^15.3.3", + "pg": "^8.16.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.3" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.18.tgz", + "integrity": "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.18.tgz", + "integrity": "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.18.tgz", + "integrity": "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.18.tgz", + "integrity": "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.18.tgz", + "integrity": "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.18.tgz", + "integrity": "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.18.tgz", + "integrity": "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.18.tgz", + "integrity": "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.18.tgz", + "integrity": "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.18.tgz", + "integrity": "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.18", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.18", + "@next/swc-darwin-x64": "15.5.18", + "@next/swc-linux-arm64-gnu": "15.5.18", + "@next/swc-linux-arm64-musl": "15.5.18", + "@next/swc-linux-x64-gnu": "15.5.18", + "@next/swc-linux-x64-musl": "15.5.18", + "@next/swc-win32-arm64-msvc": "15.5.18", + "@next/swc-win32-x64-msvc": "15.5.18", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/pg": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 0000000..5e96644 --- /dev/null +++ b/admin/package.json @@ -0,0 +1,21 @@ +{ + "name": "agendapro-admin", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev -p 3001", + "build": "next build", + "start": "next start -p 3001" + }, + "dependencies": { + "next": "^15.3.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.3", + "@types/node": "^22.15.21", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "lucide-react": "^0.511.0", + "pg": "^8.16.0" + } +} diff --git a/admin/src/app/globals.css b/admin/src/app/globals.css new file mode 100644 index 0000000..5d497d4 --- /dev/null +++ b/admin/src/app/globals.css @@ -0,0 +1,178 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap'); + +:root { + --bg-primary: #080810; + --bg-secondary: #0e0e1a; + --bg-card: #141425; + --bg-card-hover: #1a1a30; + --bg-elevated: #202038; + --border-color: rgba(255,255,255,0.06); + --border-active: rgba(245,158,11,0.5); + --text-primary: #f0f0f5; + --text-secondary: #8888a0; + --text-muted: #55556a; + --accent: #f59e0b; + --accent-hover: #d97706; + --accent-glow: rgba(245,158,11,0.15); + --success: #22c55e; + --success-bg: rgba(34,197,94,0.1); + --warning: #f59e0b; + --warning-bg: rgba(245,158,11,0.1); + --danger: #ef4444; + --danger-bg: rgba(239,68,68,0.1); + --info: #6C63FF; + --info-bg: rgba(108,99,255,0.1); + --gradient-primary: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%); + --shadow-glow: 0 0 30px rgba(245,158,11,0.12); + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 24px; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } +html, body { height: 100%; } + +body { + font-family: 'Inter', -apple-system, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +::-webkit-scrollbar { width: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--bg-elevated); border-radius: 3px; } + +.app-layout { display: flex; min-height: 100vh; } + +.sidebar { + width: 260px; background: var(--bg-secondary); border-right: 1px solid var(--border-color); + display: flex; flex-direction: column; position: fixed; top: 0; left: 0; bottom: 0; z-index: 100; +} +.sidebar-header { padding: 24px 20px; border-bottom: 1px solid var(--border-color); } +.sidebar-logo { display: flex; align-items: center; gap: 12px; } +.logo-icon { + width: 40px; height: 40px; background: var(--gradient-primary); border-radius: var(--radius-md); + display: flex; align-items: center; justify-content: center; font-size: 18px; color: white; +} +.logo-text { font-size: 18px; font-weight: 800; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } +.logo-sub { font-size: 11px; color: var(--text-muted); font-weight: 500; } + +.sidebar-nav { flex: 1; padding: 16px 12px; overflow-y: auto; } +.nav-section-title { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1.2px; padding: 16px 12px 8px; } +.nav-item { + display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm); + color: var(--text-secondary); font-size: 14px; font-weight: 500; cursor: pointer; border: none; + background: none; width: 100%; text-align: left; transition: all 0.2s; text-decoration: none; font-family: inherit; +} +.nav-item:hover { background: var(--bg-card); color: var(--text-primary); } +.nav-item.active { background: var(--accent-glow); color: var(--accent); box-shadow: inset 3px 0 0 var(--accent); } +.nav-item svg { width: 20px; height: 20px; flex-shrink: 0; } + +.main-content { flex: 1; margin-left: 260px; min-height: 100vh; } + +.topbar { + display: flex; align-items: center; justify-content: space-between; padding: 16px 32px; + border-bottom: 1px solid var(--border-color); background: rgba(8,8,16,0.8); backdrop-filter: blur(20px); + position: sticky; top: 0; z-index: 50; +} +.topbar h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; } +.topbar p { font-size: 13px; color: var(--text-secondary); margin-top: 2px; } +.page-content { padding: 32px; } + +.card { + background: var(--bg-card); border: 1px solid var(--border-color); border-radius: var(--radius-lg); + padding: 24px; transition: all 0.3s; +} +.card:hover { border-color: var(--border-active); box-shadow: var(--shadow-glow); } +.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; } +.card-title { font-size: 16px; font-weight: 600; } + +.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-bottom: 32px; } +.stat-card { + background: var(--bg-card); border: 1px solid var(--border-color); border-radius: var(--radius-lg); + padding: 24px; display: flex; align-items: flex-start; gap: 16px; transition: all 0.3s; +} +.stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); border-color: var(--border-active); } +.stat-icon { width: 48px; height: 48px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; flex-shrink: 0; } +.stat-icon.amber { background: var(--accent-glow); color: var(--accent); } +.stat-icon.green { background: var(--success-bg); color: var(--success); } +.stat-icon.red { background: var(--danger-bg); color: var(--danger); } +.stat-icon.purple { background: var(--info-bg); color: var(--info); } +.stat-value { font-size: 28px; font-weight: 800; letter-spacing: -1px; line-height: 1.2; } +.stat-label { font-size: 13px; color: var(--text-secondary); margin-top: 2px; } + +.btn { + display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; border-radius: var(--radius-sm); + font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; font-family: inherit; +} +.btn-primary { background: var(--gradient-primary); color: white; box-shadow: var(--shadow-glow); } +.btn-primary:hover { transform: translateY(-1px); } +.btn-secondary { background: var(--bg-elevated); color: var(--text-primary); border: 1px solid var(--border-color); } +.btn-danger { background: var(--danger-bg); color: var(--danger); border: 1px solid rgba(239,68,68,0.2); } +.btn-ghost { background: transparent; color: var(--text-secondary); padding: 8px 12px; border: none; cursor: pointer; font-family: inherit; } +.btn-ghost:hover { color: var(--text-primary); background: var(--bg-card); } +.btn-sm { padding: 6px 14px; font-size: 13px; } +.btn-icon { padding: 8px; border-radius: var(--radius-sm); } + +.form-group { margin-bottom: 20px; } +.form-label { display: block; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; } +.form-input, .form-select, .form-textarea { + width: 100%; padding: 10px 14px; background: var(--bg-primary); border: 1px solid var(--border-color); + border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; font-family: inherit; outline: none; transition: all 0.2s; +} +.form-input:focus, .form-select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } +.form-textarea { resize: vertical; min-height: 80px; } + +.table-container { overflow-x: auto; border-radius: var(--radius-lg); border: 1px solid var(--border-color); } +table { width: 100%; border-collapse: collapse; } +th { text-align: left; padding: 12px 16px; font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.8px; background: var(--bg-secondary); border-bottom: 1px solid var(--border-color); } +td { padding: 14px 16px; font-size: 14px; border-bottom: 1px solid var(--border-color); vertical-align: middle; } +tr:last-child td { border-bottom: none; } +tr:hover td { background: var(--bg-card-hover); } + +.badge { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; } +.badge-success { background: var(--success-bg); color: var(--success); } +.badge-warning { background: var(--warning-bg); color: var(--warning); } +.badge-danger { background: var(--danger-bg); color: var(--danger); } +.badge-info { background: var(--info-bg); color: var(--info); } +.badge-amber { background: var(--accent-glow); color: var(--accent); } + +.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; animation: fadeIn 0.2s ease; } +.modal { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-xl); width: 90%; max-width: 560px; max-height: 85vh; overflow-y: auto; animation: slideUp 0.3s ease; } +.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 24px; border-bottom: 1px solid var(--border-color); } +.modal-title { font-size: 18px; font-weight: 700; } +.modal-body { padding: 24px; } +.modal-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 16px 24px; border-top: 1px solid var(--border-color); } + +.avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--gradient-primary); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; color: white; flex-shrink: 0; } +.flex-between { display: flex; align-items: center; justify-content: space-between; } +.flex-gap { display: flex; align-items: center; gap: 12px; } +.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } +.mb-4 { margin-bottom: 16px; } +.mb-6 { margin-bottom: 24px; } +.mt-4 { margin-top: 16px; } +.mt-6 { margin-top: 24px; } + +.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border-color); margin-bottom: 24px; } +.tab { padding: 10px 20px; font-size: 14px; font-weight: 500; color: var(--text-secondary); cursor: pointer; border: none; background: none; position: relative; transition: all 0.2s; font-family: inherit; } +.tab:hover { color: var(--text-primary); } +.tab.active { color: var(--accent); } +.tab.active::after { content: ''; position: absolute; bottom: -1px; left: 0; right: 0; height: 2px; background: var(--accent); border-radius: 2px 2px 0 0; } + +.toggle { width: 44px; height: 24px; border-radius: 12px; background: var(--bg-elevated); border: 1px solid var(--border-color); cursor: pointer; position: relative; transition: all 0.3s; } +.toggle.active { background: var(--accent); border-color: var(--accent); } +.toggle::after { content: ''; width: 18px; height: 18px; border-radius: 50%; background: white; position: absolute; top: 2px; left: 2px; transition: all 0.3s; } +.toggle.active::after { transform: translateX(20px); } + +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } +.slide-up { animation: slideUp 0.4s ease; } +.fade-in { animation: fadeIn 0.3s ease; } + +.revenue-chart { display: flex; align-items: flex-end; gap: 6px; height: 160px; padding: 16px 0; } +.chart-bar { flex: 1; border-radius: 4px 4px 0 0; background: var(--gradient-primary); transition: all 0.3s; min-width: 20px; cursor: pointer; position: relative; } +.chart-bar:hover { opacity: 0.8; } diff --git a/admin/src/app/layout.tsx b/admin/src/app/layout.tsx new file mode 100644 index 0000000..ce87198 --- /dev/null +++ b/admin/src/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "AgendaPRO Admin — Gerenciador de Assinaturas", + description: "Painel administrativo para gerenciamento de assinaturas, tenants e planos do AgendaPRO SaaS.", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/admin/src/app/page.tsx b/admin/src/app/page.tsx new file mode 100644 index 0000000..5dfdce3 --- /dev/null +++ b/admin/src/app/page.tsx @@ -0,0 +1,101 @@ +'use client'; +import { useState } from 'react'; +import { LayoutDashboard, Users, CreditCard, Package, Settings, Shield, TrendingUp, Bell, LogOut } from 'lucide-react'; +import AdminDashboard from '@/components/AdminDashboard'; +import Tenants from '@/components/Tenants'; +import Plans from '@/components/Plans'; +import Payments from '@/components/Payments'; +import AdminSettings from '@/components/AdminSettings'; + +const navItems = [ + { section: 'Visão Geral' }, + { id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, + { section: 'Gestão' }, + { id: 'tenants', label: 'Assinantes', icon: Users }, + { id: 'plans', label: 'Planos', icon: Package }, + { id: 'payments', label: 'Pagamentos', icon: CreditCard }, + { section: 'Sistema' }, + { id: 'settings', label: 'Configurações', icon: Settings }, +]; + +const pages: Record = { + dashboard: { title: 'Dashboard', subtitle: 'Visão geral das assinaturas' }, + tenants: { title: 'Assinantes', subtitle: 'Gerenciar estabelecimentos' }, + plans: { title: 'Planos', subtitle: 'Gerenciar planos de assinatura' }, + payments: { title: 'Pagamentos', subtitle: 'Histórico de cobranças' }, + settings: { title: 'Configurações', subtitle: 'Configurações do sistema' }, +}; + +export default function AdminPage() { + const [currentPage, setCurrentPage] = useState('dashboard'); + + const renderPage = () => { + switch (currentPage) { + case 'dashboard': return ; + case 'tenants': return ; + case 'plans': return ; + case 'payments': return ; + case 'settings': return ; + default: return ; + } + }; + + return ( +
+ + +
+
+
+

{pages[currentPage]?.title}

+

{pages[currentPage]?.subtitle}

+
+
+ +
SA
+
+
+
+ {renderPage()} +
+
+
+ ); +} diff --git a/admin/src/components/AdminDashboard.tsx b/admin/src/components/AdminDashboard.tsx new file mode 100644 index 0000000..ef24e37 --- /dev/null +++ b/admin/src/components/AdminDashboard.tsx @@ -0,0 +1,180 @@ +import { useState, useEffect } from 'react'; +import { DollarSign, Users, TrendingUp, AlertTriangle, ArrowUpRight, CreditCard, Building2 } from 'lucide-react'; + +interface Props { onNavigate: (page: string) => void; } + +const recentTenants = [ + { id: '1', name: 'Barbearia Premium', plan: 'Professional', status: 'active', mrr: 99.90, date: '2026-05-20' }, + { id: '2', name: 'Studio Bella Nails', plan: 'Starter', status: 'active', mrr: 49.90, date: '2026-05-22' }, + { id: '3', name: 'Spa Relaxe', plan: 'Business', status: 'active', mrr: 199.90, date: '2026-05-18' }, + { id: '4', name: 'Barber Shop Elite', plan: 'Professional', status: 'trial', mrr: 0, date: '2026-05-28' }, + { id: '5', name: 'Salão Glamour', plan: 'Starter', status: 'past_due', mrr: 49.90, date: '2026-05-10' }, +]; + +const revenueData = [65, 72, 58, 80, 95, 88, 102, 91, 110, 98, 115, 125]; +const months = ['Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez', 'Jan', 'Fev', 'Mar', 'Abr', 'Mai']; + +const statusMap: Record = { + active: { label: 'Ativo', cls: 'badge-success' }, + trial: { label: 'Trial', cls: 'badge-info' }, + past_due: { label: 'Inadimplente', cls: 'badge-danger' }, + cancelled: { label: 'Cancelado', cls: 'badge-warning' }, + suspended: { label: 'Suspenso', cls: 'badge-danger' }, +}; + +export default function AdminDashboard({ onNavigate }: Props) { + const [tenants, setTenants] = useState(recentTenants); + const [mrrValue, setMrrValue] = useState(12480); + const maxRevenue = Math.max(...revenueData); + + useEffect(() => { + const getCookie = (name: string): string => { + if (typeof document === 'undefined') return ''; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()!.split(';').shift() || ''; + return ''; + }; + + const savedPlan = getCookie('agendapro_plan'); + if (savedPlan) { + setTenants(prev => prev.map(t => { + if (t.id === '1') { + let mrr = 99.90; + if (savedPlan === 'Starter') mrr = 49.90; + else if (savedPlan === 'Business') mrr = 199.90; + else if (savedPlan === 'Enterprise') mrr = 399.90; + return { ...t, plan: savedPlan, mrr }; + } + return t; + })); + + let diff = 0; + if (savedPlan === 'Starter') diff = 49.90 - 99.90; + else if (savedPlan === 'Business') diff = 199.90 - 99.90; + else if (savedPlan === 'Enterprise') diff = 399.90 - 99.90; + setMrrValue(12480 + diff); + } + }, []); + + return ( +
+
+
+
+
+
R$ {mrrValue.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
+
MRR (Receita Mensal)
+
↑ 18% vs mês anterior
+
+
+
+
+
+
87
+
Assinantes Ativos
+
↑ 12 novos este mês
+
+
+
+
+
+
94.3%
+
Taxa de Retenção
+
↑ 2.1% vs mês anterior
+
+
+
+
+
+
5
+
Inadimplentes
+
↑ 2 vs mês anterior
+
+
+
+ +
+
+
+

Receita Mensal (MRR)

+ Últimos 12 meses +
+
+ {revenueData.map((val, i) => ( +
+
+ {months[i]} +
+ ))} +
+
+ +
+
+

Distribuição de Planos

+ {[ + { name: 'Starter', count: 35, color: '#3b82f6', pct: 40 }, + { name: 'Professional', count: 32, color: '#6C63FF', pct: 37 }, + { name: 'Business', count: 15, color: '#a855f7', pct: 17 }, + { name: 'Enterprise', count: 5, color: '#f59e0b', pct: 6 }, + ].map(p => ( +
+
+ {p.name} + {p.count} ({p.pct}%) +
+
+
+
+
+ ))} +
+ +
+

Métricas Rápidas

+ {[ + { label: 'Ticket Médio', value: 'R$ 143,45' }, + { label: 'Churn Rate', value: '5.7%' }, + { label: 'LTV Médio', value: 'R$ 1.723' }, + { label: 'Em Trial', value: '14' }, + ].map((m, i) => ( +
+ {m.label} + {m.value} +
+ ))} +
+
+
+ +
+
+

+ Últimos Assinantes +

+ +
+
+ + + + {tenants.map(t => ( + + + + + + + + ))} + +
EstabelecimentoPlanoStatusMRRDesde
{t.name[0]}
{t.name}
{t.plan}{statusMap[t.status].label}{t.mrr > 0 ? `R$ ${t.mrr.toFixed(2)}` : '-'}{new Date(t.date + 'T12:00:00').toLocaleDateString('pt-BR')}
+
+
+
+ ); +} diff --git a/admin/src/components/AdminSettings.tsx b/admin/src/components/AdminSettings.tsx new file mode 100644 index 0000000..1bd5010 --- /dev/null +++ b/admin/src/components/AdminSettings.tsx @@ -0,0 +1,151 @@ +'use client'; +import { useState } from 'react'; +import { Save, Globe, Database, Shield, Key, Mail, Bell } from 'lucide-react'; + +export default function AdminSettings() { + const [activeTab, setActiveTab] = useState('general'); + const [saved, setSaved] = useState(false); + const handleSave = () => { setSaved(true); setTimeout(() => setSaved(false), 2000); }; + + const tabs = [ + { id: 'general', label: 'Geral', icon: Globe }, + { id: 'database', label: 'Banco de Dados', icon: Database }, + { id: 'security', label: 'Segurança', icon: Shield }, + { id: 'email', label: 'E-mail / SMTP', icon: Mail }, + { id: 'webhooks', label: 'Webhooks', icon: Bell }, + ]; + + const [maintenanceMode, setMaintenanceMode] = useState(false); + const [openRegistration, setOpenRegistration] = useState(true); + const [autoTrial, setAutoTrial] = useState(true); + + // Security Toggles + const [auth2FA, setAuth2FA] = useState(false); + const [autoBlock, setAutoBlock] = useState(true); + + // Webhook Toggles + const [notifyNewTenant, setNotifyNewTenant] = useState(true); + const [alertFailure, setAlertFailure] = useState(true); + const [dailyReport, setDailyReport] = useState(false); + + const toggles = [ + { label: 'Modo Manutenção', desc: 'Desabilita o acesso dos tenants à plataforma', value: maintenanceMode, setter: setMaintenanceMode }, + { label: 'Registro aberto', desc: 'Permitir que novos estabelecimentos se cadastrem', value: openRegistration, setter: setOpenRegistration }, + { label: 'Trial automático', desc: 'Novos cadastros iniciam com período trial gratuito', value: autoTrial, setter: setAutoTrial }, + ]; + + return ( +
+
+ {tabs.map(t => { + const Icon = t.icon; + return ( + + ); + })} +
+ + {activeTab === 'general' && ( +
+

Configurações Gerais da Plataforma

+
+
+
+
+
+
+
+
+
+ {toggles.map((item, i) => ( +
+
{item.label}
{item.desc}
+
item.setter(!item.value)} /> +
+ ))} + +
+ )} + + {activeTab === 'database' && ( +
+

Configuração do Banco de Dados

+
+
+
+
+
+
+
+
+
+
+ +
+ )} + + {activeTab === 'security' && ( +
+

Segurança e Autenticação

+
+
+
+
+
+ {[ + { label: 'Autenticação 2FA', desc: 'Exigir segundo fator para admins', value: auth2FA, setter: setAuth2FA }, + { label: 'Bloqueio automático', desc: 'Bloquear conta após tentativas excessivas', value: autoBlock, setter: setAutoBlock }, + ].map((item, i) => ( +
+
{item.label}
{item.desc}
+
item.setter(!item.value)} /> +
+ ))} + +
+ )} + + {activeTab === 'email' && ( +
+

Configurações de E-mail (SMTP)

+
+
+
+
+
+
+
+
+
+
+
TLS/SSL
Usar conexão segura
+
(e.currentTarget as HTMLElement).classList.toggle('active')} /> +
+ +
+ )} + + {activeTab === 'webhooks' && ( +
+

Webhooks e Integrações

+
+
+
+ {[ + { label: 'Notificação de novo assinante', desc: 'Enviar alerta quando novo tenant se cadastra', value: notifyNewTenant, setter: setNotifyNewTenant }, + { label: 'Alerta de inadimplência', desc: 'Notificar quando pagamento falhar', value: alertFailure, setter: setAlertFailure }, + { label: 'Relatório diário', desc: 'Enviar resumo diário de métricas', value: dailyReport, setter: setDailyReport }, + ].map((item, i) => ( +
+
{item.label}
{item.desc}
+
item.setter(!item.value)} /> +
+ ))} + +
+ )} +
+ ); +} diff --git a/admin/src/components/Payments.tsx b/admin/src/components/Payments.tsx new file mode 100644 index 0000000..dcbca32 --- /dev/null +++ b/admin/src/components/Payments.tsx @@ -0,0 +1,95 @@ +'use client'; +import { useState } from 'react'; +import { Search, Download, Eye, CheckCircle, AlertCircle, XCircle, DollarSign, Filter } from 'lucide-react'; + +interface Payment { + id: string; tenant: string; plan: string; amount: number; status: string; method: string; date: string; invoiceId: string; +} + +const statusMap: Record = { + paid: { label: 'Pago', cls: 'badge-success', icon: CheckCircle }, + pending: { label: 'Pendente', cls: 'badge-warning', icon: AlertCircle }, + failed: { label: 'Falhou', cls: 'badge-danger', icon: XCircle }, + refunded: { label: 'Estornado', cls: 'badge-info', icon: DollarSign }, +}; + +const initialPayments: Payment[] = [ + { id: '1', tenant: 'Barbearia Premium', plan: 'Professional', amount: 99.90, status: 'paid', method: 'Cartão de Crédito', date: '2026-05-31', invoiceId: 'INV-2026-001' }, + { id: '2', tenant: 'Studio Bella Nails', plan: 'Starter', amount: 49.90, status: 'paid', method: 'PIX', date: '2026-05-30', invoiceId: 'INV-2026-002' }, + { id: '3', tenant: 'Spa Relaxe', plan: 'Business', amount: 199.90, status: 'paid', method: 'Boleto', date: '2026-05-29', invoiceId: 'INV-2026-003' }, + { id: '4', tenant: 'Salão Glamour', plan: 'Starter', amount: 49.90, status: 'failed', method: 'Cartão de Crédito', date: '2026-05-28', invoiceId: 'INV-2026-004' }, + { id: '5', tenant: 'Estética Renova', plan: 'Business', amount: 199.90, status: 'paid', method: 'PIX', date: '2026-05-28', invoiceId: 'INV-2026-005' }, + { id: '6', tenant: 'Barbearia Premium', plan: 'Professional', amount: 99.90, status: 'paid', method: 'Cartão de Crédito', date: '2026-04-30', invoiceId: 'INV-2026-006' }, + { id: '7', tenant: 'Studio Bella Nails', plan: 'Starter', amount: 49.90, status: 'paid', method: 'PIX', date: '2026-04-30', invoiceId: 'INV-2026-007' }, + { id: '8', tenant: 'Spa Relaxe', plan: 'Business', amount: 199.90, status: 'paid', method: 'Boleto', date: '2026-04-29', invoiceId: 'INV-2026-008' }, + { id: '9', tenant: 'Salão Glamour', plan: 'Starter', amount: 49.90, status: 'pending', method: 'Boleto', date: '2026-05-15', invoiceId: 'INV-2026-009' }, + { id: '10', tenant: 'Barbearia do Zé', plan: 'Starter', amount: 49.90, status: 'refunded', method: 'PIX', date: '2026-04-20', invoiceId: 'INV-2026-010' }, +]; + +export default function Payments() { + const [search, setSearch] = useState(''); + const [filterStatus, setFilterStatus] = useState('all'); + const [filterMonth, setFilterMonth] = useState('all'); + + const filtered = initialPayments.filter(p => { + const matchSearch = p.tenant.toLowerCase().includes(search.toLowerCase()) || p.invoiceId.toLowerCase().includes(search.toLowerCase()); + const matchStatus = filterStatus === 'all' || p.status === filterStatus; + const matchMonth = filterMonth === 'all' || p.date.startsWith(filterMonth); + return matchSearch && matchStatus && matchMonth; + }); + + const totalPaid = filtered.filter(p => p.status === 'paid').reduce((s, p) => s + p.amount, 0); + const totalPending = filtered.filter(p => p.status === 'pending').reduce((s, p) => s + p.amount, 0); + const totalFailed = filtered.filter(p => p.status === 'failed').reduce((s, p) => s + p.amount, 0); + + return ( +
+
+
R$ {totalPaid.toFixed(2)}
Total Recebido
+
R$ {totalPending.toFixed(2)}
Pendente
+
R$ {totalFailed.toFixed(2)}
Falhas
+
+ +
+
+
+ + setSearch(e.target.value)} /> +
+ + +
+ +
+ +
+
+ + + + {filtered.map(p => { + const st = statusMap[p.status]; + return ( + + + + + + + + + + ); + })} + {filtered.length === 0 && } + +
FaturaEstabelecimentoPlanoValorStatusMétodoData
{p.invoiceId}{p.tenant}{p.plan}R$ {p.amount.toFixed(2)}{st.label}{p.method}{new Date(p.date + 'T12:00:00').toLocaleDateString('pt-BR')}
Nenhum pagamento encontrado
+
+
+
+ ); +} diff --git a/admin/src/components/Plans.tsx b/admin/src/components/Plans.tsx new file mode 100644 index 0000000..512ef60 --- /dev/null +++ b/admin/src/components/Plans.tsx @@ -0,0 +1,134 @@ +'use client'; +import { useState } from 'react'; +import { Plus, X, Edit, Trash2, Check, Users, DollarSign, AlertTriangle } from 'lucide-react'; + +interface Plan { id: string; name: string; slug: string; priceMonthly: number; priceYearly: number; maxProfessionals: number; maxServices: number; features: string[]; active: boolean; subscribers: number; } + +const initialPlans: Plan[] = [ + { id: '1', name: 'Starter', slug: 'starter', priceMonthly: 49.90, priceYearly: 479, maxProfessionals: 1, maxServices: 10, features: ['Agendamento online', 'Página de reservas', 'Lembretes por e-mail', 'Relatórios básicos'], active: true, subscribers: 35 }, + { id: '2', name: 'Professional', slug: 'professional', priceMonthly: 99.90, priceYearly: 959, maxProfessionals: 5, maxServices: 30, features: ['Tudo do Starter', 'Múltiplos profissionais', 'Comissionamento', 'Relatórios avançados', 'Personalização da marca'], active: true, subscribers: 32 }, + { id: '3', name: 'Business', slug: 'business', priceMonthly: 199.90, priceYearly: 1919, maxProfessionals: 15, maxServices: 100, features: ['Tudo do Professional', 'API de integração', 'Suporte prioritário', 'Multi-unidades', 'Automações avançadas'], active: true, subscribers: 15 }, + { id: '4', name: 'Enterprise', slug: 'enterprise', priceMonthly: 399.90, priceYearly: 3839, maxProfessionals: 999, maxServices: 999, features: ['Tudo do Business', 'Gerente dedicado', 'SLA garantido', 'Integrações customizadas', 'White-label'], active: true, subscribers: 5 }, +]; + +const emptyPlan: Partial = { name: '', slug: '', priceMonthly: 0, priceYearly: 0, maxProfessionals: 1, maxServices: 10, features: [], active: true }; + +export default function Plans() { + const [plans, setPlans] = useState(initialPlans); + const [showModal, setShowModal] = useState(false); + const [editing, setEditing] = useState(null); + const [form, setForm] = useState>(emptyPlan); + const [featureInput, setFeatureInput] = useState(''); + const [deleteConfirm, setDeleteConfirm] = useState(null); + + const openAdd = () => { setEditing(null); setForm({ ...emptyPlan, id: Date.now().toString(), features: [] }); setShowModal(true); }; + const openEdit = (p: Plan) => { setEditing(p); setForm({ ...p }); setShowModal(true); }; + const handleSave = () => { + if (!form.name?.trim()) return; + if (editing) { setPlans(prev => prev.map(p => p.id === editing.id ? { ...p, ...form } as Plan : p)); } + else { setPlans(prev => [...prev, { ...form, subscribers: 0 } as Plan]); } + setShowModal(false); + }; + const handleDelete = (id: string) => { setPlans(prev => prev.filter(p => p.id !== id)); setDeleteConfirm(null); }; + const addFeature = () => { if (featureInput.trim()) { setForm(p => ({ ...p, features: [...(p.features || []), featureInput.trim()] })); setFeatureInput(''); } }; + const removeFeature = (idx: number) => { setForm(p => ({ ...p, features: (p.features || []).filter((_, i) => i !== idx) })); }; + + const colors = ['#3b82f6', '#6C63FF', '#a855f7', '#f59e0b']; + + return ( +
+
+

{plans.length} planos configurados

+ +
+ +
+ {plans.map((p, idx) => ( +
+ {deleteConfirm === p.id && ( +
+ +

Excluir plano {p.name}?

+

{p.subscribers} assinantes serão afetados

+
+
+ )} +
+

{p.name}

+
+ + +
+
+
+ R$ {p.priceMonthly.toFixed(2).replace('.', ',')} + /mês +
ou R$ {p.priceYearly.toFixed(2).replace('.', ',')} /ano
+
+
+
+
{p.maxProfessionals === 999 ? '∞' : p.maxProfessionals}
+
Profissionais
+
+
+
{p.subscribers}
+
Assinantes
+
+
+
    + {p.features.map((f, i) => ( +
  • + {f} +
  • + ))} +
+
+ ))} +
+ + {showModal && ( +
setShowModal(false)}> +
e.stopPropagation()}> +
+

{editing ? 'Editar Plano' : 'Novo Plano'}

+ +
+
+
+
setForm(p => ({ ...p, name: e.target.value }))} />
+
setForm(p => ({ ...p, slug: e.target.value }))} />
+
+
+
setForm(p => ({ ...p, priceMonthly: Number(e.target.value) }))} />
+
setForm(p => ({ ...p, priceYearly: Number(e.target.value) }))} />
+
+
+
setForm(p => ({ ...p, maxProfessionals: Number(e.target.value) }))} />
+
setForm(p => ({ ...p, maxServices: Number(e.target.value) }))} />
+
+
+ +
+ setFeatureInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && addFeature()} /> + +
+
+ {(form.features || []).map((f, i) => ( +
+ {f} + +
+ ))} +
+
+
+
+ + +
+
+
+ )} +
+ ); +} diff --git a/admin/src/components/Tenants.tsx b/admin/src/components/Tenants.tsx new file mode 100644 index 0000000..56b2e89 --- /dev/null +++ b/admin/src/components/Tenants.tsx @@ -0,0 +1,219 @@ +'use client'; +import { useState, useEffect } from 'react'; +import { Search, Plus, X, Edit, Trash2, Eye, AlertTriangle, Ban, CheckCircle, Building2 } from 'lucide-react'; + +interface Tenant { + id: string; name: string; slug: string; type: string; plan: string; status: string; email: string; phone: string; mrr: number; createdAt: string; trialEnds: string; +} + +const statusMap: Record = { + active: { label: 'Ativo', cls: 'badge-success' }, trial: { label: 'Trial', cls: 'badge-info' }, + past_due: { label: 'Inadimplente', cls: 'badge-danger' }, cancelled: { label: 'Cancelado', cls: 'badge-warning' }, + suspended: { label: 'Suspenso', cls: 'badge-danger' }, +}; + +const initialTenants: Tenant[] = [ + { id: '1', name: 'Barbearia Premium', slug: 'barbearia-premium', type: 'Barbearia', plan: 'Professional', status: 'active', email: 'contato@barbeariapremium.com.br', phone: '(11) 99999-1234', mrr: 99.90, createdAt: '2026-01-15', trialEnds: '' }, + { id: '2', name: 'Studio Bella Nails', slug: 'bella-nails', type: 'Manicure', plan: 'Starter', status: 'active', email: 'bella@nails.com', phone: '(21) 98888-5555', mrr: 49.90, createdAt: '2026-02-20', trialEnds: '' }, + { id: '3', name: 'Spa Relaxe', slug: 'spa-relaxe', type: 'Massagem', plan: 'Business', status: 'active', email: 'contato@sparelaxe.com', phone: '(11) 97777-3333', mrr: 199.90, createdAt: '2026-01-05', trialEnds: '' }, + { id: '4', name: 'Barber Shop Elite', slug: 'barber-elite', type: 'Barbearia', plan: 'Professional', status: 'trial', email: 'elite@barber.com', phone: '(31) 96666-4444', mrr: 0, createdAt: '2026-05-28', trialEnds: '2026-06-27' }, + { id: '5', name: 'Salão Glamour', slug: 'salao-glamour', type: 'Salão', plan: 'Starter', status: 'past_due', email: 'glamour@salao.com', phone: '(41) 95555-7777', mrr: 49.90, createdAt: '2026-03-10', trialEnds: '' }, + { id: '6', name: 'Estética Renova', slug: 'estetica-renova', type: 'Estética', plan: 'Business', status: 'active', email: 'renova@estetica.com', phone: '(51) 94444-8888', mrr: 199.90, createdAt: '2026-04-01', trialEnds: '' }, + { id: '7', name: 'Barbearia do Zé', slug: 'barbearia-ze', type: 'Barbearia', plan: 'Starter', status: 'cancelled', email: 'ze@barbearia.com', phone: '(61) 93333-9999', mrr: 0, createdAt: '2026-02-15', trialEnds: '' }, +]; + +export default function Tenants() { + const [tenants, setTenants] = useState(initialTenants); + const [search, setSearch] = useState(''); + const [filterStatus, setFilterStatus] = useState('all'); + const [showModal, setShowModal] = useState(false); + const [editing, setEditing] = useState(null); + const [deleteConfirm, setDeleteConfirm] = useState(null); + const [form, setForm] = useState>({}); + + useEffect(() => { + // Busca dados reais do banco do Barbearia Premium para mostrar com transparência + fetch('/api/tenant/subscription?slug=barbearia-premium') + .then(res => res.json()) + .then(data => { + if (data && data.status) { + setTenants(prev => prev.map(t => { + if (t.id === '1') { + let mrr = 99.90; + if (data.subscription?.amount) { + mrr = parseFloat(data.subscription.amount); + } + return { + ...t, + plan: data.subscription?.plan_name || t.plan, + status: data.status, + mrr + }; + } + return t; + })); + } + }) + .catch(err => { + console.log('Não foi possível carregar dados em tempo real da API principal. Usando cookie fallback.'); + const getCookie = (name: string): string => { + if (typeof document === 'undefined') return ''; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()!.split(';').shift() || ''; + return ''; + }; + + const savedPlan = getCookie('agendapro_plan'); + if (savedPlan) { + setTenants(prev => prev.map(t => { + if (t.id === '1') { + let mrr = 99.90; + if (savedPlan === 'Starter') mrr = 49.90; + else if (savedPlan === 'Business') mrr = 199.90; + else if (savedPlan === 'Enterprise') mrr = 399.90; + return { ...t, plan: savedPlan, mrr }; + } + return t; + })); + } + }); + }, []); + + const filtered = tenants.filter(t => { + const matchSearch = t.name.toLowerCase().includes(search.toLowerCase()) || t.email.toLowerCase().includes(search.toLowerCase()); + const matchStatus = filterStatus === 'all' || t.status === filterStatus; + return matchSearch && matchStatus; + }); + + const openEdit = (t: Tenant) => { setEditing(t); setForm({ ...t }); setShowModal(true); }; + const openAdd = () => { setEditing(null); setForm({ id: Date.now().toString(), status: 'trial', plan: 'Starter', mrr: 0, createdAt: new Date().toISOString().split('T')[0] }); setShowModal(true); }; + const handleSave = () => { + if (!form.name?.trim()) return; + if (editing) { setTenants(prev => prev.map(t => t.id === editing.id ? { ...t, ...form } as Tenant : t)); } + else { setTenants(prev => [...prev, form as Tenant]); } + setShowModal(false); + }; + const handleDelete = (id: string) => { setTenants(prev => prev.filter(t => t.id !== id)); setDeleteConfirm(null); }; + + const toggleStatus = async (id: string, newStatus: string) => { + if (id === '1') { + try { + const res = await fetch('/api/tenant/subscription/simulate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + slug: 'barbearia-premium', + status: newStatus === 'active' ? 'active' : 'suspended' + }) + }); + if (!res.ok) { + alert('Erro ao sincronizar status no banco de dados do AgendaPRO.'); + return; + } + } catch (err) { + console.error('Falha ao conectar na API do AgendaPRO:', err); + } + } + setTenants(prev => prev.map(t => t.id === id ? { ...t, status: newStatus } : t)); + }; + + return ( +
+
+
+
+ + setSearch(e.target.value)} /> +
+ +
+ +
+ +
+
+ + + + {filtered.map(t => ( + + + + + + + + + + ))} + +
EstabelecimentoTipoPlanoStatusMRRDesdeAções
{t.name[0]}
{t.name}
{t.email}
{t.type}{t.plan}{statusMap[t.status]?.label}{t.mrr > 0 ? `R$ ${t.mrr.toFixed(2)}` : '-'}{new Date(t.createdAt + 'T12:00:00').toLocaleDateString('pt-BR')} + {deleteConfirm === t.id ? ( +
+ + +
+ ) : ( +
+ + {t.status === 'active' && } + {t.status === 'suspended' && } + +
+ )} +
+
+
+ + {showModal && ( +
setShowModal(false)}> +
e.stopPropagation()}> +
+

{editing ? 'Editar Assinante' : 'Novo Assinante'}

+ +
+
+
setForm(p => ({ ...p, name: e.target.value }))} />
+
+
setForm(p => ({ ...p, slug: e.target.value }))} />
+
+ +
+
+
+
setForm(p => ({ ...p, email: e.target.value }))} />
+
setForm(p => ({ ...p, phone: e.target.value }))} />
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+ )} +
+ ); +} diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 0000000..f48e7ee --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + }, + "target": "ES2017" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/admin/tsconfig.tsbuildinfo b/admin/tsconfig.tsbuildinfo new file mode 100644 index 0000000..bfe5c6d --- /dev/null +++ b/admin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.esnext.error.d.ts","./node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./.next/types/routes.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/shared/lib/amp.d.ts","./node_modules/next/amp.d.ts","./node_modules/next/dist/server/get-page-files.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/filereader.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/react/canary.d.ts","./node_modules/@types/react/experimental.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-dom/canary.d.ts","./node_modules/@types/react-dom/experimental.d.ts","./node_modules/next/dist/lib/fallback.d.ts","./node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/next/dist/server/config.d.ts","./node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/next/dist/server/body-streams.d.ts","./node_modules/next/dist/server/lib/cache-control.d.ts","./node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/next/dist/lib/worker.d.ts","./node_modules/next/dist/lib/constants.d.ts","./node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/next/dist/build/rendering-mode.d.ts","./node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","./node_modules/next/dist/server/require-hook.d.ts","./node_modules/next/dist/server/lib/experimental/ppr.d.ts","./node_modules/next/dist/build/webpack/plugins/app-build-manifest-plugin.d.ts","./node_modules/next/dist/lib/page-types.d.ts","./node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","./node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","./node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/next/dist/server/node-environment-baseline.d.ts","./node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","./node_modules/next/dist/server/node-environment-extensions/random.d.ts","./node_modules/next/dist/server/node-environment-extensions/date.d.ts","./node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","./node_modules/next/dist/server/node-environment.d.ts","./node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/next/dist/server/instrumentation/types.d.ts","./node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/next/dist/trace/types.d.ts","./node_modules/next/dist/trace/trace.d.ts","./node_modules/next/dist/trace/shared.d.ts","./node_modules/next/dist/trace/index.d.ts","./node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/@next/env/dist/index.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","./node_modules/next/dist/telemetry/storage.d.ts","./node_modules/next/dist/build/build-context.d.ts","./node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/next/dist/build/webpack-config.d.ts","./node_modules/next/dist/server/route-kind.d.ts","./node_modules/next/dist/server/route-definitions/route-definition.d.ts","./node_modules/next/dist/build/swc/generated-native.d.ts","./node_modules/next/dist/build/swc/types.d.ts","./node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/next/dist/next-devtools/shared/types.d.ts","./node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","./node_modules/next/dist/server/lib/parse-stack.d.ts","./node_modules/next/dist/next-devtools/server/shared.d.ts","./node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","./node_modules/@types/react/jsx-runtime.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","./node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/next/dist/server/lib/cache-handlers/types.d.ts","./node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","./node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","./node_modules/next/dist/server/render-result.d.ts","./node_modules/next/dist/server/lib/i18n-provider.d.ts","./node_modules/next/dist/server/web/next-url.d.ts","./node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/next/dist/server/after/builtin-request-context.d.ts","./node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","./node_modules/next/dist/server/web/types.d.ts","./node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/next/dist/server/base-http/node.d.ts","./node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","./node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","./node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/next/dist/client/with-router.d.ts","./node_modules/next/dist/client/router.d.ts","./node_modules/next/dist/client/route-loader.d.ts","./node_modules/next/dist/client/page-loader.d.ts","./node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","./node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","./node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/next/dist/server/app-render/cache-signal.d.ts","./node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/next/dist/server/request/fallback-params.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","./node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/next/dist/server/lib/lazy-result.d.ts","./node_modules/next/dist/server/lib/implicit-tags.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","./node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","./node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/amp-context.shared-runtime.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","./node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","./node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","./node_modules/next/dist/client/components/client-page.d.ts","./node_modules/next/dist/client/components/client-segment.d.ts","./node_modules/next/dist/server/request/search-params.d.ts","./node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","./node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/next/dist/lib/metadata/types/resolvers.d.ts","./node_modules/next/dist/lib/metadata/types/icons.d.ts","./node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","./node_modules/next/dist/lib/metadata/metadata.d.ts","./node_modules/next/dist/lib/framework/boundary-components.d.ts","./node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","./node_modules/next/dist/server/app-render/collect-segment-data.d.ts","./node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","./node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/@types/react/jsx-dev-runtime.d.ts","./node_modules/@types/react/compiler-runtime.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","./node_modules/@types/react-dom/client.d.ts","./node_modules/@types/react-dom/static.d.ts","./node_modules/@types/react-dom/server.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.d.ts","./node_modules/next/dist/server/web/adapter.d.ts","./node_modules/next/dist/server/use-cache/cache-life.d.ts","./node_modules/next/dist/server/app-render/types.d.ts","./node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/next/dist/client/flight-data-helpers.d.ts","./node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","./node_modules/next/dist/build/templates/pages.d.ts","./node_modules/next/dist/server/route-modules/pages/module.d.ts","./node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","./node_modules/next/dist/server/render.d.ts","./node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","./node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","./node_modules/next/dist/server/route-matchers/route-matcher.d.ts","./node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/next/dist/server/normalizers/normalizer.d.ts","./node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/suffix.d.ts","./node_modules/next/dist/server/normalizers/request/rsc.d.ts","./node_modules/next/dist/server/normalizers/request/prefetch-rsc.d.ts","./node_modules/next/dist/server/normalizers/request/next-data.d.ts","./node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","./node_modules/next/dist/build/static-paths/types.d.ts","./node_modules/next/dist/server/base-server.d.ts","./node_modules/next/dist/server/lib/async-callback-set.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/sharp/lib/index.d.ts","./node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/next/dist/server/next-server.d.ts","./node_modules/next/dist/server/lib/types.d.ts","./node_modules/next/dist/server/lib/lru-cache.d.ts","./node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/next/dist/server/next.d.ts","./node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","./node_modules/next/dist/server/route-modules/route-module.d.ts","./node_modules/next/dist/server/load-components.d.ts","./node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","./node_modules/next/dist/server/async-storage/work-store.d.ts","./node_modules/next/dist/server/web/http.d.ts","./node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","./node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/next/dist/client/components/redirect-error.d.ts","./node_modules/next/dist/build/templates/app-route.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","./node_modules/next/dist/build/segment-config/app/app-segments.d.ts","./node_modules/next/dist/build/utils.d.ts","./node_modules/next/dist/build/turborepo-access-trace/types.d.ts","./node_modules/next/dist/build/turborepo-access-trace/result.d.ts","./node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","./node_modules/next/dist/build/turborepo-access-trace/index.d.ts","./node_modules/next/dist/export/routes/types.d.ts","./node_modules/next/dist/export/types.d.ts","./node_modules/next/dist/export/worker.d.ts","./node_modules/next/dist/build/worker.d.ts","./node_modules/next/dist/build/index.d.ts","./node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/next/dist/server/after/after.d.ts","./node_modules/next/dist/server/after/after-context.d.ts","./node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","./node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","./node_modules/next/dist/server/request/params.d.ts","./node_modules/next/dist/server/route-matches/route-match.d.ts","./node_modules/next/dist/server/request-meta.d.ts","./node_modules/next/dist/cli/next-test.d.ts","./node_modules/next/dist/server/config-shared.d.ts","./node_modules/next/dist/server/base-http/index.d.ts","./node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/next/dist/types.d.ts","./node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/next/dist/pages/_app.d.ts","./node_modules/next/app.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/next/dist/server/use-cache/cache-tag.d.ts","./node_modules/next/cache.d.ts","./node_modules/next/dist/shared/lib/runtime-config.external.d.ts","./node_modules/next/config.d.ts","./node_modules/next/dist/pages/_document.d.ts","./node_modules/next/document.d.ts","./node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/next/dynamic.d.ts","./node_modules/next/dist/pages/_error.d.ts","./node_modules/next/error.d.ts","./node_modules/next/dist/shared/lib/head.d.ts","./node_modules/next/head.d.ts","./node_modules/next/dist/server/request/cookies.d.ts","./node_modules/next/dist/server/request/headers.d.ts","./node_modules/next/dist/server/request/draft-mode.d.ts","./node_modules/next/headers.d.ts","./node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/next/dist/client/image-component.d.ts","./node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/next/image.d.ts","./node_modules/next/dist/client/link.d.ts","./node_modules/next/link.d.ts","./node_modules/next/dist/client/components/redirect.d.ts","./node_modules/next/dist/client/components/not-found.d.ts","./node_modules/next/dist/client/components/forbidden.d.ts","./node_modules/next/dist/client/components/unauthorized.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.d.ts","./node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/next/dist/client/components/unrecognized-action-error.d.ts","./node_modules/next/dist/client/components/navigation.d.ts","./node_modules/next/navigation.d.ts","./node_modules/next/router.d.ts","./node_modules/next/dist/client/script.d.ts","./node_modules/next/script.d.ts","./node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/emoji/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/next/dist/server/after/index.d.ts","./node_modules/next/dist/server/request/root-params.d.ts","./node_modules/next/dist/server/request/connection.d.ts","./node_modules/next/server.d.ts","./node_modules/next/types/global.d.ts","./node_modules/next/types/compiled.d.ts","./node_modules/next/types.d.ts","./node_modules/next/index.d.ts","./node_modules/next/image-types/global.d.ts","./next-env.d.ts","./next.config.ts","./src/app/layout.tsx","./node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/admindashboard.tsx","./src/components/tenants.tsx","./src/components/plans.tsx","./src/components/payments.tsx","./src/components/adminsettings.tsx","./src/app/page.tsx","./.next/types/cache-life.d.ts","./.next/types/validator.ts","./.next/types/app/layout.ts","./.next/types/app/page.ts","../node_modules/pg-types/index.d.ts","../node_modules/pg-protocol/dist/messages.d.ts","../node_modules/pg-protocol/dist/serializer.d.ts","../node_modules/pg-protocol/dist/parser.d.ts","../node_modules/pg-protocol/dist/index.d.ts","../node_modules/@types/pg/lib/type-overrides.d.ts","../node_modules/@types/pg/index.d.ts"],"fileIdsList":[[100,148,165,166,341,503],[100,148,165,166,341,510],[100,148,165,166,448,449,450,451],[100,148,165,166],[83,100,148,165,166,498,503,510],[83,100,148,165,166,499,500],[100,148,165,166,499],[100,145,146,148,165,166],[100,147,148,165,166],[148,165,166],[100,148,153,165,166,183],[100,148,149,154,159,165,166,168,180,191],[100,148,149,150,159,165,166,168],[95,96,97,100,148,165,166],[100,148,151,165,166,192],[100,148,152,153,160,165,166,169],[100,148,153,165,166,180,188],[100,148,154,156,159,165,166,168],[100,147,148,155,165,166],[100,148,156,157,165,166],[100,148,158,159,165,166],[100,147,148,159,165,166],[100,148,159,160,161,165,166,180,191],[100,148,159,160,161,165,166,175,180,183],[100,141,148,156,159,162,165,166,168,180,191],[100,148,159,160,162,163,165,166,168,180,188,191],[100,148,162,164,165,166,180,188,191],[98,99,100,101,102,103,104,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197],[100,148,159,165,166],[100,148,165,166,167,191],[100,148,156,159,165,166,168,180],[100,148,165,166,169],[100,148,165,166,170],[100,147,148,165,166,171],[100,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197],[100,148,165,166,173],[100,148,165,166,174],[100,148,159,165,166,175,176],[100,148,165,166,175,177,192,194],[100,148,160,165,166],[100,148,159,165,166,180,181,183],[100,148,165,166,182,183],[100,148,165,166,180,181],[100,148,165,166,183],[100,148,165,166,184],[100,145,148,165,166,180,185,191],[100,148,159,165,166,186,187],[100,148,165,166,186,187],[100,148,153,165,166,168,180,188],[100,148,165,166,189],[100,148,165,166,168,190],[100,148,162,165,166,174,191],[100,148,153,165,166,192],[100,148,165,166,180,193],[100,148,165,166,167,194],[100,148,165,166,195],[100,141,148,165,166],[100,141,148,159,161,165,166,171,180,183,191,193,194,196],[100,148,165,166,180,197],[86,90,100,148,165,166,199,200,201,203,443,491],[86,100,148,165,166],[86,90,100,148,165,166,199,200,201,202,358,443,491],[86,90,100,148,165,166,199,200,202,203,443,491],[86,100,148,165,166,203,358,359],[86,100,148,165,166,203,358],[86,90,100,148,165,166,200,201,202,203,443,491],[86,90,100,148,165,166,199,201,202,203,443,491],[84,85,100,148,165,166],[92,100,148,165,166],[100,148,165,166,446],[100,148,165,166,453],[100,148,165,166,207,221,222,223,225,440],[100,148,165,166,207,246,248,250,251,254,440,442],[100,148,165,166,207,211,213,214,215,216,217,429,440,442],[100,148,165,166,440],[100,148,165,166,222,324,410,419,436],[100,148,165,166,207],[100,148,165,166,204,436],[100,148,165,166,258],[100,148,165,166,257,440,442],[100,148,162,165,166,306,324,353,497],[100,148,162,165,166,317,333,419,435],[100,148,162,165,166,371],[100,148,165,166,423],[100,148,165,166,422,423,424],[100,148,165,166,422],[94,100,148,162,165,166,204,207,211,214,218,219,220,222,226,234,235,364,389,420,440,443],[100,148,165,166,207,224,242,246,247,252,253,440,497],[100,148,165,166,224,497],[100,148,165,166,235,242,304,440,497],[100,148,165,166,497],[100,148,165,166,207,224,225,497],[100,148,165,166,249,497],[100,148,165,166,218,421,428],[100,148,165,166,174,266,436],[100,148,165,166,266,436],[86,100,148,165,166,266],[86,100,148,165,166,325],[100,148,165,166,321,369,436,479,480],[100,148,165,166,416,473,474,475,476,478],[100,148,165,166,415],[100,148,165,166,415,416],[100,148,165,166,215,365,366,367],[100,148,165,166,365,368,369],[100,148,165,166,477],[100,148,165,166,365,369],[86,100,148,165,166,208,467],[86,100,148,165,166,191],[86,100,148,165,166,224,294],[86,100,148,165,166,224],[100,148,165,166,292,296],[86,100,148,165,166,293,445],[86,90,100,148,162,165,166,198,199,200,201,202,203,443,489,490],[100,148,162,165,166],[100,148,162,165,166,211,273,365,375,390,410,425,426,440,441,497],[100,148,165,166,234,427],[100,148,165,166,443],[100,148,165,166,206],[86,100,148,165,166,306,320,332,342,344,435],[100,148,165,166,174,306,320,341,342,343,435,496],[100,148,165,166,335,336,337,338,339,340],[100,148,165,166,337],[100,148,165,166,341],[100,148,165,166,264,265,266,268],[86,100,148,165,166,259,260,261,267],[100,148,165,166,264,267],[100,148,165,166,262],[100,148,165,166,263],[86,100,148,165,166,266,293,445],[86,100,148,165,166,266,444,445],[86,100,148,165,166,266,445],[100,148,165,166,390,432],[100,148,165,166,432],[100,148,162,165,166,441,445],[100,148,165,166,329],[100,147,148,165,166,328],[100,148,165,166,236,274,312,314,316,317,318,319,362,365,435,438,441],[100,148,165,166,236,350,365,369],[100,148,165,166,317,435],[86,100,148,165,166,317,326,327,329,330,331,332,333,334,345,346,347,348,349,351,352,435,436,497],[100,148,165,166,311],[100,148,162,165,166,174,236,237,273,288,318,362,363,364,369,390,410,431,440,441,442,443,497],[100,148,165,166,435],[100,147,148,165,166,222,315,318,364,431,433,434,441],[100,148,165,166,317],[100,147,148,165,166,273,278,307,308,309,310,311,312,313,314,316,435,436],[100,148,162,165,166,278,279,307,441,442],[100,148,165,166,222,364,365,390,431,435,441],[100,148,162,165,166,440,442],[100,148,162,165,166,180,438,441,442],[100,148,162,165,166,174,191,204,211,224,236,237,239,274,275,280,285,288,314,318,365,375,377,380,382,385,386,387,388,389,410,430,431,436,438,440,441,442],[100,148,162,165,166,180],[100,148,165,166,207,208,209,211,216,219,224,242,430,438,439,443,445,497],[100,148,162,165,166,180,191,254,256,258,259,260,261,268,497],[100,148,165,166,174,191,204,246,256,284,285,286,287,314,365,380,389,390,396,399,400,410,431,436,438],[100,148,165,166,218,219,234,364,389,431,440],[100,148,162,165,166,191,208,211,314,394,438,440],[100,148,165,166,305],[100,148,162,165,166,397,398,407],[100,148,165,166,438,440],[100,148,165,166,312,315],[100,148,165,166,314,318,430,445],[100,148,162,165,166,174,240,246,287,380,390,396,399,402,438],[100,148,162,165,166,218,234,246,403],[100,148,165,166,207,239,405,430,440],[100,148,162,165,166,191,440],[100,148,162,165,166,224,238,239,240,251,269,404,406,430,440],[94,100,148,165,166,236,318,409,443,445],[100,148,162,165,166,174,191,211,218,226,234,237,274,280,284,285,286,287,288,314,365,377,390,391,393,395,410,430,431,436,437,438,445],[100,148,162,165,166,180,218,396,401,407,438],[100,148,165,166,229,230,231,232,233],[100,148,165,166,275,381],[100,148,165,166,383],[100,148,165,166,381],[100,148,165,166,383,384],[100,148,162,165,166,211,214,215,273,441],[100,148,162,165,166,174,206,208,236,274,288,318,373,374,410,438,442,443,445],[100,148,162,165,166,174,191,210,215,314,374,437,441],[100,148,165,166,307],[100,148,165,166,308],[100,148,165,166,309],[100,148,165,166,436],[100,148,165,166,255,271],[100,148,162,165,166,211,255,274],[100,148,165,166,270,271],[100,148,165,166,272],[100,148,165,166,255,256],[100,148,165,166,255,289],[100,148,165,166,255],[100,148,165,166,275,379,437],[100,148,165,166,378],[100,148,165,166,256,436,437],[100,148,165,166,376,437],[100,148,165,166,256,436],[100,148,165,166,362],[100,148,165,166,211,216,274,303,306,312,314,318,320,323,354,357,361,365,409,430,438,441],[100,148,165,166,297,300,301,302,321,322,369],[86,100,148,165,166,201,203,266,355,356],[86,100,148,165,166,201,203,266,355,356,360],[100,148,165,166,418],[100,148,165,166,222,279,317,318,329,333,365,409,411,412,413,414,416,417,420,430,435,440],[100,148,165,166,369],[100,148,165,166,373],[100,148,162,165,166,274,290,370,372,375,409,438,443,445],[100,148,165,166,297,298,299,300,301,302,321,322,369,444],[94,100,148,162,165,166,174,191,237,255,256,288,314,318,407,408,410,430,431,440,441,443],[100,148,165,166,279,281,284,431],[100,148,162,165,166,275,440],[100,148,165,166,278,317],[100,148,165,166,277],[100,148,165,166,279,280],[100,148,165,166,276,278,440],[100,148,162,165,166,210,279,281,282,283,440,441],[86,100,148,165,166,365,366,368],[100,148,165,166,241],[86,100,148,165,166,208],[86,100,148,165,166,436],[86,94,100,148,165,166,288,318,443,445],[100,148,165,166,208,467,468],[86,100,148,165,166,296],[86,100,148,165,166,174,191,206,253,291,293,295,445],[100,148,165,166,224,436,441],[100,148,165,166,392,436],[100,148,165,166,365],[86,100,148,160,162,165,166,174,206,242,248,296,443,444],[86,100,148,165,166,199,200,201,202,203,443,491],[86,87,88,89,90,100,148,165,166],[100,148,153,165,166],[100,148,165,166,243,244,245],[100,148,165,166,243],[86,90,100,148,162,164,165,166,174,198,199,200,201,202,203,204,206,237,341,402,440,442,445,491],[100,148,165,166,455],[100,148,165,166,457],[100,148,165,166,459],[100,148,165,166,461],[100,148,165,166,463,464,465],[100,148,165,166,469],[91,93,100,148,165,166,447,452,454,456,458,460,462,466,470,472,482,483,485,495,496,497,498],[100,148,165,166,471],[100,148,165,166,481],[100,148,165,166,293],[100,148,165,166,484],[100,147,148,165,166,279,281,282,284,332,436,486,487,488,491,492,493,494],[100,148,165,166,198],[100,148,165,166,180,198],[100,113,117,148,165,166,191],[100,113,148,165,166,180,191],[100,108,148,165,166],[100,110,113,148,165,166,188,191],[100,148,165,166,168,188],[100,108,148,165,166,198],[100,110,113,148,165,166,168,191],[100,105,106,109,112,148,159,165,166,180,191],[100,113,120,148,165,166],[100,105,111,148,165,166],[100,113,134,135,148,165,166],[100,109,113,148,165,166,183,191,198],[100,134,148,165,166,198],[100,107,108,148,165,166,198],[100,113,148,165,166],[100,107,108,109,110,111,112,113,114,115,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,135,136,137,138,139,140,148,165,166],[100,113,128,148,165,166],[100,113,120,121,148,165,166],[100,111,113,121,122,148,165,166],[100,112,148,165,166],[100,105,108,113,148,165,166],[100,113,117,121,122,148,165,166],[100,117,148,165,166],[100,111,113,116,148,165,166,191],[100,105,110,113,120,148,165,166],[100,148,165,166,180],[100,108,113,134,148,165,166,196,198],[86,100,148,165,166,504,505,506,507,508,509],[86,100,148,165,166,504],[100,148,159,165,166,180,188,198,515,516,519,520,521],[100,148,165,166,521],[100,148,165,166,198,516,517,518],[100,148,165,166,180,198,516]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"2ab096661c711e4a81cc464fa1e6feb929a54f5340b46b0a07ac6bbf857471f0","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"73f78680d4c08509933daf80947902f6ff41b6230f94dd002ae372620adb0f60","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5239f5c01bcfa9cd32f37c496cf19c61d69d37e48be9de612b541aac915805b","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"a384610388221cd70cffb4503cee7853b8b076f2b4a55324b20a4bdbd25a3538","affectsGlobalScope":true},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"bd7dee3446a5b94651d58000ddfda40296f073e9372891f65003a524b4620697","impliedFormat":1},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"0f6666b58e9276ac3a38fdc80993d19208442d6027ab885580d93aec76b4ef00","impliedFormat":1},{"version":"05fd364b8ef02fb1e174fbac8b825bdb1e5a36a016997c8e421f5fab0a6da0a0","impliedFormat":1},{"version":"631eff75b0e35d1b1b31081d55209abc43e16b49426546ab5a9b40bdd40b1f60","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"73b5fa37db36eeac90c4d752e39586f1b57187400c4f5280fd05f16437287a45","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"42bc0e1a903408137c3df2b06dfd7e402cdab5bbfa5fcfb871b22ebfdb30bd0b","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"829b9e6028b29e6a8b1c01ddb713efe59da04d857089298fa79acbdb3cfcfdef","impliedFormat":1},{"version":"24f8562308dd8ba6013120557fa7b44950b619610b2c6cb8784c79f11e3c4f90","impliedFormat":1},{"version":"5f90b8c733a1bda63e42160b15a2301051e83a6f9d5332a59d16eb12f463270d","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"496bbf339f3838c41f164238543e9fe5f1f10659cb30b68903851618464b98ba","impliedFormat":1},{"version":"5178eb4415a172c287c711dc60a619e110c3fd0b7de01ed0627e51a5336aa09c","impliedFormat":1},{"version":"ca6e5264278b53345bc1ce95f42fb0a8b733a09e3d6479c6ccfca55cdc45038c","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"fb1d8e814a3eeb5101ca13515e0548e112bd1ff3fb358ece535b93e94adf5a3a","impliedFormat":1},{"version":"ffa495b17a5ef1d0399586b590bd281056cee6ce3583e34f39926f8dcc6ecdb5","impliedFormat":1},{"version":"98b18458acb46072947aabeeeab1e410f047e0cacc972943059ca5500b0a5e95","impliedFormat":1},{"version":"361e2b13c6765d7f85bb7600b48fde782b90c7c41105b7dab1f6e7871071ba20","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"b6db56e4903e9c32e533b78ac85522de734b3d3a8541bf24d256058d464bf04b","impliedFormat":1},{"version":"24daa0366f837d22c94a5c0bad5bf1fd0f6b29e1fae92dc47c3072c3fdb2fbd5","impliedFormat":1},{"version":"570bb5a00836ffad3e4127f6adf581bfc4535737d8ff763a4d6f4cc877e60d98","impliedFormat":1},{"version":"889c00f3d32091841268f0b994beba4dceaa5df7573be12c2c829d7c5fbc232c","impliedFormat":1},{"version":"65f43099ded6073336e697512d9b80f2d4fec3182b7b2316abf712e84104db00","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"27ab780875bcbb65e09da7496f2ca36288b0c541abaa75c311450a077d54ec15","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"380647d8f3b7f852cca6d154a376dbf8ac620a2f12b936594504a8a852e71d2f","impliedFormat":1},{"version":"208c9af9429dd3c76f5927b971263174aaa4bc7621ddec63f163640cbd3c473c","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"a23185bc5ef590c287c28a91baf280367b50ae4ea40327366ad01f6f4a8edbc5","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"002eae065e6960458bda3cf695e578b0d1e2785523476f8a9170b103c709cd4f","impliedFormat":1},{"version":"c83bb0c9c5645a46c68356c2f73fdc9de339ce77f7f45a954f560c7e0b8d5ebb","impliedFormat":1},{"version":"05c97cddbaf99978f83d96de2d8af86aded9332592f08ce4a284d72d0952c391","impliedFormat":1},{"version":"72179f9dd22a86deaad4cc3490eb0fe69ee084d503b686985965654013f1391b","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"7b6ff760c8a240b40dab6e4419b989f06a5b782f4710d2967e67c695ef3e93c4","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"6a148329edecbda07c21098639ef4254ef7869fb25a69f58e5d6a8b7b69d4236","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"f63ab283a1c8f5c79fabe7ca4ef85f9633339c4f0e822fce6a767f9d59282af2","impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"a54c996c8870ef1728a2c1fa9b8eaec0bf4a8001cd2583c02dd5869289465b10","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"3754982006a3b32c502cff0867ca83584f7a43b1035989ca73603f400de13c96","impliedFormat":1},{"version":"a30ae9bb8a8fa7b90f24b8a0496702063ae4fe75deb27da731ed4a03b2eb6631","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"50b5bc34ce6b12eccb76214b51aadfa56572aa6cc79c2b9455cdbb3d6c76af1d","impliedFormat":1},{"version":"b7e16ef7f646a50991119b205794ebfd3a4d8f8e0f314981ebbe991639023d0e","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"e9dd71cf12123419c60dab867d44fbee5c358169f99529121eaef277f5c83531","impliedFormat":1},{"version":"5b6a189ba3a0befa1f5d9cb028eb9eec2af2089c32f04ff50e2411f63d70f25d","impliedFormat":1},{"version":"d6e73f8010935b7b4c7487b6fb13ea197cc610f0965b759bec03a561ccf8423a","impliedFormat":1},{"version":"174f3864e398f3f33f9a446a4f403d55a892aa55328cf6686135dfaf9e171657","impliedFormat":1},{"version":"824c76aec8d8c7e65769688cbee102238c0ef421ed6686f41b2a7d8e7e78a931","impliedFormat":1},{"version":"75b868be3463d5a8cfc0d9396f0a3d973b8c297401d00bfb008a42ab16643f13","impliedFormat":1},{"version":"15a234e5031b19c48a69ccc1607522d6e4b50f57d308ecb7fe863d44cd9f9eb3","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"6dcf60530c25194a9ee0962230e874ff29d34c59605d8e069a49928759a17e0a","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"1a42d2ec31a1fe62fdc51591768695ed4a2dc64c01be113e7ff22890bebb5e3f","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"0c7c947ff881c4274c0800deaa0086971e0bfe51f89a33bd3048eaa3792d4876","affectsGlobalScope":true,"impliedFormat":1},{"version":"db01d18853469bcb5601b9fc9826931cc84cc1a1944b33cad76fd6f1e3d8c544","affectsGlobalScope":true,"impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"15b36126e0089bfef173ab61329e8286ce74af5e809d8a72edcafd0cc049057f","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"ad10d4f0517599cdeca7755b930f148804e3e0e5b5a3847adce0f1f71bbccd74","impliedFormat":1},{"version":"1042064ece5bb47d6aba91648fbe0635c17c600ebdf567588b4ca715602f0a9d","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"72d63643a657c02d3e51cd99a08b47c9b020a565c55f246907050d3c8a5e77fb","impliedFormat":1},{"version":"1d415445ea58f8033ba199703e55ff7483c52ac6742075b803bd3e7bbe9f5d61","impliedFormat":1},{"version":"d6406c629bb3efc31aedb2de809bef471e475c86c7e67f3ef9b676b5d7e0d6b2","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"71d8ba39a9e024d9e4bb922464d18542ed8d2c25ee78efa7890c27213cc6e5d3","impliedFormat":1},{"version":"8c030e515014c10a2b98f9f48408e3ba18023dfd3f56e3312c6c2f3ae1f55a16","impliedFormat":1},{"version":"dafc31e9e8751f437122eb8582b93d477e002839864410ff782504a12f2a550c","impliedFormat":1},{"version":"754498c5208ce3c5134f6eabd49b25cf5e1a042373515718953581636491f3c3","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"f56bdc6884648806d34bc66d31cdb787c4718d04105ce2cd88535db214631f82","impliedFormat":1},{"version":"633d58a237f4bb25ec7d565e4ffa32cecdcee8660ac12189c4351c52557cee9e","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"13283350547389802aa35d9f2188effaeac805499169a06ef5cd77ce2a0bd63f","impliedFormat":1},{"version":"ce791f6ea807560f08065d1af6014581eeb54a05abd73294777a281b6dfd73c2","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"49f95e989b4632c6c2a578cc0078ee19a5831832d79cc59abecf5160ea71abad","impliedFormat":1},{"version":"9666533332f26e8995e4d6fe472bdeec9f15d405693723e6497bf94120c566c8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"e17cd049a1448de4944800399daa4a64c5db8657cc9be7ef46be66e2a2cd0e7c","impliedFormat":1},{"version":"43fa6ea8714e18adc312b30450b13562949ba2f205a1972a459180fa54471018","impliedFormat":1},{"version":"6e89c2c177347d90916bad67714d0fb473f7e37fb3ce912f4ed521fe2892cd0d","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"4d4927cbee21750904af7acf940c5e3c491b4d5ebc676530211e389dd375607a","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"8a97e578a9bc40eb4f1b0ca78f476f2e9154ecbbfd5567ee72943bab37fc156a","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"2d7db1d73456e8c5075387d4240c29a2a900847f9c1bff106a2e490da8fbd457","impliedFormat":1},{"version":"2b15c805f48e4e970f8ec0b1915f22d13ca6212375e8987663e2ef5f0205e832","impliedFormat":1},{"version":"f22d05663d873ee7a600faf78abb67f3f719d32266803440cf11d5db7ac0cab2","impliedFormat":1},{"version":"d93c544ad20197b3976b0716c6d5cd5994e71165985d31dcab6e1f77feb4b8f2","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"a8b1c79a833ee148251e88a2553d02ce1641d71d2921cce28e79678f3d8b96aa","impliedFormat":1},{"version":"126d4f950d2bba0bd45b3a86c76554d4126c16339e257e6d2fabf8b6bf1ce00c","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"2d3cc2211f352f46ea6b7cf2c751c141ffcdf514d6e7ae7ee20b7b6742da313f","impliedFormat":1},{"version":"c75445151ff8b77d9923191efed7203985b1a9e09eccf4b054e7be864e27923d","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"fa8a8fbf91ee2a4779496225f0312aac6635b0f21aa09cdafa4283fe32d519c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"0e8aef93d79b000deb6ec336b5645c87de167168e184e84521886f9ecc69a4b5","impliedFormat":1},{"version":"56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"de7052bfee2981443498239a90c04ea5cc07065d5b9bb61b12cb6c84313ad4ef","impliedFormat":1},{"version":"a3e7d932dc9c09daa99141a8e4800fc6c58c625af0d4bbb017773dc36da75426","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"4a2edd238d9104eac35b60d727f1123de5062f452b70ed8e0366cb36387dfdfd","impliedFormat":1},{"version":"ca921bf56756cb6fe957f6af693a35251b134fb932dc13f3dfff0bb7106f80b4","impliedFormat":1},{"version":"fee92c97f1aa59eb7098a0cc34ff4df7e6b11bae71526aca84359a2575f313d8","impliedFormat":1},{"version":"0bd0297484aacea217d0b76e55452862da3c5d9e33b24430e0719d1161657225","impliedFormat":1},{"version":"2ab6d334bcbf2aff3acfc4fd8c73ecd82b981d3c3aa47b3f3b89281772286904","impliedFormat":1},{"version":"d07cbc787a997d83f7bde3877fec5fb5b12ce8c1b7047eb792996ed9726b4dde","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"4805f6161c2c8cefb8d3b8bd96a080c0fe8dbc9315f6ad2e53238f9a79e528a6","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"49179c6a23701c642bd99abe30d996919748014848b738d8e85181fc159685ff","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"45490817629431853543adcb91c0673c25af52a456479588b6486daba34f68bb","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"8514c62ce38e58457d967e9e73f128eedc1378115f712b9eef7127f7c88f82ae","impliedFormat":1},{"version":"f1289e05358c546a5b664fbb35a27738954ec2cc6eb4137350353099d154fc62","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"1d17ba45cfbe77a9c7e0df92f7d95f3eefd49ee23d1104d0548b215be56945ad","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"bd5f641cc4616eee49497a362c4cb401e9346265bc52670448c4452b4d9be401","impliedFormat":1},{"version":"46273e8c29816125d0d0b56ce9a849cc77f60f9a5ba627447501d214466f0ff3","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","impliedFormat":1},{"version":"3af3584f79c57853028ef9421ec172539e1fe01853296dc05a9d615ade4ffaf6","impliedFormat":1},{"version":"f82579d87701d639ff4e3930a9b24f4ee13ca74221a9a3a792feb47f01881a9c","impliedFormat":1},{"version":"d7e5d5245a8ba34a274717d085174b2c9827722778129b0081fefd341cca8f55","impliedFormat":1},{"version":"d9d32f94056181c31f553b32ce41d0ef75004912e27450738d57efcd2409c324","impliedFormat":1},{"version":"752513f35f6cff294ffe02d6027c41373adf7bfa35e593dbfd53d95c203635ee","impliedFormat":1},{"version":"6c800b281b9e89e69165fd11536195488de3ff53004e55905e6c0059a2d8591e","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"1a7e2ea171726446850ec72f4d1525d547ff7e86724cc9e7eec509725752a758","impliedFormat":1},{"version":"8c901126d73f09ecdea4785e9a187d1ac4e793e07da308009db04a7283ec2f37","impliedFormat":1},{"version":"c1de754ab5f3b0f4036d6893c74a0fc984c7fcb07936086f19bbe2974406775b","impliedFormat":1},{"version":"aab290b8e4b7c399f2c09b957666fc95335eb4522b2dd9ead1bf0cb64da6d6ee","impliedFormat":1},{"version":"94fe3281392e1015b22f39535878610b4fa6f1388dc8d78746be3bc4e4bb8950","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"06c25ddfc2242bd06c19f66c9eae4c46d937349a267810f89783680a1d7b5259","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"bd4131091b773973ca5d2326c60b789ab1f5e02d8843b3587effe6e1ea7c9d86","impliedFormat":1},{"version":"c7f6485931085bf010fbaf46880a9b9ec1a285ad9dc8c695a9e936f5a48f34b4","impliedFormat":1},{"version":"14f6b927888a1112d662877a5966b05ac1bf7ed25d6c84386db4c23c95a5363b","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"0427df5c06fafc5fe126d14b9becd24160a288deff40e838bfbd92a35f8d0d00","impliedFormat":1},{"version":"90c54a02432d04e4246c87736e53a6a83084357acfeeba7a489c5422b22f5c7a","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"83fe880c090afe485a5c02262c0b7cdd76a299a50c48d9bde02be8e908fb4ae6","impliedFormat":1},{"version":"0a372c2d12a259da78e21b25974d2878502f14d89c6d16b97bd9c5017ab1bc12","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"6511e4503cf74c469c60aafd6589e4d14d5eb0a25f9bf043dcbecdf65f261972","impliedFormat":1},{"version":"ec1ca97598eda26b7a5e6c8053623acbd88e43be7c4d29c77ccd57abc4c43999","impliedFormat":1},{"version":"6e2261cd9836b2c25eecb13940d92c024ebed7f8efe23c4b084145cd3a13b8a6","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"a47e6d954d22dd9ebb802e7e431b560ed7c581e79fb885e44dc92ed4f60d4c07","impliedFormat":1},{"version":"f019e57d2491c159d47a107fd90219a1734bdd2e25cd8d1db3c8fae5c6b414c4","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"d1c9bf292a54312888a77bb19dba5e2503ad803f5393beafd45d78d2f4fe9b48","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"cb8d8ef7b9ce8ed3e6f1c814fcbf3f90dab0cb8863079236784fc350746e27c4","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"3be035da7bee86b4c3abf392e0edaa44fc6e45092995eefe36b39118c8a84068","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f828825d077c2fa0ea606649faeb122749273a353daab23924fe674e98ba44c","impliedFormat":1},{"version":"2896c2e673a5d3bd9b4246811f79486a073cbb03950c3d252fba10003c57411a","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"407a06ba04eede4074eec470ecba2784cbb3bf4e7de56833b097dd90a2aa0651","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"5c96bad5f78466785cdad664c056e9e2802d5482ca5f862ed19ba34ffbb7b3a4","impliedFormat":1},{"version":"81d8603ac527e75cfec72bb9391228b58f161c2b33514a9d814c7f3ebd3ef466","impliedFormat":1},{"version":"5f3dc10ae646f375776b4e028d2bed039a93eebbba105694d8b910feebbe8b9c","impliedFormat":1},{"version":"bb0cd7862b72f5eba39909c9889d566e198fcaddf7207c16737d0c2246112678","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"a3f41ed1b4f2fc3049394b945a68ae4fdefd49fa1739c32f149d32c0545d67f5","impliedFormat":1},{"version":"bad68fd0401eb90fe7da408565c8aee9c7a7021c2577aec92fa1382e8876071a","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"fec01479923e169fb52bd4f668dbeef1d7a7ea6e6d491e15617b46f2cacfa37d","impliedFormat":1},{"version":"8a8fb3097ba52f0ae6530ec6ab34e43e316506eb1d9aa29420a4b1e92a81442d","impliedFormat":1},{"version":"44e09c831fefb6fe59b8e65ad8f68a7ecc0e708d152cfcbe7ba6d6080c31c61e","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"4655709c9cb3fd6db2b866cab7c418c40ed9533ce8ea4b66b5f17ec2feea46a9","impliedFormat":1},{"version":"87affad8e2243635d3a191fa72ef896842748d812e973b7510a55c6200b3c2a4","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"3eecb25bb467a948c04874d70452b14ae7edb707660aac17dc053e42f2088b00","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"330896c1a2b9693edd617be24fbf9e5895d6e18c7955d6c08f028f272b37314d","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"5f0292a40df210ab94b9fb44c8b775c51e96777e14e073900e392b295ca1061b","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"8627ad129bcf56e82adff0ab5951627c993937aa99f5949c33240d690088b803","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"a68d4b3182e8d776cdede7ac9630c209a7bfbb59191f99a52479151816ef9f9e","impliedFormat":99},{"version":"39644b343e4e3d748344af8182111e3bbc594930fff0170256567e13bbdbebb0","impliedFormat":99},{"version":"ed7fd5160b47b0de3b1571c5c5578e8e7e3314e33ae0b8ea85a895774ee64749","impliedFormat":99},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"ecbaf0da125974be39c0aac869e403f72f033a4e7fd0d8cd821a8349b4159628","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"ceec3c81b2d81f5e3b855d9367c1d4c664ab5046dff8fd56552df015b7ccbe8f","affectsGlobalScope":true,"impliedFormat":1},{"version":"8fac4a15690b27612d8474fb2fc7cc00388df52d169791b78d1a3645d60b4c8b","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"1d63055b690a582006435ddd3aa9c03aac16a696fac77ce2ed808f3e5a06efab","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},"f4e8976c19fc926644d72610bf1058bd6bf52add97e46a02bc0b912a751625c0","91cdb366ff9e26f211a8a8470eebba3d3fd4e632e4b339ff7ad82768682d4dce","e464214b7fc72bd6f872d8c29b557e61ba03fa68189b3f3ac0cb3f1408f3d8c0",{"version":"acc9b958b822fe0a2da8d6377f7c25046f82a6bbcc2e948faaf727a7933bd58d","impliedFormat":1},"ee2489669ab4305de7b51116092198bcdf9cae65df021e547cbb4d2fea771269","3e8d63dfa2afaf442c6457a0d9a3f7c26f4f0c96e1b169cf0a23ce8b9afd78e6","cd233331acd23cc8a335f01860661ab5320719793e23f086ceadcb943c44186d","66312aab8dccb88c0470f28d8623bb5266e5232a2bd27fe228ae538f42085a3c","22489e01116ab5fb2af71d60b98b98f8912171eb0834c5cfdff835b6434e3d67","c449ee0ae544b4dfa0c4753885f099a5f8d4ef96cdd4c83714c750c468aabaea","2552a31fad45a9ed1bde87e51b038dc0e786cd364b597162263abbf57018949b","f70e1e9104f989e63e8d2419a4b378ebf3fa951107b8f668ec6a4fbf138c0352","d6f251e877dfa64a579c0cc5a139fb96e0fd46264a795d1f63f67782aa13962e","0ddaf003c91466e6a03feea177e53d38c8d2ba571f06dbffa3a5d0588cfd4cd3",{"version":"f60e3e3060207ac982da13363181fd7ee4beecc19a7c569f0d6bb034331066c2","impliedFormat":1},{"version":"17230b34bb564a3a2e36f9d3985372ccab4ad1722df2c43f7c5c2b553f68e5db","impliedFormat":1},{"version":"dcb41cc4b671f5f85d3754e68dfde7824686a8adbb4c690cb259c1501cb868e1","impliedFormat":1},{"version":"21ac4cf3f8d8c6e1201cb31f600be708c9a37867fc5c73b7ccf80560fae591c8","impliedFormat":1},{"version":"0dfe35191a04e8f9dc7caeb9f52f2ee07402736563d12cbccd15fb5f31ac877f","impliedFormat":1},{"version":"798367363a3274220cbed839b883fe2f52ba7197b25e8cb2ac59c1e1fd8af6b7","impliedFormat":1},{"version":"2636a309ed87d6876728d9aca846a76b372cf2a21a4fdf9940a82a2dd86687d0","impliedFormat":1}],"root":[83,[501,503],[505,514]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":1,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[513,1],[514,2],[511,3],[83,4],[512,5],[501,6],[502,7],[248,4],[145,8],[146,8],[147,9],[100,10],[148,11],[149,12],[150,13],[95,4],[98,14],[96,4],[97,4],[151,15],[152,16],[153,17],[154,18],[155,19],[156,20],[157,20],[158,21],[159,22],[160,23],[161,24],[101,4],[99,4],[162,25],[163,26],[164,27],[198,28],[165,29],[166,4],[167,30],[168,31],[169,32],[170,33],[171,34],[172,35],[173,36],[174,37],[175,38],[176,38],[177,39],[178,4],[179,40],[180,41],[182,42],[181,43],[183,44],[184,45],[185,46],[186,47],[187,48],[188,49],[189,50],[190,51],[191,52],[192,53],[193,54],[194,55],[195,56],[102,4],[103,4],[104,4],[142,57],[143,4],[144,4],[196,58],[197,59],[202,60],[358,61],[203,62],[201,63],[360,64],[359,65],[199,66],[356,4],[200,67],[84,4],[86,68],[355,61],[266,61],[85,4],[504,61],[93,69],[447,70],[452,3],[454,71],[224,72],[252,73],[430,74],[247,75],[235,4],[216,4],[222,4],[420,76],[283,77],[223,4],[389,78],[257,79],[258,80],[354,81],[417,82],[372,83],[424,84],[425,85],[423,86],[422,4],[421,87],[254,88],[225,89],[304,4],[305,90],[220,4],[236,91],[226,92],[288,91],[285,91],[209,91],[250,93],[249,4],[429,94],[439,4],[215,4],[330,95],[331,96],[325,61],[475,4],[333,4],[334,97],[326,98],[481,99],[479,100],[474,4],[416,101],[415,4],[473,102],[327,61],[368,103],[366,104],[476,4],[480,4],[478,105],[477,4],[367,106],[468,107],[471,108],[295,109],[294,110],[293,111],[484,61],[292,112],[277,4],[487,4],[490,4],[489,61],[491,113],[205,4],[426,114],[427,115],[428,116],[238,4],[214,117],[204,4],[346,61],[207,118],[345,119],[344,120],[335,4],[336,4],[343,4],[338,4],[341,121],[337,4],[339,122],[342,123],[340,122],[221,4],[212,4],[213,91],[267,124],[268,125],[265,126],[263,127],[264,128],[260,4],[352,97],[374,97],[446,129],[455,130],[459,131],[433,132],[432,4],[280,4],[492,133],[442,134],[328,135],[329,136],[320,137],[310,4],[351,138],[311,139],[353,140],[348,141],[347,4],[349,4],[365,142],[434,143],[435,144],[313,145],[317,146],[308,147],[412,148],[441,149],[287,150],[390,151],[210,152],[440,153],[206,75],[261,4],[269,154],[401,155],[259,4],[400,156],[94,4],[395,157],[237,4],[306,158],[391,4],[211,4],[270,4],[399,159],[219,4],[275,160],[316,161],[431,162],[315,4],[398,4],[262,4],[403,163],[404,164],[217,4],[406,165],[408,166],[407,167],[240,4],[397,152],[410,168],[396,169],[402,170],[228,4],[231,4],[229,4],[233,4],[230,4],[232,4],[234,171],[227,4],[382,172],[381,4],[387,173],[383,174],[386,175],[385,175],[388,173],[384,174],[274,176],[375,177],[438,178],[494,4],[463,179],[465,180],[312,4],[464,181],[436,143],[493,182],[332,143],[218,4],[314,183],[271,184],[272,185],[273,186],[303,187],[411,187],[289,187],[376,188],[290,188],[256,189],[255,4],[380,190],[379,191],[378,192],[377,193],[437,194],[324,195],[362,196],[323,197],[357,198],[361,199],[419,200],[418,201],[414,202],[371,203],[373,204],[370,205],[409,206],[364,4],[451,4],[363,207],[413,4],[276,208],[309,114],[307,209],[278,210],[281,211],[488,4],[279,212],[282,212],[449,4],[448,4],[450,4],[486,4],[284,213],[322,61],[92,4],[369,214],[253,4],[242,215],[318,4],[457,61],[467,216],[302,61],[461,97],[301,217],[444,218],[300,216],[208,4],[469,219],[298,61],[299,61],[291,4],[241,4],[297,220],[296,221],[239,222],[319,37],[286,37],[405,4],[393,223],[392,4],[453,4],[350,224],[321,61],[445,225],[87,61],[90,226],[91,227],[88,61],[89,4],[251,228],[246,229],[245,4],[244,230],[243,4],[443,231],[456,232],[458,233],[460,234],[462,235],[466,236],[500,237],[470,237],[499,238],[472,239],[482,240],[483,241],[485,242],[495,243],[498,117],[497,4],[496,244],[394,245],[81,4],[82,4],[13,4],[14,4],[16,4],[15,4],[2,4],[17,4],[18,4],[19,4],[20,4],[21,4],[22,4],[23,4],[24,4],[3,4],[25,4],[26,4],[4,4],[27,4],[31,4],[28,4],[29,4],[30,4],[32,4],[33,4],[34,4],[5,4],[35,4],[36,4],[37,4],[38,4],[6,4],[42,4],[39,4],[40,4],[41,4],[43,4],[7,4],[44,4],[49,4],[50,4],[45,4],[46,4],[47,4],[48,4],[8,4],[54,4],[51,4],[52,4],[53,4],[55,4],[9,4],[56,4],[57,4],[58,4],[60,4],[59,4],[61,4],[62,4],[10,4],[63,4],[64,4],[65,4],[11,4],[66,4],[67,4],[68,4],[69,4],[70,4],[1,4],[71,4],[72,4],[12,4],[76,4],[74,4],[79,4],[78,4],[73,4],[77,4],[75,4],[80,4],[120,246],[130,247],[119,246],[140,248],[111,249],[110,250],[139,244],[133,251],[138,252],[113,253],[127,254],[112,255],[136,256],[108,257],[107,244],[137,258],[109,259],[114,260],[115,4],[118,260],[105,4],[141,261],[131,262],[122,263],[123,264],[125,265],[121,266],[124,267],[134,244],[116,268],[117,269],[126,270],[106,271],[129,262],[128,260],[132,4],[135,272],[503,7],[510,273],[505,274],[509,274],[508,274],[507,274],[506,274],[521,275],[520,276],[519,277],[516,244],[518,278],[517,244],[515,4]],"affectedFilesPendingEmit":[513,514,512,502,503,510,505,509,508,507,506],"version":"5.9.3"} \ No newline at end of file diff --git a/asaas_skill.md b/asaas_skill.md new file mode 100644 index 0000000..8f99777 --- /dev/null +++ b/asaas_skill.md @@ -0,0 +1,195 @@ +# Integração Asaas Payment Gateway - AgendaPRO + +Este documento serve como guia técnico ("Skill") para a integração do gateway de pagamento **Asaas** na plataforma SaaS multi-tenant **AgendaPRO**. Ele detalha os fluxos de trabalho, estruturas de API, segurança do Webhook e regras de conciliação financeira em banco de dados PostgreSQL. + +--- + +## 1. Visão Geral da Arquitetura + +A integração é baseada no modelo **SQL-First**, onde o banco de dados PostgreSQL é a única fonte da verdade. O mapeamento é feito associando os IDs internos do sistema (`UUID`) ao campo `externalReference` fornecido pela API do Asaas. + +```mermaid +sequenceDiagram + participant Cliente/Tenant as App/Painel (AgendaPRO) + participant Lib as Serviço Asaas (src/lib/asaas.ts) + participant Asaas as Asaas API (Sandbox/Prod) + participant DB as PostgreSQL + + Cliente/Tenant->>Lib: Solicita Cobrança/Assinatura + Lib->>DB: Busca Configuração do Tenant (API Key) + DB-->>Lib: Retorna Configuração do Tenant + Lib->>Asaas: POST /v3/payments (ou /subscriptions) com externalReference + Asaas-->>Lib: Retorna Dados da Transação (ID do Asaas) + Lib-->>Cliente/Tenant: Retorna Link/QR Code Pix para o Cliente +``` + +--- + +## 2. Ambientes e Autenticação + +O Asaas possui dois ambientes distintos. As chaves de API (`API Keys`) geradas no Sandbox não funcionam em Produção e vice-versa. + +| Ambiente | URL Base da API v3 | Variável de Ambiente | +| :--- | :--- | :--- | +| **Sandbox (Homologação)** | `https://api-sandbox.asaas.com/v3` | `ASAAS_API_KEY` (Prefixo de teste) | +| **Produção** | `https://api.asaas.com/v3` | `ASAAS_API_KEY` | + +### Cabeçalhos HTTP Obrigatórios +Todas as chamadas à API devem conter: +* `access_token`: Sua chave de API do Asaas (específica para o tenant ou plataforma). +* `Content-Type`: `application/json`. +* `User-Agent`: Nome da aplicação (ex: `AgendaPRO-Integration`). + +--- + +## 3. Fluxo de Clientes (Customers) + +Antes de emitir qualquer cobrança ou assinatura, o cliente (consumidor final) deve ser cadastrado no Asaas. O ID do cliente retornado (`cus_...`) deve ser salvo no banco local (ex: no cadastro de inquilino/tenant ou no cadastro de cliente do estabelecimento). + +* **Endpoint:** `POST /v3/customers` +* **Payload Exemplo:** + ```json + { + "name": "João Mendes", + "cpfCnpj": "00000000000", + "email": "joao@email.com", + "phone": "11999991234", + "mobilePhone": "11999991234", + "externalReference": "local-client-uuid", + "notificationDisabled": false + } + ``` +* **Response de Sucesso:** Retorna o objeto do cliente contendo o `"id": "cus_000005219613"`. + +--- + +## 4. Fluxo de Cobranças Avulsas (Payments - Pix/Boleto/Cartão) + +Usado para pagamentos individuais, como a cobrança por um agendamento específico. + +* **Endpoint:** `POST /v3/payments` +* **Payload Exemplo (Pix/Boleto):** + ```json + { + "customer": "cus_000005219613", + "billingType": "PIX", + "value": 55.00, + "dueDate": "2026-06-10", + "description": "Corte Masculino - Barbearia Premium", + "externalReference": "local-payment-uuid" + } + ``` +* **Response de Sucesso:** Retorna o ID da transação (ex: `"id": "pay_080225913252"`). + +### Obtenção do QR Code Dinâmico Pix +Se a cobrança for do tipo `PIX`, você pode buscar a imagem em Base64 e a linha digitável ("copia e cola"): +* **Endpoint:** `GET /v3/payments/{id}/pixQrCode` (Sem corpo na requisição) +* **Response Exemplo:** + ```json + { + "encodedImage": "iVBORw0KGgoAAAANSUhEUgAA...", + "payload": "00020101021126660014br.gov.bcb.pix...", + "expirationDate": "2026-06-10T23:59:59Z" + } + ``` + +--- + +## 5. Fluxo de Assinaturas Recorrentes (Subscriptions) + +Usado pelo SaaS para cobrar a mensalidade do próprio inquilino (Tenant) ou pelos inquilinos para oferecer planos recorrentes. + +* **Endpoint:** `POST /v3/subscriptions` +* **Payload Exemplo (Cartão de Crédito):** + ```json + { + "customer": "cus_0T1mdomVMi39", + "billingType": "CREDIT_CARD", + "nextDueDate": "2026-07-03", + "value": 99.90, + "cycle": "MONTHLY", + "description": "Assinatura AgendaPRO - Plano Professional", + "externalReference": "local-subscription-uuid", + "creditCard": { + "holderName": "TITULAR DO CARTAO", + "number": "0000000000000000", + "expiryMonth": "12", + "expiryYear": "2030", + "ccv": "123" + }, + "creditCardHolderInfo": { + "name": "TITULAR DO CARTAO", + "email": "titular@email.com", + "cpfCnpj": "00000000000", + "postalCode": "01310000", + "addressNumber": "123", + "phone": "11999991234" + } + } + ``` + +--- + +## 6. Webhook e Segurança + +O webhook do Asaas garante a conciliação automática do status de pagamentos de forma assíncrona. + +### Validação de Autenticidade (Signature) +Diferente de gateways que usam assinaturas HMAC, o Asaas utiliza um **Token de Acesso Simples** configurado na interface do webhook. +1. Configure um Token Secreto no Painel do Asaas (ou salve em `settings` no banco local do tenant). +2. O Asaas enviará este token no cabeçalho `asaas-access-token` em cada requisição de Webhook. +3. A aplicação deve validar: `req.headers.get('asaas-access-token') === webhookSecret`. + +### Estrutura de Payload do Webhook +```json +{ + "id": "evt_05b708f961d739ea7eba7e4db318f621", + "event": "PAYMENT_RECEIVED", + "dateCreated": "2026-06-03 20:20:00", + "payment": { + "id": "pay_080225913252", + "customer": "cus_000005219613", + "subscription": "sub_VXJBYgP2u0eO", + "value": 100.00, + "netValue": 95.00, + "externalReference": "local-payment-uuid", + "billingType": "PIX", + "confirmedDate": "2026-06-03" + } +} +``` + +### Eventos Críticos a Processar +* `PAYMENT_RECEIVED` / `PAYMENT_CONFIRMED`: Pagamento efetuado com sucesso. +* `PAYMENT_REFUNDED`: Pagamento estornado pelo painel ou pelo banco. +* `PAYMENT_OVERDUE`: Vencimento expirado sem quitação. +* `PAYMENT_DELETED`: Cobrança removida manualmente. +* `SUBSCRIPTION_DELETED` / `SUBSCRIPTION_CANCELLED`: Cancelamento de recorrência. + +--- + +## 7. Estrutura do Código Implementado + +### A. Camada de Serviço (`src/lib/asaas.ts`) +Fornece funções isoladas e parametrizadas que realizam as chamadas seguras para a API, respeitando a separação de chaves por inquilino (`tenant_id`). + +### B. Handler de Webhook (`src/app/api/webhooks/asaas/route.ts`) +Endpoint público que recebe requisições do Asaas. Executa os seguintes passos: +1. Valida o token `asaas-access-token` contra o token do Tenant (se enviado com `?tenant_id=...`) ou contra o token mestre do sistema. +2. Cria e utiliza a tabela `asaas_webhook_events` para garantir **idempotência** (ignora eventos duplicados). +3. Atualiza o status financeiro local em uma transação segura (`BEGIN/COMMIT`). + +--- + +## 8. Guia de Teste no Sandbox + +1. **Configuração de Chaves:** + * Vá em [Asaas Sandbox](https://sandbox.asaas.com/) > Minha Conta > Integrações. + * Gere uma chave de API e insira o webhook apontando para a sua URL pública exposta pelo Ngrok/Cloudflare Tunnel (ex: `https://seu-dominio.trycloudflare.com/api/webhooks/asaas?tenant_id=UUID-DO-TENANT`). +2. **Simulação de Pagamentos:** + * Crie uma cobrança do tipo Pix/Boleto pela API. + * Acesse o painel de Sandbox do Asaas. + * Procure a cobrança emitida e clique em **Confirmar Recebimento** (Botão de teste que simula o pagamento do cliente). +3. **Validação de Banco de Dados:** + * Execute: `SELECT status, paid_at FROM payments WHERE id = 'seu-uuid';` + * Verifique se o status mudou para `completed` e a coluna `paid_at` foi preenchida corretamente. diff --git a/database/init.sql b/database/init.sql new file mode 100644 index 0000000..5361f39 --- /dev/null +++ b/database/init.sql @@ -0,0 +1,413 @@ +-- ============================================= +-- AgendaPRO SaaS - Database Schema +-- PostgreSQL 16+ +-- ============================================= + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- ============================================= +-- 1. PLANOS DE ASSINATURA +-- ============================================= +CREATE TABLE IF NOT EXISTS plans ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(100) NOT NULL, + slug VARCHAR(100) UNIQUE NOT NULL, + description TEXT, + price_monthly NUMERIC(10,2) NOT NULL DEFAULT 0, + price_yearly NUMERIC(10,2) NOT NULL DEFAULT 0, + max_professionals INT NOT NULL DEFAULT 1, + max_services INT NOT NULL DEFAULT 5, + max_appointments_month INT DEFAULT NULL, -- NULL = unlimited + features JSONB DEFAULT '[]', + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 2. TENANTS (ESTABELECIMENTOS) +-- ============================================= +CREATE TABLE IF NOT EXISTS tenants ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + plan_id UUID REFERENCES plans(id), + name VARCHAR(200) NOT NULL, + slug VARCHAR(200) UNIQUE NOT NULL, + business_type VARCHAR(50) NOT NULL DEFAULT 'barbearia', -- barbearia, manicure, massagem, estetica, outro + logo_url TEXT, + cover_url TEXT, + phone VARCHAR(20), + email VARCHAR(200), + address TEXT, + city VARCHAR(100), + state VARCHAR(2), + zip_code VARCHAR(10), + description TEXT, + settings JSONB DEFAULT '{ + "timezone": "America/Sao_Paulo", + "currency": "BRL", + "booking_advance_min": 60, + "booking_advance_max": 43200, + "cancellation_limit_min": 120, + "allow_online_booking": true, + "require_confirmation": false, + "send_reminders": true, + "reminder_minutes_before": 60, + "theme_color": "#6C63FF", + "working_days": [1,2,3,4,5,6] + }', + subscription_status VARCHAR(20) DEFAULT 'trial', -- trial, active, past_due, cancelled, suspended + trial_ends_at TIMESTAMPTZ, + subscription_ends_at TIMESTAMPTZ, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 3. USUÁRIOS (OWNERS, ADMINS, PROFISSIONAIS) +-- ============================================= +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + name VARCHAR(200) NOT NULL, + email VARCHAR(200) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + phone VARCHAR(20), + avatar_url TEXT, + role VARCHAR(20) NOT NULL DEFAULT 'professional', -- owner, admin, professional + is_active BOOLEAN DEFAULT true, + last_login_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(email, tenant_id) +); + +-- ============================================= +-- 4. CATEGORIAS DE SERVIÇO +-- ============================================= +CREATE TABLE IF NOT EXISTS service_categories ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + description TEXT, + icon VARCHAR(50), + sort_order INT DEFAULT 0, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 5. SERVIÇOS +-- ============================================= +CREATE TABLE IF NOT EXISTS services ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + category_id UUID REFERENCES service_categories(id) ON DELETE SET NULL, + name VARCHAR(200) NOT NULL, + description TEXT, + duration_minutes INT NOT NULL DEFAULT 30, + price NUMERIC(10,2) NOT NULL DEFAULT 0, + price_type VARCHAR(20) DEFAULT 'fixed', -- fixed, starting_at, per_hour + image_url TEXT, + is_active BOOLEAN DEFAULT true, + sort_order INT DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 6. PROFISSIONAIS <-> SERVIÇOS +-- ============================================= +CREATE TABLE IF NOT EXISTS professional_services ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + professional_id UUID REFERENCES users(id) ON DELETE CASCADE, + service_id UUID REFERENCES services(id) ON DELETE CASCADE, + custom_price NUMERIC(10,2), + custom_duration INT, + commission_percent NUMERIC(5,2) DEFAULT 0, + UNIQUE(professional_id, service_id) +); + +-- ============================================= +-- 7. HORÁRIOS DE TRABALHO +-- ============================================= +CREATE TABLE IF NOT EXISTS working_hours ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + professional_id UUID REFERENCES users(id) ON DELETE CASCADE, + day_of_week INT NOT NULL CHECK (day_of_week BETWEEN 0 AND 6), -- 0=domingo + start_time TIME NOT NULL, + end_time TIME NOT NULL, + break_start TIME, + break_end TIME, + is_active BOOLEAN DEFAULT true, + UNIQUE(professional_id, day_of_week) +); + +-- ============================================= +-- 8. BLOQUEIOS / FOLGAS +-- ============================================= +CREATE TABLE IF NOT EXISTS schedule_blocks ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + professional_id UUID REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(200), + start_at TIMESTAMPTZ NOT NULL, + end_at TIMESTAMPTZ NOT NULL, + is_all_day BOOLEAN DEFAULT false, + recurrence VARCHAR(20) DEFAULT 'none', -- none, daily, weekly, monthly + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 9. CLIENTES +-- ============================================= +CREATE TABLE IF NOT EXISTS clients ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + name VARCHAR(200) NOT NULL, + email VARCHAR(200), + phone VARCHAR(20), + cpf VARCHAR(14), + birth_date DATE, + gender VARCHAR(20), + notes TEXT, + avatar_url TEXT, + total_visits INT DEFAULT 0, + total_spent NUMERIC(10,2) DEFAULT 0, + last_visit_at TIMESTAMPTZ, + tags JSONB DEFAULT '[]', + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 10. AGENDAMENTOS +-- ============================================= +CREATE TABLE IF NOT EXISTS appointments ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + client_id UUID REFERENCES clients(id) ON DELETE SET NULL, + professional_id UUID REFERENCES users(id) ON DELETE SET NULL, + service_id UUID REFERENCES services(id) ON DELETE SET NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, confirmed, in_progress, completed, cancelled, no_show + date DATE NOT NULL, + start_time TIME NOT NULL, + end_time TIME NOT NULL, + price NUMERIC(10,2) NOT NULL DEFAULT 0, + discount NUMERIC(10,2) DEFAULT 0, + notes TEXT, + client_name VARCHAR(200), -- for walk-ins + client_phone VARCHAR(20), + source VARCHAR(20) DEFAULT 'manual', -- manual, online, whatsapp + cancelled_at TIMESTAMPTZ, + cancellation_reason TEXT, + completed_at TIMESTAMPTZ, + reminder_sent BOOLEAN DEFAULT false, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 11. PAGAMENTOS +-- ============================================= +CREATE TABLE IF NOT EXISTS payments ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + appointment_id UUID REFERENCES appointments(id) ON DELETE SET NULL, + client_id UUID REFERENCES clients(id) ON DELETE SET NULL, + amount NUMERIC(10,2) NOT NULL, + method VARCHAR(30) NOT NULL DEFAULT 'cash', -- cash, credit, debit, pix, transfer + status VARCHAR(20) DEFAULT 'completed', -- pending, completed, refunded + notes TEXT, + paid_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 12. ASSINATURAS SaaS +-- ============================================= +CREATE TABLE IF NOT EXISTS subscriptions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + plan_id UUID REFERENCES plans(id), + status VARCHAR(20) DEFAULT 'active', -- active, past_due, cancelled, suspended + billing_cycle VARCHAR(10) DEFAULT 'monthly', -- monthly, yearly + current_period_start TIMESTAMPTZ, + current_period_end TIMESTAMPTZ, + payment_method VARCHAR(30), + payment_gateway VARCHAR(30), -- stripe, asaas, manual + gateway_subscription_id VARCHAR(200), + amount NUMERIC(10,2), + last_payment_at TIMESTAMPTZ, + next_payment_at TIMESTAMPTZ, + cancelled_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 13. HISTÓRICO DE PAGAMENTOS SaaS +-- ============================================= +CREATE TABLE IF NOT EXISTS subscription_payments ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + subscription_id UUID REFERENCES subscriptions(id) ON DELETE CASCADE, + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + amount NUMERIC(10,2) NOT NULL, + status VARCHAR(20) DEFAULT 'paid', -- paid, failed, refunded + gateway_payment_id VARCHAR(200), + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- 14. NOTIFICAÇÕES +-- ============================================= +CREATE TABLE IF NOT EXISTS notifications ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + type VARCHAR(50) NOT NULL, + title VARCHAR(200) NOT NULL, + message TEXT, + data JSONB DEFAULT '{}', + is_read BOOLEAN DEFAULT false, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================= +-- INDEXES +-- ============================================= +CREATE INDEX IF NOT EXISTS idx_appointments_tenant_date ON appointments(tenant_id, date); +CREATE INDEX IF NOT EXISTS idx_appointments_professional ON appointments(professional_id, date); +CREATE INDEX IF NOT EXISTS idx_appointments_client ON appointments(client_id); +CREATE INDEX IF NOT EXISTS idx_appointments_status ON appointments(status); +CREATE INDEX IF NOT EXISTS idx_clients_tenant ON clients(tenant_id); +CREATE INDEX IF NOT EXISTS idx_clients_phone ON clients(phone); +CREATE INDEX IF NOT EXISTS idx_services_tenant ON services(tenant_id); +CREATE INDEX IF NOT EXISTS idx_payments_tenant ON payments(tenant_id); +CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id, is_read); +CREATE INDEX IF NOT EXISTS idx_users_tenant ON users(tenant_id); +CREATE INDEX IF NOT EXISTS idx_tenants_slug ON tenants(slug); + +-- ============================================= +-- SEED: DEFAULT PLANS +-- ============================================= +INSERT INTO plans (name, slug, description, price_monthly, price_yearly, max_professionals, max_services, max_appointments_month, features) VALUES + ('Starter', 'starter', 'Ideal para profissionais autônomos', 49.90, 479.00, 1, 10, 200, '["Agendamento online", "Página de reservas", "Lembretes por e-mail", "Relatórios básicos"]'), + ('Professional', 'professional', 'Para pequenos estabelecimentos', 99.90, 959.00, 5, 30, NULL, '["Tudo do Starter", "Múltiplos profissionais", "Comissionamento", "Relatórios avançados", "Personalização da marca"]'), + ('Business', 'business', 'Para estabelecimentos em crescimento', 199.90, 1919.00, 15, 100, NULL, '["Tudo do Professional", "API de integração", "Suporte prioritário", "Multi-unidades", "Automações avançadas"]'), + ('Enterprise', 'enterprise', 'Solução completa e personalizada', 399.90, 3839.00, 999, 999, NULL, '["Tudo do Business", "Gerente de conta dedicado", "SLA garantido", "Integrações customizadas", "White-label"]') +ON CONFLICT (slug) DO NOTHING; + +-- ============================================= +-- SEED: DEMO TENANT +-- ============================================= +DO $$ +DECLARE + v_plan_id UUID; + v_tenant_id UUID; + v_owner_id UUID; + v_cat_barba_id UUID; + v_cat_cabelo_id UUID; + v_cat_combo_id UUID; + v_svc1_id UUID; + v_svc2_id UUID; + v_svc3_id UUID; + v_svc4_id UUID; + v_svc5_id UUID; + v_prof1_id UUID; + v_prof2_id UUID; +BEGIN + SELECT id INTO v_plan_id FROM plans WHERE slug = 'professional'; + + INSERT INTO tenants (id, plan_id, name, slug, business_type, phone, email, description, subscription_status, trial_ends_at) + VALUES ( + uuid_generate_v4(), v_plan_id, 'Barbearia Premium', 'barbearia-premium', 'barbearia', + '(11) 99999-1234', 'contato@barbeariapremium.com.br', + 'A melhor barbearia da cidade. Cortes modernos, barbas impecáveis e ambiente premium.', + 'active', NOW() + INTERVAL '30 days' + ) + ON CONFLICT (slug) DO NOTHING + RETURNING id INTO v_tenant_id; + + IF v_tenant_id IS NOT NULL THEN + -- Owner + INSERT INTO users (id, tenant_id, name, email, password_hash, role) + VALUES (uuid_generate_v4(), v_tenant_id, 'Admin Demo', 'admin@demo.com', crypt('admin123', gen_salt('bf')), 'owner') + RETURNING id INTO v_owner_id; + + -- Professionals + INSERT INTO users (id, tenant_id, name, email, password_hash, role, phone) + VALUES (uuid_generate_v4(), v_tenant_id, 'Carlos Silva', 'carlos@demo.com', crypt('prof123', gen_salt('bf')), 'professional', '(11) 98888-1111') + RETURNING id INTO v_prof1_id; + + INSERT INTO users (id, tenant_id, name, email, password_hash, role, phone) + VALUES (uuid_generate_v4(), v_tenant_id, 'Rafael Santos', 'rafael@demo.com', crypt('prof123', gen_salt('bf')), 'professional', '(11) 98888-2222') + RETURNING id INTO v_prof2_id; + + -- Categories + INSERT INTO service_categories (id, tenant_id, name, icon, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, 'Cabelo', 'scissors', 1) RETURNING id INTO v_cat_cabelo_id; + INSERT INTO service_categories (id, tenant_id, name, icon, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, 'Barba', 'sparkles', 2) RETURNING id INTO v_cat_barba_id; + INSERT INTO service_categories (id, tenant_id, name, icon, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, 'Combos', 'star', 3) RETURNING id INTO v_cat_combo_id; + + -- Services + INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, v_cat_cabelo_id, 'Corte Masculino', 'Corte moderno com lavagem e finalização', 40, 55.00, 1) RETURNING id INTO v_svc1_id; + INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, v_cat_cabelo_id, 'Corte Infantil', 'Corte para crianças até 12 anos', 30, 40.00, 2) RETURNING id INTO v_svc2_id; + INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, v_cat_barba_id, 'Barba Completa', 'Aparar, modelar e hidratação', 30, 40.00, 3) RETURNING id INTO v_svc3_id; + INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, v_cat_barba_id, 'Design de Sobrancelha', 'Alinhamento com navalha', 15, 25.00, 4) RETURNING id INTO v_svc4_id; + INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES + (uuid_generate_v4(), v_tenant_id, v_cat_combo_id, 'Combo Corte + Barba', 'Corte masculino + barba completa', 60, 85.00, 5) RETURNING id INTO v_svc5_id; + + -- Professional <-> Services + INSERT INTO professional_services (professional_id, service_id, commission_percent) VALUES + (v_prof1_id, v_svc1_id, 40), (v_prof1_id, v_svc2_id, 40), (v_prof1_id, v_svc3_id, 40), + (v_prof1_id, v_svc4_id, 40), (v_prof1_id, v_svc5_id, 40), + (v_prof2_id, v_svc1_id, 35), (v_prof2_id, v_svc3_id, 35), (v_prof2_id, v_svc5_id, 35); + + -- Working Hours (Mon-Sat for both professionals) + FOR i IN 1..6 LOOP + INSERT INTO working_hours (tenant_id, professional_id, day_of_week, start_time, end_time, break_start, break_end) VALUES + (v_tenant_id, v_prof1_id, i, '09:00', '19:00', '12:00', '13:00'), + (v_tenant_id, v_prof2_id, i, '10:00', '20:00', '13:00', '14:00'); + END LOOP; + + -- Sample Clients + INSERT INTO clients (tenant_id, name, email, phone, total_visits) VALUES + (v_tenant_id, 'João Mendes', 'joao@email.com', '(11) 97777-1111', 12), + (v_tenant_id, 'Pedro Oliveira', 'pedro@email.com', '(11) 97777-2222', 8), + (v_tenant_id, 'Lucas Ferreira', 'lucas@email.com', '(11) 97777-3333', 5), + (v_tenant_id, 'Mateus Costa', 'mateus@email.com', '(11) 97777-4444', 3), + (v_tenant_id, 'Gabriel Souza', 'gabriel@email.com', '(11) 97777-5555', 15); + + -- Sample appointments for today + INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source) + SELECT v_tenant_id, c.id, v_prof1_id, v_svc1_id, 'confirmed', CURRENT_DATE, '10:00', '10:40', 55.00, 'online' + FROM clients c WHERE c.name = 'João Mendes' AND c.tenant_id = v_tenant_id LIMIT 1; + + INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source) + SELECT v_tenant_id, c.id, v_prof1_id, v_svc5_id, 'confirmed', CURRENT_DATE, '11:00', '12:00', 85.00, 'manual' + FROM clients c WHERE c.name = 'Pedro Oliveira' AND c.tenant_id = v_tenant_id LIMIT 1; + + INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source) + SELECT v_tenant_id, c.id, v_prof2_id, v_svc3_id, 'pending', CURRENT_DATE, '14:00', '14:30', 40.00, 'online' + FROM clients c WHERE c.name = 'Lucas Ferreira' AND c.tenant_id = v_tenant_id LIMIT 1; + + INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source) + SELECT v_tenant_id, c.id, v_prof2_id, v_svc1_id, 'completed', CURRENT_DATE, '10:00', '10:40', 55.00, 'manual' + FROM clients c WHERE c.name = 'Gabriel Souza' AND c.tenant_id = v_tenant_id LIMIT 1; + + -- Subscription record + INSERT INTO subscriptions (tenant_id, plan_id, status, billing_cycle, current_period_start, current_period_end, amount, next_payment_at) + VALUES (v_tenant_id, v_plan_id, 'active', 'monthly', NOW(), NOW() + INTERVAL '30 days', 99.90, NOW() + INTERVAL '30 days'); + END IF; +END $$; diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..2456210 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# ================================================================ +# AgendaPRO — Deploy Script para Docker Swarm via SSH +# Uso: ./deploy.sh [user@host] [dominio-app] [dominio-admin] +# Ex: ./deploy.sh root@vps.meusite.com app.agendapro.com.br admin.agendapro.com.br +# ================================================================ + +set -e + +SSH_TARGET=${1:-"root@your-server.com"} +APP_DOMAIN=${2:-"app.agendapro.com.br"} +ADMIN_DOMAIN=${3:-"admin.agendapro.com.br"} +STACK_NAME="agendapro" + +echo "🚀 AgendaPRO — Deploy para Docker Swarm" +echo "==========================================" +echo " Servidor: $SSH_TARGET" +echo " App URL: https://$APP_DOMAIN" +echo " Admin URL: https://$ADMIN_DOMAIN" +echo "==========================================" + +# 1. Build das imagens +echo "" +echo "📦 [1/6] Construindo imagens Docker..." +docker build -t agendapro-app:latest . +docker build -t agendapro-admin:latest ./admin + +# 2. Salvar imagens para transferência +echo "" +echo "💾 [2/6] Exportando imagens..." +docker save agendapro-app:latest | gzip > /tmp/agendapro-app.tar.gz +docker save agendapro-admin:latest | gzip > /tmp/agendapro-admin.tar.gz + +# 3. Transferir via SSH +echo "" +echo "📤 [3/6] Transferindo para o servidor..." +scp /tmp/agendapro-app.tar.gz $SSH_TARGET:/tmp/ +scp /tmp/agendapro-admin.tar.gz $SSH_TARGET:/tmp/ +scp docker-compose.yml $SSH_TARGET:/opt/agendapro/ +scp database/init.sql $SSH_TARGET:/opt/agendapro/database/ + +# 4. Carregar imagens no servidor +echo "" +echo "📥 [4/6] Carregando imagens no servidor..." +ssh $SSH_TARGET "docker load < /tmp/agendapro-app.tar.gz && docker load < /tmp/agendapro-admin.tar.gz" + +# 5. Criar secrets (se não existirem) +echo "" +echo "🔐 [5/6] Verificando Docker Secrets..." +ssh $SSH_TARGET " + docker swarm init 2>/dev/null || true + docker network create --driver overlay webproxy 2>/dev/null || true + + if ! docker secret inspect db_password > /dev/null 2>&1; then + echo \$(openssl rand -base64 32) | docker secret create db_password - + echo ' ✓ Secret db_password criado' + fi + if ! docker secret inspect jwt_secret > /dev/null 2>&1; then + echo \$(openssl rand -base64 64) | docker secret create jwt_secret - + echo ' ✓ Secret jwt_secret criado' + fi + if ! docker secret inspect admin_secret > /dev/null 2>&1; then + echo \$(openssl rand -base64 64) | docker secret create admin_secret - + echo ' ✓ Secret admin_secret criado' + fi +" + +# 6. Deploy da stack +echo "" +echo "🚀 [6/6] Fazendo deploy da stack..." +ssh $SSH_TARGET " + cd /opt/agendapro + # Substituir domínios no compose + sed -i 's/app.agendapro.com.br/$APP_DOMAIN/g' docker-compose.yml + sed -i 's/admin.agendapro.com.br/$ADMIN_DOMAIN/g' docker-compose.yml + + docker stack deploy -c docker-compose.yml $STACK_NAME +" + +echo "" +echo "✅ Deploy concluído com sucesso!" +echo "" +echo " 🌐 AgendaPRO App: https://$APP_DOMAIN" +echo " 🔧 Admin Panel: https://$ADMIN_DOMAIN" +echo " 📊 Traefik Dashboard: https://traefik.$APP_DOMAIN" +echo "" +echo " Comandos úteis:" +echo " ssh $SSH_TARGET 'docker service ls'" +echo " ssh $SSH_TARGET 'docker service logs ${STACK_NAME}_agendapro'" +echo " ssh $SSH_TARGET 'docker service logs ${STACK_NAME}_admin'" +echo "" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3442736 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,156 @@ +version: "3.8" + +services: + # ============================================================= + # TRAEFIK — Reverse Proxy & SSL (Let's Encrypt) + # ============================================================= + traefik: + image: traefik:v2.11 + command: + - "--api.dashboard=true" + - "--providers.docker=true" + - "--providers.docker.swarmMode=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + - "--certificatesresolvers.letsencrypt.acme.email=admin@agendapro.com.br" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + ports: + - "80:80" + - "443:443" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "traefik-certs:/letsencrypt" + networks: + - webproxy + deploy: + replicas: 1 + placement: + constraints: [node.role == manager] + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik.rule=Host(`traefik.agendapro.com.br`)" + - "traefik.http.routers.traefik.entrypoints=websecure" + - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" + - "traefik.http.routers.traefik.service=api@internal" + - "traefik.http.services.traefik.loadbalancer.server.port=8080" + + # ============================================================= + # POSTGRESQL — Banco de Dados Compartilhado + # ============================================================= + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: agendapro + POSTGRES_USER: agendapro_user + POSTGRES_PASSWORD_FILE: /run/secrets/db_password + secrets: + - db_password + volumes: + - pgdata:/var/lib/postgresql/data + - ./database/init.sql:/docker-entrypoint-initdb.d/01-init.sql + networks: + - backend + deploy: + replicas: 1 + placement: + constraints: [node.role == manager] + + # ============================================================= + # AGENDAPRO APP — Sistema Principal (app.agendapro.com.br) + # ============================================================= + agendapro: + image: agendapro-app:latest + build: + context: . + dockerfile: Dockerfile + environment: + DATABASE_URL: postgresql://agendapro_user:${DB_PASSWORD}@postgres:5432/agendapro + JWT_SECRET_FILE: /run/secrets/jwt_secret + NODE_ENV: production + secrets: + - jwt_secret + networks: + - webproxy + - backend + deploy: + replicas: 2 + update_config: + parallelism: 1 + delay: 10s + order: start-first + rollback_config: + parallelism: 1 + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + labels: + - "traefik.enable=true" + - "traefik.http.routers.agendapro.rule=Host(`app.agendapro.com.br`)" + - "traefik.http.routers.agendapro.entrypoints=websecure" + - "traefik.http.routers.agendapro.tls.certresolver=letsencrypt" + - "traefik.http.services.agendapro.loadbalancer.server.port=3000" + - "traefik.http.services.agendapro.loadbalancer.healthcheck.path=/api/health" + - "traefik.http.services.agendapro.loadbalancer.healthcheck.interval=15s" + + # ============================================================= + # ADMIN PANEL — Gerenciador de Assinaturas (admin.agendapro.com.br) + # ============================================================= + admin: + image: agendapro-admin:latest + build: + context: ./admin + dockerfile: Dockerfile + environment: + DATABASE_URL: postgresql://agendapro_user:${DB_PASSWORD}@postgres:5432/agendapro + JWT_SECRET_FILE: /run/secrets/jwt_secret + ADMIN_SECRET_FILE: /run/secrets/admin_secret + NODE_ENV: production + secrets: + - jwt_secret + - admin_secret + networks: + - webproxy + - backend + deploy: + replicas: 1 + update_config: + parallelism: 1 + delay: 10s + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + labels: + - "traefik.enable=true" + - "traefik.http.routers.admin.rule=Host(`admin.agendapro.com.br`)" + - "traefik.http.routers.admin.entrypoints=websecure" + - "traefik.http.routers.admin.tls.certresolver=letsencrypt" + - "traefik.http.services.admin.loadbalancer.server.port=3001" + # Proteção extra: Basic Auth no admin + - "traefik.http.routers.admin.middlewares=admin-auth" + - "traefik.http.middlewares.admin-auth.basicauth.users=admin:$$apr1$$xyz$$hashedpassword" + +volumes: + pgdata: + traefik-certs: + +networks: + webproxy: + driver: overlay + external: false + backend: + driver: overlay + internal: true + +secrets: + db_password: + external: true + jwt_secret: + external: true + admin_secret: + external: true diff --git a/gemini.md b/gemini.md new file mode 100644 index 0000000..f41a773 --- /dev/null +++ b/gemini.md @@ -0,0 +1,60 @@ +# Registro de Modificações - Gemini (AgendaPRO) + +Este arquivo registra todas as alterações, novos arquivos criados e configurações de dependências feitas no projeto **AgendaPRO** para a integração do gateway Asaas e controle de acesso por assinatura. + +--- + +## 1. Arquivos Criados + +### A. `src/lib/asaas.ts` +Implementação do cliente de API modular do Asaas (v3) para sandbox e produção: +* Registro de clientes (`createCustomer`). +* Geração de cobranças avulsas (`createPayment`). +* Busca de QR Code e Linha Digitável do Pix (`getPixQrCode`). +* Gerenciamento de Assinaturas recorrentes (`createSubscription` e `cancelSubscription`). + +### B. `src/app/api/webhooks/asaas/route.ts` +Endpoint de recebimento de notificações do Asaas: +* Validação do cabeçalho `asaas-access-token`. +* Idempotência via tabela `asaas_webhook_events`. +* Atualização automatizada de status de agendamentos (`payments` e `appointments`) e assinaturas dos inquilinos (`subscriptions`). + +### C. `src/app/api/tenant/subscription/route.ts` +Endpoint para controle de fluxo de planos: +* `GET`: Consulta do plano ativo do tenant, validade e lista de planos disponíveis (com auto-seeding de planos Starter, Professional, Business e Enterprise). +* `POST`: Criação e cobrança de novas assinaturas via Pix, Boleto ou Cartão de Crédito integrados ao Asaas. + +### D. `src/app/api/tenant/subscription/simulate/route.ts` +Endpoint de desenvolvimento e testes em sandbox: +* `POST`: Permite forçar instantaneamente o status do tenant como `active` (desbloqueado) ou `inactive` (bloqueado) sem depender do envio externo de webhooks. + +### E. `asaas_skill.md` +Manual de referência contendo todas as chaves, rotas, payloads de integração e procedimentos de testes no ambiente Sandbox do Asaas. + +--- + +## 2. Arquivos Modificados + +### A. `src/app/page.tsx` +* Adicionado carregamento de status do plano no carregamento inicial da página. +* Enforcamento de bloqueio: inquilinos com assinatura pendente ou vencida são forçados a visualizar apenas a aba de assinatura. +* Adicionado loader de transição visual durante a verificação de acesso. + +### B. `src/components/Sidebar.tsx` +* Suporte ao parâmetro `isLocked`. +* Desativação visual (opacidade reduzida e ícone de cadeado `🔒`) dos menus de controle (Dashboard, Agenda, Clientes, Serviços, Equipe, Configurações) quando o acesso do inquilino está bloqueado por falta de pagamento. + +### C. `src/components/Subscription.tsx` +* Exibição de banner de alerta vermelho quando o acesso está bloqueado. +* Seleção visual interativa do plano desejado e do meio de pagamento (Pix, Boleto, Cartão de Crédito). +* Formulário de preenchimento de dados de cartão. +* Retorno com QR Code visual para Pix ou link para download do boleto. +* Botão **"Simular Pagamento Webhook"** integrado para desbloqueio instantâneo do sistema em ambiente local. + +### D. `tsconfig.json` +* Adicionada a exclusão da pasta `admin` (`"admin"`) na verificação de tipos do tsconfig principal para resolver conflitos de paths entre as duas subaplicações. + +--- + +## 3. Dependências Instaladas +* `@types/pg`: Instalado como dependência de desenvolvimento (`npm i --save-dev @types/pg`) para garantir tipagem estática e evitar erros de compilação em consultas ao banco PostgreSQL. diff --git a/memory.md b/memory.md new file mode 100644 index 0000000..5b83ae5 --- /dev/null +++ b/memory.md @@ -0,0 +1,58 @@ +# Contexto de Memória - AgendaPRO SaaS + +Este arquivo contém o histórico arquitetural e as decisões do sistema **AgendaPRO** para garantir consistência em futuras sessões de desenvolvimento. + +> [!IMPORTANT] +> O **AgendaPRO** é um projeto totalmente separado e independente do EduManager. O banco de dados e os recursos do AgendaPRO não devem ter nenhuma relação com o banco de dados do EduManager. + +--- + +## 1. Estrutura do Banco de Dados (AgendaPRO) +O banco de dados oficial do AgendaPRO é estruturado para multi-inquilinato (multi-tenant) usando PostgreSQL. Abaixo estão as tabelas principais mapeadas: + +* **`tenants`**: Cadastro de inquilinos/estabelecimentos. + * `id` (UUID, PK) + * `name` (VARCHAR) + * `slug` (VARCHAR, Unique) - Identificador na URL (ex: `barbearia-premium`) + * `subscription_status` (VARCHAR) - Status da assinatura (`active`, `pending`, `past_due`, `cancelled`, `suspended`) + * `subscription_ends_at` (TIMESTAMPTZ) - Data limite de validade do plano + * `settings` (JSONB) - Configurações gerais (fuso horário, chaves integradas de gateways de pagamento) +* **`plans`**: Planos de assinatura disponíveis na plataforma. + * `id` (UUID, PK) + * `name` (VARCHAR) + * `price` (NUMERIC) + * `features` (TEXT[]) + * `slug` (VARCHAR) +* **`subscriptions`**: Vinculo do tenant ao plano. + * `id` (UUID, PK) + * `tenant_id` (UUID, FK -> tenants) + * `plan_id` (UUID, FK -> plans) + * `status` (VARCHAR) + * `gateway_subscription_id` (VARCHAR) - ID da assinatura gerada no Asaas + * `amount` (NUMERIC) +* **`subscription_payments`**: Transações e faturas do plano de assinatura. + * `id` (UUID, PK) + * `subscription_id` (UUID, FK) + * `tenant_id` (UUID, FK) + * `amount` (NUMERIC) + * `status` (VARCHAR) + * `gateway_payment_id` (VARCHAR) +* **`payments`**: Cobranças avulsas emitidas pelos inquilinos para os seus clientes (agendamentos). + * `id` (UUID, PK) + * `tenant_id` (UUID, FK) + * `external_id` (VARCHAR) - Mapeamento com ID do Asaas (`pay_...`) + * `status` (VARCHAR) - (`pending`, `completed`, `refunded`, `failed`) + * `amount` (NUMERIC) + * `appointment_id` (UUID) + +--- + +## 2. Estrutura do Código e Separação de Projetos +* **Aplicação Principal (Painel do Inquilino & Agendamento do Cliente):** Localizada na raiz do projeto (`src/...`). +* **Painel Administrativo da Plataforma (Master Admin):** Localizado na subpasta `/admin`. Possui dockerfile próprio e gerencia todos os inquilinos e faturamento global da plataforma. + +--- + +## 3. Isolamento e Segurança +* Toda requisição feita de forma administrativa para cobrar os inquilinos usa a credencial mestre do AgendaPRO (`process.env.ASAAS_API_KEY`). +* Configurações específicas dos clientes dos inquilinos (ex: Pix do salão ou barbearia) são carregadas do campo `settings` da tabela `tenants`. diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..68a6c64 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "standalone", +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0b13730 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1288 @@ +{ + "name": "agendapro", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agendapro", + "version": "1.0.0", + "dependencies": { + "@types/node": "^22.15.21", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "bcryptjs": "^3.0.2", + "date-fns": "^4.1.0", + "jsonwebtoken": "^9.0.2", + "lucide-react": "^0.511.0", + "next": "^15.3.3", + "pg": "^8.16.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.3", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@types/pg": "^8.20.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.19.tgz", + "integrity": "sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.19.tgz", + "integrity": "sha512-jx9wWlTKueHKPvVOndyr7WuaevWCkuYqsQ8gC0TMPKAVWG3MhcdMrjfo9tvIZNXd0QOUYXXvAcZ325y8Uq7uzg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.19.tgz", + "integrity": "sha512-291KFcsIQ3OenRdiUDFOR6W3wezzH4auENXm1gbm1Bjd4ANMMRgxPrWTUztQN43BnVoVuMnHCrLeECIMwgFKbA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.19.tgz", + "integrity": "sha512-WeH+nelQyyMeE2f8FxBRZNrGipya5zHZV2vjzfCOAYyiI6am+NbnWAAldOBFQBB2w0DjJcsvrKqoFT2b7+5YoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.19.tgz", + "integrity": "sha512-5xTOE0lDlDCSSfp+BAif7j17VRRCjWp//ZPZy6NI0QpdrhxtQnsZguSx0xAAZ0c9XZLrLLwCe/XVe5YPrRilKw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.19.tgz", + "integrity": "sha512-LTxRmMgqqMv05Had879W00Fm53quiJd3Zuz8h1JSNJ3nGSlbZ/7Tjs1tKyScgN3Au3t3MyPsjPlq60fMmSHLsg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.19.tgz", + "integrity": "sha512-eoNQSpA5PQfB9wBO4RA47MTDXWz1fizy9Y3Z6e4DetYIF3dvjuu8sj7aIGn/bFCU6lnFzTK34NtCaffP4NsQ7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.19.tgz", + "integrity": "sha512-6UNt2dFuCHOe446sm/Kp69nUe8/wIhnh9bm6Xcqw4qEWCOppLMOvhTBVgvM7invVUNr4SPpP6NOQsACtn2IN9Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.19.tgz", + "integrity": "sha512-PhmojAHyqMne56HBLGu9dhDnHPuFmEjrXSQMM/nW0J6j849lk3ESrVtqNJcCk8CKOV7brpTTbaYAjwKPzKM69w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "15.5.19", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.19.tgz", + "integrity": "sha512-xNOW6tYshGX1/Oi3F8uuk4gpDeWsSUE/1Z0G5uUMekIxaQ0xc03UXd9II0VQHYMWviMeA0OHpJFAKsHf8bTYVg==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.19", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.19", + "@next/swc-darwin-x64": "15.5.19", + "@next/swc-linux-arm64-gnu": "15.5.19", + "@next/swc-linux-arm64-musl": "15.5.19", + "@next/swc-linux-x64-gnu": "15.5.19", + "@next/swc-linux-x64-musl": "15.5.19", + "@next/swc-win32-arm64-msvc": "15.5.19", + "@next/swc-win32-x64-msvc": "15.5.19", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/pg": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..084ea76 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "agendapro", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@types/node": "^22.15.21", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "bcryptjs": "^3.0.2", + "date-fns": "^4.1.0", + "jsonwebtoken": "^9.0.2", + "lucide-react": "^0.511.0", + "next": "^15.3.3", + "pg": "^8.16.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.3", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@types/pg": "^8.20.0" + } +} diff --git a/src/app/[slug]/agendar/page.tsx b/src/app/[slug]/agendar/page.tsx new file mode 100644 index 0000000..f0c62d2 --- /dev/null +++ b/src/app/[slug]/agendar/page.tsx @@ -0,0 +1,400 @@ +'use client'; +import { useState, useEffect, use } from 'react'; +import { Calendar, Clock, User, Phone, Mail, ChevronLeft, ChevronRight, Check, Scissors, MapPin, Star } from 'lucide-react'; + +const establishment = { + name: 'Barbearia Premium', + type: 'barbearia', + description: 'A melhor barbearia da cidade. Cortes modernos, barbas impecáveis e ambiente premium.', + phone: '(11) 99999-1234', + address: 'Rua das Flores, 123 - Centro, São Paulo - SP', + rating: 4.9, + reviews: 127, +}; + +const services = [ + { id: '1', category: 'Cabelo', name: 'Corte Masculino', desc: 'Corte moderno com lavagem e finalização', duration: 40, price: 55 }, + { id: '2', category: 'Cabelo', name: 'Corte Infantil', desc: 'Corte para crianças até 12 anos', duration: 30, price: 40 }, + { id: '3', category: 'Barba', name: 'Barba Completa', desc: 'Aparar, modelar e hidratação', duration: 30, price: 40 }, + { id: '4', category: 'Barba', name: 'Design de Sobrancelha', desc: 'Alinhamento com navalha', duration: 15, price: 25 }, + { id: '5', category: 'Combos', name: 'Combo Corte + Barba', desc: 'Corte masculino + barba completa', duration: 60, price: 85 }, +]; + +const professionals = [ + { id: '1', name: 'Carlos Silva', role: 'Barbeiro Senior', rating: 4.8 }, + { id: '2', name: 'Rafael Santos', role: 'Barbeiro', rating: 4.6 }, +]; + +export default function PublicBookingPage({ params }: { params: Promise<{ slug: string }> }) { + const { slug } = use(params); + + const [step, setStep] = useState(1); + const [selectedService, setSelectedService] = useState(null); + const [selectedProfessional, setSelectedProfessional] = useState(null); + const [selectedDate, setSelectedDate] = useState(''); + const [selectedTime, setSelectedTime] = useState(null); + const [clientName, setClientName] = useState(''); + const [clientPhone, setClientPhone] = useState(''); + const [clientEmail, setClientEmail] = useState(''); + const [booked, setBooked] = useState(false); + const [slots, setSlots] = useState([]); + + // Carrega e gera os slots dinâmicos do barbeiro + useEffect(() => { + const getCookie = (name: string): string => { + if (typeof document === 'undefined') return ''; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()!.split(';').shift() || ''; + return ''; + }; + + const start = getCookie(`agendapro_start_time_${slug}`) || localStorage.getItem(`agendapro_start_time_${slug}`) || '09:00'; + const end = getCookie(`agendapro_end_time_${slug}`) || localStorage.getItem(`agendapro_end_time_${slug}`) || '19:00'; + const interval = Number(getCookie(`agendapro_interval_${slug}`) || localStorage.getItem(`agendapro_interval_${slug}`) || '30'); + + // Algoritmo dinâmico de fatiamento de grade + const generateSlots = (startTimeStr: string, endTimeStr: string, intervalMin: number) => { + const generated = []; + let [sh, sm] = startTimeStr.split(':').map(Number); + const [eh, em] = endTimeStr.split(':').map(Number); + + let currentMinutes = sh * 60 + sm; + const endMinutes = eh * 60 + em; + + while (currentMinutes < endMinutes) { + const h = Math.floor(currentMinutes / 60); + const m = currentMinutes % 60; + const timeStr = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; + generated.push(timeStr); + currentMinutes += intervalMin; + } + return generated; + }; + + setSlots(generateSlots(start, end, interval)); + }, [slug]); + + const today = new Date(); + const dates = Array.from({ length: 14 }, (_, i) => { + const d = new Date(today); + d.setDate(d.getDate() + i); + return d; + }); + + const svc = services.find(s => s.id === selectedService); + const prof = professionals.find(p => p.id === selectedProfessional); + + const canNext = () => { + if (step === 1) return !!selectedService; + if (step === 2) return !!selectedProfessional; + if (step === 3) return !!selectedDate && !!selectedTime; + if (step === 4) return clientName.trim() && clientPhone.trim(); + return false; + }; + + const handleBook = () => { + // 1. Monta o objeto de agendamento no formato esperado pela Agenda + const [startHour, startMin] = selectedTime ? selectedTime.split(':').map(Number) : [9, 0]; + const newAppointment = { + id: Date.now().toString(), + professionalId: selectedProfessional || '1', + client: clientName, + phone: clientPhone, + email: clientEmail, + service: svc?.name || 'Serviço', + price: svc?.price || 0, + duration: svc?.duration || 30, + startHour, + startMin, + status: 'pending', // Inicia pendente de aprovação! + date: selectedDate, // Data do agendamento (ex: "2026-05-31") + }; + + // 2. Salva no localStorage com isolamento por slug (Tenant Isolation) + const key = `agendapro_appointments_${slug}`; + const existing = localStorage.getItem(key); + let currentList = []; + if (existing) { + currentList = JSON.parse(existing); + } else { + // Se não existia e for barbearia premium demo, pega os mockAppointments padrão + currentList = slug === 'barbearia-premium' ? [ + { id: '1', professionalId: '1', client: 'João Mendes', service: 'Corte Masculino', startHour: 10, startMin: 0, duration: 40, status: 'confirmed', price: 55, date: new Date().toISOString().split('T')[0] }, + { id: '2', professionalId: '1', client: 'Pedro Oliveira', service: 'Combo Corte + Barba', startHour: 11, startMin: 0, duration: 60, status: 'confirmed', price: 85, date: new Date().toISOString().split('T')[0] }, + { id: '3', professionalId: '2', client: 'Lucas Ferreira', service: 'Barba Completa', startHour: 14, startMin: 0, duration: 30, status: 'pending', price: 40, date: new Date().toISOString().split('T')[0] }, + { id: '4', professionalId: '2', client: 'Gabriel Souza', service: 'Corte Masculino', startHour: 10, startMin: 0, duration: 40, status: 'completed', price: 55, date: new Date().toISOString().split('T')[0] }, + { id: '5', professionalId: '1', client: 'Mateus Costa', service: 'Design de Sobrancelha', startHour: 15, startMin: 30, duration: 15, status: 'confirmed', price: 25, date: new Date().toISOString().split('T')[0] } + ] : []; + } + + currentList.push(newAppointment); + localStorage.setItem(key, JSON.stringify(currentList)); + setBooked(true); + }; + + if (booked) { + return ( +
+
+
+ +
+

Agendamento Confirmado! 🎉

+

+ Seu horário foi reservado com sucesso. +

+
+
+
Serviço
{svc?.name}
+
Profissional
{prof?.name}
+
+
Data
{selectedDate ? new Date(selectedDate + 'T12:00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' }) : ''}
+
Horário
{selectedTime}
+
+
+ Total + R$ {svc?.price.toFixed(2)} +
+
+
+

+ Você receberá uma confirmação por e-mail/WhatsApp. +

+
+
+ ); + } + + return ( +
+ {/* HEADER */} +
+
+
+
✂️
+

{establishment.name}

+

+ {establishment.address} +

+
+ + {establishment.rating} + ({establishment.reviews} avaliações) +
+
+
+ + {/* STEPPER */} +
+
+ {['Serviço', 'Profissional', 'Data e Hora', 'Seus Dados'].map((label, i) => ( +
+
i + 1 ? 'var(--success)' : step === i + 1 ? 'var(--accent)' : 'var(--bg-elevated)', + color: step >= i + 1 ? 'white' : 'var(--text-muted)', + transition: 'all 0.3s' + }}> + {step > i + 1 ? : i + 1} +
+ {label} + {i < 3 &&
i + 1 ? 'var(--success)' : 'var(--bg-elevated)', borderRadius: '1px' }} />} +
+ ))} +
+ + {/* STEP 1: SELECT SERVICE */} + {step === 1 && ( +
+

Escolha o serviço

+
+ {services.map(s => ( +
setSelectedService(s.id)} style={{ + padding: '18px 20px', borderRadius: 'var(--radius-md)', cursor: 'pointer', + background: selectedService === s.id ? 'var(--accent-glow)' : 'var(--bg-card)', + border: `2px solid ${selectedService === s.id ? 'var(--accent)' : 'var(--border-color)'}`, + transition: 'all 0.2s', display: 'flex', justifyContent: 'space-between', alignItems: 'center' + }}> +
+
{s.name}
+
{s.desc}
+
+ {s.duration} min +
+
+
+
R$ {s.price.toFixed(2)}
+
+
+ ))} +
+
+ )} + + {/* STEP 2: SELECT PROFESSIONAL */} + {step === 2 && ( +
+

Escolha o profissional

+
+ {professionals.map(p => ( +
setSelectedProfessional(p.id)} style={{ + padding: '20px', borderRadius: 'var(--radius-md)', cursor: 'pointer', + background: selectedProfessional === p.id ? 'var(--accent-glow)' : 'var(--bg-card)', + border: `2px solid ${selectedProfessional === p.id ? 'var(--accent)' : 'var(--border-color)'}`, + transition: 'all 0.2s', display: 'flex', alignItems: 'center', gap: '16px' + }}> +
{p.name.split(' ').map(n => n[0]).join('')}
+
+
{p.name}
+
{p.role}
+
+
+ {p.rating} +
+
+ ))} +
+
+ )} + + {/* STEP 3: SELECT DATE & TIME */} + {step === 3 && ( +
+

Escolha data e horário

+ +
+

+ Selecione a data +

+
+ {dates.map(d => { + const iso = d.toISOString().split('T')[0]; + const isSelected = selectedDate === iso; + const isToday = d.toDateString() === today.toDateString(); + const dayName = d.toLocaleDateString('pt-BR', { weekday: 'short' }).replace('.', ''); + return ( +
{ setSelectedDate(iso); setSelectedTime(null); }} style={{ + minWidth: '64px', padding: '12px 8px', borderRadius: 'var(--radius-md)', cursor: 'pointer', + textAlign: 'center', flexShrink: 0, transition: 'all 0.2s', + background: isSelected ? 'var(--accent)' : 'var(--bg-card)', + border: `2px solid ${isSelected ? 'var(--accent)' : 'var(--border-color)'}`, + color: isSelected ? 'white' : 'var(--text-primary)' + }}> +
{dayName}
+
{d.getDate()}
+
{d.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', '')}
+ {isToday &&
HOJE
} +
+ ); + })} +
+
+ + {selectedDate && ( +
+

+ Horários disponíveis +

+
+ {(slots.length > 0 ? slots : ['09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00', '17:30', '18:00']).map(slot => ( +
setSelectedTime(slot)} style={{ + padding: '12px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', textAlign: 'center', + fontWeight: 600, fontSize: '14px', transition: 'all 0.2s', + background: selectedTime === slot ? 'var(--accent)' : 'var(--bg-card)', + border: `1px solid ${selectedTime === slot ? 'var(--accent)' : 'var(--border-color)'}`, + color: selectedTime === slot ? 'white' : 'var(--text-primary)' + }}> + {slot} +
+ ))} +
+
+ )} +
+ )} + + {/* STEP 4: CLIENT DATA */} + {step === 4 && ( +
+

Seus dados

+ +
+

Resumo do agendamento

+
+
Serviço{svc?.name}
+
Profissional{prof?.name}
+
Data{selectedDate ? new Date(selectedDate + 'T12:00:00').toLocaleDateString('pt-BR') : ''}
+
Horário{selectedTime}
+
Duração{svc?.duration} min
+
+ Total + R$ {svc?.price.toFixed(2)} +
+
+
+ +
+ + setClientName(e.target.value)} /> +
+
+
+ + setClientPhone(e.target.value)} /> +
+
+ + setClientEmail(e.target.value)} /> +
+
+
+ )} + + {/* NAVIGATION BUTTONS */} +
+ {step > 1 ? ( + + ) :
} + {step < 4 ? ( + + ) : ( + + )} +
+
+ + {/* FOOTER */} +
+

+ Powered by AgendaPRO +

+
+
+ ); +} diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..3ec01eb --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from 'next/server'; + +export async function GET() { + return NextResponse.json({ status: 'ok', timestamp: new Date().toISOString() }); +} diff --git a/src/app/api/tenant/subscription/route.ts b/src/app/api/tenant/subscription/route.ts new file mode 100644 index 0000000..13988fa --- /dev/null +++ b/src/app/api/tenant/subscription/route.ts @@ -0,0 +1,286 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { query, getClient } from '@/lib/db'; +import { createCustomer, createSubscription, createPayment, getPixQrCode } from '@/lib/asaas'; + +/** + * GET: Retrieves the tenant's current subscription status and plan info. + * Query Parameter: ?slug=barbearia-premium + */ +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const slug = searchParams.get('slug'); + + if (!slug) { + return NextResponse.json({ error: 'Missing tenant slug' }, { status: 400 }); + } + + try { + // 1. Fetch the tenant from the database + let tenant; + let plans; + let activeSubscription = null; + + try { + let tenantRes = await query('SELECT * FROM tenants WHERE slug = $1', [slug]); + + // Auto-create demo tenant if it doesn't exist + if (tenantRes.rows.length === 0) { + await query( + `INSERT INTO tenants (name, slug, subscription_status, subscription_ends_at, settings) + VALUES ($1, $2, 'active', NOW() + INTERVAL '30 days', '{"timezone": "America/Sao_Paulo", "currency": "BRL"}')`, + [slug.replace('-', ' ').replace(/\b\w/g, c => c.toUpperCase()), slug] + ); + tenantRes = await query('SELECT * FROM tenants WHERE slug = $1', [slug]); + } + + tenant = tenantRes.rows[0]; + + // 2. Fetch the plans + const plansRes = await query('SELECT * FROM plans ORDER BY price ASC'); + plans = plansRes.rows; + + // Auto-seed plans if database is empty + if (plans.length === 0) { + const defaultPlans = [ + { name: 'Starter', price: 49.90, slug: 'starter', features: ['1 profissional', '10 serviços', '200 agendamentos/mês', 'Agendamento online', 'Página de reservas', 'Lembretes por e-mail', 'Relatórios básicos'] }, + { name: 'Professional', price: 99.90, slug: 'professional', features: ['5 profissionais', '30 serviços', 'Agendamentos ilimitados', 'Tudo do Starter', 'Múltiplos profissionais', 'Comissionamento', 'Relatórios avançados', 'Personalização da marca'] }, + { name: 'Business', price: 199.90, slug: 'business', features: ['15 profissionais', '100 serviços', 'Agendamentos ilimitados', 'Tudo do Professional', 'API de integração', 'Suporte prioritário', 'Multi-unidades', 'Automações avançadas'] }, + { name: 'Enterprise', price: 399.90, slug: 'enterprise', features: ['Profissionais ilimitados', 'Serviços ilimitados', 'Agendamentos ilimitados', 'Tudo do Business', 'Gerente dedicado', 'SLA garantido', 'Integrações customizadas', 'White-label'] } + ]; + for (const p of defaultPlans) { + await query( + 'INSERT INTO plans (name, price, slug, features) VALUES ($1, $2, $3, $4)', + [p.name, p.price, p.slug, p.features] + ); + } + const plansRetry = await query('SELECT * FROM plans ORDER BY price ASC'); + plans = plansRetry.rows; + } + + // 3. Fetch active subscription + const subRes = await query( + `SELECT s.*, p.name as plan_name, p.price as plan_price + FROM subscriptions s + JOIN plans p ON s.plan_id = p.id + WHERE s.tenant_id = $1 + ORDER BY s.created_at DESC LIMIT 1`, + [tenant.id] + ); + activeSubscription = subRes.rows[0] || null; + + } catch (dbErr) { + console.warn('Postgres connection failed. Falling back to local mock data.', dbErr); + return NextResponse.json({ + tenantId: 'mock-tenant-uuid', + name: slug.replace('-', ' ').replace(/\b\w/g, c => c.toUpperCase()), + slug: slug, + status: 'inactive', // Forces subscription checkout view + endsAt: null, + subscription: null, + plans: [ + { name: 'Starter', price: 49.90, slug: 'starter', features: ['1 profissional', '10 serviços', '200 agendamentos/mês', 'Agendamento online', 'Página de reservas', 'Lembretes por e-mail', 'Relatórios básicos'] }, + { name: 'Professional', price: 99.90, slug: 'professional', features: ['5 profissionais', '30 serviços', 'Agendamentos ilimitados', 'Tudo do Starter', 'Múltiplos profissionais', 'Comissionamento', 'Relatórios avançados', 'Personalização da marca'] }, + { name: 'Business', price: 199.90, slug: 'business', features: ['15 profissionais', '100 serviços', 'Agendamentos ilimitados', 'Tudo do Professional', 'API de integração', 'Suporte prioritário', 'Multi-unidades', 'Automações avançadas'] }, + { name: 'Enterprise', price: 399.90, slug: 'enterprise', features: ['Profissionais ilimitados', 'Serviços ilimitados', 'Agendamentos ilimitados', 'Tudo do Business', 'Gerente dedicado', 'SLA garantido', 'Integrações customizadas', 'White-label'] } + ] + }); + } + + return NextResponse.json({ + tenantId: tenant.id, + name: tenant.name, + slug: tenant.slug, + status: tenant.subscription_status || 'inactive', + endsAt: tenant.subscription_ends_at, + subscription: activeSubscription, + plans, + }); + } catch (error: any) { + console.error('Failed to get subscription info:', error); + return NextResponse.json({ error: error.message || 'Internal server error' }, { status: 500 }); + } +} + +/** + * POST: Initiates a subscription/payment flow. + */ +export async function POST(req: NextRequest) { + try { + const { slug, planName, billingType, creditCard, creditCardHolderInfo, billingInfo } = await req.json(); + + if (!slug || !planName || !billingType) { + return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); + } + + const userBilling = billingInfo || {}; + + // 1. Fetch tenant and plan + let tenant; + let plan; + let dbFailed = false; + + try { + const tenantRes = await query('SELECT * FROM tenants WHERE slug = $1', [slug]); + const planRes = await query('SELECT * FROM plans WHERE name = $1', [planName]); + + if (tenantRes.rows.length === 0) { + return NextResponse.json({ error: 'Tenant not found' }, { status: 404 }); + } + if (planRes.rows.length === 0) { + return NextResponse.json({ error: 'Plan not found' }, { status: 404 }); + } + + tenant = tenantRes.rows[0]; + plan = planRes.rows[0]; + } catch (dbErr) { + console.warn('Postgres offline in POST. Using mock records to continue Asaas integration.', dbErr); + dbFailed = true; + tenant = { id: 'mock-uuid', name: slug.toUpperCase(), slug, settings: {} }; + plan = { id: 'mock-plan-uuid', name: planName, price: 199.90 }; + if (planName === 'Starter') plan.price = 49.90; + else if (planName === 'Professional') plan.price = 99.90; + else if (planName === 'Business') plan.price = 199.90; + else if (planName === 'Enterprise') plan.price = 399.90; + } + + const settings = tenant.settings || {}; + + // 2. Ensure customer exists in Asaas (AgendaPRO's account) + let asaasCustomerId = settings.asaas_customer_id; + + if (!asaasCustomerId) { + console.log(`Creating Asaas customer for tenant ${tenant.name}...`); + try { + const customer = await createCustomer('', { + name: userBilling.name || tenant.name, + cpfCnpj: userBilling.cpfCnpj ? userBilling.cpfCnpj.replace(/\D/g, '') : '81804938069', + email: userBilling.email || settings.email || `${slug}@agendapro.com`, + phone: userBilling.phone ? userBilling.phone.replace(/\D/g, '') : settings.phone || '11999991234', + externalReference: tenant.id, + }); + + asaasCustomerId = customer.id; + + if (!dbFailed) { + settings.asaas_customer_id = asaasCustomerId; + await query('UPDATE tenants SET settings = $1 WHERE id = $2', [settings, tenant.id]); + } + } catch (err: any) { + console.error('Failed to create customer in Asaas:', err); + return NextResponse.json({ error: `Asaas customer creation failed: ${err.message}` }, { status: 400 }); + } + } + + // 3. Setup dates + const nextDueDate = new Date(); + nextDueDate.setDate(nextDueDate.getDate() + 3); // Due in 3 days + const formattedDueDate = nextDueDate.toISOString().split('T')[0]; + + // 4. Create charging entity in Asaas (Subscription or single payment) + let gatewaySubscriptionId = ''; + let gatewayPaymentId = ''; + let pixCode = null; + let pixQrCodeImage = null; + let localSubId = dbFailed ? 'mock-sub-id' : ''; + + if (billingType === 'PIX' || billingType === 'BOLETO') { + console.log(`Creating Asaas subscription for tenant ${tenant.name}...`); + const asaasSub = await createSubscription('', { + customer: asaasCustomerId, + billingType, + value: parseFloat(plan.price), + nextDueDate: formattedDueDate, + cycle: 'MONTHLY', + description: `Mensalidade AgendaPRO - Plano ${plan.name}`, + externalReference: localSubId, + }); + + gatewaySubscriptionId = asaasSub.id; + + if (billingType === 'PIX') { + const paymentRes = await createPayment('', { + customer: asaasCustomerId, + billingType: 'PIX', + value: parseFloat(plan.price), + dueDate: formattedDueDate, + description: `Mensalidade AgendaPRO - Plano ${plan.name} (Primeira Cobrança)`, + externalReference: localSubId, + }); + + gatewayPaymentId = paymentRes.id; + + const pixData = await getPixQrCode('', gatewayPaymentId); + pixCode = pixData.payload; + pixQrCodeImage = pixData.encodedImage; + } + } else if (billingType === 'CREDIT_CARD') { + console.log(`Creating Asaas credit card subscription for tenant ${tenant.name}...`); + const asaasSub = await createSubscription('', { + customer: asaasCustomerId, + billingType: 'CREDIT_CARD', + value: parseFloat(plan.price), + nextDueDate: formattedDueDate, + cycle: 'MONTHLY', + description: `Mensalidade AgendaPRO - Plano ${plan.name}`, + externalReference: localSubId, + ...({ + creditCard, + creditCardHolderInfo, + } as any) + }); + + gatewaySubscriptionId = asaasSub.id; + } + + // 5. Update local subscription database ONLY if DB is active + if (!dbFailed) { + const client = await getClient(); + await client.query('BEGIN'); + try { + await client.query("UPDATE subscriptions SET status = 'cancelled' WHERE tenant_id = $1", [tenant.id]); + + const localSubRes = await client.query( + `INSERT INTO subscriptions (tenant_id, plan_id, status, billing_cycle, payment_method, payment_gateway, amount) + VALUES ($1, $2, 'pending', 'monthly', $3, 'asaas', $4) + RETURNING id`, + [tenant.id, plan.id, billingType.toLowerCase(), plan.price] + ); + localSubId = localSubRes.rows[0].id; + + await client.query( + `UPDATE subscriptions + SET gateway_subscription_id = $1, + updated_at = NOW() + WHERE id = $2`, + [gatewaySubscriptionId, localSubId] + ); + + await client.query( + `INSERT INTO subscription_payments (subscription_id, tenant_id, amount, status, gateway_payment_id) + VALUES ($1, $2, $3, 'pending', $4)`, + [localSubId, tenant.id, plan.price, gatewayPaymentId || gatewaySubscriptionId] + ); + + await client.query('COMMIT'); + } catch (err) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } + } + + return NextResponse.json({ + success: true, + subscriptionId: localSubId, + gatewaySubscriptionId, + gatewayPaymentId, + pixCode, + pixQrCodeImage, + }); + + } catch (error: any) { + console.error('Failed to initiate subscription payment:', error); + return NextResponse.json({ error: error.message || 'Internal server error' }, { status: 500 }); + } +} diff --git a/src/app/api/tenant/subscription/simulate/route.ts b/src/app/api/tenant/subscription/simulate/route.ts new file mode 100644 index 0000000..7deae3d --- /dev/null +++ b/src/app/api/tenant/subscription/simulate/route.ts @@ -0,0 +1,110 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { query, getClient } from '@/lib/db'; + +/** + * POST: Simulates a successful Asaas payment webhook callback. + * Body: { slug: string, status: 'active' | 'inactive' | 'past_due' } + */ +export async function POST(req: NextRequest) { + try { + const { slug, status } = await req.json(); + + if (!slug || !status) { + return NextResponse.json({ error: 'Missing slug or status' }, { status: 400 }); + } + + // Find the tenant + const tenantRes = await query('SELECT * FROM tenants WHERE slug = $1', [slug]); + if (tenantRes.rows.length === 0) { + return NextResponse.json({ error: 'Tenant not found' }, { status: 404 }); + } + + const tenant = tenantRes.rows[0]; + + const client = await getClient(); + await client.query('BEGIN'); + + try { + if (status === 'active') { + // Unlock: set tenant status to active and extend expiration + await client.query( + `UPDATE tenants + SET subscription_status = 'active', + subscription_ends_at = NOW() + INTERVAL '30 days', + updated_at = NOW() + WHERE id = $1`, + [tenant.id] + ); + + // Find pending subscription + const subRes = await client.query( + `SELECT id FROM subscriptions WHERE tenant_id = $1 ORDER BY created_at DESC LIMIT 1`, + [tenant.id] + ); + + if (subRes.rows.length > 0) { + const subId = subRes.rows[0].id; + + // Update subscription record to active + await client.query( + `UPDATE subscriptions + SET status = 'active', + current_period_start = NOW(), + current_period_end = NOW() + INTERVAL '30 days', + last_payment_at = NOW(), + next_payment_at = NOW() + INTERVAL '30 days', + updated_at = NOW() + WHERE id = $1`, + [subId] + ); + + // Update subscription payment record to paid + await client.query( + `UPDATE subscription_payments + SET status = 'paid', + paid_at = NOW(), + updated_at = NOW() + WHERE subscription_id = $1`, + [subId] + ); + } + } else { + // Lock: set tenant status to inactive/past_due and set ends_at to past + await client.query( + `UPDATE tenants + SET subscription_status = $1, + subscription_ends_at = NOW() - INTERVAL '1 day', + updated_at = NOW()`, + [status] + ); + + // Cancel subscriptions + await client.query( + `UPDATE subscriptions + SET status = 'cancelled', + updated_at = NOW() + WHERE tenant_id = $1`, + [tenant.id] + ); + } + + await client.query('COMMIT'); + + return NextResponse.json({ + success: true, + message: `Subscription simulated successfully to status: ${status}`, + tenantStatus: status, + }); + + } catch (err: any) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } + + } catch (error: any) { + console.error('Failed to simulate subscription:', error); + return NextResponse.json({ error: error.message || 'Internal server error' }, { status: 500 }); + } +} diff --git a/src/app/api/webhooks/asaas/route.ts b/src/app/api/webhooks/asaas/route.ts new file mode 100644 index 0000000..72a49f0 --- /dev/null +++ b/src/app/api/webhooks/asaas/route.ts @@ -0,0 +1,236 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { query, getClient } from '@/lib/db'; +import { getTenantAsaasConfig } from '@/lib/asaas'; + +export async function POST(req: NextRequest) { + const { searchParams } = new URL(req.url); + const tenantId = searchParams.get('tenant_id'); + + // 1. Retrieve the secure token from headers + const token = req.headers.get('asaas-access-token'); + if (!token) { + console.error('Webhook error: Missing asaas-access-token header'); + return NextResponse.json({ error: 'Missing security token' }, { status: 401 }); + } + + // 2. Parse the webhook body + let body: any; + try { + body = await req.json(); + } catch (err) { + console.error('Webhook error: Failed to parse body JSON', err); + return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }); + } + + const eventId = body.id; + const eventType = body.event; + + if (!eventId || !eventType) { + return NextResponse.json({ error: 'Invalid payload' }, { status: 400 }); + } + + // Ensure our idempotency and tracking table exists + try { + await query(` + CREATE TABLE IF NOT EXISTS asaas_webhook_events ( + event_id VARCHAR(200) PRIMARY KEY, + event_type VARCHAR(100), + processed_at TIMESTAMPTZ DEFAULT NOW() + ) + `); + } catch (err) { + console.error('Failed to ensure asaas_webhook_events table exists:', err); + } + + // 3. Resolve the expected webhook secret + let expectedSecret = ''; + try { + if (tenantId) { + // Scoped webhook for a tenant + const config = await getTenantAsaasConfig(tenantId); + expectedSecret = config.webhookSecret; + } else { + // Master platform webhook (SaaS subscriptions) + expectedSecret = process.env.ASAAS_WEBHOOK_SECRET || ''; + } + } catch (err: any) { + console.error(`Webhook configuration resolution failed: ${err.message}`); + return NextResponse.json({ error: 'Config not found' }, { status: 400 }); + } + + // Validate the secret + if (!expectedSecret || token !== expectedSecret) { + console.error('Webhook error: Invalid security token'); + return NextResponse.json({ error: 'Unauthorized security token' }, { status: 401 }); + } + + // 4. Implement Idempotency Check + try { + const existingEvent = await query( + 'SELECT 1 FROM asaas_webhook_events WHERE event_id = $1', + [eventId] + ); + if (existingEvent.rows.length > 0) { + console.log(`Webhook warning: Event ${eventId} was already processed. Skipping.`); + return NextResponse.json({ received: true, status: 'already_processed' }, { status: 200 }); + } + } catch (err) { + console.error('Idempotency query failed:', err); + } + + const client = await getClient(); + try { + await client.query('BEGIN'); + + // Record the event for idempotency + await client.query( + 'INSERT INTO asaas_webhook_events (event_id, event_type) VALUES ($1, $2)', + [eventId, eventType] + ); + + // 5. Process events based on category + if (tenantId) { + // CLIENT PAYMENTS (Appointments / Bookings paid by clients) + const paymentData = body.payment; + if (paymentData) { + const externalReference = paymentData.externalReference; + + if (externalReference) { + // Verify if this payment belongs to the tenant + const paymentCheck = await client.query( + 'SELECT id, appointment_id FROM payments WHERE id = $1 AND tenant_id = $2', + [externalReference, tenantId] + ); + + if (paymentCheck.rows.length > 0) { + const paymentRecord = paymentCheck.rows[0]; + + if (eventType === 'PAYMENT_RECEIVED' || eventType === 'PAYMENT_CONFIRMED') { + const paidAt = paymentData.confirmedDate || paymentData.paymentDate || 'NOW()'; + + // Update local payment record + await client.query( + `UPDATE payments + SET status = 'completed', paid_at = $1, notes = COALESCE(notes, '') || '\nAsaas ID: ' || $2 + WHERE id = $3`, + [paidAt, paymentData.id, externalReference] + ); + + // Update the linked appointment status if it was pending + if (paymentRecord.appointment_id) { + await client.query( + `UPDATE appointments + SET status = 'confirmed', updated_at = NOW() + WHERE id = $1 AND status = 'pending'`, + [paymentRecord.appointment_id] + ); + } + } else if (eventType === 'PAYMENT_REFUNDED') { + await client.query( + `UPDATE payments SET status = 'refunded' WHERE id = $1`, + [externalReference] + ); + } else if (eventType === 'PAYMENT_OVERDUE' || eventType === 'PAYMENT_DELETED') { + await client.query( + `UPDATE payments SET status = 'failed' WHERE id = $1`, + [externalReference] + ); + } + } else { + console.warn(`Payment ${externalReference} not found for tenant ${tenantId}`); + } + } + } + } else { + // PLATFORM SUBSCRIBERS (Tenants subscribing to AgendaPRO plans) + const paymentData = body.payment; + if (paymentData) { + const asaasSubscriptionId = paymentData.subscription; + + if (asaasSubscriptionId) { + // Find the SaaS subscription by gateway ID + const subRes = await client.query( + 'SELECT id, tenant_id, plan_id, billing_cycle FROM subscriptions WHERE gateway_subscription_id = $1', + [asaasSubscriptionId] + ); + + if (subRes.rows.length > 0) { + const subscription = subRes.rows[0]; + + if (eventType === 'PAYMENT_RECEIVED' || eventType === 'PAYMENT_CONFIRMED') { + const paidAt = paymentData.confirmedDate || paymentData.paymentDate || 'NOW()'; + const value = paymentData.value; + + // Insert subscription payment history record + await client.query( + `INSERT INTO subscription_payments (subscription_id, tenant_id, amount, status, gateway_payment_id, paid_at) + VALUES ($1, $2, $3, 'paid', $4, $5)`, + [subscription.id, subscription.tenant_id, value, paymentData.id, paidAt] + ); + + // Calculate subscription duration period + const periodInterval = subscription.billing_cycle === 'yearly' ? '1 year' : '30 days'; + + // Update subscription table + await client.query( + `UPDATE subscriptions + SET status = 'active', + current_period_start = NOW(), + current_period_end = NOW() + CAST($1 AS INTERVAL), + last_payment_at = $2, + next_payment_at = NOW() + CAST($1 AS INTERVAL), + updated_at = NOW() + WHERE id = $3`, + [periodInterval, paidAt, subscription.id] + ); + + // Update tenant status + await client.query( + `UPDATE tenants + SET subscription_status = 'active', + subscription_ends_at = NOW() + CAST($1 AS INTERVAL), + updated_at = NOW() + WHERE id = $2`, + [periodInterval, subscription.tenant_id] + ); + } + } + } + } + + // Handle subscription-specific events if any + const subscriptionData = body.subscription; + if (subscriptionData) { + const asaasSubscriptionId = subscriptionData.id; + const externalReference = subscriptionData.externalReference; + + if (eventType === 'SUBSCRIPTION_DELETED' || eventType === 'SUBSCRIPTION_CANCELLED') { + // Cancel subscription locally + const queryStr = asaasSubscriptionId + ? 'UPDATE subscriptions SET status = $1, cancelled_at = NOW(), updated_at = NOW() WHERE gateway_subscription_id = $2 RETURNING tenant_id' + : 'UPDATE subscriptions SET status = $1, cancelled_at = NOW(), updated_at = NOW() WHERE id = $2 RETURNING tenant_id'; + + const target = asaasSubscriptionId || externalReference; + const subCancelRes = await client.query(queryStr, ['cancelled', target]); + + if (subCancelRes.rows.length > 0) { + const tenantId = subCancelRes.rows[0].tenant_id; + await client.query( + `UPDATE tenants SET subscription_status = 'cancelled', updated_at = NOW() WHERE id = $1`, + [tenantId] + ); + } + } + } + } + + await client.query('COMMIT'); + return NextResponse.json({ received: true }, { status: 200 }); + } catch (error) { + await client.query('ROLLBACK'); + console.error('Webhook processing transaction failed:', error); + return NextResponse.json({ error: 'Internal processing error' }, { status: 500 }); + } finally { + client.release(); + } +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..14a80d6 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,852 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap'); + +:root { + --bg-primary: #0a0a0f; + --bg-secondary: #12121a; + --bg-card: #1a1a2e; + --bg-card-hover: #22223a; + --bg-elevated: #252540; + --border-color: rgba(255,255,255,0.06); + --border-active: rgba(108,99,255,0.5); + --text-primary: #f0f0f5; + --text-secondary: #8888a0; + --text-muted: #55556a; + --accent: #6C63FF; + --accent-hover: #5a52e0; + --accent-glow: rgba(108,99,255,0.25); + --success: #22c55e; + --success-bg: rgba(34,197,94,0.1); + --warning: #f59e0b; + --warning-bg: rgba(245,158,11,0.1); + --danger: #ef4444; + --danger-bg: rgba(239,68,68,0.1); + --info: #3b82f6; + --info-bg: rgba(59,130,246,0.1); + --gradient-primary: linear-gradient(135deg, #6C63FF 0%, #a855f7 100%); + --gradient-card: linear-gradient(145deg, rgba(26,26,46,0.8), rgba(18,18,26,0.9)); + --shadow-sm: 0 2px 8px rgba(0,0,0,0.3); + --shadow-md: 0 4px 20px rgba(0,0,0,0.4); + --shadow-lg: 0 8px 40px rgba(0,0,0,0.5); + --shadow-glow: 0 0 30px rgba(108,99,255,0.15); + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 24px; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +html, body { height: 100%; } + +body { + font-family: 'Inter', -apple-system, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +/* === SCROLLBAR === */ +::-webkit-scrollbar { width: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--bg-elevated); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: var(--accent); } + +/* === LAYOUT === */ +.app-layout { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 260px; + background: var(--bg-secondary); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 100; + transition: transform 0.3s ease; +} + +.sidebar-header { + padding: 24px 20px; + border-bottom: 1px solid var(--border-color); +} + +.sidebar-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; +} + +.logo-icon { + width: 40px; + height: 40px; + background: var(--gradient-primary); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: white; + box-shadow: var(--shadow-glow); +} + +.logo-text { + font-size: 20px; + font-weight: 800; + background: var(--gradient-primary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + letter-spacing: -0.5px; +} + +.sidebar-nav { + flex: 1; + padding: 16px 12px; + overflow-y: auto; +} + +.nav-section-title { + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1.2px; + padding: 16px 12px 8px; +} + +.nav-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + border-radius: var(--radius-sm); + color: var(--text-secondary); + text-decoration: none; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: none; + background: none; + width: 100%; + text-align: left; +} + +.nav-item:hover { + background: var(--bg-card); + color: var(--text-primary); +} + +.nav-item.active { + background: var(--accent-glow); + color: var(--accent); + box-shadow: inset 3px 0 0 var(--accent); +} + +.nav-item svg { width: 20px; height: 20px; flex-shrink: 0; } + +.sidebar-footer { + padding: 16px; + border-top: 1px solid var(--border-color); +} + +.plan-badge { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + background: var(--accent-glow); + border-radius: var(--radius-sm); + font-size: 12px; + color: var(--accent); + font-weight: 600; +} + +/* === MAIN CONTENT === */ +.main-content { + flex: 1; + margin-left: 260px; + min-height: 100vh; +} + +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 32px; + border-bottom: 1px solid var(--border-color); + background: rgba(10,10,15,0.8); + backdrop-filter: blur(20px); + position: sticky; + top: 0; + z-index: 50; +} + +.topbar-left h1 { + font-size: 22px; + font-weight: 700; + letter-spacing: -0.5px; +} + +.topbar-left p { + font-size: 13px; + color: var(--text-secondary); + margin-top: 2px; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 12px; +} + +.page-content { + padding: 32px; +} + +/* === CARDS === */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 24px; + transition: all 0.3s ease; +} + +.card:hover { + border-color: var(--border-active); + box-shadow: var(--shadow-glow); +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; +} + +.card-title { + font-size: 16px; + font-weight: 600; +} + +/* === STATS === */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 20px; + margin-bottom: 32px; +} + +.stat-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 24px; + display: flex; + align-items: flex-start; + gap: 16px; + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-glow); + border-color: var(--border-active); +} + +.stat-icon { + width: 48px; + height: 48px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.stat-icon.purple { background: var(--accent-glow); color: var(--accent); } +.stat-icon.green { background: var(--success-bg); color: var(--success); } +.stat-icon.orange { background: var(--warning-bg); color: var(--warning); } +.stat-icon.blue { background: var(--info-bg); color: var(--info); } + +.stat-value { + font-size: 28px; + font-weight: 800; + letter-spacing: -1px; + line-height: 1.2; +} + +.stat-label { + font-size: 13px; + color: var(--text-secondary); + margin-top: 2px; +} + +.stat-change { + font-size: 12px; + font-weight: 600; + margin-top: 4px; +} + +.stat-change.up { color: var(--success); } +.stat-change.down { color: var(--danger); } + +/* === BUTTONS === */ +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: var(--radius-sm); + font-size: 14px; + font-weight: 600; + cursor: pointer; + border: none; + transition: all 0.2s ease; + text-decoration: none; + font-family: inherit; +} + +.btn-primary { + background: var(--gradient-primary); + color: white; + box-shadow: var(--shadow-glow); +} + +.btn-primary:hover { + transform: translateY(-1px); + box-shadow: 0 0 40px rgba(108,99,255,0.3); +} + +.btn-secondary { + background: var(--bg-elevated); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { border-color: var(--accent); } + +.btn-danger { + background: var(--danger-bg); + color: var(--danger); + border: 1px solid rgba(239,68,68,0.2); +} + +.btn-ghost { + background: transparent; + color: var(--text-secondary); + padding: 8px 12px; +} + +.btn-ghost:hover { color: var(--text-primary); background: var(--bg-card); } + +.btn-sm { padding: 6px 14px; font-size: 13px; } +.btn-icon { padding: 8px; border-radius: var(--radius-sm); } + +/* === FORMS === */ +.form-group { margin-bottom: 20px; } + +.form-label { + display: block; + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 6px; +} + +.form-input, .form-select, .form-textarea { + width: 100%; + padding: 10px 14px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-size: 14px; + font-family: inherit; + transition: all 0.2s; + outline: none; +} + +.form-input:focus, .form-select:focus, .form-textarea:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +.form-textarea { resize: vertical; min-height: 80px; } +.form-select { cursor: pointer; } + +/* === TABLE === */ +.table-container { + overflow-x: auto; + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); +} + +table { + width: 100%; + border-collapse: collapse; +} + +th { + text-align: left; + padding: 12px 16px; + font-size: 12px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.8px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); +} + +td { + padding: 14px 16px; + font-size: 14px; + border-bottom: 1px solid var(--border-color); + vertical-align: middle; +} + +tr:last-child td { border-bottom: none; } + +tr:hover td { background: var(--bg-card-hover); } + +/* === BADGES === */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 20px; + font-size: 12px; + font-weight: 600; +} + +.badge-success { background: var(--success-bg); color: var(--success); } +.badge-warning { background: var(--warning-bg); color: var(--warning); } +.badge-danger { background: var(--danger-bg); color: var(--danger); } +.badge-info { background: var(--info-bg); color: var(--info); } +.badge-purple { background: var(--accent-glow); color: var(--accent); } + +/* === MODAL === */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.7); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + animation: fadeIn 0.2s ease; +} + +.modal { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-xl); + width: 90%; + max-width: 560px; + max-height: 85vh; + overflow-y: auto; + box-shadow: var(--shadow-lg); + animation: slideUp 0.3s ease; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24px; + border-bottom: 1px solid var(--border-color); +} + +.modal-title { font-size: 18px; font-weight: 700; } + +.modal-body { padding: 24px; } + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 12px; + padding: 16px 24px; + border-top: 1px solid var(--border-color); +} + +/* === CALENDAR === */ +.calendar-grid { + display: grid; + grid-template-columns: 80px repeat(auto-fill, minmax(150px, 1fr)); + gap: 0; + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.time-slot { + padding: 8px 12px; + font-size: 12px; + color: var(--text-muted); + border-bottom: 1px solid var(--border-color); + text-align: right; +} + +.appointment-block { + padding: 8px 12px; + border-radius: var(--radius-sm); + font-size: 13px; + cursor: pointer; + transition: all 0.2s; + margin: 2px 4px; +} + +.appointment-block:hover { transform: scale(1.02); } + +.appointment-block.confirmed { + background: var(--accent-glow); + border-left: 3px solid var(--accent); + color: var(--text-primary); +} + +.appointment-block.pending { + background: var(--warning-bg); + border-left: 3px solid var(--warning); + color: var(--text-primary); +} + +.appointment-block.completed { + background: var(--success-bg); + border-left: 3px solid var(--success); + color: var(--text-primary); +} + +/* === TABS === */ +.tabs { + display: flex; + gap: 4px; + border-bottom: 1px solid var(--border-color); + margin-bottom: 24px; +} + +.tab { + padding: 10px 20px; + font-size: 14px; + font-weight: 500; + color: var(--text-secondary); + cursor: pointer; + border: none; + background: none; + position: relative; + transition: all 0.2s; + font-family: inherit; +} + +.tab:hover { color: var(--text-primary); } + +.tab.active { + color: var(--accent); +} + +.tab.active::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: var(--accent); + border-radius: 2px 2px 0 0; +} + +/* === AVATAR === */ +.avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--gradient-primary); + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 14px; + color: white; + flex-shrink: 0; +} + +.avatar-lg { width: 48px; height: 48px; font-size: 18px; } + +/* === SEARCH === */ +.search-box { + position: relative; +} + +.search-box svg { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); + width: 18px; + height: 18px; +} + +.search-box input { + padding-left: 42px; +} + +/* === ANIMATIONS === */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes slideIn { + from { opacity: 0; transform: translateX(-10px); } + to { opacity: 1; transform: translateX(0); } +} + +.fade-in { animation: fadeIn 0.3s ease; } +.slide-up { animation: slideUp 0.4s ease; } + +/* === GRID UTILS === */ +.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } +.flex-between { display: flex; align-items: center; justify-content: space-between; } +.flex-center { display: flex; align-items: center; justify-content: center; } +.flex-gap { display: flex; align-items: center; gap: 12px; } +.gap-sm { gap: 8px; } +.mt-4 { margin-top: 16px; } +.mt-6 { margin-top: 24px; } +.mb-4 { margin-bottom: 16px; } +.mb-6 { margin-bottom: 24px; } + +/* === TOGGLE === */ +.toggle { + width: 44px; + height: 24px; + border-radius: 12px; + background: var(--bg-elevated); + border: 1px solid var(--border-color); + cursor: pointer; + position: relative; + transition: all 0.3s; +} + +.toggle.active { + background: var(--accent); + border-color: var(--accent); +} + +.toggle::after { + content: ''; + width: 18px; + height: 18px; + border-radius: 50%; + background: white; + position: absolute; + top: 2px; + left: 2px; + transition: all 0.3s; +} + +.toggle.active::after { transform: translateX(20px); } + +/* === SCHEDULE VIEW === */ +.schedule-container { + display: grid; + grid-template-columns: 70px 1fr; + gap: 0; +} + +.schedule-times { + display: flex; + flex-direction: column; +} + +.schedule-time-label { + height: 60px; + display: flex; + align-items: flex-start; + justify-content: flex-end; + padding-right: 12px; + font-size: 12px; + color: var(--text-muted); + font-weight: 500; +} + +.schedule-columns { + display: grid; + grid-template-columns: repeat(var(--cols, 2), 1fr); + gap: 8px; +} + +.schedule-column { + position: relative; + min-height: 600px; + border-left: 1px solid var(--border-color); +} + +.schedule-column-header { + text-align: center; + padding: 12px; + font-weight: 600; + font-size: 14px; + border-bottom: 1px solid var(--border-color); + position: sticky; + top: 0; + background: var(--bg-secondary); + z-index: 5; +} + +.schedule-event { + position: absolute; + left: 4px; + right: 4px; + border-radius: var(--radius-sm); + padding: 6px 10px; + font-size: 12px; + cursor: pointer; + overflow: hidden; + transition: all 0.2s; + z-index: 2; +} + +.schedule-event:hover { + z-index: 10; + box-shadow: var(--shadow-md); + transform: scale(1.02); +} + +.schedule-event-title { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.schedule-event-time { + font-size: 11px; + opacity: 0.8; +} + +.hour-line { + position: absolute; + left: 0; + right: 0; + height: 1px; + background: var(--border-color); +} + +/* === PRICING CARDS === */ +.pricing-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 24px; +} + +.pricing-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-xl); + padding: 32px 24px; + text-align: center; + transition: all 0.3s; + position: relative; +} + +.pricing-card.featured { + border-color: var(--accent); + box-shadow: var(--shadow-glow); +} + +.pricing-card.featured::before { + content: 'Popular'; + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + background: var(--gradient-primary); + color: white; + padding: 4px 16px; + border-radius: 20px; + font-size: 12px; + font-weight: 700; +} + +.pricing-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-glow); +} + +.pricing-name { font-size: 20px; font-weight: 700; margin-bottom: 8px; } + +.pricing-price { + font-size: 40px; + font-weight: 900; + margin: 16px 0; + letter-spacing: -2px; +} + +.pricing-price span { + font-size: 16px; + font-weight: 500; + color: var(--text-secondary); +} + +.pricing-features { + list-style: none; + text-align: left; + margin: 24px 0; +} + +.pricing-features li { + padding: 8px 0; + font-size: 14px; + color: var(--text-secondary); + display: flex; + align-items: center; + gap: 8px; +} + +.pricing-features li svg { color: var(--success); width: 16px; height: 16px; flex-shrink: 0; } + +/* === EMPTY STATE === */ +.empty-state { + text-align: center; + padding: 60px 20px; + color: var(--text-secondary); +} + +.empty-state svg { + width: 64px; + height: 64px; + color: var(--text-muted); + margin-bottom: 16px; +} + +.empty-state h3 { font-size: 18px; color: var(--text-primary); margin-bottom: 8px; } +.empty-state p { max-width: 400px; margin: 0 auto 20px; font-size: 14px; } + +/* === RESPONSIVE === */ +@media (max-width: 768px) { + .sidebar { transform: translateX(-100%); } + .sidebar.open { transform: translateX(0); } + .main-content { margin-left: 0; } + .stats-grid { grid-template-columns: 1fr 1fr; } + .grid-2, .grid-3 { grid-template-columns: 1fr; } + .topbar { padding: 16px; } + .page-content { padding: 16px; } + .pricing-grid { grid-template-columns: 1fr; } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..1201bb7 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "AgendaPRO — Sistema de Agendamento SaaS", + description: "Plataforma completa de agendamento para barbearias, manicures, massagens e estética. Gerencie sua agenda, clientes e pagamentos.", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..6e7e041 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,74 @@ +'use client'; +import { useState } from 'react'; +import { Mail, Lock, Shield, ArrowRight } from 'lucide-react'; +import { useRouter } from 'next/navigation'; + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const router = useRouter(); + + const handleLogin = (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + // Simulação de login (em produção faria requisição à API) + setTimeout(() => { + setLoading(false); + localStorage.setItem('agendapro_token', 'demo_token'); + router.push('/'); + }, 1000); + }; + + return ( +
+ {/* Lado Esquerdo - Decorativo */} +
+
+
+
+ +
+

AgendaPRO

+

O sistema definitivo para gerenciar o seu estabelecimento com inteligência e beleza.

+
+
+ + {/* Lado Direito - Formulário */} +
+
+

Bem-vindo de volta

+

Faça login para acessar o seu painel

+ +
+
+ + setEmail(e.target.value)} /> +
+
+
+ + Esqueceu a senha? +
+ setPassword(e.target.value)} /> +
+ + +
+ +

+ Ainda não tem uma conta? Crie agora +

+
+
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..bfee4a2 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,153 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import Sidebar from '@/components/Sidebar'; +import Topbar from '@/components/Topbar'; +import Dashboard from '@/components/Dashboard'; +import Agenda from '@/components/Agenda'; +import Clients from '@/components/Clients'; +import Services from '@/components/Services'; +import Team from '@/components/Team'; +import Settings from '@/components/Settings'; +import Subscription from '@/components/Subscription'; + +// Helper para cookies +const getCookie = (name: string): string => { + if (typeof document === 'undefined') return ''; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()!.split(';').shift() || ''; + return ''; +}; + +const setCookie = (name: string, value: string, days: number = 7) => { + if (typeof document === 'undefined') return; + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + const expires = "; expires=" + date.toUTCString(); + document.cookie = name + "=" + (value || "") + expires + "; path=/"; +}; + +export default function Home() { + const [currentPage, setCurrentPage] = useState('dashboard'); + const [sidebarOpen, setSidebarOpen] = useState(false); + const [currentPlan, setCurrentPlan] = useState('Professional'); + const [isLocked, setIsLocked] = useState(false); + const [loadingSubscription, setLoadingSubscription] = useState(true); + const router = useRouter(); + + const checkSubscriptionStatus = async () => { + const slug = localStorage.getItem('agendapro_tenant_slug') || 'barbearia-premium'; + + // Verifica primeiro se há um status ativo local/homologação + const localStatus = localStorage.getItem('agendapro_subscription_status') || getCookie('agendapro_subscription_status'); + if (localStatus === 'active') { + setIsLocked(false); + setLoadingSubscription(false); + return; + } + + try { + const res = await fetch(`/api/tenant/subscription?slug=${slug}`); + if (res.ok) { + const data = await res.json(); + const inactive = data.status !== 'active'; + setIsLocked(inactive); + if (inactive) { + setCurrentPage('subscription'); + } + if (data.subscription?.plan_name) { + setCurrentPlan(data.subscription.plan_name); + } + } + } catch (err) { + console.error('Erro ao verificar assinatura:', err); + } finally { + setLoadingSubscription(false); + } + }; + + useEffect(() => { + const token = localStorage.getItem('agendapro_token'); + if (!token) { + router.push('/login'); + } else { + checkSubscriptionStatus(); + } + + const handleStorageChange = () => { + checkSubscriptionStatus(); + }; + window.addEventListener('storage', handleStorageChange); + return () => window.removeEventListener('storage', handleStorageChange); + }, [router]); + + const handlePlanChange = (newPlan: string) => { + setCurrentPlan(newPlan); + localStorage.setItem('agendapro_plan', newPlan); + setCookie('agendapro_plan', newPlan, 30); + }; + + const pages: Record = { + dashboard: { title: 'Dashboard', subtitle: 'Visão geral do seu negócio' }, + agenda: { title: 'Agenda', subtitle: 'Gerencie seus agendamentos' }, + clients: { title: 'Clientes', subtitle: 'Base de clientes' }, + services: { title: 'Serviços', subtitle: 'Catálogo de serviços' }, + team: { title: 'Equipe', subtitle: 'Profissionais e horários' }, + settings: { title: 'Configurações', subtitle: 'Personalize seu estabelecimento' }, + subscription: { title: 'Assinatura', subtitle: 'Gerencie seu plano' }, + }; + + const renderPage = () => { + switch (currentPage) { + case 'dashboard': return ; + case 'agenda': return ; + case 'clients': return ; + case 'services': return ; + case 'team': return ; + case 'settings': return ; + case 'subscription': return ( + + ); + default: return ; + } + }; + + if (loadingSubscription) { + return ( +
+
✂️
+
Carregando dados do AgendaPRO...
+
+ ); + } + + return ( +
+ setSidebarOpen(false)} + currentPlan={currentPlan} + isLocked={isLocked} + /> +
+ setSidebarOpen(true)} + /> +
+ {renderPage()} +
+
+
+ ); +} diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx new file mode 100644 index 0000000..fba5222 --- /dev/null +++ b/src/app/register/page.tsx @@ -0,0 +1,111 @@ +'use client'; +import { useState } from 'react'; +import { Mail, Lock, Shield, ArrowRight, Building, Globe } from 'lucide-react'; +import { useRouter } from 'next/navigation'; + +export default function RegisterPage() { + const [name, setName] = useState(''); + const [slug, setSlug] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const router = useRouter(); + + const handleRegister = (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + // Simulação do provisionamento do novo tenant do SaaS + setTimeout(() => { + setLoading(false); + // Salva o novo tenant simulado nos cookies ou localstorage para o login funcionar + localStorage.setItem('agendapro_token', 'demo_token'); + localStorage.setItem('agendapro_tenant_name', name); + localStorage.setItem('agendapro_plan', 'Starter'); // Plano inicial trial + + // Cria cookie para o admin enxergar o novo tenant se necessário + const date = new Date(); + date.setTime(date.getTime() + (7*24*60*60*1000)); + document.cookie = "agendapro_new_tenant=" + encodeURIComponent(name) + "; expires=" + date.toUTCString() + "; path=/"; + + router.push('/'); + }, 1500); + }; + + const handleNameChange = (val: string) => { + setName(val); + // Gera o slug automaticamente baseado no nome + setSlug(val.toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-')); + }; + + return ( +
+ {/* Lado Esquerdo - Decorativo */} +
+
+
+
+ +
+

AgendaPRO

+

Cadastre seu estabelecimento em segundos e comece a automatizar seus agendamentos de forma inteligente.

+
+
+ + {/* Lado Direito - Formulário */} +
+
+

Crie sua conta SaaS

+

Preencha os dados do seu estabelecimento

+ +
+
+ + handleNameChange(e.target.value)} /> +
+ +
+ +
+ agenda.pro/ + setSlug(e.target.value)} /> +
+
+ +
+ + setEmail(e.target.value)} /> +
+ +
+ + setPassword(e.target.value)} /> +
+ + +
+ +

+ Já tem uma conta? Faça login +

+
+
+
+ ); +} diff --git a/src/components/Agenda.tsx b/src/components/Agenda.tsx new file mode 100644 index 0000000..86eb3a9 --- /dev/null +++ b/src/components/Agenda.tsx @@ -0,0 +1,274 @@ +import { useState, useEffect } from 'react'; +import { Plus, ChevronLeft, ChevronRight, X, Clock, User, Calendar } from 'lucide-react'; + +const professionals = [ + { id: '1', name: 'Carlos Silva', color: '#6C63FF' }, + { id: '2', name: 'Rafael Santos', color: '#a855f7' }, +]; + +const mockAppointments = [ + { id: '1', professionalId: '1', client: 'João Mendes', service: 'Corte Masculino', startHour: 10, startMin: 0, duration: 40, status: 'confirmed', price: 55 }, + { id: '2', professionalId: '1', client: 'Pedro Oliveira', service: 'Combo Corte + Barba', startHour: 11, startMin: 0, duration: 60, status: 'confirmed', price: 85 }, + { id: '3', professionalId: '2', client: 'Lucas Ferreira', service: 'Barba Completa', startHour: 14, startMin: 0, duration: 30, status: 'pending', price: 40 }, + { id: '4', professionalId: '2', client: 'Gabriel Souza', service: 'Corte Masculino', startHour: 10, startMin: 0, duration: 40, status: 'completed', price: 55 }, + { id: '5', professionalId: '1', client: 'Mateus Costa', service: 'Design de Sobrancelha', startHour: 15, startMin: 30, duration: 15, status: 'confirmed', price: 25 }, +]; + +const hours = Array.from({ length: 12 }, (_, i) => i + 8); + +const statusColors: Record = { + confirmed: 'var(--accent-glow)', + pending: 'var(--warning-bg)', + completed: 'var(--success-bg)', + cancelled: 'var(--danger-bg)', +}; +const statusBorders: Record = { + confirmed: 'var(--accent)', + pending: 'var(--warning)', + completed: 'var(--success)', + cancelled: 'var(--danger)', +}; + +export default function Agenda() { + const [showModal, setShowModal] = useState(false); + const today = new Date(); + const [currentDate, setCurrentDate] = useState(today); + const [appointments, setAppointments] = useState(mockAppointments); + + useEffect(() => { + const activeSlug = localStorage.getItem('agendapro_tenant_slug') || 'barbearia-premium'; + const savedAppts = localStorage.getItem(`agendapro_appointments_${activeSlug}`); + if (savedAppts) { + setAppointments(JSON.parse(savedAppts)); + } else { + const savedName = localStorage.getItem('agendapro_tenant_name'); + if (savedName) { + setAppointments([]); + localStorage.setItem(`agendapro_appointments_${activeSlug}`, JSON.stringify([])); + } else { + setAppointments(mockAppointments); + } + } + }, []); + + // States do Modal de Novo Agendamento + const [newClient, setNewClient] = useState(''); + const [newPhone, setNewPhone] = useState(''); + const [newService, setNewService] = useState('Corte Masculino - R$ 55,00 (40min)'); + const [newProf, setNewProf] = useState('1'); + const [newTime, setNewTime] = useState('10:00'); + + const dateStr = currentDate.toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); + + const changeDate = (days: number) => { + const newDate = new Date(currentDate); + newDate.setDate(newDate.getDate() + days); + setCurrentDate(newDate); + }; + + const goToToday = () => { + setCurrentDate(new Date()); + }; + + return ( +
+
+
+ +

{dateStr}

+ +
+
+ + +
+
+ +
+
+
+ {professionals.map(p => ( +
+
+
+ {p.name} +
+
+ ))} +
+ +
+
+ {hours.map(h => ( +
+ {String(h).padStart(2, '0')}:00 +
+ ))} +
+ + {professionals.map(prof => { + // Lógica simples para simular agendamentos diferentes em dias diferentes + const daySeed = currentDate.getDate(); + const isToday = daySeed === new Date().getDate(); + + // Se for hoje, mostra tudo que for do profissional. Se não, filtra baseado no dia para parecer que mudou. + const visibleAppointments = appointments.filter(a => + a.professionalId === prof.id && (isToday || (parseInt(a.id) + daySeed) % 2 === 0) + ); + + return ( +
+ {hours.map(h => ( +
+ ))} + {visibleAppointments.map(apt => { + const scale = 2; // 2px per minute + const top = (apt.startHour - 8) * 60 * scale + (apt.startMin * scale); + const height = apt.duration * scale; + const isSmall = apt.duration <= 20; + + return ( +
{ (e.currentTarget as HTMLElement).style.transform = 'scale(1.02)'; (e.currentTarget as HTMLElement).style.zIndex = '10'; }} + onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLElement).style.zIndex = '2'; }} + > +
+ {apt.client} +
+
+ {isSmall ? `- ${apt.service}` : `${apt.service} • R$ ${apt.price}`} +
+ {!isSmall && height >= 60 && ( +
+ {String(apt.startHour).padStart(2, '0')}:{String(apt.startMin).padStart(2, '0')} - {apt.duration}min +
+ )} +
+ ); + })} +
+ ); + })} +
+
+ + {showModal && ( +
setShowModal(false)}> +
e.stopPropagation()}> +
+

Novo Agendamento

+ +
+
+
+ + setNewClient(e.target.value)} /> +
+
+
+ + setNewPhone(e.target.value)} /> +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + setNewTime(e.target.value)} /> +
+
+
+ +