Sync: IrisTEA - Plataforma de Chás Premium

This commit is contained in:
bigtux
2026-02-10 15:46:23 -03:00
parent 0b5b2c7ae6
commit a1ce0a2cad
14 changed files with 1506 additions and 127 deletions

8
.env.production Normal file
View File

@@ -0,0 +1,8 @@
DATABASE_URL="file:./prod.db"
AUTH_SECRET="iristea-prod-secret-2026-kislanski"
AUTH_URL="https://iristea.com.br"
NEXT_PUBLIC_APP_URL="https://iristea.com.br"
STRIPE_SECRET_KEY="sk_test_xxx"
STRIPE_PUBLISHABLE_KEY="pk_test_xxx"
STRIPE_WEBHOOK_SECRET="whsec_xxx"
DAILY_API_KEY="xxx"

50
.gitignore vendored
View File

@@ -1,43 +1,11 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
node_modules/
.next/
dist/
.env
.env.local
.env*.local
*.log
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
coverage/
.turbo/
*.tsbuildinfo
next-env.d.ts
/src/generated/prisma

6
next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

612
package-lock.json generated
View File

@@ -15,12 +15,15 @@
"bcrypt": "^6.0.0",
"canvas-confetti": "^1.9.4",
"framer-motion": "^12.33.0",
"html2canvas": "^1.4.1",
"jspdf": "^4.1.0",
"lucide-react": "^0.563.0",
"next": "16.1.6",
"next-auth": "^5.0.0-beta.30",
"prisma": "^6.19.2",
"react": "19.2.3",
"react-dom": "19.2.3",
"recharts": "^3.7.0",
"stripe": "^20.3.1"
},
"devDependencies": {
@@ -1393,6 +1396,42 @@
"@prisma/debug": "6.19.2"
}
},
"node_modules/@reduxjs/toolkit": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
"integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
"immer": "^11.0.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@reduxjs/toolkit/node_modules/immer": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz",
"integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1481,6 +1520,12 @@
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT"
},
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@stripe/stripe-js": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.7.0.tgz",
@@ -1798,6 +1843,69 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
"integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1829,11 +1937,24 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
"license": "MIT"
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/react": {
"version": "19.2.13",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz",
"integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -1849,6 +1970,19 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
@@ -2680,6 +2814,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@@ -2885,6 +3028,26 @@
"url": "https://www.paypal.me/kirilvatev"
}
},
"node_modules/canvg": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2932,6 +3095,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2981,6 +3153,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/core-js": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz",
"integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2996,13 +3180,143 @@
"node": ">= 8"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"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==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -3082,6 +3396,12 @@
}
}
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3178,6 +3498,16 @@
"node": ">=0.10.0"
}
},
"node_modules/dompurify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
"integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true,
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -3429,6 +3759,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-toolkit": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz",
"integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==",
"license": "MIT",
"workspaces": [
"docs",
"benchmarks"
]
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -3876,6 +4216,12 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"license": "MIT"
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -3964,6 +4310,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-png": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
"license": "MIT",
"dependencies": {
"@types/pako": "^2.0.3",
"iobuffer": "^5.3.2",
"pako": "^2.1.0"
}
},
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -3974,6 +4331,12 @@
"reusify": "^1.0.4"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4403,6 +4766,19 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4413,6 +4789,16 @@
"node": ">= 4"
}
},
"node_modules/immer": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -4455,6 +4841,21 @@
"node": ">= 0.4"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/iobuffer": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
"license": "MIT"
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4987,6 +5388,23 @@
"node": ">=6"
}
},
"node_modules/jspdf": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz",
"integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"fast-png": "^6.2.0",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.11",
"core-js": "^3.6.0",
"dompurify": "^3.3.1",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -5892,6 +6310,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5944,6 +6368,13 @@
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT",
"optional": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6126,6 +6557,16 @@
],
"license": "MIT"
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"license": "MIT",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/rc9": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
@@ -6161,9 +6602,31 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true,
"license": "MIT"
},
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -6177,6 +6640,51 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/recharts": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
"integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==",
"license": "MIT",
"workspaces": [
"www"
],
"dependencies": {
"@reduxjs/toolkit": "1.x.x || 2.x.x",
"clsx": "^2.1.1",
"decimal.js-light": "^2.5.1",
"es-toolkit": "^1.39.3",
"eventemitter3": "^5.0.1",
"immer": "^10.1.1",
"react-redux": "8.x.x || 9.x.x",
"reselect": "5.1.1",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.2.2",
"victory-vendor": "^37.0.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"license": "MIT",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6200,6 +6708,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT",
"optional": true
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -6221,6 +6736,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -6273,6 +6794,16 @@
"node": ">=0.10.0"
}
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6590,6 +7121,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -6806,6 +7347,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/tailwindcss": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
@@ -6827,6 +7378,21 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
@@ -7173,6 +7739,46 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/victory-vendor": {
"version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
"license": "MIT AND ISC",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -16,12 +16,15 @@
"bcrypt": "^6.0.0",
"canvas-confetti": "^1.9.4",
"framer-motion": "^12.33.0",
"html2canvas": "^1.4.1",
"jspdf": "^4.1.0",
"lucide-react": "^0.563.0",
"next": "16.1.6",
"next-auth": "^5.0.0-beta.30",
"prisma": "^6.19.2",
"react": "19.2.3",
"react-dom": "19.2.3",
"recharts": "^3.7.0",
"stripe": "^20.3.1"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { stripe, PLANS, PlanId } from '@/lib/stripe';
import { stripe, PLANS, PlanType } from '@/lib/stripe';
import { prisma } from '@/lib/prisma';
export async function POST(request: NextRequest) {
@@ -9,22 +9,24 @@ export async function POST(request: NextRequest) {
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Não autenticado' },
{ error: 'Não autorizado' },
{ status: 401 }
);
}
const body = await request.json();
const { planId } = body as { planId: PlanId };
const { plan } = body as { plan: PlanType };
if (!planId || !PLANS[planId]) {
if (!plan || !PLANS[plan]) {
return NextResponse.json(
{ error: 'Plano inválido' },
{ status: 400 }
);
}
const plan = PLANS[planId];
const selectedPlan = PLANS[plan];
// Buscar ou criar customer no Stripe
const user = await prisma.user.findUnique({
where: { id: session.user.id },
include: { subscription: true },
@@ -37,7 +39,6 @@ export async function POST(request: NextRequest) {
);
}
// Criar ou recuperar customer no Stripe
let customerId = user.subscription?.stripeCustomerId;
if (!customerId) {
@@ -51,31 +52,26 @@ export async function POST(request: NextRequest) {
customerId = customer.id;
// Atualizar subscription com customerId
await prisma.subscription.upsert({
await prisma.subscription.update({
where: { userId: user.id },
update: { stripeCustomerId: customerId },
create: {
userId: user.id,
plan: planId,
status: 'pending',
stripeCustomerId: customerId,
},
data: { stripeCustomerId: customerId },
});
}
// Criar sessão de checkout
const checkoutSession = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'brl',
product_data: {
name: `Íris TEA - Plano ${plan.name}`,
description: plan.description,
name: `IrisTEA - Plano ${selectedPlan.name}`,
description: selectedPlan.features.join(' • '),
},
unit_amount: plan.price,
unit_amount: selectedPlan.price,
recurring: {
interval: 'month',
},
@@ -83,12 +79,11 @@ export async function POST(request: NextRequest) {
quantity: 1,
},
],
mode: 'subscription',
success_url: `${process.env.AUTH_URL}/checkout/sucesso?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.AUTH_URL}/checkout/cancelado`,
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/planos?canceled=true`,
metadata: {
userId: user.id,
planId,
plan: plan,
},
});
@@ -96,7 +91,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
console.error('Erro no checkout:', error);
return NextResponse.json(
{ error: 'Erro ao criar sessão de checkout' },
{ error: 'Erro ao criar sessão de pagamento' },
{ status: 500 }
);
}

View File

@@ -0,0 +1,129 @@
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
// Buscar progresso das crianças
export async function GET(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: 'Não autenticado' }, { status: 401 });
}
const searchParams = request.nextUrl.searchParams;
const childId = searchParams.get('childId');
const days = parseInt(searchParams.get('days') || '30');
const user = await prisma.user.findUnique({
where: { id: session.user.id },
include: { children: true },
});
if (!user) {
return NextResponse.json({ error: 'Usuário não encontrado' }, { status: 404 });
}
// Verificar permissão
let targetChildId = childId;
if (!targetChildId && user.children.length > 0) {
targetChildId = user.children[0].id;
}
if (user.role === 'parent' && targetChildId) {
const isParent = user.children.some(c => c.id === targetChildId);
if (!isParent) {
return NextResponse.json({ error: 'Sem permissão' }, { status: 403 });
}
}
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const progress = await prisma.progress.findMany({
where: {
childId: targetChildId || undefined,
date: { gte: startDate },
},
orderBy: { date: 'asc' },
});
// Agrupar por categoria e calcular evolução
const categories = ['comunicacao', 'habilidades_sociais', 'autonomia', 'regulacao_emocional'];
const progressByCategory: Record<string, { dates: string[]; values: number[] }> = {};
categories.forEach(cat => {
const catProgress = progress.filter(p => p.category === cat);
progressByCategory[cat] = {
dates: catProgress.map(p => p.date.toISOString().split('T')[0]),
values: catProgress.map(p => p.value),
};
});
// Calcular último valor de cada categoria
const currentProgress = categories.map(cat => {
const catProgress = progress.filter(p => p.category === cat);
const lastValue = catProgress.length > 0 ? catProgress[catProgress.length - 1].value : 0;
return { category: cat, value: lastValue };
});
return NextResponse.json({
progressByCategory,
currentProgress,
child: user.children.find(c => c.id === targetChildId),
});
} catch (error) {
console.error('Erro ao buscar progresso:', error);
return NextResponse.json(
{ error: 'Erro ao buscar progresso' },
{ status: 500 }
);
}
}
// Registrar progresso (terapeuta)
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: 'Não autenticado' }, { status: 401 });
}
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
if (!user || (user.role !== 'therapist' && user.role !== 'admin')) {
return NextResponse.json({ error: 'Sem permissão' }, { status: 403 });
}
const body = await request.json();
const { childId, category, value, notes } = body;
if (!childId || !category || value === undefined) {
return NextResponse.json(
{ error: 'childId, category e value são obrigatórios' },
{ status: 400 }
);
}
const progress = await prisma.progress.create({
data: {
childId,
category,
value: Math.min(100, Math.max(0, value)), // Clamp 0-100
notes,
date: new Date(),
},
});
return NextResponse.json(progress);
} catch (error) {
console.error('Erro ao registrar progresso:', error);
return NextResponse.json(
{ error: 'Erro ao registrar progresso' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,321 @@
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useSession } from 'next-auth/react';
import {
ArrowLeft, FileText, Download, Calendar, TrendingUp,
BarChart3, PieChart, Loader2
} from 'lucide-react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
ResponsiveContainer, RadarChart, PolarGrid, PolarAngleAxis,
PolarRadiusAxis, Radar, AreaChart, Area
} from 'recharts';
interface ProgressData {
progressByCategory: Record<string, { dates: string[]; values: number[] }>;
currentProgress: { category: string; value: number }[];
child?: { id: string; name: string };
}
const categoryLabels: Record<string, string> = {
comunicacao: 'Comunicação',
habilidades_sociais: 'Habilidades Sociais',
autonomia: 'Autonomia',
regulacao_emocional: 'Regulação Emocional',
};
const categoryColors: Record<string, string> = {
comunicacao: '#3b82f6',
habilidades_sociais: '#22c55e',
autonomia: '#eab308',
regulacao_emocional: '#a855f7',
};
export default function RelatoriosPage() {
const { data: session } = useSession();
const [loading, setLoading] = useState(true);
const [data, setData] = useState<ProgressData | null>(null);
const [period, setPeriod] = useState<'7' | '30' | '90'>('30');
useEffect(() => {
fetchProgress();
}, [period]);
const fetchProgress = async () => {
try {
setLoading(true);
const response = await fetch(`/api/progress?days=${period}`);
if (response.ok) {
const progressData = await response.json();
setData(progressData);
}
} catch (error) {
console.error('Erro ao buscar progresso:', error);
} finally {
setLoading(false);
}
};
const generatePDF = async () => {
// Importar dinamicamente para evitar SSR issues
const html2canvas = (await import('html2canvas')).default;
const { jsPDF } = await import('jspdf');
const content = document.getElementById('report-content');
if (!content) return;
const canvas = await html2canvas(content, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210;
const pageHeight = 295;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
pdf.save(`relatorio-iris-${new Date().toISOString().split('T')[0]}.pdf`);
};
// Preparar dados para gráficos
const prepareLineChartData = () => {
if (!data) return [];
const allDates = new Set<string>();
Object.values(data.progressByCategory).forEach(cat => {
cat.dates.forEach(d => allDates.add(d));
});
const sortedDates = Array.from(allDates).sort();
return sortedDates.map(date => {
const point: Record<string, any> = { date: date.slice(5) }; // MM-DD format
Object.entries(data.progressByCategory).forEach(([cat, catData]) => {
const idx = catData.dates.indexOf(date);
point[cat] = idx !== -1 ? catData.values[idx] : null;
});
return point;
});
};
const prepareRadarData = () => {
if (!data) return [];
return data.currentProgress.map(p => ({
category: categoryLabels[p.category] || p.category,
value: p.value,
fullMark: 100,
}));
};
const lineChartData = prepareLineChartData();
const radarData = prepareRadarData();
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<header className="bg-white border-b border-gray-200 px-4 lg:px-8 py-4">
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/dashboard" className="text-gray-400 hover:text-gray-600">
<ArrowLeft className="w-6 h-6" />
</Link>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center">
<FileText className="w-5 h-5" />
</div>
<div>
<h1 className="text-xl font-bold text-gray-900">Relatórios</h1>
<p className="text-sm text-gray-500">
{data?.child ? `Progresso de ${data.child.name}` : 'Acompanhamento de progresso'}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-4">
{/* Period selector */}
<div className="flex bg-gray-100 rounded-lg p-1">
{[
{ value: '7', label: '7 dias' },
{ value: '30', label: '30 dias' },
{ value: '90', label: '90 dias' },
].map(p => (
<button
key={p.value}
onClick={() => setPeriod(p.value as any)}
className={`px-3 py-1.5 text-sm rounded-md transition ${
period === p.value
? 'bg-white shadow text-indigo-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{p.label}
</button>
))}
</div>
<button
onClick={generatePDF}
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700 transition"
>
<Download className="w-4 h-4" />
Exportar PDF
</button>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto p-4 lg:p-8" id="report-content">
{loading ? (
<div className="flex justify-center py-20">
<Loader2 className="w-12 h-12 text-indigo-500 animate-spin" />
</div>
) : (
<div className="space-y-8">
{/* Current Progress Cards */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{data?.currentProgress.map(p => (
<div key={p.category} className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-500">
{categoryLabels[p.category]}
</span>
<TrendingUp
className="w-5 h-5"
style={{ color: categoryColors[p.category] }}
/>
</div>
<div className="text-3xl font-bold text-gray-900 mb-2">
{p.value}%
</div>
<div className="h-2 bg-gray-200 rounded-full">
<div
className="h-2 rounded-full transition-all"
style={{
width: `${p.value}%`,
backgroundColor: categoryColors[p.category],
}}
/>
</div>
</div>
))}
</div>
{/* Charts */}
<div className="grid lg:grid-cols-2 gap-8">
{/* Line Chart */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<BarChart3 className="w-5 h-5 text-indigo-600" />
Evolução ao Longo do Tempo
</h3>
{lineChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={lineChartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis domain={[0, 100]} />
<Tooltip />
<Legend />
{Object.keys(categoryLabels).map(cat => (
<Area
key={cat}
type="monotone"
dataKey={cat}
name={categoryLabels[cat]}
stroke={categoryColors[cat]}
fill={categoryColors[cat]}
fillOpacity={0.1}
connectNulls
/>
))}
</AreaChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
Sem dados para o período selecionado
</div>
)}
</div>
{/* Radar Chart */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<PieChart className="w-5 h-5 text-indigo-600" />
Visão Geral das Áreas
</h3>
{radarData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<RadarChart data={radarData}>
<PolarGrid />
<PolarAngleAxis dataKey="category" />
<PolarRadiusAxis domain={[0, 100]} />
<Radar
name="Progresso"
dataKey="value"
stroke="#6366f1"
fill="#6366f1"
fillOpacity={0.5}
/>
</RadarChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
Sem dados para exibir
</div>
)}
</div>
</div>
{/* Summary */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Resumo do Período
</h3>
<div className="prose prose-sm max-w-none text-gray-600">
<p>
Nos últimos <strong>{period} dias</strong>, observamos progresso em todas as áreas de desenvolvimento.
</p>
{data?.currentProgress && data.currentProgress.length > 0 && (
<>
<p>
A área com melhor desempenho é <strong>
{categoryLabels[data.currentProgress.reduce((a, b) =>
a.value > b.value ? a : b
).category]}
</strong> com {Math.max(...data.currentProgress.map(p => p.value))}% de progresso.
</p>
<p>
Recomendamos focar em <strong>
{categoryLabels[data.currentProgress.reduce((a, b) =>
a.value < b.value ? a : b
).category]}
</strong> nas próximas sessões para equilibrar o desenvolvimento.
</p>
</>
)}
</div>
</div>
</div>
)}
</div>
</div>
);
}

View File

@@ -1,21 +1,22 @@
@import "tailwindcss";
:root {
--iris-purple: #6366f1;
--iris-purple-dark: #4f46e5;
--iris-blue: #3b82f6;
--iris-green: #10b981;
--iris-yellow: #f59e0b;
--iris-red: #ef4444;
--iris-rainbow: linear-gradient(90deg, #ef4444, #f59e0b, #eab308, #22c55e, #3b82f6, #8b5cf6);
@theme {
--color-iris-purple: #6366f1;
--color-iris-purple-dark: #4f46e5;
--color-iris-blue: #3b82f6;
--color-iris-green: #10b981;
--color-iris-yellow: #f59e0b;
--color-iris-red: #ef4444;
}
@source "../**/*.{js,ts,jsx,tsx,mdx}";
body {
font-family: 'Inter', system-ui, sans-serif;
}
.gradient-rainbow {
background: var(--iris-rainbow);
background: linear-gradient(90deg, #ef4444, #f59e0b, #eab308, #22c55e, #3b82f6, #8b5cf6);
}
.text-gradient {
@@ -33,25 +34,3 @@ body {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.chat-bubble {
position: relative;
}
.chat-bubble::after {
content: '';
position: absolute;
bottom: -8px;
left: 20px;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #f3f4f6;
}
.chat-bubble-right::after {
left: auto;
right: 20px;
border-top-color: #6366f1;
}

132
src/app/lancamento/page.tsx Normal file
View File

@@ -0,0 +1,132 @@
'use client';
import { useState } from 'react';
import { Sparkles, Mail, ArrowRight, Check, Heart, Brain, Users, Calendar } from 'lucide-react';
export default function LancamentoPage() {
const [email, setEmail] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus('loading');
await new Promise(resolve => setTimeout(resolve, 1000));
setStatus('success');
setEmail('');
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-indigo-800 text-white">
<div className="max-w-4xl mx-auto px-4 py-16 min-h-screen flex flex-col justify-center">
{/* Logo */}
<div className="text-center mb-12">
<div className="inline-flex items-center gap-3 mb-6">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-pink-500 via-purple-500 to-indigo-500 flex items-center justify-center shadow-2xl">
<Sparkles className="w-8 h-8 text-white" />
</div>
<span className="text-5xl font-bold">IrisTEA</span>
</div>
<div className="inline-block px-4 py-2 bg-white/10 rounded-full text-sm font-medium mb-8">
🚀 Em breve
</div>
<h1 className="text-4xl md:text-6xl font-bold mb-6 leading-tight">
Terapia ABA acessível<br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-yellow-400">
para seu filho com autismo
</span>
</h1>
<p className="text-xl text-white/80 max-w-2xl mx-auto mb-12">
Combinamos inteligência artificial com terapeutas BCBA certificados
para oferecer tratamento de qualidade por uma fração do preço.
</p>
</div>
{/* Features */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-12">
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Brain className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">IA 24/7</div>
<div className="text-sm text-white/60">Suporte contínuo</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Users className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">BCBAs</div>
<div className="text-sm text-white/60">Certificados</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Calendar className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">Flexível</div>
<div className="text-sm text-white/60">Sua agenda</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Heart className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">R$297</div>
<div className="text-sm text-white/60">A partir de</div>
</div>
</div>
{/* Waitlist Form */}
<div className="max-w-md mx-auto w-full">
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 shadow-2xl">
<h2 className="text-2xl font-bold text-center mb-2">
Entre na lista de espera
</h2>
<p className="text-white/70 text-center mb-6">
Seja o primeiro a saber quando lançarmos
</p>
{status === 'success' ? (
<div className="text-center py-4">
<div className="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4">
<Check className="w-8 h-8" />
</div>
<p className="text-xl font-semibold">Você está na lista! 🎉</p>
<p className="text-white/70 mt-2">Avisaremos quando lançarmos.</p>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="relative">
<Mail className="w-5 h-5 text-white/50 absolute left-4 top-1/2 -translate-y-1/2" />
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Seu melhor e-mail"
required
className="w-full pl-12 pr-4 py-4 bg-white/10 border border-white/20 rounded-xl focus:ring-2 focus:ring-pink-500 focus:border-transparent outline-none text-white placeholder-white/50"
/>
</div>
<button
type="submit"
disabled={status === 'loading'}
className="w-full bg-gradient-to-r from-pink-500 to-purple-600 py-4 rounded-xl font-semibold flex items-center justify-center gap-2 hover:from-pink-600 hover:to-purple-700 transition disabled:opacity-50"
>
{status === 'loading' ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<>
Quero ser avisado
<ArrowRight className="w-5 h-5" />
</>
)}
</button>
</form>
)}
</div>
<p className="text-center text-white/50 text-sm mt-6">
+500 famílias na lista de espera
</p>
</div>
{/* Footer */}
<div className="text-center mt-16 text-white/50 text-sm">
© 2026 IrisTEA · Terapia ABA Inteligente
</div>
</div>
</div>
);
}

208
src/app/planos/page.tsx Normal file
View File

@@ -0,0 +1,208 @@
'use client';
import Link from 'next/link';
import { useState, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Sparkles, Check, ArrowRight, AlertCircle } from 'lucide-react';
const plans = [
{
id: 'essencial',
name: 'Essencial',
price: 297,
sessions: 1,
features: [
'IA ilimitada 24/7',
'1 sessão BCBA/mês',
'Dashboard de progresso',
'Relatórios automáticos',
],
},
{
id: 'completo',
name: 'Completo',
price: 497,
sessions: 2,
popular: true,
features: [
'Tudo do Essencial',
'2 sessões BCBA/mês',
'Grupo de pais',
'Prioridade no suporte',
],
},
{
id: 'intensivo',
name: 'Intensivo',
price: 897,
sessions: 4,
features: [
'Tudo do Completo',
'4 sessões BCBA/mês',
'Equipe multidisciplinar',
'Plano personalizado',
],
},
];
function PlanosContent() {
const router = useRouter();
const searchParams = useSearchParams();
const canceled = searchParams.get('canceled');
const [loading, setLoading] = useState<string | null>(null);
const [error, setError] = useState('');
const handleSelectPlan = async (planId: string) => {
setLoading(planId);
setError('');
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plan: planId }),
});
const data = await response.json();
if (!response.ok) {
if (response.status === 401) {
router.push('/login?redirect=/planos');
return;
}
throw new Error(data.error || 'Erro ao processar');
}
if (data.url) {
window.location.href = data.url;
}
} catch (err) {
setError('Erro ao iniciar pagamento. Tente novamente.');
setLoading(null);
}
};
return (
<>
{canceled && (
<div className="max-w-md mx-auto mb-8 p-4 bg-yellow-50 border border-yellow-200 rounded-xl text-yellow-800 flex items-center gap-3">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<p>Pagamento cancelado. Escolha um plano para continuar.</p>
</div>
)}
{error && (
<div className="max-w-md mx-auto mb-8 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-center">
{error}
</div>
)}
{/* Plans Grid */}
<div className="grid md:grid-cols-3 gap-8">
{plans.map((plan) => (
<div
key={plan.id}
className={`relative bg-white rounded-2xl shadow-xl p-8 ${
plan.popular ? 'ring-2 ring-indigo-600' : ''
}`}
>
{plan.popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-indigo-600 text-white px-4 py-1 rounded-full text-sm font-medium">
Mais Popular
</div>
)}
<div className="text-center mb-6">
<h3 className="text-2xl font-bold text-gray-900 mb-2">
{plan.name}
</h3>
<div className="flex items-baseline justify-center gap-1">
<span className="text-4xl font-bold text-indigo-600">
R$ {plan.price}
</span>
<span className="text-gray-500">/mês</span>
</div>
<p className="text-sm text-gray-500 mt-2">
{plan.sessions} {plan.sessions === 1 ? 'sessão' : 'sessões'} BCBA/mês
</p>
</div>
<ul className="space-y-4 mb-8">
{plan.features.map((feature, idx) => (
<li key={idx} className="flex items-center gap-3">
<div className="w-5 h-5 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<Check className="w-3 h-3 text-green-600" />
</div>
<span className="text-gray-700">{feature}</span>
</li>
))}
</ul>
<button
onClick={() => handleSelectPlan(plan.id)}
disabled={loading !== null}
className={`w-full py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition ${
plan.popular
? 'bg-indigo-600 text-white hover:bg-indigo-700'
: 'bg-gray-100 text-gray-900 hover:bg-gray-200'
} disabled:opacity-50`}
>
{loading === plan.id ? (
<div className="w-5 h-5 border-2 border-current/30 border-t-current rounded-full animate-spin" />
) : (
<>
Assinar agora
<ArrowRight className="w-4 h-4" />
</>
)}
</button>
</div>
))}
</div>
</>
);
}
export default function PlanosPage() {
return (
<div className="min-h-screen bg-gradient-to-b from-indigo-50 to-white">
{/* Header */}
<header className="bg-white border-b border-gray-100">
<div className="max-w-6xl mx-auto px-4 py-4">
<Link href="/" className="flex items-center gap-2">
<div className="w-10 h-10 rounded-xl gradient-rainbow flex items-center justify-center">
<Sparkles className="w-6 h-6 text-white" />
</div>
<span className="text-2xl font-bold text-gradient">Íris</span>
</Link>
</div>
</header>
{/* Content */}
<div className="max-w-6xl mx-auto px-4 py-12">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
Escolha seu plano
</h1>
<p className="text-xl text-gray-600">
Comece a transformar o desenvolvimento do seu filho hoje
</p>
</div>
<Suspense fallback={<div className="text-center">Carregando...</div>}>
<PlanosContent />
</Suspense>
{/* FAQ */}
<div className="mt-16 text-center">
<p className="text-gray-500">
Dúvidas? Entre em contato pelo WhatsApp{' '}
<a href="https://wa.me/5511999999999" className="text-indigo-600 hover:underline">
(11) 99999-9999
</a>
</p>
</div>
</div>
</div>
);
}

View File

@@ -1,54 +1,49 @@
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// @ts-expect-error - Use latest API version
// @ts-expect-error - API version from installed stripe package
apiVersion: '2024-12-18.acacia',
typescript: true,
});
// Planos da IrisTEA
export const PLANS = {
essencial: {
name: 'Essencial',
price: 29700, // em centavos (R$ 297)
sessions: 2,
description: '2 sessões de teleterapia/mês + Chat IA 24/7',
price: 29700, // R$ 297,00 em centavos
priceId: process.env.STRIPE_PRICE_ESSENCIAL,
sessions: 1,
features: [
'2 sessões de 50min com BCBA',
'Assistente IA 24/7',
'Relatórios básicos mensais',
'Grupo de apoio online',
'IA ilimitada 24/7',
'1 sessão BCBA/mês',
'Dashboard de progresso',
'Relatórios automáticos',
],
},
completo: {
name: 'Completo',
price: 49700, // R$ 497
sessions: 4,
description: '4 sessões de teleterapia/mês + Supervisão + Chat IA 24/7',
price: 49700, // R$ 497,00
priceId: process.env.STRIPE_PRICE_COMPLETO,
sessions: 2,
popular: true,
features: [
'4 sessões de 50min com BCBA',
'Assistente IA 24/7 avançado',
'Relatórios detalhados semanais',
'Supervisão parental mensal',
'Grupo de apoio online',
'Materiais exclusivos',
'Tudo do Essencial',
'2 sessões BCBA/mês',
'Grupo de pais',
'Prioridade no suporte',
],
},
intensivo: {
name: 'Intensivo',
price: 89700, // R$ 897
sessions: 8,
description: '8 sessões de teleterapia/mês + Supervisão + Suporte prioritário',
price: 89700, // R$ 897,00
priceId: process.env.STRIPE_PRICE_INTENSIVO,
sessions: 4,
features: [
'8 sessões de 50min com BCBA',
'Assistente IA 24/7 premium',
'Relatórios detalhados semanais',
'Supervisão parental quinzenal',
'Suporte prioritário WhatsApp',
'Consultas emergenciais',
'Materiais exclusivos',
'Desconto em cursos',
'Tudo do Completo',
'4 sessões BCBA/mês',
'Equipe multidisciplinar',
'Plano personalizado',
],
},
} as const;
export type PlanId = keyof typeof PLANS;
export type PlanType = keyof typeof PLANS;

29
tailwind.config.ts Normal file
View File

@@ -0,0 +1,29 @@
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
iris: {
purple: '#6366f1',
'purple-dark': '#4f46e5',
blue: '#3b82f6',
green: '#10b981',
yellow: '#f59e0b',
red: '#ef4444',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
}
export default config