umpordez 1.5.0 → 1.6.1
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.
- package/.claude/settings.local.json +20 -1
- package/create.mjs +104 -0
- package/package.json +5 -5
- package/template/.github/copilot-instructions.md +1 -1
- package/template/AGENTS.md +1 -1
- package/template/gitignore +2 -0
- package/template/install.sh +4 -0
- package/template/mobile/{{PROJECT_SLUG}}/README.md +17 -2
- package/template/mobile/{{PROJECT_SLUG}}/package.json +23 -25
- package/template/scripts/clean-appledouble.sh +42 -0
- package/template/scripts/prebuild.sh +8 -1
- package/template/scripts/run-ios.sh +5 -0
- package/template/server/apps/api/app.ts +1 -1
- package/template/server/apps/api/routes/account.ts +32 -10
- package/template/server/apps/api/routes/admin.ts +13 -6
- package/template/server/apps/api/routes/auth.ts +109 -29
- package/template/server/apps/api/routes/files.ts +14 -5
- package/template/server/apps/api/routes/health.ts +2 -2
- package/template/server/apps/api/routes/user.ts +2 -1
- package/template/server/apps/shared/middlewares/request-logger.ts +3 -1
- package/template/server/apps/shared/utils.ts +2 -2
- package/template/server/apps/site-api/app.ts +1 -1
- package/template/server/console/index.ts +1 -1
- package/template/server/console/tasks/seed-admin.ts +8 -2
- package/template/server/core/context.ts +2 -2
- package/template/server/core/email.ts +9 -2
- package/template/server/core/knexfile.ts +1 -1
- package/template/server/core/models/account.ts +43 -13
- package/template/server/core/models/auth.ts +10 -3
- package/template/server/core/models/user.ts +61 -27
- package/template/server/core/s3.ts +29 -14
- package/template/server/eslint.config.js +9 -1
- package/template/server/migrations/20260208000000_initial-schema.ts +4 -2
- package/template/server/package.json +27 -28
- package/template/server/types/api.ts +3 -0
- package/template/ui/admin/CLAUDE.md +5 -4
- package/template/ui/admin/eslint.config.js +29 -1
- package/template/ui/admin/package.json +31 -39
- package/template/ui/admin/src/components/nav-user.tsx +4 -2
- package/template/ui/admin/src/components/theme-provider.tsx +7 -1
- package/template/ui/admin/src/components/ui/badge.tsx +1 -1
- package/template/ui/admin/src/components/ui/button.tsx +1 -1
- package/template/ui/admin/src/components/ui/card.tsx +1 -1
- package/template/ui/admin/src/components/ui/checkbox.tsx +1 -1
- package/template/ui/admin/src/components/ui/date-picker.tsx +3 -3
- package/template/ui/admin/src/components/ui/dialog.tsx +1 -1
- package/template/ui/admin/src/components/ui/dropdown-menu.tsx +6 -6
- package/template/ui/admin/src/components/ui/input.tsx +1 -1
- package/template/ui/admin/src/components/ui/popover.tsx +1 -1
- package/template/ui/admin/src/components/ui/select.tsx +4 -4
- package/template/ui/admin/src/components/ui/separator.tsx +1 -1
- package/template/ui/admin/src/components/ui/sheet.tsx +1 -1
- package/template/ui/admin/src/components/ui/sidebar.tsx +24 -24
- package/template/ui/admin/src/components/ui/sonner.tsx +4 -4
- package/template/ui/admin/src/components/ui/switch.tsx +1 -1
- package/template/ui/admin/src/components/ui/table.tsx +3 -3
- package/template/ui/admin/src/components/ui/tabs.tsx +2 -2
- package/template/ui/admin/src/components/ui/textarea.tsx +1 -1
- package/template/ui/admin/src/hooks/queries/use-accounts.ts +29 -9
- package/template/ui/admin/src/hooks/queries/use-auth.ts +20 -6
- package/template/ui/admin/src/hooks/queries/use-dashboard.ts +13 -5
- package/template/ui/admin/src/hooks/queries/use-members.ts +32 -10
- package/template/ui/admin/src/hooks/queries/use-users.ts +32 -10
- package/template/ui/admin/src/hooks/use-confirm.tsx +1 -1
- package/template/ui/admin/src/hooks/use-user.tsx +4 -1
- package/template/ui/admin/src/index.css +68 -6
- package/template/ui/admin/src/layouts/account.tsx +19 -6
- package/template/ui/admin/src/layouts/user.tsx +3 -1
- package/template/ui/admin/src/lib/fetch-api.ts +38 -14
- package/template/ui/admin/src/lib/query-keys.ts +2 -1
- package/template/ui/admin/src/pages/account/dashboard.tsx +10 -3
- package/template/ui/admin/src/pages/account/members.tsx +22 -12
- package/template/ui/admin/src/pages/account/settings.tsx +22 -11
- package/template/ui/admin/src/pages/admin/accounts.tsx +2 -3
- package/template/ui/admin/src/pages/admin/dashboard.tsx +10 -3
- package/template/ui/admin/src/pages/admin/users.tsx +19 -10
- package/template/ui/admin/src/pages/public/error.tsx +4 -1
- package/template/ui/admin/src/pages/public/forgot-password.tsx +2 -2
- package/template/ui/admin/src/pages/public/login.tsx +13 -3
- package/template/ui/admin/src/pages/public/reset-password.tsx +5 -3
- package/template/ui/admin/src/pages/public/signup.tsx +11 -3
- package/template/ui/admin/src/pages/user/profile.tsx +16 -11
- package/template/ui/admin/src/pages/user/select-account.tsx +3 -1
- package/template/ui/admin/src/vite-env.d.ts +1 -0
- package/template/ui/admin/tsconfig.json +0 -1
- package/template/ui/admin/vite.config.ts +2 -1
- package/template/ui/site/CLAUDE.md +1 -2
- package/template/ui/site/_variants/app/home.ejs +6 -6
- package/template/ui/site/app.ts +1 -1
- package/template/ui/site/eslint.config.js +9 -1
- package/template/ui/site/i18n/index.ts +0 -1
- package/template/ui/site/package.json +17 -17
- package/template/ui/site/styles/input.css +11 -3
- package/template/ui/site/views/pages/home.ejs +4 -4
- package/template/ui/site/views/partials/header.ejs +1 -1
- package/template-variables.mjs +1 -1
- package/template/ui/admin/postcss.config.js +0 -6
- package/template/ui/admin/tailwind.config.ts +0 -71
- package/template/ui/site/tailwind.config.js +0 -16
- /package/template/mobile/{{PROJECT_SLUG}}/{eslint.config.js → eslint.config.mjs} +0 -0
|
@@ -57,7 +57,26 @@
|
|
|
57
57
|
"Bash(git *)",
|
|
58
58
|
"Bash(npm init *)",
|
|
59
59
|
"Bash(awk 'NR>=383 && NR<=386 {print NR\": \"substr\\($0,1,120\\)}' '/Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/AuthLanding.tsx')",
|
|
60
|
-
"Bash(awk 'NR>=394 && NR<=410 {print NR\": \"substr\\($0,1,100\\)}' '/Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/AuthLanding.tsx')"
|
|
60
|
+
"Bash(awk 'NR>=394 && NR<=410 {print NR\": \"substr\\($0,1,100\\)}' '/Users/ligeiro/dev/umpordez-cli/template/mobile/{{PROJECT_SLUG}}/src/pages/auth/AuthLanding.tsx')",
|
|
61
|
+
"Bash(diskutil info *)",
|
|
62
|
+
"Bash(mount)",
|
|
63
|
+
"Bash(bash -n template/scripts/clean-appledouble.sh)",
|
|
64
|
+
"Bash(bash -n template/scripts/prebuild.sh)",
|
|
65
|
+
"Bash(bash -n template/scripts/run-ios.sh)",
|
|
66
|
+
"Bash(bash -n template/install.sh)",
|
|
67
|
+
"WebSearch",
|
|
68
|
+
"Bash(tar -xzf expo-57.0.7.tgz package/bundledNativeModules.json)",
|
|
69
|
+
"Bash(npm -v)",
|
|
70
|
+
"Read(//dev/**)",
|
|
71
|
+
"Bash(npm ls *)",
|
|
72
|
+
"Bash(LC_ALL=C sed -i '' -e 's/dotenv\\\\.config\\(\\)/dotenv.config\\({ quiet: true }\\)/' apps/api/app.ts apps/site-api/app.ts console/index.ts)",
|
|
73
|
+
"Bash(LC_ALL=C sed -i '' -e \"s/dotenv\\\\.config\\({ path: path.resolve\\(dirname, '.env'\\) }\\)/dotenv.config\\({ path: path.resolve\\(dirname, '.env'\\), quiet: true }\\)/\" core/knexfile.ts)",
|
|
74
|
+
"Bash(npx eslint *)",
|
|
75
|
+
"Bash(ln -sfn /private/tmp/claude-501/-Users-ligeiro-dev-umpordez-cli/12177a90-5ebe-47b5-977f-9bb132141080/scratchpad/acme/server/node_modules /Users/ligeiro/dev/umpordez-cli/template/server/node_modules)",
|
|
76
|
+
"Bash(rm -f tailwind.config.ts postcss.config.js)",
|
|
77
|
+
"Bash(cp ../../server/eslint.config.js eslint.config.js)",
|
|
78
|
+
"Bash(ln -sfn /private/tmp/claude-501/-Users-ligeiro-dev-umpordez-cli/12177a90-5ebe-47b5-977f-9bb132141080/scratchpad/acme/ui/admin/node_modules /Users/ligeiro/dev/umpordez-cli/template/ui/admin/node_modules)",
|
|
79
|
+
"Bash(awk '{print $2}')"
|
|
61
80
|
]
|
|
62
81
|
}
|
|
63
82
|
}
|
package/create.mjs
CHANGED
|
@@ -394,6 +394,104 @@ function validatePort(v) {
|
|
|
394
394
|
: 'Must be a valid port number';
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
// Filesystems that can't store extended attributes or symlinks. On
|
|
398
|
+
// macOS these make the OS scatter AppleDouble `._*` sidecar files next
|
|
399
|
+
// to everything npm installs — CocoaPods then fails parsing a
|
|
400
|
+
// `._Foo.podspec` during `expo prebuild`, and node_modules symlinks
|
|
401
|
+
// break. Generating an Expo project on one of these is a trap, so we
|
|
402
|
+
// warn before doing it.
|
|
403
|
+
const NON_NATIVE_FS = new Set([
|
|
404
|
+
'exfat', 'msdos', 'fat32', 'vfat',
|
|
405
|
+
'ntfs', 'ntfs-3g', 'fuseblk',
|
|
406
|
+
'smbfs', 'cifs', 'nfs', 'afpfs', 'webdav',
|
|
407
|
+
]);
|
|
408
|
+
|
|
409
|
+
// Resolve the filesystem type backing `targetPath`. The output dir
|
|
410
|
+
// usually doesn't exist yet, so we walk up to the nearest existing
|
|
411
|
+
// ancestor, then match it against the longest mount point in `mount`.
|
|
412
|
+
// Returns { mountPoint, fsType } or null (non-darwin, or undetectable).
|
|
413
|
+
function volumeInfo(targetPath) {
|
|
414
|
+
if (process.platform !== 'darwin') {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let dir = path.resolve(targetPath);
|
|
419
|
+
while (dir !== path.dirname(dir) && !fs.existsSync(dir)) {
|
|
420
|
+
dir = path.dirname(dir);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let mounts;
|
|
424
|
+
try {
|
|
425
|
+
mounts = execSync('mount', { encoding: 'utf-8' });
|
|
426
|
+
} catch {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Lines look like: `/dev/disk10s1 on /Volumes/big (exfat, local, …)`
|
|
431
|
+
let best = null;
|
|
432
|
+
for (const line of mounts.split('\n')) {
|
|
433
|
+
const m = line.match(/ on (.+?) \(([^,)]+)/);
|
|
434
|
+
if (!m) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const mountPoint = m[1];
|
|
438
|
+
const fsType = m[2].trim().toLowerCase();
|
|
439
|
+
const prefix = mountPoint.endsWith('/') ? mountPoint : mountPoint + '/';
|
|
440
|
+
if (dir === mountPoint || dir.startsWith(prefix)) {
|
|
441
|
+
if (!best || mountPoint.length > best.mountPoint.length) {
|
|
442
|
+
best = { mountPoint, fsType };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return best;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Warn (and confirm) before generating a mobile project onto a
|
|
450
|
+
// filesystem macOS can't build native code on. Returns true to
|
|
451
|
+
// proceed, false to abort. Only meaningful when a mobile app is part
|
|
452
|
+
// of the project — the acute failure is CocoaPods + `expo prebuild`.
|
|
453
|
+
async function confirmFilesystem(outputDir, components) {
|
|
454
|
+
if (!components.mobile) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
const vol = volumeInfo(outputDir);
|
|
458
|
+
if (!vol || !NON_NATIVE_FS.has(vol.fsType)) {
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log('');
|
|
463
|
+
console.log(red.bold(
|
|
464
|
+
` ⚠ ${vol.mountPoint} is a ${vol.fsType} volume.`
|
|
465
|
+
));
|
|
466
|
+
console.log(dim(
|
|
467
|
+
' macOS can\'t store extended attributes or symlinks there,'
|
|
468
|
+
));
|
|
469
|
+
console.log(dim(
|
|
470
|
+
' so it scatters AppleDouble `._*` files next to everything'
|
|
471
|
+
));
|
|
472
|
+
console.log(dim(
|
|
473
|
+
' npm installs. `expo prebuild` then fails when CocoaPods'
|
|
474
|
+
));
|
|
475
|
+
console.log(dim(
|
|
476
|
+
' tries to parse a `._Foo.podspec`, and node_modules'
|
|
477
|
+
));
|
|
478
|
+
console.log(dim(' symlinks break.'));
|
|
479
|
+
console.log('');
|
|
480
|
+
console.log(dim(
|
|
481
|
+
' Recommended: generate on your internal drive (e.g. ~/dev),'
|
|
482
|
+
));
|
|
483
|
+
console.log(dim(' an APFS / Mac OS Extended volume.'));
|
|
484
|
+
console.log('');
|
|
485
|
+
|
|
486
|
+
const { proceed } = await inquirer.prompt([{
|
|
487
|
+
type: 'confirm',
|
|
488
|
+
name: 'proceed',
|
|
489
|
+
message: `Generate on the ${vol.fsType} volume anyway?`,
|
|
490
|
+
default: false,
|
|
491
|
+
}]);
|
|
492
|
+
return proceed;
|
|
493
|
+
}
|
|
494
|
+
|
|
397
495
|
function componentsLabel(c) {
|
|
398
496
|
const parts = [];
|
|
399
497
|
if (c.server) parts.push('server API');
|
|
@@ -604,6 +702,11 @@ export async function createProject() {
|
|
|
604
702
|
|
|
605
703
|
const outputDir = path.resolve(details.outputDir);
|
|
606
704
|
|
|
705
|
+
if (!await confirmFilesystem(outputDir, details.components)) {
|
|
706
|
+
console.log(chalk.yellow('Aborted. No files were created.'));
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
607
710
|
if (fs.existsSync(outputDir)) {
|
|
608
711
|
const { overwrite } = await inquirer.prompt([{
|
|
609
712
|
type: 'confirm',
|
|
@@ -710,6 +813,7 @@ export async function createProject() {
|
|
|
710
813
|
if (details.components.mobile) {
|
|
711
814
|
scripts.push(
|
|
712
815
|
'scripts/bump-version.sh',
|
|
816
|
+
'scripts/clean-appledouble.sh',
|
|
713
817
|
'scripts/prebuild.sh',
|
|
714
818
|
'scripts/run-ios.sh',
|
|
715
819
|
'scripts/release-android.sh',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "umpordez",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"description": "SaaS starter kit generator — server, admin SPA, public site (i18n), mobile app (offline-first, IAP, native push)",
|
|
6
6
|
"main": "cli.mjs",
|
|
7
7
|
"scripts": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/umpordez/cli#readme",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"chalk": "^5.
|
|
28
|
-
"figlet": "^1.
|
|
29
|
-
"inquirer": "^
|
|
30
|
-
"sharp": "^0.
|
|
27
|
+
"chalk": "^5.6.2",
|
|
28
|
+
"figlet": "^1.11.2",
|
|
29
|
+
"inquirer": "^14.0.2",
|
|
30
|
+
"sharp": "^0.35.3"
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -9,7 +9,7 @@ Multi-tenant SaaS application built with the **umpordez** architecture pattern f
|
|
|
9
9
|
## Tech Stack
|
|
10
10
|
|
|
11
11
|
- **Backend:** TypeScript, Express.js, PostgreSQL, Knex.js, Zod
|
|
12
|
-
- **Admin UI:** React
|
|
12
|
+
- **Admin UI:** React 19, Vite 8, TailwindCSS v4, shadcn/ui (Radix), React Router DOM 7, TanStack Query v5
|
|
13
13
|
- **Site UI:** Express + EJS + TailwindCSS
|
|
14
14
|
- **Auth:** httpOnly JWT cookies (30-day expiry)
|
|
15
15
|
- **Uploads:** AWS S3 via multer-s3 with signed URLs
|
package/template/AGENTS.md
CHANGED
|
@@ -59,7 +59,7 @@ ui/site/ Public marketing site (Express + EJS, port 3000)
|
|
|
59
59
|
## Tech Stack
|
|
60
60
|
|
|
61
61
|
- **Backend:** TypeScript, Express.js, PostgreSQL, Knex.js, Zod
|
|
62
|
-
- **Admin UI:** React
|
|
62
|
+
- **Admin UI:** React 19, Vite 8, TailwindCSS v4, shadcn/ui (Radix), React Router DOM 7, React Query (TanStack Query v5)
|
|
63
63
|
- **Site UI:** Express + EJS + TailwindCSS
|
|
64
64
|
- **Auth:** httpOnly JWT cookies (30-day expiry)
|
|
65
65
|
- **Uploads:** AWS S3 via multer-s3 with signed URLs
|
package/template/gitignore
CHANGED
package/template/install.sh
CHANGED
|
@@ -55,6 +55,10 @@ if [ -d "mobile/{{PROJECT_SLUG}}" ]; then
|
|
|
55
55
|
|
|
56
56
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
57
57
|
if [ -d "mobile/{{PROJECT_SLUG}}/ios" ]; then
|
|
58
|
+
# Strip AppleDouble `._*` files (exFAT/NTFS/network
|
|
59
|
+
# volumes) so pod install doesn't choke on a
|
|
60
|
+
# `._Foo.podspec`. No-op on APFS.
|
|
61
|
+
./scripts/clean-appledouble.sh "mobile/{{PROJECT_SLUG}}"
|
|
58
62
|
echo "Installing iOS pods..."
|
|
59
63
|
(cd "mobile/{{PROJECT_SLUG}}/ios" && pod install)
|
|
60
64
|
fi
|
|
@@ -6,11 +6,26 @@ typed fetch client pointed at the project's admin API.
|
|
|
6
6
|
|
|
7
7
|
## Quick start
|
|
8
8
|
|
|
9
|
+
**This is a development-build app, not an Expo Go app.** It ships
|
|
10
|
+
native modules (Apple Sign-In, `expo-build-properties`, and optionally
|
|
11
|
+
`expo-iap`) and rides the newest Expo SDK, so the public Expo Go from
|
|
12
|
+
the App Store either won't run it (`"requires a newer version of Expo
|
|
13
|
+
Go"` right after an SDK release) or crashes on a native import. Use a
|
|
14
|
+
development build instead — `expo-dev-client` is already a dependency:
|
|
15
|
+
|
|
9
16
|
```bash
|
|
10
17
|
npm install
|
|
11
|
-
npm run
|
|
12
|
-
npm run ios # build + run on iOS (requires Xcode)
|
|
18
|
+
npm run ios # build + run a dev client on iOS (requires Xcode)
|
|
13
19
|
npm run android # build + run on Android (requires Android Studio)
|
|
20
|
+
npm run start # Metro — connect from an installed dev build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Need a device build without Xcode/Android Studio, or want the Expo Go
|
|
24
|
+
experience on the current SDK? Use EAS-hosted Expo Go, which always
|
|
25
|
+
matches the SDK you target (unlike the store build):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx eas go # installs a matching Expo Go for this SDK
|
|
14
29
|
```
|
|
15
30
|
|
|
16
31
|
For a clean iOS run that picks a connected device automatically:
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"ios": "expo run:ios",
|
|
10
10
|
"web": "expo start --web",
|
|
11
11
|
"prebuild": "bash ../../scripts/prebuild.sh",
|
|
12
|
-
"lint": "eslint src
|
|
12
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
13
13
|
"typecheck": "tsc --noEmit"
|
|
14
14
|
},
|
|
15
15
|
"expo": {
|
|
@@ -20,41 +20,39 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@noble/hashes": "^2.2.0",
|
|
22
22
|
"@react-native-async-storage/async-storage": "2.2.0",
|
|
23
|
-
"@react-navigation/bottom-tabs": "^7.
|
|
24
|
-
"@react-navigation/native": "^7.
|
|
25
|
-
"@react-navigation/native-stack": "^7.
|
|
26
|
-
"expo": "~57.0.
|
|
27
|
-
"expo-apple-authentication": "~57.0.
|
|
28
|
-
"expo-
|
|
29
|
-
"expo-
|
|
30
|
-
"expo-
|
|
31
|
-
"expo-file-system": "~57.0.
|
|
32
|
-
"expo-
|
|
33
|
-
"expo-
|
|
34
|
-
"expo-
|
|
35
|
-
"expo-
|
|
36
|
-
"expo-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"i18next": "^23.16.5",
|
|
40
|
-
"lucide-react-native": "^1.24.0",
|
|
23
|
+
"@react-navigation/bottom-tabs": "^7.18.11",
|
|
24
|
+
"@react-navigation/native": "^7.3.11",
|
|
25
|
+
"@react-navigation/native-stack": "^7.18.3",
|
|
26
|
+
"expo": "~57.0.7",
|
|
27
|
+
"expo-apple-authentication": "~57.0.1",
|
|
28
|
+
"expo-build-properties": "~57.0.6",
|
|
29
|
+
"expo-constants": "~57.0.6",
|
|
30
|
+
"expo-dev-client": "~57.0.7",
|
|
31
|
+
"expo-file-system": "~57.0.1",{{IAP_DEP_JSON}}
|
|
32
|
+
"expo-localization": "~57.0.1",
|
|
33
|
+
"expo-splash-screen": "~57.0.4",
|
|
34
|
+
"expo-sqlite": "~57.0.1",
|
|
35
|
+
"expo-system-ui": "~57.0.1",
|
|
36
|
+
"expo-web-browser": "~57.0.1",
|
|
37
|
+
"i18next": "^23.16.8",
|
|
38
|
+
"lucide-react-native": "^1.25.0",
|
|
41
39
|
"react": "19.2.3",
|
|
42
40
|
"react-native": "0.86.0",
|
|
43
|
-
"react-i18next": "^15.
|
|
41
|
+
"react-i18next": "^15.7.4",
|
|
44
42
|
"react-native-gesture-handler": "~2.32.0",
|
|
45
43
|
"react-native-reanimated": "4.5.0",
|
|
46
44
|
"react-native-safe-area-context": "~5.7.0",
|
|
47
45
|
"react-native-screens": "4.25.2",
|
|
48
46
|
"react-native-svg": "15.15.4",
|
|
49
|
-
"react-native-toast-message": "^2.
|
|
47
|
+
"react-native-toast-message": "^2.4.0",
|
|
50
48
|
"react-native-worklets": "0.10.0"
|
|
51
49
|
},
|
|
52
50
|
"devDependencies": {
|
|
53
|
-
"@stylistic/eslint-plugin": "^
|
|
51
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
54
52
|
"@types/react": "~19.2.2",
|
|
55
|
-
"babel-preset-expo": "~57.0.
|
|
56
|
-
"eslint": "^
|
|
53
|
+
"babel-preset-expo": "~57.0.3",
|
|
54
|
+
"eslint": "^10.7.0",
|
|
57
55
|
"typescript": "~5.9.3",
|
|
58
|
-
"typescript-eslint": "^8.
|
|
56
|
+
"typescript-eslint": "^8.64.0"
|
|
59
57
|
}
|
|
60
58
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# clean-appledouble — remove macOS AppleDouble `._*` sidecar files.
|
|
4
|
+
#
|
|
5
|
+
# macOS drops a `._<name>` metadata file next to every file that carries
|
|
6
|
+
# extended attributes when it lives on a filesystem that can't store
|
|
7
|
+
# xattrs natively (exFAT, MS-DOS/FAT, NTFS, SMB/NFS network mounts).
|
|
8
|
+
# CocoaPods then scans for `*.podspec`, finds `._Foo.podspec`, tries to
|
|
9
|
+
# parse the binary metadata as Ruby, and `pod install` — and therefore
|
|
10
|
+
# `expo prebuild` / `expo run:ios` — dies with:
|
|
11
|
+
#
|
|
12
|
+
# Invalid `Podfile` file: Invalid podspec file at path
|
|
13
|
+
# .../node_modules/.../ios/._Foo.podspec
|
|
14
|
+
#
|
|
15
|
+
# On APFS / Mac OS Extended there are none, so this is a fast no-op. Run
|
|
16
|
+
# it before any pod install / prebuild / native build.
|
|
17
|
+
#
|
|
18
|
+
# The real fix is to keep the project on an APFS / Mac OS Extended disk
|
|
19
|
+
# (e.g. ~/dev) — exFAT/NTFS regenerate these files on every npm install.
|
|
20
|
+
#
|
|
21
|
+
# Usage: ./scripts/clean-appledouble.sh [dir] (default: repo root)
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
# AppleDouble files are a macOS-only artifact.
|
|
26
|
+
[[ "$(uname)" == "Darwin" ]] || exit 0
|
|
27
|
+
|
|
28
|
+
TARGET="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
29
|
+
[[ -d "$TARGET" ]] || exit 0
|
|
30
|
+
|
|
31
|
+
n=$(
|
|
32
|
+
find "$TARGET" -name '._*' -type f -print -delete 2>/dev/null \
|
|
33
|
+
| wc -l | tr -d ' '
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if [[ "${n:-0}" -gt 0 ]]; then
|
|
37
|
+
echo "==> Removed $n AppleDouble ._* file(s) under $TARGET"
|
|
38
|
+
echo " This disk can't store macOS extended attributes (likely"
|
|
39
|
+
echo " exFAT/NTFS/network), so they'll return on the next npm"
|
|
40
|
+
echo " install. For clean native builds, move the project to an"
|
|
41
|
+
echo " APFS / Mac OS Extended disk (e.g. ~/dev)."
|
|
42
|
+
fi
|
|
@@ -24,5 +24,12 @@ for arg in "$@"; do
|
|
|
24
24
|
esac
|
|
25
25
|
done
|
|
26
26
|
|
|
27
|
-
cd "$(dirname "$0")
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
28
|
+
cd "$SCRIPT_DIR/../mobile/{{PROJECT_SLUG}}"
|
|
29
|
+
|
|
30
|
+
# Strip macOS AppleDouble `._*` files before prebuild — CocoaPods
|
|
31
|
+
# chokes on `._Foo.podspec` when the project lives on exFAT/NTFS/network
|
|
32
|
+
# volumes. No-op on APFS. See scripts/clean-appledouble.sh.
|
|
33
|
+
"$SCRIPT_DIR/clean-appledouble.sh" "$PWD"
|
|
34
|
+
|
|
28
35
|
exec npx expo prebuild "$@"
|
|
@@ -53,6 +53,11 @@ done
|
|
|
53
53
|
|
|
54
54
|
cd "$APP_DIR"
|
|
55
55
|
|
|
56
|
+
# Strip macOS AppleDouble `._*` files so the pod install triggered by
|
|
57
|
+
# `expo run:ios` doesn't choke on `._Foo.podspec` (exFAT/NTFS/network
|
|
58
|
+
# volumes). No-op on APFS. See scripts/clean-appledouble.sh.
|
|
59
|
+
"$SCRIPT_DIR/clean-appledouble.sh" "$APP_DIR"
|
|
60
|
+
|
|
56
61
|
if [[ $CLEAN -eq 1 ]]; then
|
|
57
62
|
echo "==> Cleaning iOS build artifacts..."
|
|
58
63
|
rm -rf ios/build
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import type { Express, Request, Response } from 'express';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import trySetUserByTokenMiddleware
|
|
4
|
+
import trySetUserByTokenMiddleware
|
|
5
|
+
from '#shared/middlewares/try-set-user-by-token.js';
|
|
5
6
|
import demandUserMiddleware from '#shared/middlewares/demand-user.js';
|
|
6
|
-
import demandAccountAccessMiddleware
|
|
7
|
+
import demandAccountAccessMiddleware
|
|
8
|
+
from '#shared/middlewares/demand-account-access.js';
|
|
7
9
|
import { buildHandler } from '#shared/utils.js';
|
|
8
10
|
|
|
9
11
|
const router = express.Router();
|
|
10
12
|
|
|
11
13
|
const updateSettingsSchema = z.object({
|
|
12
14
|
name: z.string().min(1, 'Nome é obrigatório'),
|
|
13
|
-
settings: z.record(z.unknown()).optional()
|
|
15
|
+
settings: z.record(z.string(), z.unknown()).optional()
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
const addMemberSchema = z.object({
|
|
17
|
-
email: z.
|
|
19
|
+
email: z.email('Email inválido'),
|
|
18
20
|
role: z.enum(['owner', 'manager', 'user']).default('user'),
|
|
19
21
|
name: z.string().min(1).optional(),
|
|
20
22
|
password: z.string().min(6).optional()
|
|
@@ -41,14 +43,21 @@ async function handleGetSettings(req: Request, res: Response): Promise<void> {
|
|
|
41
43
|
res.json({ ok: true, account });
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
async function handleUpdateSettings(
|
|
46
|
+
async function handleUpdateSettings(
|
|
47
|
+
req: Request,
|
|
48
|
+
res: Response
|
|
49
|
+
): Promise<void> {
|
|
45
50
|
const data = updateSettingsSchema.parse(req.body);
|
|
46
51
|
const account = await req.context.account.update(req.accountId!, data);
|
|
47
52
|
res.json({ ok: true, account });
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
async function handleDeleteAccount(req: Request, res: Response): Promise<void> {
|
|
51
|
-
await req.context.account.delete(
|
|
56
|
+
await req.context.account.delete(
|
|
57
|
+
req.accountId!,
|
|
58
|
+
req.user!.id,
|
|
59
|
+
req.accountRole!
|
|
60
|
+
);
|
|
52
61
|
res.json({ ok: true });
|
|
53
62
|
}
|
|
54
63
|
|
|
@@ -57,7 +66,10 @@ async function handleListMembers(req: Request, res: Response): Promise<void> {
|
|
|
57
66
|
const limit = parseInt(req.query.limit as string) || 20;
|
|
58
67
|
const search = (req.query.search as string) || '';
|
|
59
68
|
|
|
60
|
-
const result = await req.context.account.listMembers(
|
|
69
|
+
const result = await req.context.account.listMembers(
|
|
70
|
+
req.accountId!,
|
|
71
|
+
{ page, limit, search }
|
|
72
|
+
);
|
|
61
73
|
res.json({ ok: true, ...result });
|
|
62
74
|
}
|
|
63
75
|
|
|
@@ -67,14 +79,24 @@ async function handleAddMember(req: Request, res: Response): Promise<void> {
|
|
|
67
79
|
res.json({ ok: true, member });
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
async function handleUpdateMemberRole(
|
|
82
|
+
async function handleUpdateMemberRole(
|
|
83
|
+
req: Request,
|
|
84
|
+
res: Response
|
|
85
|
+
): Promise<void> {
|
|
71
86
|
const { role } = updateMemberRoleSchema.parse(req.body);
|
|
72
|
-
await req.context.account.updateMemberRole(
|
|
87
|
+
await req.context.account.updateMemberRole(
|
|
88
|
+
req.accountId!,
|
|
89
|
+
req.params.userId as string,
|
|
90
|
+
role
|
|
91
|
+
);
|
|
73
92
|
res.json({ ok: true });
|
|
74
93
|
}
|
|
75
94
|
|
|
76
95
|
async function handleRemoveMember(req: Request, res: Response): Promise<void> {
|
|
77
|
-
await req.context.account.removeMember(
|
|
96
|
+
await req.context.account.removeMember(
|
|
97
|
+
req.accountId!,
|
|
98
|
+
req.params.userId as string
|
|
99
|
+
);
|
|
78
100
|
res.json({ ok: true });
|
|
79
101
|
}
|
|
80
102
|
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import type { Express, Request, Response } from 'express';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import trySetUserByTokenMiddleware
|
|
4
|
+
import trySetUserByTokenMiddleware
|
|
5
|
+
from '#shared/middlewares/try-set-user-by-token.js';
|
|
5
6
|
import demandUserMiddleware from '#shared/middlewares/demand-user.js';
|
|
6
|
-
import demandAdminUserMiddleware
|
|
7
|
+
import demandAdminUserMiddleware
|
|
8
|
+
from '#shared/middlewares/demand-admin-user.js';
|
|
7
9
|
import { buildHandler } from '#shared/utils.js';
|
|
8
10
|
|
|
9
11
|
const router = express.Router();
|
|
10
12
|
|
|
11
13
|
const createUserSchema = z.object({
|
|
12
14
|
name: z.string().min(1, 'Nome é obrigatório'),
|
|
13
|
-
email: z.
|
|
15
|
+
email: z.email('Email inválido'),
|
|
14
16
|
password: z.string().min(6, 'Senha deve ter no mínimo 6 caracteres'),
|
|
15
17
|
role: z.enum(['admin', 'user']).default('user')
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
const updateUserSchema = z.object({
|
|
19
21
|
name: z.string().min(1, 'Nome é obrigatório'),
|
|
20
|
-
email: z.
|
|
22
|
+
email: z.email('Email inválido'),
|
|
21
23
|
role: z.enum(['admin', 'user']).optional(),
|
|
22
|
-
password: z.string()
|
|
24
|
+
password: z.string()
|
|
25
|
+
.min(6, 'Senha deve ter no mínimo 6 caracteres')
|
|
26
|
+
.optional()
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
async function handleGetDashboard(req: Request, res: Response): Promise<void> {
|
|
@@ -53,7 +57,10 @@ async function handleCreateUser(req: Request, res: Response): Promise<void> {
|
|
|
53
57
|
|
|
54
58
|
async function handleUpdateUser(req: Request, res: Response): Promise<void> {
|
|
55
59
|
const data = updateUserSchema.parse(req.body);
|
|
56
|
-
const user = await req.context.user.adminUpdate(
|
|
60
|
+
const user = await req.context.user.adminUpdate(
|
|
61
|
+
req.params.id as string,
|
|
62
|
+
data
|
|
63
|
+
);
|
|
57
64
|
res.json({ ok: true, user });
|
|
58
65
|
}
|
|
59
66
|
|