umpordez 1.2.1 → 1.3.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 +3 -1
- package/README.md +28 -1
- package/cli.mjs +4 -2
- package/create.mjs +72 -6
- package/package.json +1 -1
- package/template/CLAUDE.md +1 -1
- package/template/misc/build-local.sh +82 -0
- package/template/mobile/{{PROJECT_SLUG}}/App.tsx +5 -2
- package/template/mobile/{{PROJECT_SLUG}}/CHANGELOG.md +28 -0
- package/template/mobile/{{PROJECT_SLUG}}/CLAUDE.md +10 -1
- package/template/mobile/{{PROJECT_SLUG}}/docs/releases.md +78 -0
- package/template/mobile/{{PROJECT_SLUG}}/docs/screenshots.md +84 -0
- package/template/mobile/{{PROJECT_SLUG}}/docs/store-copy.md +122 -0
- package/template/mobile/{{PROJECT_SLUG}}/docs/store-listing.md +107 -0
- package/template/mobile/{{PROJECT_SLUG}}/src/config.ts +6 -0
- package/template/scripts/README.md +18 -1
- package/template/scripts/appstore-resize.sh +70 -0
- package/template/scripts/frame-shots.mjs +246 -0
- package/template/scripts/gen-mockups.mjs +120 -0
- package/template/scripts/screenshots-capture.sh +50 -0
- package/template/scripts/screenshots.flow.yaml +54 -0
- package/template/ui/site/_variants/app/en.ts +131 -0
- package/template/ui/site/_variants/app/footer.ejs +46 -0
- package/template/ui/site/_variants/app/header.ejs +102 -0
- package/template/ui/site/_variants/app/home.ejs +237 -0
- package/template/ui/site/_variants/app/not-found.ejs +22 -0
- package/template/ui/site/_variants/app/pt-BR.ts +203 -0
- package/template/ui/site/tailwind.config.js +9 -1
- package/template-variables.mjs +34 -0
- package/update.mjs +30 -1
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
"Bash(md5 /tmp/umptest/everything/mobile/testapp/assets/icon.png /Users/ligeiro/dev/tranqs/app/mobile/tranqs/assets/icon.png)",
|
|
47
47
|
"Bash(git merge *)",
|
|
48
48
|
"Bash(git tag *)",
|
|
49
|
-
"Bash(npm pack *)"
|
|
49
|
+
"Bash(npm pack *)",
|
|
50
|
+
"Read(//Users/ligeiro/dev/dropa/**)",
|
|
51
|
+
"Read(//Users/ligeiro/dev/growerheleper-v2/app/**)"
|
|
50
52
|
]
|
|
51
53
|
}
|
|
52
54
|
}
|
package/README.md
CHANGED
|
@@ -98,11 +98,19 @@ It asks **what to generate** first:
|
|
|
98
98
|
|---|---|
|
|
99
99
|
| everything | server + admin UI + site + mobile |
|
|
100
100
|
| web-only | server + admin UI + site (no mobile) |
|
|
101
|
+
| mobile-and-site | mobile app + app-store landing page (no server, no admin) |
|
|
101
102
|
| mobile-and-server | server + mobile (no UI) |
|
|
102
103
|
| just-mobile | mobile only (assumes external API) |
|
|
103
104
|
|
|
105
|
+
The **public site** ships two landing flavors and the CLI picks by
|
|
106
|
+
preset: a **SaaS landing** (CTAs → admin signup) for server/admin
|
|
107
|
+
presets, and an **app-store landing** (App Store / Play buttons,
|
|
108
|
+
pricing, download) for `mobile-and-site`, branded from your primary
|
|
109
|
+
color.
|
|
110
|
+
|
|
104
111
|
Then a few questions (name, domain, ports, primary color, mobile
|
|
105
|
-
bundle ID
|
|
112
|
+
bundle ID + **splash style** — instant show-up or animated — if
|
|
113
|
+
applicable). After scaffold:
|
|
106
114
|
|
|
107
115
|
```bash
|
|
108
116
|
cd my-project
|
|
@@ -212,6 +220,25 @@ umpordez --version check version
|
|
|
212
220
|
- **fetch-api** now supports PUT and PATCH methods plus safer
|
|
213
221
|
URL parameter filtering (skips null/undefined values).
|
|
214
222
|
|
|
223
|
+
## What's new in 1.3
|
|
224
|
+
|
|
225
|
+
Ship a mobile app + its marketing site in one shot:
|
|
226
|
+
|
|
227
|
+
- **`mobile-and-site` preset** — mobile app + app-store landing page,
|
|
228
|
+
no server or admin. The "indie mobile app" shape.
|
|
229
|
+
- **App-store landing** — App Store / Play buttons, phone mockup,
|
|
230
|
+
pricing, download CTAs; branded from your primary color. The CLI
|
|
231
|
+
picks SaaS vs app landing by preset (and `umpordez update` keeps
|
|
232
|
+
the right flavor).
|
|
233
|
+
- **Splash choice** — instant show-up (default; native splash hides on
|
|
234
|
+
font load) or the animated splash, chosen at scaffold time.
|
|
235
|
+
- **Store launch kit** (every mobile preset) — `CHANGELOG.md` +
|
|
236
|
+
`docs/store-listing.md`, `store-copy.md`, `releases.md`,
|
|
237
|
+
`screenshots.md`.
|
|
238
|
+
- **Screenshot + release automation** — `frame-shots.mjs`,
|
|
239
|
+
`gen-mockups.mjs`, `screenshots-capture.sh` + Maestro flow,
|
|
240
|
+
`appstore-resize.sh`, and `misc/build-local.sh`.
|
|
241
|
+
|
|
215
242
|
## What changed in 1.1
|
|
216
243
|
|
|
217
244
|
The big one. v1.0.x was server + web only. v1.1 adds:
|
package/cli.mjs
CHANGED
|
@@ -41,13 +41,15 @@ function showHelp() {
|
|
|
41
41
|
console.log(` umpordez --version ${dim('Show version')}`);
|
|
42
42
|
console.log('');
|
|
43
43
|
console.log(green.bold('What you get:'));
|
|
44
|
+
console.log(` ${green('>')} Presets: everything / web-only / mobile+site / mobile+server / just-mobile`);
|
|
44
45
|
console.log(` ${green('>')} Multi-tenant auth (cookie-based JWT)`);
|
|
45
46
|
console.log(` ${green('>')} Admin API + Site API (Express)`);
|
|
46
47
|
console.log(` ${green('>')} React admin panel (Vite + shadcn/ui)`);
|
|
47
48
|
console.log(` ${green('>')} PostgreSQL + Knex.js migrations`);
|
|
48
49
|
console.log(` ${green('>')} S3 file uploads with signed URLs`);
|
|
49
|
-
console.log(` ${green('>')}
|
|
50
|
-
console.log(` ${green('>')}
|
|
50
|
+
console.log(` ${green('>')} Public site — SaaS or app-store landing (EJS + i18n + SEO)`);
|
|
51
|
+
console.log(` ${green('>')} Mobile app (Expo + TS) — instant or animated splash`);
|
|
52
|
+
console.log(` ${green('>')} Store launch kit (listing, copy, changelog, screenshots)`);
|
|
51
53
|
console.log(` ${green('>')} Role-based access (admin/owner/manager/user)`);
|
|
52
54
|
console.log(` ${green('>')} Production build system with versioning`);
|
|
53
55
|
console.log('');
|
package/create.mjs
CHANGED
|
@@ -4,7 +4,11 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
buildReplacements,
|
|
9
|
+
APP_LANDING_SWAPS,
|
|
10
|
+
SITE_VARIANT_DIR
|
|
11
|
+
} from './template-variables.mjs';
|
|
8
12
|
import { generateIcons } from './icons.mjs';
|
|
9
13
|
import { writeInitialManifest } from './update.mjs';
|
|
10
14
|
|
|
@@ -45,6 +49,17 @@ const PRESETS = {
|
|
|
45
49
|
wantsAdminUi: true,
|
|
46
50
|
wantsSite: true,
|
|
47
51
|
},
|
|
52
|
+
'mobile-and-site': {
|
|
53
|
+
label: 'Mobile app + landing page (no server, no admin)',
|
|
54
|
+
includes: ['mobile', 'ui/site', 'scripts', 'doc'],
|
|
55
|
+
wantsMobile: true,
|
|
56
|
+
wantsServer: false,
|
|
57
|
+
wantsAdminUi: false,
|
|
58
|
+
wantsSite: true,
|
|
59
|
+
// Swaps the SaaS landing for the app-marketing landing
|
|
60
|
+
// (App Store / Play buttons, pricing, download CTA).
|
|
61
|
+
appLanding: true,
|
|
62
|
+
},
|
|
48
63
|
'mobile-and-server': {
|
|
49
64
|
label: 'Mobile + server only (no admin UI, no site)',
|
|
50
65
|
includes: ['server', 'mobile', 'misc', 'scripts', 'doc'],
|
|
@@ -70,6 +85,8 @@ const ROOT_FILES_BY_PRESET = {
|
|
|
70
85
|
'web-only': ['CLAUDE.md', 'AGENTS.md', 'README.md',
|
|
71
86
|
'architecture.md', 'code.md',
|
|
72
87
|
'install.sh', 'seed.sh', 'dev.sh'],
|
|
88
|
+
'mobile-and-site': ['CLAUDE.md', 'AGENTS.md', 'README.md',
|
|
89
|
+
'code.md', 'install.sh', 'dev.sh'],
|
|
73
90
|
'mobile-and-server': ['CLAUDE.md', 'AGENTS.md', 'README.md',
|
|
74
91
|
'architecture.md', 'code.md',
|
|
75
92
|
'install.sh', 'seed.sh', 'dev.sh'],
|
|
@@ -266,6 +283,16 @@ async function promptProjectDetails() {
|
|
|
266
283
|
? true
|
|
267
284
|
: 'Lowercase alphanumeric, dashes allowed',
|
|
268
285
|
},
|
|
286
|
+
{
|
|
287
|
+
type: 'list',
|
|
288
|
+
name: 'splashStyle',
|
|
289
|
+
message: 'Splash screen:',
|
|
290
|
+
choices: [
|
|
291
|
+
{ name: 'Instant show-up (recommended)', value: 'instant' },
|
|
292
|
+
{ name: 'Animated (glow + logo + pulse)', value: 'animated' },
|
|
293
|
+
],
|
|
294
|
+
default: 'instant',
|
|
295
|
+
},
|
|
269
296
|
];
|
|
270
297
|
|
|
271
298
|
const answers = await inquirer.prompt([
|
|
@@ -296,6 +323,7 @@ async function promptProjectDetails() {
|
|
|
296
323
|
androidPackage: answers.androidPackage
|
|
297
324
|
?? toReverseDomain(answers.domain),
|
|
298
325
|
mobileScheme: answers.mobileScheme ?? answers.slug,
|
|
326
|
+
splashStyle: answers.splashStyle ?? 'instant',
|
|
299
327
|
...answers,
|
|
300
328
|
};
|
|
301
329
|
}
|
|
@@ -331,17 +359,28 @@ function showSummary(details) {
|
|
|
331
359
|
}
|
|
332
360
|
console.log(` ${chalk.bold('Color:')} ${chalk.hex(details.primaryColor)('██')} ${details.primaryColor}`);
|
|
333
361
|
console.log(` ${chalk.bold('Theme:')} ${details.defaultTheme}`);
|
|
362
|
+
if (presetDef.wantsSite) {
|
|
363
|
+
console.log(` ${chalk.bold('Landing:')} ${cyan(
|
|
364
|
+
presetDef.appLanding ? 'app store (download)' : 'SaaS (signup)'
|
|
365
|
+
)}`);
|
|
366
|
+
}
|
|
334
367
|
if (presetDef.wantsMobile) {
|
|
335
368
|
console.log(` ${chalk.bold('iOS:')} ${details.iosBundleId}`);
|
|
336
369
|
console.log(` ${chalk.bold('Android:')} ${details.androidPackage}`);
|
|
337
370
|
console.log(` ${chalk.bold('Scheme:')} ${details.mobileScheme}://`);
|
|
371
|
+
console.log(` ${chalk.bold('Splash:')} ${details.splashStyle === 'animated'
|
|
372
|
+
? 'animated (glow + logo)'
|
|
373
|
+
: 'instant show-up'}`);
|
|
338
374
|
}
|
|
339
375
|
console.log('');
|
|
340
376
|
console.log(dim(' Generates:'));
|
|
341
377
|
if (presetDef.wantsServer) console.log(dim(` ${green('>')} Admin API + Site API (Express + TS)`));
|
|
342
378
|
if (presetDef.wantsAdminUi) console.log(dim(` ${green('>')} React admin panel (Vite + shadcn/ui)`));
|
|
343
|
-
if (presetDef.wantsSite) console.log(dim(` ${green('>')}
|
|
379
|
+
if (presetDef.wantsSite) console.log(dim(` ${green('>')} ${presetDef.appLanding
|
|
380
|
+
? 'App landing page (store buttons + pricing)'
|
|
381
|
+
: 'Public site (EJS + Tailwind)'}`));
|
|
344
382
|
if (presetDef.wantsMobile) console.log(dim(` ${green('>')} Mobile app (Expo + TS + auth + theme)`));
|
|
383
|
+
if (presetDef.wantsMobile) console.log(dim(` ${green('>')} Store launch kit (listing, copy, changelog, screenshots)`));
|
|
345
384
|
if (presetDef.wantsServer) console.log(dim(` ${green('>')} PostgreSQL migrations + seed`));
|
|
346
385
|
console.log('');
|
|
347
386
|
}
|
|
@@ -383,7 +422,7 @@ function replacePlaceholders(content, replacements) {
|
|
|
383
422
|
// config, git ignore) doesn't belong to any single sub-app.
|
|
384
423
|
const ALWAYS_KEEP = new Set(['.claude']);
|
|
385
424
|
|
|
386
|
-
async function copyTemplate(outputDir, preset) {
|
|
425
|
+
export async function copyTemplate(outputDir, preset) {
|
|
387
426
|
const presetDef = PRESETS[preset];
|
|
388
427
|
await fs.promises.cp(TEMPLATE_DIR, outputDir, { recursive: true });
|
|
389
428
|
|
|
@@ -442,6 +481,25 @@ async function copyTemplate(outputDir, preset) {
|
|
|
442
481
|
await fs.promises.rm(path.join(outputDir, entry.name));
|
|
443
482
|
}
|
|
444
483
|
}
|
|
484
|
+
|
|
485
|
+
// Landing-page variant swap. App-focused presets replace the SaaS
|
|
486
|
+
// landing with the app-marketing one before placeholder replacement
|
|
487
|
+
// runs (so the swapped files get their placeholders filled too). The
|
|
488
|
+
// _variants staging dir is always removed — it never ships.
|
|
489
|
+
if (presetDef.appLanding) {
|
|
490
|
+
for (const [variant, canonical] of APP_LANDING_SWAPS) {
|
|
491
|
+
const from = path.join(outputDir, variant);
|
|
492
|
+
const to = path.join(outputDir, canonical);
|
|
493
|
+
if (fs.existsSync(from)) {
|
|
494
|
+
await fs.promises.rm(to, { force: true });
|
|
495
|
+
await fs.promises.rename(from, to);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
await fs.promises.rm(
|
|
500
|
+
path.join(outputDir, SITE_VARIANT_DIR),
|
|
501
|
+
{ recursive: true, force: true }
|
|
502
|
+
);
|
|
445
503
|
}
|
|
446
504
|
|
|
447
505
|
export async function createProject() {
|
|
@@ -565,9 +623,14 @@ export async function createProject() {
|
|
|
565
623
|
'scripts/run-ios.sh',
|
|
566
624
|
'scripts/release-android.sh',
|
|
567
625
|
'scripts/generate-icons.sh',
|
|
568
|
-
'scripts/install-native.sh'
|
|
626
|
+
'scripts/install-native.sh',
|
|
627
|
+
'scripts/appstore-resize.sh',
|
|
628
|
+
'scripts/screenshots-capture.sh'
|
|
569
629
|
);
|
|
570
630
|
}
|
|
631
|
+
if (details.presetDef.wantsServer) {
|
|
632
|
+
scripts.push('misc/build-local.sh');
|
|
633
|
+
}
|
|
571
634
|
|
|
572
635
|
for (const script of scripts) {
|
|
573
636
|
const scriptPath = path.join(outputDir, script);
|
|
@@ -638,8 +701,10 @@ function showNextSteps(details) {
|
|
|
638
701
|
console.log(` ${green('3.')} ./seed.sh`);
|
|
639
702
|
console.log(` ${green('4.')} ./dev.sh`);
|
|
640
703
|
} else {
|
|
641
|
-
|
|
642
|
-
|
|
704
|
+
// No DB to seed, but install.sh + dev.sh still orchestrate the
|
|
705
|
+
// site + mobile (they guard on directory existence).
|
|
706
|
+
console.log(` ${green('2.')} ./install.sh`);
|
|
707
|
+
console.log(` ${green('3.')} ./dev.sh`);
|
|
643
708
|
}
|
|
644
709
|
console.log('');
|
|
645
710
|
if (presetDef.wantsAdminUi) {
|
|
@@ -653,6 +718,7 @@ function showNextSteps(details) {
|
|
|
653
718
|
}
|
|
654
719
|
if (presetDef.wantsMobile) {
|
|
655
720
|
console.log(dim(` Mobile: cd mobile/${details.slug} && npm run ios | android | web`));
|
|
721
|
+
console.log(dim(` Store kit: mobile/${details.slug}/docs/ (listing, copy, screenshots) + CHANGELOG.md`));
|
|
656
722
|
}
|
|
657
723
|
console.log('');
|
|
658
724
|
console.log(dim(' Happy hacking! - ') + cyan('youtube.com/ligeiro'));
|
package/package.json
CHANGED
package/template/CLAUDE.md
CHANGED
|
@@ -58,7 +58,7 @@ URLs:
|
|
|
58
58
|
Folders that the preset didn't ship are simply absent. `dev.sh`
|
|
59
59
|
and `install.sh` guard on existence per directory, so the same
|
|
60
60
|
script works for every preset (everything / web-only /
|
|
61
|
-
mobile-and-server / just-mobile).
|
|
61
|
+
mobile-and-site / mobile-and-server / just-mobile).
|
|
62
62
|
|
|
63
63
|
## Multi-tenancy model
|
|
64
64
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# build-local — produce a production build of every web sub-project this
|
|
4
|
+
# repo ships (server, admin SPA, public site) into a sibling `builds/`
|
|
5
|
+
# directory, bump a build number and (optionally) commit + push it.
|
|
6
|
+
#
|
|
7
|
+
# Layout produced (matches CLAUDE.md → Deployment):
|
|
8
|
+
# ../builds/server/ server `npm run build` output + deps
|
|
9
|
+
# ../builds/ui/admin/dist/ Vite build (nginx serves this)
|
|
10
|
+
# ../builds/ui/site/ compiled site + views + public
|
|
11
|
+
#
|
|
12
|
+
# Usage (from the project root):
|
|
13
|
+
# ./misc/build-local.sh # build into ../builds
|
|
14
|
+
# ./misc/build-local.sh --push # also git commit + tag + push builds
|
|
15
|
+
#
|
|
16
|
+
# Each block is guarded on directory existence, so the same script works
|
|
17
|
+
# whatever preset generated this project.
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
21
|
+
cd "$ROOT"
|
|
22
|
+
|
|
23
|
+
BUILD_DIR="${BUILD_DIR:-../builds}"
|
|
24
|
+
PUSH=0
|
|
25
|
+
for arg in "$@"; do
|
|
26
|
+
case "$arg" in
|
|
27
|
+
--push) PUSH=1 ;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
mkdir -p "$BUILD_DIR"
|
|
32
|
+
|
|
33
|
+
# ── Build number ────────────────────────────────────────────────────
|
|
34
|
+
NUM_FILE="$BUILD_DIR/build-number"
|
|
35
|
+
number=$(cat "$NUM_FILE" 2>/dev/null || echo 0)
|
|
36
|
+
next=$((number + 1))
|
|
37
|
+
|
|
38
|
+
# ── Server ──────────────────────────────────────────────────────────
|
|
39
|
+
if [ -d server ]; then
|
|
40
|
+
echo "==> Building server…"
|
|
41
|
+
(cd server && npm run build)
|
|
42
|
+
mkdir -p "$BUILD_DIR/server"
|
|
43
|
+
rsync -a --delete \
|
|
44
|
+
--exclude node_modules --exclude .env \
|
|
45
|
+
server/dist/ "$BUILD_DIR/server/dist/" 2>/dev/null || true
|
|
46
|
+
cp server/package.json server/package-lock.json "$BUILD_DIR/server/" 2>/dev/null || true
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# ── Admin SPA ───────────────────────────────────────────────────────
|
|
50
|
+
if [ -d ui/admin ]; then
|
|
51
|
+
echo "==> Building admin SPA…"
|
|
52
|
+
(cd ui/admin && npm run build)
|
|
53
|
+
mkdir -p "$BUILD_DIR/ui/admin"
|
|
54
|
+
rsync -a --delete ui/admin/dist/ "$BUILD_DIR/ui/admin/dist/"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# ── Public site ─────────────────────────────────────────────────────
|
|
58
|
+
if [ -d ui/site ]; then
|
|
59
|
+
echo "==> Building public site…"
|
|
60
|
+
(cd ui/site && npm run build)
|
|
61
|
+
mkdir -p "$BUILD_DIR/ui/site"
|
|
62
|
+
rsync -a --delete \
|
|
63
|
+
--exclude node_modules --exclude .env \
|
|
64
|
+
ui/site/build/ "$BUILD_DIR/ui/site/build/" 2>/dev/null || true
|
|
65
|
+
cp ui/site/package.json "$BUILD_DIR/ui/site/" 2>/dev/null || true
|
|
66
|
+
cp -R ui/site/views "$BUILD_DIR/ui/site/" 2>/dev/null || true
|
|
67
|
+
cp -R ui/site/public "$BUILD_DIR/ui/site/" 2>/dev/null || true
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo "$next" > "$NUM_FILE"
|
|
71
|
+
echo ""
|
|
72
|
+
echo "Build #$next written to $BUILD_DIR"
|
|
73
|
+
|
|
74
|
+
# ── Optional: commit + push the builds repo ─────────────────────────
|
|
75
|
+
if [ "$PUSH" = "1" ] && [ -d "$BUILD_DIR/.git" ]; then
|
|
76
|
+
echo "==> Committing + pushing builds…"
|
|
77
|
+
(cd "$BUILD_DIR" \
|
|
78
|
+
&& git add . \
|
|
79
|
+
&& git commit -m "build: #$next" \
|
|
80
|
+
&& git tag -a "$next" -m "#$next" \
|
|
81
|
+
&& git push && git push origin --tags)
|
|
82
|
+
fi
|
|
@@ -15,13 +15,16 @@ import AnimatedSplash from './src/components/AnimatedSplash';
|
|
|
15
15
|
import { toastConfig } from './src/components/ToastConfig';
|
|
16
16
|
import { initDb } from './src/services/db';
|
|
17
17
|
import { syncAllContent } from './src/services/content';
|
|
18
|
+
import { SPLASH_ANIMATED } from './src/config';
|
|
18
19
|
|
|
19
20
|
// Hide the native splash screen so our AnimatedSplash takes over the
|
|
20
21
|
// transition. Best-effort — if the call fails (rare), we just continue.
|
|
21
22
|
SplashScreen.preventAutoHideAsync().catch(() => { /* best-effort */ });
|
|
22
23
|
|
|
23
24
|
export default function App() {
|
|
24
|
-
|
|
25
|
+
// Instant show-up (SPLASH_ANIMATED = false) starts already "done" so
|
|
26
|
+
// no overlay ever mounts — the app appears the moment fonts load.
|
|
27
|
+
const [splashDone, setSplashDone] = useState<boolean>(!SPLASH_ANIMATED);
|
|
25
28
|
|
|
26
29
|
useEffect(() => {
|
|
27
30
|
// Init runs in the background. App rendering doesn't wait on
|
|
@@ -79,7 +82,7 @@ export default function App() {
|
|
|
79
82
|
</AuthProvider>
|
|
80
83
|
</ThemeProvider>
|
|
81
84
|
|
|
82
|
-
{!splashDone && (
|
|
85
|
+
{SPLASH_ANIMATED && !splashDone && (
|
|
83
86
|
<AnimatedSplash onDone={() => setSplashDone(true)} />
|
|
84
87
|
)}
|
|
85
88
|
</SafeAreaProvider>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **{{PROJECT_NAME}}** (the mobile app). This is the
|
|
4
|
+
source of truth for what shipped; store "What's New" copy is derived from
|
|
5
|
+
it in [`docs/releases.md`](./docs/releases.md).
|
|
6
|
+
|
|
7
|
+
Format loosely follows [Keep a Changelog](https://keepachangelog.com);
|
|
8
|
+
versions match `app.json` (`version` / `ios.buildNumber` /
|
|
9
|
+
`android.versionCode`).
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- First public build.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
-
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
-
|
|
21
|
+
|
|
22
|
+
<!--
|
|
23
|
+
When you cut a release:
|
|
24
|
+
1. `../../scripts/bump-version.sh patch|minor|major`
|
|
25
|
+
2. Move the items above under a new `## [x.y.z] - YYYY-MM-DD` heading.
|
|
26
|
+
3. Reset [Unreleased] to empty Added/Changed/Fixed.
|
|
27
|
+
4. Translate the highlights into store copy in docs/releases.md.
|
|
28
|
+
-->
|
|
@@ -6,7 +6,7 @@ auth, 5-tab navigator, offline SQLite, custom-native modules.
|
|
|
6
6
|
```
|
|
7
7
|
src/
|
|
8
8
|
├── App.tsx, index.ts
|
|
9
|
-
├── config.ts API base URL +
|
|
9
|
+
├── config.ts API base URL + client IDs + SPLASH_ANIMATED
|
|
10
10
|
├── theme/colors.ts Palette generated from primary color
|
|
11
11
|
├── i18n/ pt-BR default + en (react-i18next)
|
|
12
12
|
├── contexts/
|
|
@@ -95,6 +95,15 @@ hasCompletedOnboarding
|
|
|
95
95
|
Use `./scripts/prebuild.sh` (refuses --clean). To genuinely
|
|
96
96
|
reset, move customizations into `plugins/` first.
|
|
97
97
|
|
|
98
|
+
## Splash
|
|
99
|
+
|
|
100
|
+
`config.ts` exports `SPLASH_ANIMATED` (set at scaffold time). When
|
|
101
|
+
`false` (instant show-up, the default) the native splash hides the
|
|
102
|
+
moment fonts load and the app appears immediately — `AnimatedSplash`
|
|
103
|
+
never mounts. When `true`, `App.tsx` overlays `AnimatedSplash`
|
|
104
|
+
(glow + logo + pulse ring, ~2s) until it finishes. The component ships
|
|
105
|
+
either way; flip the flag to switch behavior.
|
|
106
|
+
|
|
98
107
|
## Adding a screen
|
|
99
108
|
|
|
100
109
|
1. **Page** — `src/pages/<scope>/<Name>.tsx`. Typed via
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Store release notes
|
|
2
|
+
|
|
3
|
+
Copy-paste texts for publishing {{PROJECT_NAME}} updates to the **Apple
|
|
4
|
+
App Store** (App Store Connect → *What's New in This Version*) and **Google
|
|
5
|
+
Play** (Play Console → release → *Release notes*).
|
|
6
|
+
|
|
7
|
+
Character limits to respect:
|
|
8
|
+
|
|
9
|
+
- **Apple — Promotional Text**: max **170 chars** per language. Evergreen
|
|
10
|
+
blurb above the description; updatable without a build.
|
|
11
|
+
- **Apple — "What's New"**: max **4000 chars** per language. Roomy; use a
|
|
12
|
+
structured highlights + fixes block.
|
|
13
|
+
- **Google Play — release notes**: max **500 chars per language**. Paste
|
|
14
|
+
the whole locale-tagged block; Play parses the `<locale>` tags. Keep each
|
|
15
|
+
language tight and benefit-first.
|
|
16
|
+
|
|
17
|
+
Primary market is Brazil, so **pt-BR is the lead copy**; en mirrors it.
|
|
18
|
+
Source of truth for what shipped: [`../CHANGELOG.md`](../CHANGELOG.md).
|
|
19
|
+
|
|
20
|
+
House style: never a hyphen as a separator; use `;` `,` `.` instead.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## App Store — Promotional Text (≤170 chars / language)
|
|
25
|
+
|
|
26
|
+
Evergreen marketing blurb shown above the description. Not tied to a build
|
|
27
|
+
— update anytime. Order: pt-BR, en.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## App Store — "What's New" (≤4000 chars / language)
|
|
40
|
+
|
|
41
|
+
Per release. Lead with the biggest user-facing win. Order: pt-BR, en.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Novidades desta versão:
|
|
45
|
+
|
|
46
|
+
•
|
|
47
|
+
•
|
|
48
|
+
|
|
49
|
+
Correções e melhorias de estabilidade.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
What's new in this version:
|
|
54
|
+
|
|
55
|
+
•
|
|
56
|
+
•
|
|
57
|
+
|
|
58
|
+
Fixes and stability improvements.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Google Play — Release notes (≤500 chars / language)
|
|
64
|
+
|
|
65
|
+
Paste the whole block; Play parses the locale tags.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
<pt-BR>
|
|
69
|
+
•
|
|
70
|
+
•
|
|
71
|
+
Correções e melhorias.
|
|
72
|
+
</pt-BR>
|
|
73
|
+
<en-US>
|
|
74
|
+
•
|
|
75
|
+
•
|
|
76
|
+
Fixes and improvements.
|
|
77
|
+
</en-US>
|
|
78
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Store screenshots
|
|
2
|
+
|
|
3
|
+
Produce the full set — **N screens × 2 locales (pt-BR, en) × store sizes**
|
|
4
|
+
(iPhone, Android phone, iPad, Android tablet) — with a brand caption and no
|
|
5
|
+
design tool. Both routes below feed the same framer,
|
|
6
|
+
`scripts/frame-shots.mjs`.
|
|
7
|
+
|
|
8
|
+
> Apple accepts up to 10 screenshots; Google Play caps at 8. Produce 8 and
|
|
9
|
+
> they upload as-is to both.
|
|
10
|
+
|
|
11
|
+
Everything lands under `mobile/{{PROJECT_SLUG}}/screenshots/` (git-ignored):
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
screenshots/raw/<locale>/NN-*.png phone source screens
|
|
15
|
+
screenshots/raw-tablet/<locale>/NN-*.png tablet source screens
|
|
16
|
+
screenshots/store/<preset>/<locale>/*.png framed, upload-ready
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Presets → sizes: `iphone` 1290×2796 · `android` 1080×2340 · `ipad`
|
|
20
|
+
2048×2732 · `androidtablet` 1600×2560.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Option A — Headless mockups (no simulator)
|
|
25
|
+
|
|
26
|
+
`scripts/gen-mockups.mjs` renders recreations of your screens straight to
|
|
27
|
+
PNGs — no simulator, runs anywhere in ~1s. Ships as a **starting point**:
|
|
28
|
+
edit the `DATA` block (copy shown) and the `SCREENS` list (which screens,
|
|
29
|
+
what layout) to match your app, then:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
node scripts/gen-mockups.mjs # → screenshots/raw + raw-tablet
|
|
33
|
+
node scripts/frame-shots.mjs # → screenshots/store/**
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Both stores allow marketing recreations of the UI. Dev deps:
|
|
37
|
+
`@resvg/resvg-js`, `lucide-static` (add them to the mobile app if missing).
|
|
38
|
+
|
|
39
|
+
## Option B — Real device captures (Maestro)
|
|
40
|
+
|
|
41
|
+
Pixel-true captures of the live app on a booted simulator/emulator, driven
|
|
42
|
+
by [Maestro](https://maestro.mobile.dev/). Tune the screen labels in
|
|
43
|
+
`scripts/screenshots.flow.yaml` to your nav first (it matches tab labels in
|
|
44
|
+
both languages).
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
./scripts/run-ios.sh # or: cd mobile/{{PROJECT_SLUG}} && npm run android
|
|
48
|
+
./scripts/screenshots-capture.sh phone # → screenshots/raw
|
|
49
|
+
# boot an iPad / Android tablet, then:
|
|
50
|
+
./scripts/screenshots-capture.sh tablet # → screenshots/raw-tablet
|
|
51
|
+
node scripts/frame-shots.mjs # frame both sets
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The flow resets the app, gets past onboarding, switches the in-app language
|
|
55
|
+
per locale and captures each screen. Bundle IDs:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
iOS {{IOS_BUNDLE_ID}}
|
|
59
|
+
Android {{ANDROID_PACKAGE}} (pass --app-id {{ANDROID_PACKAGE}} on Android)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Captions & order
|
|
65
|
+
|
|
66
|
+
Numeric filename prefixes (`00-…`, `01-…`) drive both the order **and** the
|
|
67
|
+
caption lookup in `frame-shots.mjs` (the `CAPTIONS` map). Keep them
|
|
68
|
+
numbered. Captions should mirror [`store-copy.md`](./store-copy.md); the
|
|
69
|
+
first and last carry the strongest download pitch (most visitors only see
|
|
70
|
+
the first 2–3 frames).
|
|
71
|
+
|
|
72
|
+
Preview the framing without any raw captures:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
node scripts/frame-shots.mjs --selftest
|
|
76
|
+
node scripts/frame-shots.mjs --preset=iphone --locale=en
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Store requirements
|
|
80
|
+
|
|
81
|
+
- **Apple** requires a 6.9"/6.5" iPhone set; add the iPad set when
|
|
82
|
+
`supportsTablet: true` in `app.json`.
|
|
83
|
+
- **Google Play** takes up to 8 phone + tablet screenshots, long edge
|
|
84
|
+
320–3840px.
|