diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..a1ec3ce --- /dev/null +++ b/.env.production @@ -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" diff --git a/.gitignore b/.gitignore index f390d12..250865f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..9edff1c --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +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. diff --git a/package-lock.json b/package-lock.json index 2e8915c..416367f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 5f5b10f..527b6cf 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/app/api/checkout/route.ts b/src/app/api/checkout/route.ts index 975e251..239151c 100644 --- a/src/app/api/checkout/route.ts +++ b/src/app/api/checkout/route.ts @@ -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 } ); } diff --git a/src/app/api/progress/route.ts b/src/app/api/progress/route.ts new file mode 100644 index 0000000..36a0da2 --- /dev/null +++ b/src/app/api/progress/route.ts @@ -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 = {}; + + 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 } + ); + } +} diff --git a/src/app/dashboard/relatorios/page.tsx b/src/app/dashboard/relatorios/page.tsx new file mode 100644 index 0000000..cf65c25 --- /dev/null +++ b/src/app/dashboard/relatorios/page.tsx @@ -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; + currentProgress: { category: string; value: number }[]; + child?: { id: string; name: string }; +} + +const categoryLabels: Record = { + comunicacao: 'Comunicação', + habilidades_sociais: 'Habilidades Sociais', + autonomia: 'Autonomia', + regulacao_emocional: 'Regulação Emocional', +}; + +const categoryColors: Record = { + 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(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(); + 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 = { 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 ( +
+ {/* Header */} +
+
+
+ + + +
+
+ +
+
+

Relatórios

+

+ {data?.child ? `Progresso de ${data.child.name}` : 'Acompanhamento de progresso'} +

+
+
+
+ +
+ {/* Period selector */} +
+ {[ + { value: '7', label: '7 dias' }, + { value: '30', label: '30 dias' }, + { value: '90', label: '90 dias' }, + ].map(p => ( + + ))} +
+ + +
+
+
+ +
+ {loading ? ( +
+ +
+ ) : ( +
+ {/* Current Progress Cards */} +
+ {data?.currentProgress.map(p => ( +
+
+ + {categoryLabels[p.category]} + + +
+
+ {p.value}% +
+
+
+
+
+ ))} +
+ + {/* Charts */} +
+ {/* Line Chart */} +
+

+ + Evolução ao Longo do Tempo +

+ + {lineChartData.length > 0 ? ( + + + + + + + + {Object.keys(categoryLabels).map(cat => ( + + ))} + + + ) : ( +
+ Sem dados para o período selecionado +
+ )} +
+ + {/* Radar Chart */} +
+

+ + Visão Geral das Áreas +

+ + {radarData.length > 0 ? ( + + + + + + + + + ) : ( +
+ Sem dados para exibir +
+ )} +
+
+ + {/* Summary */} +
+

+ Resumo do Período +

+
+

+ Nos últimos {period} dias, observamos progresso em todas as áreas de desenvolvimento. +

+ {data?.currentProgress && data.currentProgress.length > 0 && ( + <> +

+ A área com melhor desempenho é + {categoryLabels[data.currentProgress.reduce((a, b) => + a.value > b.value ? a : b + ).category]} + com {Math.max(...data.currentProgress.map(p => p.value))}% de progresso. +

+

+ Recomendamos focar em + {categoryLabels[data.currentProgress.reduce((a, b) => + a.value < b.value ? a : b + ).category]} + nas próximas sessões para equilibrar o desenvolvimento. +

+ + )} +
+
+
+ )} +
+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 2514c10..cb876d8 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; -} diff --git a/src/app/lancamento/page.tsx b/src/app/lancamento/page.tsx new file mode 100644 index 0000000..757589f --- /dev/null +++ b/src/app/lancamento/page.tsx @@ -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 ( +
+
+ {/* Logo */} +
+
+
+ +
+ IrisTEA +
+ +
+ 🚀 Em breve +
+ +

+ Terapia ABA acessível
+ + para seu filho com autismo + +

+ +

+ Combinamos inteligência artificial com terapeutas BCBA certificados + para oferecer tratamento de qualidade por uma fração do preço. +

+
+ + {/* Features */} +
+
+ +
IA 24/7
+
Suporte contínuo
+
+
+ +
BCBAs
+
Certificados
+
+
+ +
Flexível
+
Sua agenda
+
+
+ +
R$297
+
A partir de
+
+
+ + {/* Waitlist Form */} +
+
+

+ Entre na lista de espera +

+

+ Seja o primeiro a saber quando lançarmos +

+ + {status === 'success' ? ( +
+
+ +
+

Você está na lista! 🎉

+

Avisaremos quando lançarmos.

+
+ ) : ( +
+
+ + 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" + /> +
+ +
+ )} +
+ +

+ +500 famílias já na lista de espera +

+
+ + {/* Footer */} +
+ © 2026 IrisTEA · Terapia ABA Inteligente +
+
+
+ ); +} diff --git a/src/app/planos/page.tsx b/src/app/planos/page.tsx new file mode 100644 index 0000000..31972ce --- /dev/null +++ b/src/app/planos/page.tsx @@ -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(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 && ( +
+ +

Pagamento cancelado. Escolha um plano para continuar.

+
+ )} + + {error && ( +
+ {error} +
+ )} + + {/* Plans Grid */} +
+ {plans.map((plan) => ( +
+ {plan.popular && ( +
+ Mais Popular +
+ )} + +
+

+ {plan.name} +

+
+ + R$ {plan.price} + + /mês +
+

+ {plan.sessions} {plan.sessions === 1 ? 'sessão' : 'sessões'} BCBA/mês +

+
+ +
    + {plan.features.map((feature, idx) => ( +
  • +
    + +
    + {feature} +
  • + ))} +
+ + +
+ ))} +
+ + ); +} + +export default function PlanosPage() { + return ( +
+ {/* Header */} +
+
+ +
+ +
+ Íris + +
+
+ + {/* Content */} +
+
+

+ Escolha seu plano +

+

+ Comece a transformar o desenvolvimento do seu filho hoje +

+
+ + Carregando...
}> + + + + {/* FAQ */} +
+

+ Dúvidas? Entre em contato pelo WhatsApp{' '} + + (11) 99999-9999 + +

+
+
+
+ ); +} diff --git a/src/lib/stripe.ts b/src/lib/stripe.ts index 72d3c5c..ad9f613 100644 --- a/src/lib/stripe.ts +++ b/src/lib/stripe.ts @@ -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; diff --git a/src/middleware.ts b/src/middleware.ts.bak similarity index 100% rename from src/middleware.ts rename to src/middleware.ts.bak diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..cbaa096 --- /dev/null +++ b/tailwind.config.ts @@ -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