@flaredev/cli 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/index.js +114 -25
  2. package/package.json +2 -2
  3. package/templates/app/README.md +1 -1
  4. package/templates/app/_gitignore +7 -0
  5. package/templates/app/app/dashboard/layout.tsx +2 -2
  6. package/templates/app/app/dashboard/page.tsx +101 -26
  7. package/templates/app/app/dashboard/users/actions.ts +86 -0
  8. package/templates/app/app/dashboard/users/page.tsx +54 -0
  9. package/templates/app/components/dashboard/dashboard-header.tsx +2 -2
  10. package/templates/app/components/dashboard/dashboard-sidebar.tsx +13 -10
  11. package/templates/app/components/dashboard/user-actions.tsx +171 -0
  12. package/templates/app/components/dashboard/user-menu.tsx +17 -23
  13. package/templates/app/components/dashboard/users-table.tsx +109 -0
  14. package/templates/app/lib/api-docs.ts +1 -1
  15. package/templates/app/lib/auth.ts +3 -3
  16. package/templates/app/lib/dashboard.ts +5 -1
  17. package/templates/app/lib/mail.ts +2 -2
  18. package/templates/app/lib/site.ts +2 -2
  19. package/templates/app/vite.config.ts +3 -1
  20. package/templates/locks/next/package-lock.json +92 -91
  21. package/templates/locks/next/pnpm-lock.yaml +84 -93
  22. package/templates/locks/next/yarn.lock +83 -95
  23. package/templates/next/README.md +1 -1
  24. package/templates/next/app/dashboard/layout.tsx +2 -2
  25. package/templates/next/app/dashboard/observability/page.tsx +156 -0
  26. package/templates/next/app/dashboard/page.tsx +87 -23
  27. package/templates/next/app/dashboard/users/page.tsx +51 -0
  28. package/templates/next/lib/auth.ts +3 -3
  29. package/templates/next/lib/dashboard-nav.ts +29 -0
  30. package/templates/next/lib/dashboard.ts +5 -1
  31. package/templates/next/lib/mail.ts +2 -2
  32. package/templates/next/prisma/schema/base.prisma +6 -0
  33. package/templates/next/tsconfig.json +2 -2
  34. /package/templates/app/app/{forgot-password → (auth)/forgot-password}/page.tsx +0 -0
  35. /package/templates/app/app/{invite → (auth)/invite}/[token]/route.ts +0 -0
  36. /package/templates/app/app/{reset-password → (auth)/reset-password}/page.tsx +0 -0
  37. /package/templates/app/app/{sign-in → (auth)/sign-in}/page.tsx +0 -0
  38. /package/templates/app/app/{sign-up → (auth)/sign-up}/page.tsx +0 -0
  39. /package/templates/app/app/{two-factor → (auth)/two-factor}/page.tsx +0 -0
  40. /package/templates/app/app/{verify-email → (auth)/verify-email}/page.tsx +0 -0
@@ -1343,42 +1343,42 @@
1343
1343
  tslib "^2.8.1"
1344
1344
  tsyringe "^4.10.0"
1345
1345
 
1346
- "@prisma/adapter-pg@7.10.0":
1347
- version "7.10.0"
1348
- resolved "https://registry.yarnpkg.com/@prisma/adapter-pg/-/adapter-pg-7.10.0.tgz#8181c52b8406e7c8e9d6d52aa8b1e1f95aefa40e"
1349
- integrity sha512-N7nwSor0HO1Kz6xBv0TPAjAPysKK0fac6p4fVN3ensLOuzc/83Fgmln5k92eK/cvzqdkSR/2kkAqlbcdwVrwpw==
1346
+ "@prisma/adapter-pg@7.10.1":
1347
+ version "7.10.1"
1348
+ resolved "https://registry.yarnpkg.com/@prisma/adapter-pg/-/adapter-pg-7.10.1.tgz#7cfcbaa00bb620bc4530df80220de7b5795b90d3"
1349
+ integrity sha512-EP9ruPYHjKP80WZlb5qVaEXKgZjJieBFjAxcBtQnDAHD6+16UN4keVmBh6L/RUpuDQT2FQRbvKNgq5d4Ix+IkQ==
1350
1350
  dependencies:
1351
- "@prisma/driver-adapter-utils" "7.10.0"
1351
+ "@prisma/driver-adapter-utils" "7.10.1"
1352
1352
  "@types/pg" "^8.16.0"
1353
1353
  pg "^8.16.3"
1354
1354
  postgres-array "3.0.4"
1355
1355
 
1356
- "@prisma/client-runtime-utils@7.10.0":
1357
- version "7.10.0"
1358
- resolved "https://registry.yarnpkg.com/@prisma/client-runtime-utils/-/client-runtime-utils-7.10.0.tgz#6c0e5bfcf43309aafbd89be660172e6150945ce5"
1359
- integrity sha512-cnCy7lUV8/CctgKVEmqAbSLAmwqJdE/qAlqTBk/0NDk59zEb2cZ0M0M0E4vVPnqbSEYudRroQDvOWfUZH6RIfw==
1356
+ "@prisma/client-runtime-utils@7.10.1":
1357
+ version "7.10.1"
1358
+ resolved "https://registry.yarnpkg.com/@prisma/client-runtime-utils/-/client-runtime-utils-7.10.1.tgz#af603fe5cbcd44e381772a97fedbb1ee1d363a58"
1359
+ integrity sha512-AzoATVIgmO3lcZfM443+9SI7+cyLMW2+Psw4op8WQHpHvHCX//CKxv8fwhiL0yGWF8DjdQrFewxrZo5McBdB8g==
1360
1360
 
1361
- "@prisma/client@7.10.0":
1362
- version "7.10.0"
1363
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-7.10.0.tgz#3fed90b7577a8110417c7a6af69f5dab0a385e98"
1364
- integrity sha512-Ubw/QS9JGIBSBUsyxAUQuK/Jcu0Tsva7le7QbLd91Kix9yJvYDdj5QkwgEbbZniH80dd+sziQcALPc+HnvQC8Q==
1361
+ "@prisma/client@7.10.1":
1362
+ version "7.10.1"
1363
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-7.10.1.tgz#c3a6acd14b79ccd788fc93e150df26bae4be5c83"
1364
+ integrity sha512-5tgAbFGh//zVOC6WPNVgziOErJHYkUE+wmqCwbIyq6IrTXUYtv9y2iBFGh/Owo99Mx0+cr0j1FOrxkYBMuyhaQ==
1365
1365
  dependencies:
1366
- "@prisma/client-runtime-utils" "7.10.0"
1366
+ "@prisma/client-runtime-utils" "7.10.1"
1367
1367
 
1368
- "@prisma/config@7.10.0":
1369
- version "7.10.0"
1370
- resolved "https://registry.yarnpkg.com/@prisma/config/-/config-7.10.0.tgz#365dfc46fae23867734b60af6fbf9ed297507f31"
1371
- integrity sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==
1368
+ "@prisma/config@7.10.1":
1369
+ version "7.10.1"
1370
+ resolved "https://registry.yarnpkg.com/@prisma/config/-/config-7.10.1.tgz#769d708a197eefbc6dd2769d7251d5f8a23a3f19"
1371
+ integrity sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==
1372
1372
  dependencies:
1373
1373
  c12 "3.3.4"
1374
- deepmerge-ts "7.1.5"
1374
+ deepmerge-ts "8.0.2"
1375
1375
  effect "3.20.0"
1376
1376
  empathic "2.0.0"
1377
1377
 
1378
- "@prisma/debug@7.10.0":
1379
- version "7.10.0"
1380
- resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-7.10.0.tgz#9ac8ed485699bd08978fe83f97a80993f30d4638"
1381
- integrity sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==
1378
+ "@prisma/debug@7.10.1":
1379
+ version "7.10.1"
1380
+ resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-7.10.1.tgz#6ef089971593d98668e762c5810ea335461773e8"
1381
+ integrity sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==
1382
1382
 
1383
1383
  "@prisma/debug@7.2.0":
1384
1384
  version "7.2.0"
@@ -1406,43 +1406,43 @@
1406
1406
  valibot "1.4.2"
1407
1407
  zeptomatch "2.1.0"
1408
1408
 
1409
- "@prisma/driver-adapter-utils@7.10.0":
1410
- version "7.10.0"
1411
- resolved "https://registry.yarnpkg.com/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.10.0.tgz#c0a8480d48a86fb944f4ab6afff2d052a38beedc"
1412
- integrity sha512-u8zkcRLlaryO652T4qavBg0HmzNW5tSKdsCn6hc1PhWAp/J6k0vrxLuUs+b9o+HcjsK7Dfa01o4OFSn0frauJA==
1409
+ "@prisma/driver-adapter-utils@7.10.1":
1410
+ version "7.10.1"
1411
+ resolved "https://registry.yarnpkg.com/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.10.1.tgz#72c9bc9f5a7489e15ca7aefd1c195643924d42a3"
1412
+ integrity sha512-DJ4c6xvVnm3s9ZUyql9nCUicADSqyvH+CjXRCM9L6m5zJxCNmebCElG6X4irICq3rosBwlX+S19Dxq/4IrZk3A==
1413
1413
  dependencies:
1414
- "@prisma/debug" "7.10.0"
1414
+ "@prisma/debug" "7.10.1"
1415
1415
 
1416
- "@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3":
1417
- version "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3"
1418
- resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3.tgz#3f30522c9f6c84e703bdf49b4db4f8a5356f3505"
1419
- integrity sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==
1416
+ "@prisma/engines-version@8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8":
1417
+ version "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8"
1418
+ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8.tgz#2f8c62e2799a4cd36f2d65b0db5452e9667461ad"
1419
+ integrity sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==
1420
1420
 
1421
- "@prisma/engines@7.10.0":
1422
- version "7.10.0"
1423
- resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-7.10.0.tgz#c8d8e1cbdcfcbc72ae6b665b28b8668b5768a0ec"
1424
- integrity sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==
1421
+ "@prisma/engines@7.10.1":
1422
+ version "7.10.1"
1423
+ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-7.10.1.tgz#ed58180bb8b3eeb42bac83e33bec61c54bbdd081"
1424
+ integrity sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==
1425
1425
  dependencies:
1426
- "@prisma/debug" "7.10.0"
1427
- "@prisma/engines-version" "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3"
1428
- "@prisma/fetch-engine" "7.10.0"
1429
- "@prisma/get-platform" "7.10.0"
1426
+ "@prisma/debug" "7.10.1"
1427
+ "@prisma/engines-version" "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8"
1428
+ "@prisma/fetch-engine" "7.10.1"
1429
+ "@prisma/get-platform" "7.10.1"
1430
1430
 
1431
- "@prisma/fetch-engine@7.10.0":
1432
- version "7.10.0"
1433
- resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-7.10.0.tgz#85ffc73d3b8abebd8e5ebe47a818d66cbfa20dcb"
1434
- integrity sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==
1431
+ "@prisma/fetch-engine@7.10.1":
1432
+ version "7.10.1"
1433
+ resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-7.10.1.tgz#3100866a89302bc58784c0e8baba97a20b808018"
1434
+ integrity sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==
1435
1435
  dependencies:
1436
- "@prisma/debug" "7.10.0"
1437
- "@prisma/engines-version" "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3"
1438
- "@prisma/get-platform" "7.10.0"
1436
+ "@prisma/debug" "7.10.1"
1437
+ "@prisma/engines-version" "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8"
1438
+ "@prisma/get-platform" "7.10.1"
1439
1439
 
1440
- "@prisma/get-platform@7.10.0":
1441
- version "7.10.0"
1442
- resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-7.10.0.tgz#0993e4990da4b1dc34196121eeed765507853ccb"
1443
- integrity sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==
1440
+ "@prisma/get-platform@7.10.1":
1441
+ version "7.10.1"
1442
+ resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-7.10.1.tgz#536225b4e7b9c6e93678ef45047fb47536dbf767"
1443
+ integrity sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==
1444
1444
  dependencies:
1445
- "@prisma/debug" "7.10.0"
1445
+ "@prisma/debug" "7.10.1"
1446
1446
 
1447
1447
  "@prisma/get-platform@7.2.0":
1448
1448
  version "7.2.0"
@@ -3226,7 +3226,7 @@ async-sema@^3.1.1:
3226
3226
  resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-3.1.1.tgz#e527c08758a0f8f6f9f15f799a173ff3c40ea808"
3227
3227
  integrity sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==
3228
3228
 
3229
- aws-ssl-profiles@^1.1.1:
3229
+ aws-ssl-profiles@^1.1.2:
3230
3230
  version "1.1.2"
3231
3231
  resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641"
3232
3232
  integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==
@@ -3610,10 +3610,10 @@ debug@4.3.4:
3610
3610
  dependencies:
3611
3611
  ms "2.1.2"
3612
3612
 
3613
- deepmerge-ts@7.1.5:
3614
- version "7.1.5"
3615
- resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz#ff818564007f5c150808d2b7b732cac83aa415ab"
3616
- integrity sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==
3613
+ deepmerge-ts@8.0.2:
3614
+ version "8.0.2"
3615
+ resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz#4f3e5588c3a7c59ea80d54b583948d6f404cf13e"
3616
+ integrity sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==
3617
3617
 
3618
3618
  define-lazy-prop@^2.0.0:
3619
3619
  version "2.0.0"
@@ -3632,11 +3632,6 @@ delaunator@5:
3632
3632
  dependencies:
3633
3633
  robust-predicates "^3.0.2"
3634
3634
 
3635
- denque@^2.1.0:
3636
- version "2.1.0"
3637
- resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
3638
- integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
3639
-
3640
3635
  depd@~1.1.2:
3641
3636
  version "1.1.2"
3642
3637
  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -4115,7 +4110,7 @@ iconv-lite@0.4.24:
4115
4110
  dependencies:
4116
4111
  safer-buffer ">= 2.1.2 < 3"
4117
4112
 
4118
- iconv-lite@^0.7.0:
4113
+ iconv-lite@^0.7.3:
4119
4114
  version "0.7.3"
4120
4115
  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.3.tgz#84ee12f963e7de50bc01a13e160a078b3b0f415f"
4121
4116
  integrity sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==
@@ -4334,7 +4329,7 @@ lodash@^4.17.21:
4334
4329
  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c"
4335
4330
  integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
4336
4331
 
4337
- long@^5.2.1:
4332
+ long@^5.3.2:
4338
4333
  version "5.3.2"
4339
4334
  resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83"
4340
4335
  integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
@@ -4356,7 +4351,7 @@ lru-cache@^6.0.0:
4356
4351
  dependencies:
4357
4352
  yallist "^4.0.0"
4358
4353
 
4359
- lru.min@^1.0.0, lru.min@^1.1.0:
4354
+ lru.min@^1.1.0, lru.min@^1.1.4:
4360
4355
  version "1.1.5"
4361
4356
  resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.5.tgz#bd32483f4e342261d0eb011b06d2219e6b5ef818"
4362
4357
  integrity sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==
@@ -4480,22 +4475,20 @@ ms@2.1.3, ms@^2.1.3:
4480
4475
  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
4481
4476
  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
4482
4477
 
4483
- mysql2@3.15.3:
4484
- version "3.15.3"
4485
- resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.15.3.tgz#f0348d9c7401bb98cb1f45ffc5a773b109f70808"
4486
- integrity sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==
4478
+ mysql2@3.24.4:
4479
+ version "3.24.4"
4480
+ resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.24.4.tgz#7752de0cf10b1d2984103bdbc60f696c60756f31"
4481
+ integrity sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==
4487
4482
  dependencies:
4488
- aws-ssl-profiles "^1.1.1"
4489
- denque "^2.1.0"
4483
+ aws-ssl-profiles "^1.1.2"
4490
4484
  generate-function "^2.3.1"
4491
- iconv-lite "^0.7.0"
4492
- long "^5.2.1"
4493
- lru.min "^1.0.0"
4494
- named-placeholders "^1.1.3"
4495
- seq-queue "^0.0.5"
4496
- sqlstring "^2.3.2"
4497
-
4498
- named-placeholders@^1.1.3:
4485
+ iconv-lite "^0.7.3"
4486
+ long "^5.3.2"
4487
+ lru.min "^1.1.4"
4488
+ named-placeholders "^1.1.6"
4489
+ sql-escaper "^1.5.1"
4490
+
4491
+ named-placeholders@^1.1.6:
4499
4492
  version "1.1.6"
4500
4493
  resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.6.tgz#c50c6920b43f258f59c16add1e56654f5cc02bb5"
4501
4494
  integrity sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==
@@ -4873,16 +4866,16 @@ pretty-ms@7.0.1:
4873
4866
  dependencies:
4874
4867
  parse-ms "^2.1.0"
4875
4868
 
4876
- prisma@7.10.0:
4877
- version "7.10.0"
4878
- resolved "https://registry.yarnpkg.com/prisma/-/prisma-7.10.0.tgz#f8fd28b6665620926f5124b278ada2e9f3d24bc3"
4879
- integrity sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==
4869
+ prisma@7.10.1:
4870
+ version "7.10.1"
4871
+ resolved "https://registry.yarnpkg.com/prisma/-/prisma-7.10.1.tgz#d4051bf0b65463509e641dbaa73c4dc076cc9f5b"
4872
+ integrity sha512-GoVeErHGGtsSjsflQ1r2BA3RKTXtxNraQJz8OLkLhoLr7OgCscdx4qw4WMM4HnERYc+a1cq2VnMNbep+vOjNcQ==
4880
4873
  dependencies:
4881
- "@prisma/config" "7.10.0"
4874
+ "@prisma/config" "7.10.1"
4882
4875
  "@prisma/dev" "0.24.17"
4883
- "@prisma/engines" "7.10.0"
4876
+ "@prisma/engines" "7.10.1"
4884
4877
  "@prisma/studio-core" "0.33.0"
4885
- mysql2 "3.15.3"
4878
+ mysql2 "3.24.4"
4886
4879
  postgres "3.4.7"
4887
4880
 
4888
4881
  promisepipe@3.0.0:
@@ -5204,11 +5197,6 @@ semver@^7.5.3, semver@^7.8.5:
5204
5197
  resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.6.tgz#67c0cda133a3ceb6f70b803e67625bf969000424"
5205
5198
  integrity sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==
5206
5199
 
5207
- seq-queue@^0.0.5:
5208
- version "0.0.5"
5209
- resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
5210
- integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==
5211
-
5212
5200
  set-cookie-parser@^3.1.2:
5213
5201
  version "3.2.2"
5214
5202
  resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-3.2.2.tgz#e1aded19f953adcc2a7bc2904837c32067df46a8"
@@ -5301,10 +5289,10 @@ split2@^4.1.0:
5301
5289
  resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
5302
5290
  integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
5303
5291
 
5304
- sqlstring@^2.3.2:
5305
- version "2.3.3"
5306
- resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c"
5307
- integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==
5292
+ sql-escaper@^1.5.1:
5293
+ version "1.5.2"
5294
+ resolved "https://registry.yarnpkg.com/sql-escaper/-/sql-escaper-1.5.2.tgz#5b59840dc4034bd23a9b7d00501ba947a90364b7"
5295
+ integrity sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==
5308
5296
 
5309
5297
  srvx@0.11.16:
5310
5298
  version "0.11.16"
@@ -1,4 +1,4 @@
1
- # __APP_NAME__
1
+ # __APP_TITLE__
2
2
 
3
3
  A [Flare](https://flare-docs.codetotech.com) app on Next.js, Postgres and Prisma.
4
4
 
@@ -53,9 +53,9 @@ export default async function DashboardLayout({ children }: { children: React.Re
53
53
 
54
54
  return (
55
55
  <SidebarProvider defaultOpen={!collapsed}>
56
- <DashboardSidebar appName="__APP_NAME__" resources={resources} links={dashboardLinks} isAdmin={ADMIN_ROLES.includes(role)} user={account} />
56
+ <DashboardSidebar appName="__APP_TITLE__" resources={resources} links={dashboardLinks} isAdmin={ADMIN_ROLES.includes(role)} user={account} />
57
57
  <SidebarInset>
58
- <DashboardHeader user={account} initialTheme={theme} notices={notices} />
58
+ <DashboardHeader user={account} initialTheme={theme} notices={notices} isAdmin={ADMIN_ROLES.includes(role)} />
59
59
  <main className="flex flex-1 flex-col gap-6 p-4 md:p-6 lg:p-8">{children}</main>
60
60
  </SidebarInset>
61
61
  <Toaster theme={theme} />
@@ -0,0 +1,156 @@
1
+ import Link from "next/link";
2
+ import { DatabaseIcon, HistoryIcon, KeyRoundIcon, UsersIcon } from "lucide-react";
3
+ import { Badge } from "@/components/ui/badge";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5
+ import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@/components/ui/empty";
6
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
7
+ import { PageHeader } from "@/components/dashboard/page-header";
8
+ import { LocalTime } from "@/components/dashboard/local-time";
9
+ import { StatCards, type Stat } from "@/components/dashboard/stat-card";
10
+ import { auditCount, recentAudit } from "@/lib/audit";
11
+ import { recordCount, requireDashboard, resourcePath, visibleResources } from "@/lib/dashboard";
12
+ import { prisma } from "@/lib/db";
13
+
14
+ export const metadata = { title: "Observability" };
15
+
16
+ const ACTION_TONE: Record<string, "secondary" | "destructive" | "outline"> = {
17
+ create: "secondary",
18
+ update: "outline",
19
+ delete: "destructive",
20
+ "bulk-delete": "destructive",
21
+ import: "secondary",
22
+ };
23
+
24
+ /**
25
+ * How the app is behaving, from what this app can actually see: the database, the
26
+ * accounts and the record of who changed what.
27
+ *
28
+ * Request traffic, errors and latency are Vercel's to measure — they happen at their
29
+ * edge, before a byte reaches this code — so the page links there rather than printing
30
+ * three dashes and calling them "not connected".
31
+ */
32
+ export default async function ObservabilityPage() {
33
+ await requireDashboard("/dashboard/observability");
34
+
35
+ const [resources, changes, audit, people, sessions] = await Promise.all([
36
+ visibleResources(),
37
+ auditCount(7),
38
+ recentAudit(20),
39
+ prisma.user.count(),
40
+ prisma.session.count({ where: { expiresAt: { gt: new Date() } } }),
41
+ ]);
42
+
43
+ const tables = await Promise.all(resources.map(async (resource) => ({ resource, total: await recordCount(resource.name) })));
44
+ const records = tables.reduce((total, table) => total + table.total, 0);
45
+
46
+ const stats: Stat[] = [
47
+ { label: "Users", value: people, icon: UsersIcon, href: "/dashboard/users" },
48
+ // Users are records too — the hint counts their table, so the number must as well.
49
+ { label: "Records", value: records + people, hint: `across ${tables.length + 1} ${tables.length === 0 ? "table" : "tables"}`, icon: DatabaseIcon },
50
+ { label: "Signed-in sessions", value: sessions, hint: "not yet expired", icon: KeyRoundIcon },
51
+ { label: "Changes this week", value: changes, hint: "creates, edits and deletes", icon: HistoryIcon },
52
+ ];
53
+
54
+ return (
55
+ <>
56
+ <PageHeader
57
+ title="Observability"
58
+ description="The database, the accounts, and the record of who changed what."
59
+ crumbs={[{ label: "Dashboard", href: "/dashboard" }, { label: "Observability" }]}
60
+ />
61
+
62
+ <StatCards stats={stats} />
63
+
64
+ <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.6fr)]">
65
+ <Card className="h-fit">
66
+ <CardHeader>
67
+ <CardTitle className="flex items-center gap-2 text-base">
68
+ <DatabaseIcon className="size-4" />
69
+ Database
70
+ </CardTitle>
71
+ <CardDescription>What each table holds right now.</CardDescription>
72
+ </CardHeader>
73
+ <CardContent className="flex flex-col">
74
+ <div className="flex items-center justify-between gap-3 border-b py-2.5 text-sm last:border-b-0 last:pb-0">
75
+ <Link href="/dashboard/users" className="hover:underline">
76
+ Users
77
+ </Link>
78
+ <span className="tabular-nums text-muted-foreground">{people.toLocaleString()}</span>
79
+ </div>
80
+ {tables.map(({ resource, total }) => (
81
+ <div key={resource.name} className="flex items-center justify-between gap-3 border-b py-2.5 text-sm last:border-b-0 last:pb-0">
82
+ <Link href={resourcePath(resource)} className="hover:underline">
83
+ {resource.pluralLabel}
84
+ </Link>
85
+ <span className="tabular-nums text-muted-foreground">{total.toLocaleString()}</span>
86
+ </div>
87
+ ))}
88
+ <p className="mt-4 text-xs text-muted-foreground">
89
+ Request traffic, errors and latency happen at Vercel&apos;s edge before they reach this code, so they live in{" "}
90
+ <a href="https://vercel.com/dashboard" target="_blank" rel="noreferrer" className="underline underline-offset-2">
91
+ your Vercel dashboard
92
+ </a>{" "}
93
+ under Observability.
94
+ </p>
95
+ </CardContent>
96
+ </Card>
97
+
98
+ <Card>
99
+ <CardHeader>
100
+ <CardTitle className="flex items-center gap-2 text-base">
101
+ <HistoryIcon className="size-4" />
102
+ Recent changes
103
+ </CardTitle>
104
+ <CardDescription>Every write through the dashboard, newest first.</CardDescription>
105
+ </CardHeader>
106
+ <CardContent>
107
+ {audit.length === 0 ? (
108
+ <Empty>
109
+ <EmptyHeader>
110
+ <EmptyTitle>Nothing yet</EmptyTitle>
111
+ <EmptyDescription>Creates, edits, deletes and imports show up here as they happen.</EmptyDescription>
112
+ </EmptyHeader>
113
+ </Empty>
114
+ ) : (
115
+ <div className="overflow-x-auto">
116
+ <Table>
117
+ <TableHeader>
118
+ <TableRow>
119
+ <TableHead>What</TableHead>
120
+ <TableHead>Record</TableHead>
121
+ <TableHead>Who</TableHead>
122
+ <TableHead className="text-right">When</TableHead>
123
+ </TableRow>
124
+ </TableHeader>
125
+ <TableBody>
126
+ {audit.map((entry) => (
127
+ <TableRow key={entry.id}>
128
+ <TableCell>
129
+ <Badge variant={ACTION_TONE[entry.action] ?? "secondary"}>{entry.action}</Badge>
130
+ <span className="ml-2 text-muted-foreground">{entry.resource}</span>
131
+ </TableCell>
132
+ <TableCell className="max-w-56 truncate">{entry.recordLabel ?? describeBulk(entry.changes)}</TableCell>
133
+ <TableCell className="max-w-48 truncate text-muted-foreground">{entry.userEmail ?? "—"}</TableCell>
134
+ <TableCell className="text-right whitespace-nowrap text-muted-foreground tabular-nums">
135
+ <LocalTime value={entry.createdAt} />
136
+ </TableCell>
137
+ </TableRow>
138
+ ))}
139
+ </TableBody>
140
+ </Table>
141
+ </div>
142
+ )}
143
+ </CardContent>
144
+ </Card>
145
+ </div>
146
+ </>
147
+ );
148
+ }
149
+
150
+ /** Bulk actions have no single record, so say how many they touched. */
151
+ function describeBulk(changes: Record<string, unknown> | null): string {
152
+ if (!changes) return "—";
153
+ if (typeof changes.deleted === "number") return `${changes.deleted} deleted`;
154
+ if (typeof changes.created === "number") return `${changes.created} imported`;
155
+ return "—";
156
+ }
@@ -1,22 +1,31 @@
1
1
  import Link from "next/link";
2
- import { ArrowRightIcon, CheckCircle2Icon, CircleIcon, PlusIcon } from "lucide-react";
2
+ import { ArrowRightIcon, CheckCircle2Icon, CircleIcon, PlusIcon, UsersIcon } from "lucide-react";
3
3
  import { Button } from "@/components/ui/button";
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5
- import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@/components/ui/empty";
6
5
  import { PageHeader } from "@/components/dashboard/page-header";
7
6
  import { resourceIcon } from "@/components/dashboard/resource-icon";
8
7
  import { StatCards, type Stat } from "@/components/dashboard/stat-card";
9
8
  import { prisma } from "@/lib/db";
10
9
  import { authConfig, twoFactorAvailable } from "@/lib/auth-config";
11
- import { adminPermissions, resourcePath, resourceStats, trend, visibleResources } from "@/lib/dashboard";
10
+ import { ADMIN_ROLES, adminPermissions, dashboardSession, resourcePath, resourceStats, trend, visibleResources } from "@/lib/dashboard";
12
11
  import { requireSession } from "@/lib/session";
13
12
 
14
13
  export const metadata = { title: "Dashboard" };
15
14
 
15
+ /** The three commands that turn an empty app into one with data, with the stack's real steps. */
16
+ const FIRST_RESOURCE_STEPS = [
17
+ { command: "npx flare gen resource Contact --fields 'name:string, email:email!'", note: "the table, API and admin screens" },
18
+ { command: "npx flare migrate", note: "create its table" },
19
+ { command: "npx flare seed:resource Contact 1000", note: "fill it with sample rows" },
20
+ ];
21
+
16
22
  /** The first thing you see when you sign in: how much data there is, and what's left to set up. */
17
23
  export default async function DashboardPage() {
18
24
  const { user } = await requireSession("/dashboard");
25
+ const { role } = await dashboardSession();
26
+ const isAdmin = role !== null && ADMIN_ROLES.includes(role);
19
27
  const resources = await visibleResources();
28
+ const week = new Date(Date.now() - 7 * 86_400_000);
20
29
 
21
30
  const cards = await Promise.all(
22
31
  resources.map(async (resource) => {
@@ -26,14 +35,33 @@ export default async function DashboardPage() {
26
35
  }),
27
36
  );
28
37
 
29
- const stats: Stat[] = cards.slice(0, 4).map(({ resource, total, recent }) => ({
30
- label: resource.pluralLabel,
31
- value: total,
32
- hint: recent.current > 0 ? `${recent.current.toLocaleString()} this week` : "none this week",
33
- change: trend(recent.current, recent.previous),
34
- icon: resourceIcon(resource.icon),
35
- href: resourcePath(resource),
36
- }));
38
+ // The one table every app has from its first sign-in. Admin only, because it links to
39
+ // the accounts page, which is.
40
+ const [people, peopleThisWeek] = isAdmin
41
+ ? await Promise.all([prisma.user.count(), prisma.user.count({ where: { createdAt: { gte: week } } })])
42
+ : [0, 0];
43
+
44
+ const stats: Stat[] = [
45
+ ...(isAdmin
46
+ ? [
47
+ {
48
+ label: "Users",
49
+ value: people,
50
+ hint: peopleThisWeek > 0 ? `${peopleThisWeek.toLocaleString()} this week` : "none this week",
51
+ icon: UsersIcon,
52
+ href: "/dashboard/users",
53
+ } satisfies Stat,
54
+ ]
55
+ : []),
56
+ ...cards.slice(0, isAdmin ? 3 : 4).map(({ resource, total, recent }) => ({
57
+ label: resource.pluralLabel,
58
+ value: total,
59
+ hint: recent.current > 0 ? `${recent.current.toLocaleString()} this week` : "none this week",
60
+ change: trend(recent.current, recent.previous),
61
+ icon: resourceIcon(resource.icon),
62
+ href: resourcePath(resource),
63
+ })),
64
+ ];
37
65
 
38
66
  const passkeys = await prisma.passkey.count({ where: { userId: user.id } });
39
67
  const twoFactorOn = Boolean((user as { twoFactorEnabled?: boolean | null }).twoFactorEnabled);
@@ -48,31 +76,67 @@ export default async function DashboardPage() {
48
76
  <>
49
77
  <PageHeader
50
78
  title={`Welcome${user.name ? `, ${user.name.split(" ")[0]}` : ""}`}
51
- description={resources.length > 0 ? "Everything in your app, and how it's moving." : "Your app is running. Add a resource to fill this page."}
79
+ description={resources.length > 0 ? "Everything in your app, and how it's moving." : "Your app is running. Add a resource and this page fills in."}
52
80
  />
53
81
 
54
82
  <StatCards stats={stats} />
55
83
 
56
84
  {resources.length === 0 && (
57
- <Empty className="rounded-xl border border-dashed">
58
- <EmptyHeader>
59
- <EmptyTitle>No resources yet</EmptyTitle>
60
- <EmptyDescription>
61
- Generate one with <code>flare gen resource Contact --fields &quot;name:string, email:string&quot;</code>, then fill it with{" "}
62
- <code>flare seed:resource Contact --count 1000</code>.
63
- </EmptyDescription>
64
- </EmptyHeader>
65
- </Empty>
85
+ <Card>
86
+ <CardHeader>
87
+ <CardTitle className="text-base">Add your first resource</CardTitle>
88
+ <CardDescription>
89
+ A resource is a table, its API and its admin screens, generated from one line. Three commands from a terminal in this
90
+ app:
91
+ </CardDescription>
92
+ </CardHeader>
93
+ <CardContent className="flex flex-col">
94
+ {FIRST_RESOURCE_STEPS.map((step, index) => (
95
+ <div key={step.command} className="flex flex-wrap items-baseline gap-x-3 gap-y-1 border-b py-3 last:border-b-0 last:pb-0">
96
+ <span className="grid size-6 shrink-0 translate-y-0.5 place-items-center rounded-full bg-muted text-xs font-medium text-muted-foreground">
97
+ {index + 1}
98
+ </span>
99
+ <code className="rounded bg-muted px-2 py-1 font-mono text-[13px]">{step.command}</code>
100
+ <span className="text-xs text-muted-foreground">{step.note}</span>
101
+ </div>
102
+ ))}
103
+ <p className="mt-4 text-xs text-muted-foreground">
104
+ Then refresh this page: the sidebar, these cards and the admin screens are already wired to it.
105
+ </p>
106
+ </CardContent>
107
+ </Card>
66
108
  )}
67
109
 
68
110
  <div className="grid gap-6 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
69
- {cards.length > 0 && (
111
+ {(cards.length > 0 || isAdmin) && (
70
112
  <Card>
71
113
  <CardHeader>
72
114
  <CardTitle className="text-base">Your data</CardTitle>
73
- <CardDescription>Every resource you can see.</CardDescription>
115
+ <CardDescription>Every table in the app, and who it belongs to.</CardDescription>
74
116
  </CardHeader>
75
117
  <CardContent className="flex flex-col">
118
+ {isAdmin && (
119
+ <div className="flex items-center gap-3 border-b py-3 last:border-b-0 last:pb-0">
120
+ <span className="grid size-9 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground">
121
+ <UsersIcon className="size-4.5" />
122
+ </span>
123
+ <div className="flex min-w-0 flex-col">
124
+ <Link href="/dashboard/users" className="truncate text-sm font-medium hover:underline">
125
+ Users
126
+ </Link>
127
+ <span className="text-xs text-muted-foreground">
128
+ {people.toLocaleString()} {people === 1 ? "account" : "accounts"}
129
+ </span>
130
+ </div>
131
+ <div className="ml-auto flex items-center gap-1">
132
+ <Button variant="ghost" size="icon" aria-label="Open users" asChild>
133
+ <Link href="/dashboard/users">
134
+ <ArrowRightIcon />
135
+ </Link>
136
+ </Button>
137
+ </div>
138
+ </div>
139
+ )}
76
140
  {cards.map(({ resource, total, permissions }) => {
77
141
  const Icon = resourceIcon(resource.icon);
78
142
  return (