eletrotupi / tcc/ commit / b13456d

Merge branch 'pl-old-version'

Pedro Lucas Porcellis porcellis@eletrotupi.com · 6 months ago b13456d98a5b20510f062a4d4fb619887728bd8f Browse files
Changed files
20
Additions
+833
Deletions
-40
api/ava.config.mjs modified+8 -17
api/ava.config.mjs
1 1
export default {
2 -
  "typescript": {
3 -
    "extensions": [
4 -
      "ts",
5 -
      "tsx"
6 -
    ],
7 -
    "rewritePaths": {
8 -
      "src/": "dist/"
9 -
    },
10 -
    compile: false
2 +
  extensions: {
3 +
    ts: 'commonjs'
11 4
  },
12 -
 // extensions: {
13 -
 //   ts: 'module'
14 -
 // },
15 -
 // nodeArguments: [
16 -
 //   '--import=tsx'
17 -
 // ],
5 +
  nodeArguments: [
6 +
    '--require=tsx/cjs',
7 +
    '--require=tsconfig-paths/register'
8 +
  ],
18 9
  files: [
19 10
    'tests/**/*.test.ts'
20 11
  ],
21 12
  environmentVariables: {
22 -
    NODE_ENV: 'test'
13 +
    NODE_ENV: 'test',
14 +
    TS_NODE_PROJECT: './tsconfig.json'
23 15
  },
24 16
  timeout: '30s',
25 17
  concurrency: 1, // Run tests serially to avoid database conflicts
26 18
  verbose: true
27 -
};
19 +
};
/dev/null → api/nodemon.json
1 +
{
2 +
  "watch": ["src"],
3 +
  "ext": "ts",
4 +
  "exec": "node -r ts-node/register -r tsconfig-paths/register ./src/index.ts"
5 +
}
6 +

api/package-lock.json modified+195 -7
api/package-lock.json
1 1
{
2 2
  "name": "orbit-api",
3 3
  "version": "0.0.0",
4 4
  "lockfileVersion": 3,
5 5
  "requires": true,
14 14
        "@prisma/client": "^7.5.0",
15 15
        "@types/cors": "^2.8.19",
16 16
        "@types/jsonwebtoken": "^9.0.10",
17 17
        "bcryptjs": "^3.0.3",
18 18
        "better-sqlite3": "^12.5.0",
19 +
        "body-parser": "^2.2.2",
19 20
        "cors": "^2.8.6",
20 21
        "date-fns": "^4.1.0",
21 22
        "express": "^5.2.1",
22 23
        "jsonwebtoken": "^9.0.3",
23 24
        "morgan": "^1.10.1",
30 31
        "@types/morgan": "^1.9.10",
31 32
        "@types/node": "^25.5.2",
32 33
        "ava": "^6.4.1",
33 34
        "cross-var": "^1.1.0",
34 35
        "dotenv-cli": "^11.0.0",
36 +
        "nodemon": "^3.1.14",
35 37
        "prisma": "^7.5.0",
36 38
        "supertest": "^7.1.4",
37 39
        "ts-node": "^10.9.2",
38 40
        "tsc-alias": "^1.8.16",
39 41
        "tsconfig-paths": "^4.2.0",
2691 2693
      "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==",
2692 2694
      "dev": true,
2693 2695
      "license": "MIT"
2694 2696
    },
2695 2697
    "node_modules/body-parser": {
2696 -
      "version": "2.2.1",
2697 -
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
2698 -
      "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
2698 +
      "version": "2.2.2",
2699 +
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
2700 +
      "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
2699 2701
      "license": "MIT",
2700 2702
      "dependencies": {
2701 2703
        "bytes": "^3.1.2",
2702 2704
        "content-type": "^1.0.5",
2703 2705
        "debug": "^4.4.3",
2704 2706
        "http-errors": "^2.0.0",
2705 2707
        "iconv-lite": "^0.7.0",
2706 2708
        "on-finished": "^2.4.1",
2707 -
        "qs": "^6.14.0",
2709 +
        "qs": "^6.14.1",
2708 2710
        "raw-body": "^3.0.1",
2709 2711
        "type-is": "^2.0.1"
2710 2712
      },
2711 2713
      "engines": {
2712 2714
        "node": ">=18"
4513 4515
      "license": "MIT",
4514 4516
      "engines": {
4515 4517
        "node": ">=0.10.0"
4516 4518
      }
4517 4519
    },
4520 +
    "node_modules/has-flag": {
4521 +
      "version": "3.0.0",
4522 +
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
4523 +
      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
4524 +
      "dev": true,
4525 +
      "license": "MIT",
4526 +
      "engines": {
4527 +
        "node": ">=4"
4528 +
      }
4529 +
    },
4518 4530
    "node_modules/has-symbols": {
4519 4531
      "version": "1.1.0",
4520 4532
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
4521 4533
      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
4522 4534
      "license": "MIT",
5550 5562
        "node-gyp-build": "bin.js",
5551 5563
        "node-gyp-build-optional": "optional.js",
5552 5564
        "node-gyp-build-test": "build-test.js"
5553 5565
      }
5554 5566
    },
5567 +
    "node_modules/nodemon": {
5568 +
      "version": "3.1.14",
5569 +
      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
5570 +
      "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
5571 +
      "dev": true,
5572 +
      "license": "MIT",
5573 +
      "dependencies": {
5574 +
        "chokidar": "^3.5.2",
5575 +
        "debug": "^4",
5576 +
        "ignore-by-default": "^1.0.1",
5577 +
        "minimatch": "^10.2.1",
5578 +
        "pstree.remy": "^1.1.8",
5579 +
        "semver": "^7.5.3",
5580 +
        "simple-update-notifier": "^2.0.0",
5581 +
        "supports-color": "^5.5.0",
5582 +
        "touch": "^3.1.0",
5583 +
        "undefsafe": "^2.0.5"
5584 +
      },
5585 +
      "bin": {
5586 +
        "nodemon": "bin/nodemon.js"
5587 +
      },
5588 +
      "engines": {
5589 +
        "node": ">=10"
5590 +
      },
5591 +
      "funding": {
5592 +
        "type": "opencollective",
5593 +
        "url": "https://opencollective.com/nodemon"
5594 +
      }
5595 +
    },
5596 +
    "node_modules/nodemon/node_modules/balanced-match": {
5597 +
      "version": "4.0.4",
5598 +
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
5599 +
      "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
5600 +
      "dev": true,
5601 +
      "license": "MIT",
5602 +
      "engines": {
5603 +
        "node": "18 || 20 || >=22"
5604 +
      }
5605 +
    },
5606 +
    "node_modules/nodemon/node_modules/brace-expansion": {
5607 +
      "version": "5.0.5",
5608 +
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
5609 +
      "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
5610 +
      "dev": true,
5611 +
      "license": "MIT",
5612 +
      "dependencies": {
5613 +
        "balanced-match": "^4.0.2"
5614 +
      },
5615 +
      "engines": {
5616 +
        "node": "18 || 20 || >=22"
5617 +
      }
5618 +
    },
5619 +
    "node_modules/nodemon/node_modules/chokidar": {
5620 +
      "version": "3.6.0",
5621 +
      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
5622 +
      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
5623 +
      "dev": true,
5624 +
      "license": "MIT",
5625 +
      "dependencies": {
5626 +
        "anymatch": "~3.1.2",
5627 +
        "braces": "~3.0.2",
5628 +
        "glob-parent": "~5.1.2",
5629 +
        "is-binary-path": "~2.1.0",
5630 +
        "is-glob": "~4.0.1",
5631 +
        "normalize-path": "~3.0.0",
5632 +
        "readdirp": "~3.6.0"
5633 +
      },
5634 +
      "engines": {
5635 +
        "node": ">= 8.10.0"
5636 +
      },
5637 +
      "funding": {
5638 +
        "url": "https://paulmillr.com/funding/"
5639 +
      },
5640 +
      "optionalDependencies": {
5641 +
        "fsevents": "~2.3.2"
5642 +
      }
5643 +
    },
5644 +
    "node_modules/nodemon/node_modules/ignore-by-default": {
5645 +
      "version": "1.0.1",
5646 +
      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
5647 +
      "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
5648 +
      "dev": true,
5649 +
      "license": "ISC"
5650 +
    },
5651 +
    "node_modules/nodemon/node_modules/minimatch": {
5652 +
      "version": "10.2.5",
5653 +
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
5654 +
      "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
5655 +
      "dev": true,
5656 +
      "license": "BlueOak-1.0.0",
5657 +
      "dependencies": {
5658 +
        "brace-expansion": "^5.0.5"
5659 +
      },
5660 +
      "engines": {
5661 +
        "node": "18 || 20 || >=22"
5662 +
      },
5663 +
      "funding": {
5664 +
        "url": "https://github.com/sponsors/isaacs"
5665 +
      }
5666 +
    },
5667 +
    "node_modules/nodemon/node_modules/picomatch": {
5668 +
      "version": "2.3.2",
5669 +
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
5670 +
      "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
5671 +
      "dev": true,
5672 +
      "license": "MIT",
5673 +
      "engines": {
5674 +
        "node": ">=8.6"
5675 +
      },
5676 +
      "funding": {
5677 +
        "url": "https://github.com/sponsors/jonschlinkert"
5678 +
      }
5679 +
    },
5680 +
    "node_modules/nodemon/node_modules/readdirp": {
5681 +
      "version": "3.6.0",
5682 +
      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
5683 +
      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
5684 +
      "dev": true,
5685 +
      "license": "MIT",
5686 +
      "dependencies": {
5687 +
        "picomatch": "^2.2.1"
5688 +
      },
5689 +
      "engines": {
5690 +
        "node": ">=8.10.0"
5691 +
      }
5692 +
    },
5693 +
    "node_modules/nodemon/node_modules/supports-color": {
5694 +
      "version": "5.5.0",
5695 +
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
5696 +
      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
5697 +
      "dev": true,
5698 +
      "license": "MIT",
5699 +
      "dependencies": {
5700 +
        "has-flag": "^3.0.0"
5701 +
      },
5702 +
      "engines": {
5703 +
        "node": ">=4"
5704 +
      }
5705 +
    },
5555 5706
    "node_modules/nofilter": {
5556 5707
      "version": "3.1.0",
5557 5708
      "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz",
5558 5709
      "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==",
5559 5710
      "dev": true,
6182 6333
      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
6183 6334
      "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==",
6184 6335
      "dev": true,
6185 6336
      "license": "ISC"
6186 6337
    },
6338 +
    "node_modules/pstree.remy": {
6339 +
      "version": "1.1.8",
6340 +
      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
6341 +
      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
6342 +
      "dev": true,
6343 +
      "license": "MIT"
6344 +
    },
6187 6345
    "node_modules/pump": {
6188 6346
      "version": "3.0.3",
6189 6347
      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
6190 6348
      "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
6191 6349
      "license": "MIT",
6210 6368
        }
6211 6369
      ],
6212 6370
      "license": "MIT"
6213 6371
    },
6214 6372
    "node_modules/qs": {
6215 -
      "version": "6.14.0",
6216 -
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
6217 -
      "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
6373 +
      "version": "6.15.1",
6374 +
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
6375 +
      "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
6218 6376
      "license": "BSD-3-Clause",
6219 6377
      "dependencies": {
6220 6378
        "side-channel": "^1.1.0"
6221 6379
      },
6222 6380
      "engines": {
6823 6981
        "decompress-response": "^6.0.0",
6824 6982
        "once": "^1.3.1",
6825 6983
        "simple-concat": "^1.0.0"
6826 6984
      }
6827 6985
    },
6986 +
    "node_modules/simple-update-notifier": {
6987 +
      "version": "2.0.0",
6988 +
      "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
6989 +
      "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
6990 +
      "dev": true,
6991 +
      "license": "MIT",
6992 +
      "dependencies": {
6993 +
        "semver": "^7.5.3"
6994 +
      },
6995 +
      "engines": {
6996 +
        "node": ">=10"
6997 +
      }
6998 +
    },
6828 6999
    "node_modules/slash": {
6829 7000
      "version": "5.1.0",
6830 7001
      "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
6831 7002
      "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
6832 7003
      "dev": true,
7271 7442
      "license": "MIT",
7272 7443
      "engines": {
7273 7444
        "node": ">=0.6"
7274 7445
      }
7275 7446
    },
7447 +
    "node_modules/touch": {
7448 +
      "version": "3.1.1",
7449 +
      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
7450 +
      "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
7451 +
      "dev": true,
7452 +
      "license": "ISC",
7453 +
      "bin": {
7454 +
        "nodetouch": "bin/nodetouch.js"
7455 +
      }
7456 +
    },
7276 7457
    "node_modules/tr46": {
7277 7458
      "version": "0.0.3",
7278 7459
      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
7279 7460
      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
7280 7461
      "dev": true,
7533 7714
        "tsserver": "bin/tsserver"
7534 7715
      },
7535 7716
      "engines": {
7536 7717
        "node": ">=14.17"
7537 7718
      }
7719 +
    },
7720 +
    "node_modules/undefsafe": {
7721 +
      "version": "2.0.5",
7722 +
      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
7723 +
      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
7724 +
      "dev": true,
7725 +
      "license": "MIT"
7538 7726
    },
7539 7727
    "node_modules/undici-types": {
7540 7728
      "version": "7.18.2",
7541 7729
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
7542 7730
      "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
7945 8133
        "graphmatch": "^1.1.0"
7946 8134
      }
7947 8135
    }
7948 8136
  }
7949 8137
}
api/package.json modified+3 -1
api/package.json
1 1
{
2 2
  "name": "orbit-api",
3 3
  "version": "0.0.0",
4 4
  "description": "",
5 5
  "license": "ISC",
8 8
  "directories": {
9 9
    "test": "tests"
10 10
  },
11 11
  "scripts": {
12 12
    "build": "tsc && tsc-alias",
13 -
    "start": "ts-node -r tsconfig-paths/register src/index.ts",
13 +
    "start": "nodemon src/index.ts",
14 14
    "test": "npm run test:setup && dotenv -e .env.test -o -- npx ava",
15 15
    "test:setup": "npm run test:setup:generate && npm run test:setup:migrate",
16 16
    "test:setup:generate": "dotenv -e .env.test -o -- npx prisma generate",
17 17
    "test:setup:migrate": "dotenv -e .env.test -o -- npx prisma db push",
18 18
    "test:watch": "npm run test:setup && NODE_ENV=test npx ava --watch",
24 24
    "@prisma/client": "^7.5.0",
25 25
    "@types/cors": "^2.8.19",
26 26
    "@types/jsonwebtoken": "^9.0.10",
27 27
    "bcryptjs": "^3.0.3",
28 28
    "better-sqlite3": "^12.5.0",
29 +
    "body-parser": "^2.2.2",
29 30
    "cors": "^2.8.6",
30 31
    "date-fns": "^4.1.0",
31 32
    "express": "^5.2.1",
32 33
    "jsonwebtoken": "^9.0.3",
33 34
    "morgan": "^1.10.1",
40 41
    "@types/morgan": "^1.9.10",
41 42
    "@types/node": "^25.5.2",
42 43
    "ava": "^6.4.1",
43 44
    "cross-var": "^1.1.0",
44 45
    "dotenv-cli": "^11.0.0",
46 +
    "nodemon": "^3.1.14",
45 47
    "prisma": "^7.5.0",
46 48
    "supertest": "^7.1.4",
47 49
    "ts-node": "^10.9.2",
48 50
    "tsc-alias": "^1.8.16",
49 51
    "tsconfig-paths": "^4.2.0",
50 52
    "tsx": "^4.21.0",
51 53
    "typescript": "^6.0.2"
52 54
  }
53 55
}
api/src/controllers/users.ts
1 1
import { Request, Response, NextFunction } from 'express';
2 2
import {
3 3
  createUser,
4 +
  findUserById,
5 +
  findUserByEmail,
6 +
  updateUser,
4 7
  generateToken
5 8
} from '@app/models/user';
6 9

7 10
import {
8 11
  validateRequiredFields
9 12
} from '@app/utils/validators'
10 13

11 14
export const UsersController = {
12 15
  create: async (req: Request, res: Response, next: NextFunction) => {
13 16
    try {
14 -
      const { email, password, first_name: firstName, last_name: lastName } = req.body;
17 +
      const { email, password, firstName, lastName } = req.body;
15 18

16 19
      // Validate required fields
17 20
      const requiredValidation = validateRequiredFields(req.body, [
18 -
        'email', 'password', 'first_name'
21 +
        'email', 'password', 'firstName'
19 22
      ]);
20 23

21 24
      if (!requiredValidation.valid) {
22 25
        return res.status(400).json({
23 26
          error: "Campos faltantes",
30 33
      );
31 34

32 35
      const jwtToken = generateToken(user.id, user.email);
33 36

34 37
      res.status(201).json({ token: jwtToken });
38 +
    } catch (err) {
39 +
      next(err);
40 +
    }
41 +
  },
42 +

43 +
  update: async (req: Request, res: Response, next: NextFunction) => {
44 +
    try {
45 +
      const { id } = req.params
46 +
      const { email, password, firstName, lastName } = req.body;
47 +

48 +
      const user = await findUserById(Number(id));
49 +

50 +
      if (!user) {
51 +
        return res.status(404).json({
52 +
          error: "Usuário não encontrado"
53 +
        })
54 +
      }
55 +

56 +
      if (email || password || firstName || lastName) {
57 +
        const response = await updateUser(user, {
58 +
          email, firstName, lastName
59 +
        })
60 +
      }
61 +

62 +
      const updated = await findUserById(Number(id))
63 +

64 +
      // TODO: Drop encrypted password & token here
65 +
      return res.status(200).json({
66 +
        user: updated
67 +
      })
35 68
    } catch (err) {
36 69
      next(err);
37 70
    }
38 71
  }
39 72
};
api/src/create-app.ts → api/src/createApp.ts
1 1
import express from 'express';
2 2
import cors from 'cors';
3 +
import bodyParser from 'body-parser';
3 4
import morgan from 'morgan';
4 5
import userRouter from '@app/routes/users';
5 6
import authRouter from '@app/routes/auth';
6 7
import { errorHandler } from '@app/middleware/errorHandler';
7 8
import { PrismaClient } from '@prisma/client';
14 15
    origin: process.env.FRONTEND_URL || '*',
15 16
    credentials: true
16 17
  }));
17 18

18 19
  app.use(express.json());
20 +
  app.use(bodyParser.json());
19 21

20 22
  // Only use morgan in non-test environments
21 23
  if (process.env.NODE_ENV !== 'test') {
22 24
    app.use(morgan('combined'));
23 25
  }
31 33
  app.use("/auth", authRouter);
32 34

33 35
  app.use(errorHandler); // always last
34 36

35 37
  return app;
36 -
}
38 +
}
api/src/index.ts modified+1 -1
api/src/index.ts
1 -
import { createApp } from '@app/create-app';
1 +
import { createApp } from '@app/createApp';
2 2

3 3
const port = process.env.PORT || 3000;
4 4

5 5
const app = createApp();
6 6

7 7
app.listen(port, () => {
8 8
  console.log(`Server booted on port ${port}`)
9 9
})
10 10

api/src/middleware/auth.ts
1 1
import { Request, Response, NextFunction } from 'express';
2 2
import { verifyToken } from '@app/lib/jwt';
3 3

4 4
export interface AuthenticatedRequest extends Request {
5 5
  userId?: number;
28 28
    if (err.name === 'TokenExpiredError') {
29 29
      return res.status(401).json({ error: 'Unauthorized: Token expired' });
30 30
    }
31 31
    next(err);
32 32
  }
33 -
};
33 +
};
api/src/middleware/errorHandler.ts
1 1
import { Request, Response, NextFunction } from 'express';
2 2
import { Prisma } from '@prisma/client' // Bleh
3 3
import { isDomainError } from "@app/lib/errors/base";
4 4

5 5
export const errorHandler = (
6 6
  err: unknown,
7 7
  _req: Request,
8 8
  res: Response,
9 9
  _next: NextFunction
10 10
) => {
11 -
  // TODO: Rig some improved, centralized error logging here
12 -
  console.error(`[ErrorHandler]: ${err}`);
11 +
  if (process.env.NODE_ENV === "development") {
12 +
    // TODO: Rig some improved, centralized error logging here
13 +
    console.error(`[ErrorHandler]: ${err}`);
14 +
  }
13 15

14 16
  if (isDomainError(err)) {
15 17
    return res.status(err.status)
16 18
      .json({ error: err.message });
17 19
  }
25 27
      .json({ error: "Unique constraint violation" });
26 28
  }
27 29
  
28 30
  res.status(500).json({ error: "Internal server error" });
29 31
}
api/src/models/user.ts modified+24 -4
api/src/models/user.ts
1 1
import bcrypt from "bcryptjs";
2 2
import { prisma } from '../lib/prisma'
3 3
import {
4 4
  generateToken as createJWT,
5 5
  generatePasswordResetToken,
14 14
  validateRequiredFields
15 15
} from "@app/utils/validators";
16 16

17 17
import { InvalidEmailError, ShortPasswordError } from '@app/lib/errors/UserErrors'
18 18

19 +
type UserOptions = {
20 +
  email: string;
21 +
  firstName: string;
22 +
  lastName: string;
23 +
};
24 +

19 25
const createUser = async (email: string, firstName: string, lastName: string, password: string) => {
20 26
  // Validate email format
21 27
  if (!isValidEmail(email)) {
22 28
    throw new InvalidEmailError()
23 29
  }
24 30

25 31
  // Validate password strength
26 32
  if (!isValidPassword(password)) {
27 33
    throw new ShortPasswordError()
28 34
  }
29 -
  
35 +

30 36
  const encryptedPassword = bcrypt.hashSync(password, 10);
31 37

32 38
  try {
33 39
    const user = await prisma.user.create({
34 40
      data: {
39 45
      }
40 46
    });
41 47

42 48
    return user;
43 49
  } catch (err: any) {
44 -
    console.log(err)
45 -
    throw new Error("Something was off when creating user")
50 +
    throw err;
46 51
  }
47 52
}
48 53

49 54
const generateToken = (userId: number, email: string) => {
50 55
  return createJWT(userId, email);
51 56
}
57 +

58 +
const findUserById = async (id: number) => {
59 +
  return await prisma.user.findUnique({
60 +
    where: { id }
61 +
  });
62 +
};
52 63

53 64
const findUserByEmail = async (email: string) => {
54 65
  return await prisma.user.findUnique({
55 66
    where: { email }
56 67
  });
132 143
  });
133 144

134 145
  return { success: true };
135 146
};
136 147

148 +
const updateUser = async (user: any, data: UserOptions) => {
149 +
  return await prisma.user.update({
150 +
    where: { id: user.id },
151 +
    data
152 +
  });
153 +
}
154 +

137 155
export {
138 156
  createUser,
157 +
  findUserById,
139 158
  generateToken,
140 159
  findUserByEmail,
141 160
  authenticateUser,
142 161
  initiatePasswordReset,
143 -
  resetPassword
162 +
  resetPassword,
163 +
  updateUser
144 164
}
api/src/routes/users.ts
1 1
import { Router } from "express";
2 2
import { UsersController } from '@app/controllers/users'
3 3

4 4
const router = Router();
5 5

6 6
router.post('/', UsersController.create);
7 +
router.put('/:id', UsersController.update);
7 8

8 9
export default router;
api/tests/database-auth.test.ts
1 1
import test from "ava";
2 2
import { PrismaClient } from '@prisma/client';
3 3
import { PrismaPg } from '@prisma/adapter-pg';
4 4
import bcrypt from 'bcryptjs';
5 5
import 'dotenv/config';
22 22
    console.error('Error cleaning test DB:', err);
23 23
  }
24 24
});
25 25

26 26
test.after.always(async () => {
27 +
  const deleteUsers = prisma.user.deleteMany();
28 +

29 +
  await prisma.$transaction([
30 +
    deleteUsers
31 +
  ])
32 +

27 33
  await prisma?.$disconnect();
28 34
});
29 35

30 36
// Basic database connectivity test
31 37
test("database connection works", async (t) => {
250 256
  t.deepEqual(updatedUser.createdAt, user.createdAt);
251 257
  t.true(updatedUser.updatedAt >= beforeUpdate);
252 258
  t.true(updatedUser.updatedAt <= afterUpdate);
253 259
  t.true(updatedUser.updatedAt > user.updatedAt);
254 260
});
/dev/null → api/tests/users/create.test.ts
1 +
import test from "ava";
2 +
import { PrismaClient } from '@prisma/client';
3 +
import { PrismaPg } from '@prisma/adapter-pg';
4 +
import request from 'supertest';
5 +
import jwt from 'jsonwebtoken';
6 +
import 'dotenv/config';
7 +
import { Express } from 'express';
8 +
import { createApp } from '../../src/createApp';
9 +

10 +
let prisma: PrismaClient;
11 +
let app: Express;
12 +

13 +
test.before(() => {
14 +
  process.env.NODE_ENV = 'test';
15 +
  process.env.JWT_SECRET = 'test-secret-key';
16 +

17 +
  const connectionString = process.env.DATABASE_URL || 'postgres://user:password@orbit_db:5432/orbit_test';
18 +
  const adapter = new PrismaPg({ connectionString });
19 +
  prisma = new PrismaClient({ adapter });
20 +

21 +
  // Create Express app with test Prisma client
22 +
  app = createApp(prisma);
23 +
});
24 +

25 +
test.beforeEach(async () => {
26 +
  // Clean database before each test
27 +
  try {
28 +
    await prisma.user.deleteMany({});
29 +
  } catch (err) {
30 +
    console.error('Error cleaning test DB:', err);
31 +
  }
32 +
});
33 +

34 +
test.after.always(async () => {
35 +
  await prisma?.$disconnect();
36 +
});
37 +

38 +
test("POST /users creates a new user and returns JWT token", async (t) => {
39 +
  const userData = {
40 +
    email: "falamansa@example.com",
41 +
    firstName: "Fala",
42 +
    lastName: "Mansa",
43 +
    password: "IscreviSeuNomeNaAreia"
44 +
  };
45 +

46 +
  const response = await request(app)
47 +
    .post('/users')
48 +
    .send(userData)
49 +
    .expect('Content-Type', /json/)
50 +
    .expect(201);
51 +

52 +
  // Should return a JWT token
53 +
  t.truthy(response.body.token);
54 +

55 +
  // Verify the token is valid
56 +
  const decoded = jwt.verify(response.body.token, process.env.JWT_SECRET!) as any;
57 +
  t.is(decoded.email, userData.email);
58 +
  t.truthy(decoded.userId);
59 +

60 +
  // Verify user was created in database
61 +
  const dbUser = await prisma.user.findUnique({
62 +
    where: { email: userData.email }
63 +
  });
64 +
  t.truthy(dbUser);
65 +
  t.is(dbUser!.email, userData.email);
66 +
  t.is(dbUser!.firstName, userData.firstName);
67 +
  t.is(dbUser!.lastName, userData.lastName);
68 +
});
69 +

70 +
test("POST /users returns 400 for missing required fields", async (t) => {
71 +
  const response = await request(app)
72 +
    .post('/users')
73 +
    .send({
74 +
      email: "invalid@example.com"
75 +
      // Missing firstName and password
76 +
    })
77 +
    .expect('Content-Type', /json/)
78 +
    .expect(400);
79 +

80 +
  t.truthy(response.body.error);
81 +
  t.is(response.body.error, "Campos faltantes"); // TODO: i18n?
82 +
  t.truthy(response.body.missing);
83 +
  t.true(response.body.missing.includes('password'));
84 +
  t.true(response.body.missing.includes('firstName'));
85 +
});
86 +

87 +
test("POST /users returns 409 for duplicate email", async (t) => {
88 +
  const email = "duplicate@example.com";
89 +

90 +
  // Create first user
91 +
  await prisma.user.create({
92 +
    data: {
93 +
      email,
94 +
      firstName: "First",
95 +
      lastName: "User",
96 +
      encryptedPassword: "$2a$10$test"
97 +
    }
98 +
  });
99 +

100 +
  // Try to create second user with same email
101 +
  const response = await request(app)
102 +
    .post('/users')
103 +
    .send({
104 +
      email,
105 +
      firstName: "Second",
106 +
      lastName: "User",
107 +
      password: "hunter2"
108 +
    })
109 +
    .expect('Content-Type', /json/)
110 +
    .expect(409);
111 +

112 +
  t.truthy(response.body.error);
113 +
});
114 +

115 +
test("createApp successfully integrates with Prisma and Express", async (t) => {
116 +
  // Test that the app factory works correctly
117 +
  const testApp = createApp(prisma);
118 +

119 +
  t.truthy(testApp);
120 +
  t.is(typeof testApp.use, 'function');
121 +
  t.is(typeof testApp.listen, 'function');
122 +

123 +
  // Verify the app has the prisma client in locals
124 +
  t.is(testApp.locals.prisma, prisma);
125 +
});
/dev/null → api/tests/users/delete.test.ts
1 +
import test from "ava";
2 +
import { PrismaClient } from '@prisma/client';
3 +
import { PrismaPg } from '@prisma/adapter-pg';
4 +
import request from 'supertest';
5 +
import bcrypt from 'bcryptjs';
6 +
import 'dotenv/config';
7 +
import { Express } from 'express';
8 +
import { createApp } from '../../src/createApp';
9 +

10 +
let prisma: PrismaClient;
11 +
let app: Express;
12 +

13 +
test.before(() => {
14 +
  process.env.NODE_ENV = 'test';
15 +

16 +
  const connectionString = process.env.DATABASE_URL || 'postgres://user:password@orbit_db:5432/orbit_test';
17 +
  const adapter = new PrismaPg({ connectionString });
18 +
  prisma = new PrismaClient({ adapter });
19 +

20 +
  // Create Express app with test Prisma client
21 +
  app = createApp(prisma);
22 +
});
23 +

24 +
test.beforeEach(async () => {
25 +
  // Clean database before each test
26 +
  try {
27 +
    await prisma.user.deleteMany({});
28 +
  } catch (err) {
29 +
    console.error('Error cleaning test DB:', err);
30 +
  }
31 +
});
32 +

33 +
test.after.always(async () => {
34 +
  await prisma?.$disconnect();
35 +
});
36 +

37 +
//test("DELETE /users/:id deletes a user", async (t) => {
38 +
//  t.pass()
39 +
//  // Create a user first
40 +
//  const user = await prisma.user.create({
41 +
//    data: {
42 +
//      email: "delete@example.com",
43 +
//      firstName: "Delete",
44 +
//      lastName: "Me",
45 +
//      encryptedPassword: bcrypt.hashSync("password", 10)
46 +
//    }
47 +
//  });
48 +
//
49 +
//  await request(app)
50 +
//    .delete(`/users/${user.id}`)
51 +
//    .expect(204);
52 +
//
53 +
//  // Verify user was deleted
54 +
//  const dbUser = await prisma.user.findUnique({
55 +
//    where: { id: user.id }
56 +
//  });
57 +
//  t.is(dbUser, null);
58 +
//});
/dev/null → api/tests/users/fetch.test.ts
1 +
import test from "ava";
2 +
import { PrismaClient } from '@prisma/client';
3 +
import { PrismaPg } from '@prisma/adapter-pg';
4 +
import request from 'supertest';
5 +
import bcrypt from 'bcryptjs';
6 +
import 'dotenv/config';
7 +
import { Express } from 'express';
8 +
import { createApp } from '../../src/createApp';
9 +

10 +
let prisma: PrismaClient;
11 +
let app: Express;
12 +

13 +
test.before(() => {
14 +
  process.env.NODE_ENV = 'test';
15 +

16 +
  const connectionString = process.env.DATABASE_URL || 'postgres://user:password@orbit_db:5432/orbit_test';
17 +
  const adapter = new PrismaPg({ connectionString });
18 +
  prisma = new PrismaClient({ adapter });
19 +

20 +
  // Create Express app with test Prisma client
21 +
  app = createApp(prisma);
22 +
});
23 +

24 +
test.beforeEach(async () => {
25 +
  // Clean database before each test
26 +
  try {
27 +
    await prisma.user.deleteMany({});
28 +
  } catch (err) {
29 +
    console.error('Error cleaning test DB:', err);
30 +
  }
31 +
});
32 +

33 +
test.after.always(async () => {
34 +
  await prisma?.$disconnect();
35 +
});
36 +

37 +
//test("GET /users/:id retrieves a user", async (t) => {
38 +
//  t.pass()
39 +
//  // Create a user first
40 +
//  const user = await prisma.user.create({
41 +
//    data: {
42 +
//      email: "get@example.com",
43 +
//      firstName: "Get",
44 +
//      lastName: "User",
45 +
//      encryptedPassword: bcrypt.hashSync("password", 10)
46 +
//    }
47 +
//  });
48 +
//
49 +
//  const response = await request(app)
50 +
//    .get(`/users/${user.id}`)
51 +
//    .expect('Content-Type', /json/)
52 +
//    .expect(200);
53 +
//
54 +
//  t.is(response.body.id, user.id);
55 +
//  t.is(response.body.email, user.email);
56 +
//  t.is(response.body.firstName, user.firstName);
57 +
//  t.is(response.body.lastName, user.lastName);
58 +
//  t.falsy(response.body.encryptedPassword); // Should not expose password
59 +
//});
60 +
//
61 +
//test("GET /users/:id returns 404 for non-existent user", async (t) => {
62 +
//  const response = await request(app)
63 +
//    .get('/users/999999')
64 +
//    .expect('Content-Type', /json/)
65 +
//    .expect(404);
66 +
//
67 +
//  t.truthy(response.body.error);
68 +
//});
/dev/null → api/tests/users/update.test.ts
1 +
import test from "ava";
2 +
import { PrismaClient } from '@prisma/client';
3 +
import { PrismaPg } from '@prisma/adapter-pg';
4 +
import request from 'supertest';
5 +
import bcrypt from 'bcryptjs';
6 +
import 'dotenv/config';
7 +
import { Express } from 'express';
8 +
import { createApp } from '../../src/createApp';
9 +

10 +
let prisma: PrismaClient;
11 +
let app: Express;
12 +

13 +
test.before(() => {
14 +
  process.env.NODE_ENV = 'test';
15 +

16 +
  const connectionString = process.env.DATABASE_URL || 'postgres://user:password@orbit_db:5432/orbit_test';
17 +
  const adapter = new PrismaPg({ connectionString });
18 +
  prisma = new PrismaClient({ adapter });
19 +

20 +
  // Create Express app with test Prisma client
21 +
  app = createApp(prisma);
22 +
});
23 +

24 +
test.beforeEach(async () => {
25 +
  // Clean database before each test
26 +
  try {
27 +
    await prisma.user.deleteMany({});
28 +
  } catch (err) {
29 +
    console.error('Error cleaning test DB:', err);
30 +
  }
31 +
});
32 +

33 +
test.after.always(async () => {
34 +
  await prisma?.$disconnect();
35 +
});
36 +

37 +
test("PUT /users/:id updates user profile", async (t) => {
38 +
  // Create a user first
39 +
  const user = await prisma.user.create({
40 +
    data: {
41 +
      email: "update@example.com",
42 +
      firstName: "Original",
43 +
      lastName: "Name",
44 +
      encryptedPassword: bcrypt.hashSync("password", 10)
45 +
    }
46 +
  });
47 +

48 +
  const updateData = {
49 +
    firstName: "Updated",
50 +
    lastName: "Profile"
51 +
  };
52 +

53 +
  // TODO: Gotta check why its failing. Most likely due to the
54 +
  // cors/credentials: true line
55 +
  const response = await request(app)
56 +
    .put(`/users/${user.id}`)
57 +
    .send(updateData)
58 +
    .expect('Content-Type', /json/)
59 +
    .expect(200);
60 +

61 +
  t.is(response.body.user.firstName, updateData.firstName);
62 +
  t.is(response.body.user.lastName, updateData.lastName);
63 +
  t.is(response.body.user.email, user.email); // Should remain unchanged
64 +

65 +
  // Verify in database
66 +
  const dbUser = await prisma.user.findUnique({
67 +
    where: { id: user.id }
68 +
  });
69 +

70 +
  t.is(dbUser!.firstName, updateData.firstName);
71 +
  t.is(dbUser!.lastName, updateData.lastName);
72 +
});
73 +

74 +
test("PUT /users/:id returns 404 for non-existent user", async (t) => {
75 +
  const response = await request(app)
76 +
    .put('/users/999999')
77 +
    .send({
78 +
      firstName: "Updated",
79 +
      lastName: "Name"
80 +
    })
81 +
    .expect('Content-Type', /json/)
82 +
    .expect(404);
83 +

84 +
  t.truthy(response.body.error);
85 +
});
/dev/null → api/tsconfig.test.json
1 +
{
2 +
  "extends": "./tsconfig.json",
3 +
  "compilerOptions": {
4 +
    "module": "ESNext",
5 +
    "moduleResolution": "node",
6 +
    "allowImportingTsExtensions": true,
7 +
    "resolveJsonModule": true,
8 +
    "noEmit": true
9 +
  },
10 +
  "ts-node": {
11 +
    "esm": true,
12 +
    "experimentalSpecifierResolution": "node",
13 +
    "transpileOnly": true,
14 +
    "require": ["tsconfig-paths/register"]
15 +
  }
16 +
}
frontend/app/(tabs)/settings.tsx
1 -
import { StyleSheet } from 'react-native';
1 +
import { TouchableOpacity, StyleSheet, FlatList, View } from 'react-native';
2 +
import { useState } from 'react';
2 3
import ParallaxScrollView from '@/components/misc/parallax-scroll-view';
4 +
import { Link } from 'expo-router'
3 5

4 6
import { ThemedText } from '@/components/misc/themed-text';
5 7
import { ThemedView } from '@/components/misc/themed-view';
6 8
import { IconSymbol } from '@/components/ui/icon-symbol';
7 9
import { Fonts } from '@/constants/theme';
10 +
import { useThemeColor } from '@/hooks/use-theme-color';
8 11

9 12
export default function Settings() {
13 +
  const [options] = useState([
14 +
    { id: 'profile', name: 'Perfil', path: '/profile'}
15 +
  ])
16 +

17 +
  const textColor = useThemeColor({}, 'text');
18 +
  const tintColor = useThemeColor({}, 'tint');
19 +

20 +
  const renderItem = ({item}) => (
21 +
    <Link key={item.id} href={item.path} asChild style={[styles.item, { color: tintColor }]}>
22 +
      <ThemedText>
23 +
        {item.name}
24 +
      </ThemedText>
25 +
    </Link>
26 +
  )
27 +

10 28
  return (
11 29
    <ParallaxScrollView
12 30
      headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
13 31
      headerImage={
14 32
        <IconSymbol
15 33
          size={310}
16 -
          color="#808080"
34 +
          color="pink"
17 35
          name="chevron.left.forwardslash.chevron.right"
18 36
          style={styles.headerImage}
19 37
        />
20 38
      }>
21 39
      <ThemedView style={styles.titleContainer}>
22 40
        <ThemedText
23 41
          type="title"
24 42
          style={{
25 43
            fontFamily: Fonts.rounded,
26 44
          }}>
27 -
          Explore
45 +
          Configurações
28 46
        </ThemedText>
29 47
      </ThemedView>
30 -
      <ThemedText>
31 -
        This app includes example code to help you get started.
32 -
      </ThemedText>
48 +

49 +
      {(options.map((opt) => (
50 +
        renderItem({ item: opt })
51 +
      )))}
33 52
    </ParallaxScrollView>
34 53
  )
35 54
}
36 55

37 56

39 58
  headerImage: {
40 59
    color: '#808080',
41 60
    bottom: -90,
42 61
    left: -35,
43 62
    position: 'absolute',
63 +
  },
64 +
  item: {
65 +
    paddingTop: 16,
66 +
    paddingBottom: 16,
67 +
  },
68 +
  itemName: {
69 +
    fontSize: 16,
70 +
    fontWeight: 'bold',
71 +
  },
72 +
  separator: {
73 +
    height: 1,
74 +
    backgroundColor: '#ccc'
44 75
  },
45 76
  titleContainer: {
46 77
    flexDirection: 'row',
47 78
    gap: 8,
48 79
  },
49 80
});
/dev/null → frontend/app/profile.tsx
1 +
import { useState, useEffect } from 'react';
2 +
import {
3 +
  ThemedView
4 +
} from '@/components/ui/themed-view'
5 +

6 +
import {
7 +
  ThemedText
8 +
} from '@/components/ui/themed-text'
9 +

10 +
import {
11 +
  View,
12 +
  Text,
13 +
  StyleSheet,
14 +
  ScrollView,
15 +
  KeyboardAvoidingView,
16 +
  Platform,
17 +
  Alert,
18 +
} from 'react-native';
19 +
import { Link, useLocalSearchParams, router } from 'expo-router';
20 +
import { SafeAreaView } from 'react-native-safe-area-context';
21 +
import { useThemeColor } from '@/hooks/use-theme-color';
22 +
import { Button, Input } from '@/components/ui';
23 +

24 +
export default function Profile() {
25 +
  const [firstName, setFirstName] = useState();
26 +
  const [lastName, setLastName] = useState();
27 +
  const [password, setPassword] = useState();
28 +
  const [confirmPassword, setConfirmPassword] = useState();
29 +
  const [email, setEmail] = useState();
30 +
  const [loading, setLoading] = useState(false);
31 +
  const [errors, setErrors] = useState<{
32 +
    password?: string;
33 +
    confirmPassword?: string;
34 +
  }>({});
35 +

36 +
  const backgroundColor = useThemeColor({}, 'background');
37 +
  const textColor = useThemeColor({}, 'text');
38 +

39 +
  const handleUpdate = () => {}
40 +

41 +
  return (
42 +
    <SafeAreaView style={[styles.container, { backgroundColor }]}>
43 +
      <KeyboardAvoidingView
44 +
        behavior='height'
45 +
        style={styles.keyboardView}
46 +
      >
47 +
        <ScrollView
48 +
          contentContainerStyle={styles.scrollContainer}
49 +
          showsVerticalScrollIndicator={false}
50 +
        >
51 +
          <View style={styles.header}>
52 +
            <Text style={[styles.title, { color: textColor }]}>Edite o seu perfil</Text>
53 +
            <Text style={[styles.subtitle, { color: textColor, opacity: 0.7 }]}>
54 +
              Altere suas configurações
55 +
            </Text>
56 +
          </View>
57 +

58 +
          <View style={styles.form}>
59 +
            <Input
60 +
              label="Primeiro Nome"
61 +
              type="text"
62 +
              value={firstName}
63 +
              onChangeText={setFirstName}
64 +
              placeholder="Qual seu nome?"
65 +
              error={errors.firstName}
66 +
            />
67 +

68 +
            <Input
69 +
              label="Sobrenome"
70 +
              type="text"
71 +
              value={lastName}
72 +
              onChangeText={setLastName}
73 +
              placeholder="Qual seu sobrenome?"
74 +
              error={errors.lastName}
75 +
            />
76 +

77 +
            <Input
78 +
              label="Nova senha"
79 +
              type="password"
80 +
              value={password}
81 +
              onChangeText={setPassword}
82 +
              placeholder="Digite uma nova senha"
83 +
              error={errors.password}
84 +
              showPasswordToggle
85 +
            />
86 +

87 +
            <Input
88 +
              label="Confirmar nova senha"
89 +
              type="password"
90 +
              value={confirmPassword}
91 +
              onChangeText={setConfirmPassword}
92 +
              placeholder="Confirme a nova senha"
93 +
              error={errors.confirmPassword}
94 +
              showPasswordToggle
95 +
            />
96 +

97 +
            <Button
98 +
              title="Trocar senha"
99 +
              onPress={handleUpdate}
100 +
              loading={loading}
101 +
              style={styles.resetButton}
102 +
            />
103 +
          </View>
104 +

105 +
        </ScrollView>
106 +
      </KeyboardAvoidingView>
107 +
    </SafeAreaView>
108 +
  )
109 +
}
110 +

111 +
const styles = StyleSheet.create({
112 +
  container: {
113 +
    flex: 1,
114 +
  },
115 +
  keyboardView: {
116 +
    flex: 1,
117 +
  },
118 +
  scrollContainer: {
119 +
    flexGrow: 1,
120 +
    paddingHorizontal: 24,
121 +
    justifyContent: 'start',
122 +
    minHeight: '100%',
123 +
  },
124 +
  header: {
125 +
    alignItems: 'left',
126 +
    marginBottom: 40,
127 +
  },
128 +
  title: {
129 +
    fontSize: 28,
130 +
    fontWeight: 'bold',
131 +
    marginBottom: 8,
132 +
  },
133 +
  subtitle: {
134 +
    fontSize: 16,
135 +
    textAlign: 'left',
136 +
  },
137 +
  form: {
138 +
    marginBottom: 32,
139 +
  },
140 +
  resetButton: {
141 +
    marginTop: 8,
142 +
  },
143 +
  link: {
144 +
    textAlign: 'center',
145 +
    fontSize: 14,
146 +
    fontWeight: '500',
147 +
  },
148 +
  footer: {
149 +
    flexDirection: 'row',
150 +
    justifyContent: 'center',
151 +
    alignItems: 'center',
152 +
    marginTop: 'auto',
153 +
    paddingBottom: 20,
154 +
  },
155 +
  footerText: {
156 +
    fontSize: 14,
157 +
  },
158 +
});
/dev/null → frontend/public/tamagui.generated.css
Binary file