@kaikybrofc/omnizap-system 2.2.10 → 2.3.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.
- package/README.md +20 -18
- package/app/config/adminIdentity.js +1 -3
- package/app/connection/socketController.js +10 -20
- package/app/controllers/messageController.js +7 -28
- package/app/modules/aiModule/catCommand.js +29 -192
- package/app/modules/broadcastModule/noticeCommand.js +28 -97
- package/app/modules/gameModule/diceCommand.js +6 -32
- package/app/modules/playModule/playCommand.js +57 -258
- package/app/modules/quoteModule/quoteCommand.js +2 -4
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
- package/app/modules/statsModule/noMessageCommand.js +16 -84
- package/app/modules/statsModule/rankingCommand.js +5 -25
- package/app/modules/statsModule/rankingCommon.js +1 -9
- package/app/modules/stickerModule/convertToWebp.js +4 -27
- package/app/modules/stickerModule/stickerCommand.js +13 -24
- package/app/modules/stickerModule/stickerTextCommand.js +13 -25
- package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
- package/app/modules/stickerPackModule/domainEvents.js +2 -11
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
- package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
- package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +13 -34
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
- package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
- package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
- package/app/modules/stickerPackModule/stickerPackService.js +50 -115
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
- package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
- package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
- package/app/modules/systemMetricsModule/pingCommand.js +9 -39
- package/app/modules/tiktokModule/tiktokCommand.js +17 -109
- package/app/modules/userModule/userCommand.js +2 -88
- package/app/observability/metrics.js +5 -16
- package/app/services/captchaService.js +1 -6
- package/app/services/dbWriteQueue.js +3 -18
- package/app/services/featureFlagService.js +2 -8
- package/app/services/newsBroadcastService.js +0 -1
- package/app/services/queueUtils.js +2 -4
- package/app/services/whatsappLoginLinkService.js +7 -9
- package/app/store/premiumUserStore.js +1 -2
- package/app/utils/antiLink/antiLinkModule.js +3 -233
- package/app/utils/logger/loggerModule.js +9 -34
- package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
- package/database/init.js +1 -8
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
- package/docs/seo/satellite-page-template.md +2 -0
- package/docs/seo/satellite-pages-phase1.json +40 -177
- package/eslint.config.js +2 -15
- package/index.js +8 -36
- package/ml/clip_classifier/README.md +4 -6
- package/observability/alert-rules.yml +12 -12
- package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
- package/package.json +6 -3
- package/public/api-docs/index.html +220 -193
- package/public/bot-whatsapp-para-grupo/index.html +291 -261
- package/public/bot-whatsapp-sem-programar/index.html +291 -261
- package/public/comandos/index.html +421 -406
- package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
- package/public/como-criar-comandos-whatsapp/index.html +291 -261
- package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
- package/public/como-moderar-grupo-whatsapp/index.html +291 -261
- package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
- package/public/css/github-project-panel.css +13 -8
- package/public/css/stickers-admin.css +25 -9
- package/public/css/styles.css +23 -16
- package/public/index.html +1117 -994
- package/public/js/apps/apiDocsApp.js +17 -167
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +94 -74
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +496 -1397
- package/public/js/catalog.js +11 -74
- package/public/js/github-panel/components/ErrorState.js +1 -8
- package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
- package/public/js/github-panel/components/SkeletonPanel.js +1 -11
- package/public/js/github-panel/components/StatCard.js +1 -7
- package/public/js/github-panel/vendor/react.js +1 -9
- package/public/js/runtime/react-runtime.js +1 -9
- package/public/licenca/index.html +104 -86
- package/public/login/index.html +315 -325
- package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -44
- package/public/stickers/index.html +96 -107
- package/public/termos-de-uso/index.html +142 -115
- package/public/user/index.html +347 -350
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +10 -13
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/sticker-catalog-loadtest.mjs +13 -11
- package/scripts/sticker-worker-task.mjs +1 -4
- package/scripts/sync-readme-snapshot.mjs +3 -2
- package/server/controllers/stickerCatalogController.js +67 -5
- package/server/http/httpServer.js +2 -10
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
- package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
- package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
- package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
- package/server/routes/stickerCatalog/catalogRouter.js +11 -13
|
@@ -43,11 +43,7 @@ const removeControlChars = (value) => String(value || '').replace(/[\u0000-\u001
|
|
|
43
43
|
|
|
44
44
|
const sanitizePackNameInput = (value, maxLength = 120) => removeControlChars(value).slice(0, maxLength);
|
|
45
45
|
|
|
46
|
-
const sanitizePackName = (value, maxLength = 120) =>
|
|
47
|
-
removeControlChars(value)
|
|
48
|
-
.replace(/\s+/g, ' ')
|
|
49
|
-
.trim()
|
|
50
|
-
.slice(0, maxLength);
|
|
46
|
+
const sanitizePackName = (value, maxLength = 120) => removeControlChars(value).replace(/\s+/g, ' ').trim().slice(0, maxLength);
|
|
51
47
|
|
|
52
48
|
const toBytesLabel = (bytes) => `${Math.round(Number(bytes || 0) / 1024)} KB`;
|
|
53
49
|
const normalizeTag = (value) =>
|
|
@@ -322,16 +318,8 @@ const uploadStickerWithRetry = async (params) => {
|
|
|
322
318
|
|
|
323
319
|
function StepPill({ step, active, done }) {
|
|
324
320
|
return html`
|
|
325
|
-
<div className=${`flex min-w-0 items-center gap-1.5 rounded-xl border px-2.5 py-1.5 transition sm:gap-2 sm:rounded-2xl sm:px-3 sm:py-2 ${
|
|
326
|
-
|
|
327
|
-
? 'border-accent/50 bg-accent/10 text-accent'
|
|
328
|
-
: done
|
|
329
|
-
? 'border-emerald-400/30 bg-emerald-400/10 text-emerald-300'
|
|
330
|
-
: 'border-line/70 bg-panelSoft/80 text-slate-300'
|
|
331
|
-
}`}>
|
|
332
|
-
<span className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-black/25 text-[11px] font-extrabold sm:h-6 sm:w-6 sm:text-xs">
|
|
333
|
-
${done ? '✓' : step.id}
|
|
334
|
-
</span>
|
|
321
|
+
<div className=${`flex min-w-0 items-center gap-1.5 rounded-xl border px-2.5 py-1.5 transition sm:gap-2 sm:rounded-2xl sm:px-3 sm:py-2 ${active ? 'border-accent/50 bg-accent/10 text-accent' : done ? 'border-emerald-400/30 bg-emerald-400/10 text-emerald-300' : 'border-line/70 bg-panelSoft/80 text-slate-300'}`}>
|
|
322
|
+
<span className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-black/25 text-[11px] font-extrabold sm:h-6 sm:w-6 sm:text-xs"> ${done ? '✓' : step.id} </span>
|
|
335
323
|
<p className="truncate text-[10px] font-semibold sm:text-[11px]">${step.title}</p>
|
|
336
324
|
</div>
|
|
337
325
|
`;
|
|
@@ -347,18 +335,8 @@ function FloatingField({ label, value, onChange, maxLength, hint = '', multiline
|
|
|
347
335
|
<label className="block">
|
|
348
336
|
<span className="mb-1.5 inline-block text-xs font-semibold text-slate-300">${label}</span>
|
|
349
337
|
<div className="relative">
|
|
350
|
-
<${Tag}
|
|
351
|
-
|
|
352
|
-
atLimit ? 'border-rose-400/60 focus:border-rose-300' : 'border-line focus:border-accent/60'
|
|
353
|
-
} ${multiline ? 'min-h-[96px] max-h-44 resize-none overflow-y-auto md:min-h-[110px] md:max-h-52' : 'h-11 md:h-12'}`}
|
|
354
|
-
placeholder=${label}
|
|
355
|
-
value=${value}
|
|
356
|
-
maxlength=${maxLength}
|
|
357
|
-
onInput=${onChange}
|
|
358
|
-
/>
|
|
359
|
-
<span className="pointer-events-none absolute left-3.5 top-[-9px] rounded-md bg-base px-1.5 text-[10px] font-semibold uppercase tracking-[.08em] text-slate-400 md:left-4 md:bg-panel md:px-2">
|
|
360
|
-
${label}
|
|
361
|
-
</span>
|
|
338
|
+
<${Tag} className=${`w-full rounded-2xl border bg-panel/70 px-3.5 py-2.5 text-sm text-slate-100 outline-none transition placeholder:text-transparent md:bg-panel/80 md:px-4 md:py-3 ${atLimit ? 'border-rose-400/60 focus:border-rose-300' : 'border-line focus:border-accent/60'} ${multiline ? 'min-h-[96px] max-h-44 resize-none overflow-y-auto md:min-h-[110px] md:max-h-52' : 'h-11 md:h-12'}`} placeholder=${label} value=${value} maxlength=${maxLength} onInput=${onChange} />
|
|
339
|
+
<span className="pointer-events-none absolute left-3.5 top-[-9px] rounded-md bg-base px-1.5 text-[10px] font-semibold uppercase tracking-[.08em] text-slate-400 md:left-4 md:bg-panel md:px-2"> ${label} </span>
|
|
362
340
|
</div>
|
|
363
341
|
<div className="mt-1.5 flex items-center justify-between gap-3 text-[11px]">
|
|
364
342
|
<span className="line-clamp-2 text-slate-400">${hint}</span>
|
|
@@ -370,27 +348,11 @@ function FloatingField({ label, value, onChange, maxLength, hint = '', multiline
|
|
|
370
348
|
|
|
371
349
|
function StickerThumb({ item, index, selectedCoverId, onSetCover, onRemove, onDragStart, onDropOn }) {
|
|
372
350
|
return html`
|
|
373
|
-
<article
|
|
374
|
-
|
|
375
|
-
onDragStart=${() => onDragStart(item.id)}
|
|
376
|
-
onDragOver=${(e) => e.preventDefault()}
|
|
377
|
-
onDrop=${() => onDropOn(item.id)}
|
|
378
|
-
className="group relative overflow-hidden rounded-2xl border border-line bg-panelSoft"
|
|
379
|
-
>
|
|
380
|
-
${item.mediaKind === 'video'
|
|
381
|
-
? html`<video src=${item.dataUrl} muted=${true} playsInline=${true} preload="metadata" className="aspect-square w-full object-cover bg-slate-900/80"></video>`
|
|
382
|
-
: html`<img src=${item.dataUrl} alt=${item.file.name} className="aspect-square w-full object-contain bg-slate-900/80" />`}
|
|
351
|
+
<article draggable=${true} onDragStart=${() => onDragStart(item.id)} onDragOver=${(e) => e.preventDefault()} onDrop=${() => onDropOn(item.id)} className="group relative overflow-hidden rounded-2xl border border-line bg-panelSoft">
|
|
352
|
+
${item.mediaKind === 'video' ? html`<video src=${item.dataUrl} muted=${true} playsinline=${true} preload="metadata" className="aspect-square w-full object-cover bg-slate-900/80"></video>` : html`<img src=${item.dataUrl} alt=${item.file.name} className="aspect-square w-full object-contain bg-slate-900/80" />`}
|
|
383
353
|
<span className="absolute left-2 top-2 rounded-md bg-black/50 px-1.5 py-0.5 text-[10px] font-bold">#${index + 1}</span>
|
|
384
354
|
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/80 to-transparent p-2">
|
|
385
|
-
<button
|
|
386
|
-
type="button"
|
|
387
|
-
onClick=${() => onSetCover(item.id)}
|
|
388
|
-
className=${`rounded-lg px-2 py-1 text-[10px] font-bold ${
|
|
389
|
-
selectedCoverId === item.id ? 'bg-accent text-slate-900' : 'bg-white/15 text-slate-100'
|
|
390
|
-
}`}
|
|
391
|
-
>
|
|
392
|
-
${selectedCoverId === item.id ? 'Capa' : 'Definir capa'}
|
|
393
|
-
</button>
|
|
355
|
+
<button type="button" onClick=${() => onSetCover(item.id)} className=${`rounded-lg px-2 py-1 text-[10px] font-bold ${selectedCoverId === item.id ? 'bg-accent text-slate-900' : 'bg-white/15 text-slate-100'}`}>${selectedCoverId === item.id ? 'Capa' : 'Definir capa'}</button>
|
|
394
356
|
<button type="button" onClick=${() => onRemove(item.id)} className="rounded-lg bg-rose-500/80 px-2 py-1 text-[10px] font-bold text-white">Remover</button>
|
|
395
357
|
</div>
|
|
396
358
|
</article>
|
|
@@ -406,11 +368,7 @@ function PackPreviewPanel({ preview, quality, compact = false }) {
|
|
|
406
368
|
<p className=${`${compact ? 'text-base' : 'text-lg'} line-clamp-2 font-display font-bold`}>${preview.name}</p>
|
|
407
369
|
<p className="line-clamp-2 text-sm text-slate-300">${preview.description || 'Descrição do pack aparecerá aqui.'}</p>
|
|
408
370
|
<p className="text-xs text-slate-400">por ${preview.publisher}</p>
|
|
409
|
-
<div className="flex flex-wrap items-center gap-1">
|
|
410
|
-
${preview.tags.length
|
|
411
|
-
? preview.tags.map((tag) => html`<span key=${tag} className="rounded-full border border-line/70 px-2 py-0.5 text-[10px] text-slate-300">#${tag}</span>`)
|
|
412
|
-
: html`<span className="text-[10px] text-slate-500">Adicione tags para melhorar descoberta</span>`}
|
|
413
|
-
</div>
|
|
371
|
+
<div className="flex flex-wrap items-center gap-1">${preview.tags.length ? preview.tags.map((tag) => html`<span key=${tag} className="rounded-full border border-line/70 px-2 py-0.5 text-[10px] text-slate-300">#${tag}</span>`) : html`<span className="text-[10px] text-slate-500">Adicione tags para melhorar descoberta</span>`}</div>
|
|
414
372
|
<div className="flex flex-wrap items-center gap-1.5 text-xs">
|
|
415
373
|
<span className="rounded-full border border-line/70 px-2 py-1 text-slate-300">${preview.visibility}</span>
|
|
416
374
|
<span className="rounded-full border border-line/70 px-2 py-1 text-slate-300">🧩 ${preview.stickerCount}</span>
|
|
@@ -485,37 +443,15 @@ function CreatePackApp() {
|
|
|
485
443
|
[authRedirecting, buildLoginRedirectUrl],
|
|
486
444
|
);
|
|
487
445
|
|
|
488
|
-
const canStep2 = useMemo(
|
|
489
|
-
() =>
|
|
490
|
-
sanitizePackName(name, limits.pack_name_max_length).length > 0 &&
|
|
491
|
-
hasGoogleLogin &&
|
|
492
|
-
googleSessionChecked &&
|
|
493
|
-
!authRedirecting,
|
|
494
|
-
[name, limits.pack_name_max_length, hasGoogleLogin, googleSessionChecked, authRedirecting],
|
|
495
|
-
);
|
|
446
|
+
const canStep2 = useMemo(() => sanitizePackName(name, limits.pack_name_max_length).length > 0 && hasGoogleLogin && googleSessionChecked && !authRedirecting, [name, limits.pack_name_max_length, hasGoogleLogin, googleSessionChecked, authRedirecting]);
|
|
496
447
|
const canStep3 = useMemo(() => files.length > 0, [files.length]);
|
|
497
448
|
const publishReady = canStep2 && canStep3 && !busy;
|
|
498
449
|
const completionPercentage = Math.round((step / STEPS.length) * 100);
|
|
499
|
-
const failedUploadsCount = useMemo(
|
|
500
|
-
|
|
501
|
-
[files, uploadMap],
|
|
502
|
-
);
|
|
503
|
-
const pendingUploadsCount = useMemo(
|
|
504
|
-
() => files.reduce((acc, item) => (uploadMap[item.id]?.status === 'done' ? acc : acc + 1), 0),
|
|
505
|
-
[files, uploadMap],
|
|
506
|
-
);
|
|
450
|
+
const failedUploadsCount = useMemo(() => files.reduce((acc, item) => (uploadMap[item.id]?.status === 'error' ? acc + 1 : acc), 0), [files, uploadMap]);
|
|
451
|
+
const pendingUploadsCount = useMemo(() => files.reduce((acc, item) => (uploadMap[item.id]?.status === 'done' ? acc : acc + 1), 0), [files, uploadMap]);
|
|
507
452
|
const hasPartialUploadedSession = Boolean(activeSession?.packKey && pendingUploadsCount > 0 && pendingUploadsCount < files.length);
|
|
508
|
-
const backendPackStatus = String(
|
|
509
|
-
|
|
510
|
-
).toLowerCase();
|
|
511
|
-
const publishLabel =
|
|
512
|
-
backendPackStatus === 'failed'
|
|
513
|
-
? '🛠️ Reparar pack'
|
|
514
|
-
: failedUploadsCount > 0
|
|
515
|
-
? `🔁 Reenviar falhas (${failedUploadsCount})`
|
|
516
|
-
: hasPartialUploadedSession
|
|
517
|
-
? '▶ Retomar envio'
|
|
518
|
-
: '🚀 Publicar Pack';
|
|
453
|
+
const backendPackStatus = String(backendPublishState?.status || result?.status || activeSession?.created?.status || '').toLowerCase();
|
|
454
|
+
const publishLabel = backendPackStatus === 'failed' ? '🛠️ Reparar pack' : failedUploadsCount > 0 ? `🔁 Reenviar falhas (${failedUploadsCount})` : hasPartialUploadedSession ? '▶ Retomar envio' : '🚀 Publicar Pack';
|
|
519
455
|
|
|
520
456
|
const suggestedFromText = useMemo(() => {
|
|
521
457
|
const haystack = `${name} ${description}`
|
|
@@ -663,8 +599,7 @@ function CreatePackApp() {
|
|
|
663
599
|
return;
|
|
664
600
|
}
|
|
665
601
|
|
|
666
|
-
const restoredName =
|
|
667
|
-
typeof parsed.name === 'string' ? sanitizePackNameInput(parsed.name, DEFAULT_LIMITS.pack_name_max_length) : '';
|
|
602
|
+
const restoredName = typeof parsed.name === 'string' ? sanitizePackNameInput(parsed.name, DEFAULT_LIMITS.pack_name_max_length) : '';
|
|
668
603
|
if (restoredName) setName(restoredName);
|
|
669
604
|
if (typeof parsed.description === 'string') setDescription(parsed.description);
|
|
670
605
|
if (typeof parsed.publisher === 'string') setPublisher(parsed.publisher);
|
|
@@ -685,8 +620,12 @@ function CreatePackApp() {
|
|
|
685
620
|
},
|
|
686
621
|
hash: /^[a-f0-9]{64}$/.test(String(item.hash || '').toLowerCase()) ? String(item.hash || '').toLowerCase() : '',
|
|
687
622
|
mediaKind:
|
|
688
|
-
String(item.type || '')
|
|
689
|
-
|
|
623
|
+
String(item.type || '')
|
|
624
|
+
.toLowerCase()
|
|
625
|
+
.startsWith('video/') ||
|
|
626
|
+
String(item.name || '')
|
|
627
|
+
.toLowerCase()
|
|
628
|
+
.match(/\.(mp4|webm|mov|m4v)$/i)
|
|
690
629
|
? 'video'
|
|
691
630
|
: 'image',
|
|
692
631
|
dataUrl: String(item.dataUrl),
|
|
@@ -809,9 +748,7 @@ function CreatePackApp() {
|
|
|
809
748
|
|
|
810
749
|
const uploads = Array.isArray(publishState.uploads) ? publishState.uploads : [];
|
|
811
750
|
const uploadsById = new Map(uploads.map((entry) => [String(entry.upload_id || ''), entry]));
|
|
812
|
-
const uploadsByHash = new Map(
|
|
813
|
-
uploads.filter((entry) => entry?.sticker_hash).map((entry) => [String(entry.sticker_hash || ''), entry]),
|
|
814
|
-
);
|
|
751
|
+
const uploadsByHash = new Map(uploads.filter((entry) => entry?.sticker_hash).map((entry) => [String(entry.sticker_hash || ''), entry]));
|
|
815
752
|
|
|
816
753
|
setUploadMap((prev) => {
|
|
817
754
|
const next = { ...prev };
|
|
@@ -887,23 +824,14 @@ function CreatePackApp() {
|
|
|
887
824
|
const lowerName = String(file.name || '').toLowerCase();
|
|
888
825
|
const lowerType = String(file.type || '').toLowerCase();
|
|
889
826
|
const isImage = lowerType.startsWith('image/');
|
|
890
|
-
const isVideo =
|
|
891
|
-
lowerType.startsWith('video/') ||
|
|
892
|
-
lowerName.endsWith('.mp4') ||
|
|
893
|
-
lowerName.endsWith('.webm') ||
|
|
894
|
-
lowerName.endsWith('.mov') ||
|
|
895
|
-
lowerName.endsWith('.m4v');
|
|
827
|
+
const isVideo = lowerType.startsWith('video/') || lowerName.endsWith('.mp4') || lowerName.endsWith('.webm') || lowerName.endsWith('.mov') || lowerName.endsWith('.m4v');
|
|
896
828
|
if (!isImage && !isVideo) return false;
|
|
897
829
|
const maxBytes = Number(limits.sticker_upload_source_max_bytes || 0);
|
|
898
830
|
return Number(file.size || 0) <= maxBytes;
|
|
899
831
|
});
|
|
900
832
|
|
|
901
833
|
if (!filtered.length) {
|
|
902
|
-
setError(
|
|
903
|
-
`Envie imagem ou vídeo até ${toBytesLabel(
|
|
904
|
-
limits.sticker_upload_source_max_bytes,
|
|
905
|
-
)}. O sistema converte automaticamente para webp.`,
|
|
906
|
-
);
|
|
834
|
+
setError(`Envie imagem ou vídeo até ${toBytesLabel(limits.sticker_upload_source_max_bytes)}. O sistema converte automaticamente para webp.`);
|
|
907
835
|
return;
|
|
908
836
|
}
|
|
909
837
|
|
|
@@ -923,8 +851,12 @@ function CreatePackApp() {
|
|
|
923
851
|
file,
|
|
924
852
|
hash: await computeDataUrlSha256(dataUrl),
|
|
925
853
|
mediaKind:
|
|
926
|
-
String(file.type || '')
|
|
927
|
-
|
|
854
|
+
String(file.type || '')
|
|
855
|
+
.toLowerCase()
|
|
856
|
+
.startsWith('video/') ||
|
|
857
|
+
String(file.name || '')
|
|
858
|
+
.toLowerCase()
|
|
859
|
+
.match(/\.(mp4|webm|mov|m4v)$/i)
|
|
928
860
|
? 'video'
|
|
929
861
|
: 'image',
|
|
930
862
|
dataUrl,
|
|
@@ -1395,33 +1327,20 @@ function CreatePackApp() {
|
|
|
1395
1327
|
}
|
|
1396
1328
|
};
|
|
1397
1329
|
|
|
1398
|
-
const visibilityHelp =
|
|
1399
|
-
visibility === 'private'
|
|
1400
|
-
? 'Privado: apenas você acessa este pack.'
|
|
1401
|
-
: visibility === 'unlisted'
|
|
1402
|
-
? 'Não listado: acesso por link direto.'
|
|
1403
|
-
: 'Público: aparece no catálogo para descoberta.';
|
|
1330
|
+
const visibilityHelp = visibility === 'private' ? 'Privado: apenas você acessa este pack.' : visibility === 'unlisted' ? 'Não listado: acesso por link direto.' : 'Público: aparece no catálogo para descoberta.';
|
|
1404
1331
|
|
|
1405
1332
|
const uploadProgressTotal = Math.max(0, Number(progress.total || files.length || 0));
|
|
1406
1333
|
const uploadProgressDone = Math.max(0, Math.min(uploadProgressTotal || 0, Number(progress.current || 0)));
|
|
1407
|
-
const uploadProgressPercent = Math.max(
|
|
1408
|
-
0,
|
|
1409
|
-
Math.min(100, Math.round((uploadProgressDone / Math.max(1, uploadProgressTotal || 1)) * 100)),
|
|
1410
|
-
);
|
|
1334
|
+
const uploadProgressPercent = Math.max(0, Math.min(100, Math.round((uploadProgressDone / Math.max(1, uploadProgressTotal || 1)) * 100)));
|
|
1411
1335
|
const uploadHasFailures = failedUploadsCount > 0;
|
|
1412
1336
|
const backendStateFailed = backendPackStatus === 'failed';
|
|
1413
|
-
const publishCompleted = Boolean(
|
|
1414
|
-
result && String(backendPackStatus || result?.status || '').toLowerCase() === PACK_STATUS_PUBLISHED && !busy,
|
|
1415
|
-
);
|
|
1337
|
+
const publishCompleted = Boolean(result && String(backendPackStatus || result?.status || '').toLowerCase() === PACK_STATUS_PUBLISHED && !busy);
|
|
1416
1338
|
const showUploadProgressCard = step === 3 && busy;
|
|
1417
1339
|
const showUploadFailureCard = step === 3 && !busy && (uploadHasFailures || backendStateFailed);
|
|
1418
|
-
const publishedPackUrl =
|
|
1419
|
-
String(result?.web_url || activeSession?.webUrl || '').trim() ||
|
|
1420
|
-
(result?.pack_key ? `${webPath}/${encodeURIComponent(String(result.pack_key || ''))}` : '');
|
|
1340
|
+
const publishedPackUrl = String(result?.web_url || activeSession?.webUrl || '').trim() || (result?.pack_key ? `${webPath}/${encodeURIComponent(String(result.pack_key || ''))}` : '');
|
|
1421
1341
|
const finalStepPrimaryLabel = publishCompleted ? '👁 Ver pack criado' : publishLabel;
|
|
1422
1342
|
const mobilePrimaryActionLabel = step < 3 ? 'Continuar' : finalStepPrimaryLabel;
|
|
1423
|
-
const mobilePrimaryActionClass =
|
|
1424
|
-
step < 3 ? 'bg-accent text-slate-900' : 'bg-accent2 text-slate-900';
|
|
1343
|
+
const mobilePrimaryActionClass = step < 3 ? 'bg-accent text-slate-900' : 'bg-accent2 text-slate-900';
|
|
1425
1344
|
const toggleMobilePreview = () => setMobilePreviewOpen((prev) => !prev);
|
|
1426
1345
|
const openCreatedPack = () => {
|
|
1427
1346
|
if (!publishedPackUrl) return;
|
|
@@ -1449,21 +1368,11 @@ function CreatePackApp() {
|
|
|
1449
1368
|
<span className="hidden rounded-full border border-line/60 bg-panel/70 px-3 py-1 sm:inline-flex">🧩 Até ${limits.stickers_per_pack} stickers</span>
|
|
1450
1369
|
<span className="hidden rounded-full border border-line/60 bg-panel/70 px-3 py-1 sm:inline-flex">📦 Até ${limits.packs_per_owner} packs</span>
|
|
1451
1370
|
<span className="hidden rounded-full border border-line/60 bg-panel/70 px-3 py-1 md:inline-flex">✍ ${limits.pack_name_max_length} caracteres no nome</span>
|
|
1452
|
-
<button
|
|
1453
|
-
type="button"
|
|
1454
|
-
onClick=${restartCreateFlow}
|
|
1455
|
-
disabled=${busy}
|
|
1456
|
-
className="h-8 rounded-full border border-line/70 bg-panel/70 px-3 text-[11px] font-semibold text-slate-200 disabled:opacity-60"
|
|
1457
|
-
title="Limpar rascunho local e recomeçar"
|
|
1458
|
-
>
|
|
1459
|
-
Recomeçar
|
|
1460
|
-
</button>
|
|
1371
|
+
<button type="button" onClick=${restartCreateFlow} disabled=${busy} className="h-8 rounded-full border border-line/70 bg-panel/70 px-3 text-[11px] font-semibold text-slate-200 disabled:opacity-60" title="Limpar rascunho local e recomeçar">Recomeçar</button>
|
|
1461
1372
|
</div>
|
|
1462
1373
|
</header>
|
|
1463
1374
|
|
|
1464
|
-
<div className="mb-3 grid grid-cols-3 gap-2 md:mb-5">
|
|
1465
|
-
${STEPS.map((item) => html`<${StepPill} key=${item.id} step=${item} active=${step === item.id} done=${step > item.id} />`)}
|
|
1466
|
-
</div>
|
|
1375
|
+
<div className="mb-3 grid grid-cols-3 gap-2 md:mb-5">${STEPS.map((item) => html`<${StepPill} key=${item.id} step=${item} active=${step === item.id} done=${step > item.id} />`)}</div>
|
|
1467
1376
|
<div className="mb-4 md:mb-6">
|
|
1468
1377
|
<div className="mb-1 flex items-center justify-between text-[11px] font-semibold text-slate-400">
|
|
1469
1378
|
<span>Progresso</span>
|
|
@@ -1479,42 +1388,17 @@ function CreatePackApp() {
|
|
|
1479
1388
|
${step === 1
|
|
1480
1389
|
? html`
|
|
1481
1390
|
<div className="space-y-3 md:space-y-4">
|
|
1482
|
-
<${FloatingField}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
maxLength=${limits.pack_name_max_length}
|
|
1486
|
-
hint="Use um nome curto e fácil de encontrar."
|
|
1487
|
-
onChange=${(e) => setName(sanitizePackNameInput(e.target.value, limits.pack_name_max_length))}
|
|
1488
|
-
/>
|
|
1489
|
-
<${FloatingField}
|
|
1490
|
-
label="Descrição"
|
|
1491
|
-
value=${description}
|
|
1492
|
-
multiline=${true}
|
|
1493
|
-
maxLength=${limits.description_max_length}
|
|
1494
|
-
hint="Explique o tema do pack em uma frase curta"
|
|
1495
|
-
onChange=${(e) => setDescription(clampInputText(e.target.value, limits.description_max_length))}
|
|
1496
|
-
/>
|
|
1497
|
-
<${FloatingField}
|
|
1498
|
-
label="Autor"
|
|
1499
|
-
value=${publisher}
|
|
1500
|
-
maxLength=${limits.publisher_max_length}
|
|
1501
|
-
hint="Como seu nome será exibido no catálogo."
|
|
1502
|
-
onChange=${(e) => setPublisher(clampInputText(e.target.value, limits.publisher_max_length))}
|
|
1503
|
-
/>
|
|
1391
|
+
<${FloatingField} label="Nome do pack" value=${name} maxLength=${limits.pack_name_max_length} hint="Use um nome curto e fácil de encontrar." onChange=${(e) => setName(sanitizePackNameInput(e.target.value, limits.pack_name_max_length))} />
|
|
1392
|
+
<${FloatingField} label="Descrição" value=${description} multiline=${true} maxLength=${limits.description_max_length} hint="Explique o tema do pack em uma frase curta" onChange=${(e) => setDescription(clampInputText(e.target.value, limits.description_max_length))} />
|
|
1393
|
+
<${FloatingField} label="Autor" value=${publisher} maxLength=${limits.publisher_max_length} hint="Como seu nome será exibido no catálogo." onChange=${(e) => setPublisher(clampInputText(e.target.value, limits.publisher_max_length))} />
|
|
1504
1394
|
<div className="rounded-2xl border border-line/70 bg-panel/70 p-3 md:p-4">
|
|
1505
1395
|
<div className="flex items-start justify-between gap-3">
|
|
1506
1396
|
<div>
|
|
1507
1397
|
<p className="text-xs font-semibold uppercase tracking-[.08em] text-slate-400">Sessão da conta</p>
|
|
1508
1398
|
<p className="mt-1 text-sm font-semibold text-slate-100">Conta Google vinculada</p>
|
|
1509
|
-
<p className="mt-1 text-xs text-slate-400">
|
|
1510
|
-
${googleSessionChecked
|
|
1511
|
-
? 'Sua sessão foi validada para criar packs.'
|
|
1512
|
-
: 'Validando sua sessão para liberar a criação.'}
|
|
1513
|
-
</p>
|
|
1399
|
+
<p className="mt-1 text-xs text-slate-400">${googleSessionChecked ? 'Sua sessão foi validada para criar packs.' : 'Validando sua sessão para liberar a criação.'}</p>
|
|
1514
1400
|
</div>
|
|
1515
|
-
${hasGoogleLogin
|
|
1516
|
-
? html`<span className="rounded-full border border-emerald-400/40 bg-emerald-400/10 px-2.5 py-1 text-[11px] font-semibold text-emerald-300">Conectado</span>`
|
|
1517
|
-
: html`<span className="rounded-full border border-amber-400/40 bg-amber-400/10 px-2.5 py-1 text-[11px] font-semibold text-amber-300">Validando</span>`}
|
|
1401
|
+
${hasGoogleLogin ? html`<span className="rounded-full border border-emerald-400/40 bg-emerald-400/10 px-2.5 py-1 text-[11px] font-semibold text-emerald-300">Conectado</span>` : html`<span className="rounded-full border border-amber-400/40 bg-amber-400/10 px-2.5 py-1 text-[11px] font-semibold text-amber-300">Validando</span>`}
|
|
1518
1402
|
</div>
|
|
1519
1403
|
<div className="mt-3 rounded-xl border border-line/70 bg-panelSoft/80 p-2.5 md:p-3">
|
|
1520
1404
|
<p className="truncate text-sm font-semibold text-slate-100">${googleAuth.user?.name || 'Conta Google'}</p>
|
|
@@ -1525,30 +1409,16 @@ function CreatePackApp() {
|
|
|
1525
1409
|
<span className="mb-2 inline-block text-xs font-semibold text-slate-300">Tags do pack</span>
|
|
1526
1410
|
<div className="rounded-2xl border border-line/70 bg-panelSoft/80 px-3 py-3">
|
|
1527
1411
|
<div className="mb-2 flex flex-wrap gap-2">
|
|
1528
|
-
${tags.map(
|
|
1529
|
-
|
|
1530
|
-
key=${tag}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
#${tag}
|
|
1537
|
-
<span aria-hidden="true">×</span>
|
|
1538
|
-
</button>
|
|
1539
|
-
`)}
|
|
1412
|
+
${tags.map(
|
|
1413
|
+
(tag) => html`
|
|
1414
|
+
<button key=${tag} type="button" onClick=${() => removeTag(tag)} className="inline-flex items-center gap-1 rounded-full border border-accent/40 bg-accent/10 px-2.5 py-1 text-[11px] font-semibold text-accent" title="Remover tag">
|
|
1415
|
+
#${tag}
|
|
1416
|
+
<span aria-hidden="true">×</span>
|
|
1417
|
+
</button>
|
|
1418
|
+
`,
|
|
1419
|
+
)}
|
|
1540
1420
|
</div>
|
|
1541
|
-
<input
|
|
1542
|
-
type="text"
|
|
1543
|
-
value=${tagInput}
|
|
1544
|
-
maxlength=${40}
|
|
1545
|
-
onInput=${(e) => setTagInput(String(e.target.value || ''))}
|
|
1546
|
-
onKeyDown=${onTagInputKeyDown}
|
|
1547
|
-
onBlur=${() => addTag(tagInput)}
|
|
1548
|
-
placeholder=${tags.length >= MAX_MANUAL_TAGS ? `Limite de ${MAX_MANUAL_TAGS} tags` : 'Digite e pressione Enter para adicionar'}
|
|
1549
|
-
disabled=${tags.length >= MAX_MANUAL_TAGS}
|
|
1550
|
-
className="h-11 w-full rounded-xl border border-line/70 bg-panel/80 px-3 text-sm outline-none transition focus:border-accent/60 disabled:opacity-60"
|
|
1551
|
-
/>
|
|
1421
|
+
<input type="text" value=${tagInput} maxlength=${40} onInput=${(e) => setTagInput(String(e.target.value || ''))} onKeyDown=${onTagInputKeyDown} onBlur=${() => addTag(tagInput)} placeholder=${tags.length >= MAX_MANUAL_TAGS ? `Limite de ${MAX_MANUAL_TAGS} tags` : 'Digite e pressione Enter para adicionar'} disabled=${tags.length >= MAX_MANUAL_TAGS} className="h-11 w-full rounded-xl border border-line/70 bg-panel/80 px-3 text-sm outline-none transition focus:border-accent/60 disabled:opacity-60" />
|
|
1552
1422
|
${tagInput.trim() && tags.length < MAX_MANUAL_TAGS && tagTypeaheadSuggestions.length
|
|
1553
1423
|
? html`
|
|
1554
1424
|
<div className="mt-2 rounded-xl border border-line/70 bg-panel/70 p-2">
|
|
@@ -1556,56 +1426,26 @@ function CreatePackApp() {
|
|
|
1556
1426
|
<p className="text-[10px] font-semibold uppercase tracking-[.08em] text-slate-400">Sugestões</p>
|
|
1557
1427
|
<p className="text-[10px] text-slate-500">Tab completa a primeira</p>
|
|
1558
1428
|
</div>
|
|
1559
|
-
<div className="flex flex-wrap gap-1.5">
|
|
1560
|
-
${tagTypeaheadSuggestions.map((tag) => html`
|
|
1561
|
-
<button
|
|
1562
|
-
key=${`typeahead-${tag}`}
|
|
1563
|
-
type="button"
|
|
1564
|
-
onMouseDown=${(e) => e.preventDefault()}
|
|
1565
|
-
onClick=${() => addTag(tag)}
|
|
1566
|
-
className="rounded-full border border-accent/35 bg-accent/10 px-2 py-1 text-[10px] font-semibold text-accent transition hover:border-accent/60"
|
|
1567
|
-
>
|
|
1568
|
-
#${tag}
|
|
1569
|
-
</button>
|
|
1570
|
-
`)}
|
|
1571
|
-
</div>
|
|
1429
|
+
<div className="flex flex-wrap gap-1.5">${tagTypeaheadSuggestions.map((tag) => html` <button key=${`typeahead-${tag}`} type="button" onMouseDown=${(e) => e.preventDefault()} onClick=${() => addTag(tag)} className="rounded-full border border-accent/35 bg-accent/10 px-2 py-1 text-[10px] font-semibold text-accent transition hover:border-accent/60">#${tag}</button> `)}</div>
|
|
1572
1430
|
</div>
|
|
1573
1431
|
`
|
|
1574
1432
|
: null}
|
|
1575
1433
|
<p className="mt-2 text-[11px] text-slate-400">${tags.length}/${MAX_MANUAL_TAGS} tags selecionadas.</p>
|
|
1576
|
-
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
1577
|
-
${suggestedFromText.map((tag) => html`
|
|
1578
|
-
<button
|
|
1579
|
-
key=${tag}
|
|
1580
|
-
type="button"
|
|
1581
|
-
onMouseDown=${(e) => e.preventDefault()}
|
|
1582
|
-
onClick=${() => addTag(tag)}
|
|
1583
|
-
className="rounded-full border border-line bg-panel px-2 py-1 text-[10px] font-semibold text-slate-300 transition hover:border-accent/50 hover:text-accent"
|
|
1584
|
-
>
|
|
1585
|
-
+ ${tag}
|
|
1586
|
-
</button>
|
|
1587
|
-
`)}
|
|
1588
|
-
</div>
|
|
1434
|
+
<div className="mt-2 flex flex-wrap gap-1.5">${suggestedFromText.map((tag) => html` <button key=${tag} type="button" onMouseDown=${(e) => e.preventDefault()} onClick=${() => addTag(tag)} className="rounded-full border border-line bg-panel px-2 py-1 text-[10px] font-semibold text-slate-300 transition hover:border-accent/50 hover:text-accent">+ ${tag}</button> `)}</div>
|
|
1589
1435
|
</div>
|
|
1590
1436
|
</label>
|
|
1591
1437
|
<label className="block">
|
|
1592
1438
|
<span className="mb-2 inline-block text-xs font-semibold text-slate-300">Visibilidade</span>
|
|
1593
|
-
<select
|
|
1594
|
-
value=${visibility}
|
|
1595
|
-
onChange=${(e) => setVisibility(String(e.target.value || 'public'))}
|
|
1596
|
-
className="h-11 w-full rounded-2xl border border-line/70 bg-panelSoft/80 px-4 text-sm outline-none focus:border-accent/60 md:h-12"
|
|
1597
|
-
>
|
|
1439
|
+
<select value=${visibility} onChange=${(e) => setVisibility(String(e.target.value || 'public'))} className="h-11 w-full rounded-2xl border border-line/70 bg-panelSoft/80 px-4 text-sm outline-none focus:border-accent/60 md:h-12">
|
|
1598
1440
|
<option value="public">Público</option>
|
|
1599
1441
|
<option value="unlisted">Não listado</option>
|
|
1600
1442
|
<option value="private">Privado</option>
|
|
1601
1443
|
</select>
|
|
1602
1444
|
<p className="mt-2 text-[11px] text-slate-400">${visibilityHelp}</p>
|
|
1603
1445
|
</label>
|
|
1604
|
-
|
|
1605
1446
|
</div>
|
|
1606
1447
|
`
|
|
1607
1448
|
: null}
|
|
1608
|
-
|
|
1609
1449
|
${step === 2
|
|
1610
1450
|
? html`
|
|
1611
1451
|
<div className="space-y-3 md:space-y-4">
|
|
@@ -1619,11 +1459,7 @@ function CreatePackApp() {
|
|
|
1619
1459
|
className=${`rounded-2xl border border-dashed p-4 text-center transition md:rounded-3xl md:border-2 md:p-6 ${dragActive ? 'border-accent bg-accent/10' : 'border-line/70 bg-panelSoft/80'}`}
|
|
1620
1460
|
>
|
|
1621
1461
|
<p className="text-sm font-bold md:text-base">Arraste e solte seus stickers aqui</p>
|
|
1622
|
-
<p className="mt-1 text-xs text-slate-400">
|
|
1623
|
-
Imagens e vídeos até ${toBytesLabel(
|
|
1624
|
-
limits.sticker_upload_source_max_bytes,
|
|
1625
|
-
)} cada (conversão automática para .webp)
|
|
1626
|
-
</p>
|
|
1462
|
+
<p className="mt-1 text-xs text-slate-400">Imagens e vídeos até ${toBytesLabel(limits.sticker_upload_source_max_bytes)} cada (conversão automática para .webp)</p>
|
|
1627
1463
|
<input
|
|
1628
1464
|
id="webp-upload"
|
|
1629
1465
|
type="file"
|
|
@@ -1643,26 +1479,10 @@ function CreatePackApp() {
|
|
|
1643
1479
|
<span>Arraste para reordenar • toque para definir capa</span>
|
|
1644
1480
|
</div>
|
|
1645
1481
|
|
|
1646
|
-
${files.length
|
|
1647
|
-
? html`
|
|
1648
|
-
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 sm:gap-3 lg:grid-cols-4">
|
|
1649
|
-
${files.map((item, index) => html`<${StickerThumb}
|
|
1650
|
-
key=${item.id}
|
|
1651
|
-
item=${item}
|
|
1652
|
-
index=${index}
|
|
1653
|
-
selectedCoverId=${coverId}
|
|
1654
|
-
onSetCover=${setCoverId}
|
|
1655
|
-
onRemove=${removeSticker}
|
|
1656
|
-
onDragStart=${setDraggingStickerId}
|
|
1657
|
-
onDropOn=${(targetId) => reorderStickers(draggingStickerId, targetId)}
|
|
1658
|
-
/>`)}
|
|
1659
|
-
</div>
|
|
1660
|
-
`
|
|
1661
|
-
: html`<p className="rounded-2xl border border-line/70 bg-panelSoft/80 p-3 text-center text-sm text-slate-400 md:p-4">Nenhum sticker selecionado ainda.</p>`}
|
|
1482
|
+
${files.length ? html` <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 sm:gap-3 lg:grid-cols-4">${files.map((item, index) => html`<${StickerThumb} key=${item.id} item=${item} index=${index} selectedCoverId=${coverId} onSetCover=${setCoverId} onRemove=${removeSticker} onDragStart=${setDraggingStickerId} onDropOn=${(targetId) => reorderStickers(draggingStickerId, targetId)} />`)}</div> ` : html`<p className="rounded-2xl border border-line/70 bg-panelSoft/80 p-3 text-center text-sm text-slate-400 md:p-4">Nenhum sticker selecionado ainda.</p>`}
|
|
1662
1483
|
</div>
|
|
1663
1484
|
`
|
|
1664
1485
|
: null}
|
|
1665
|
-
|
|
1666
1486
|
${step === 3
|
|
1667
1487
|
? html`
|
|
1668
1488
|
<div className="space-y-3 md:space-y-4">
|
|
@@ -1672,9 +1492,7 @@ function CreatePackApp() {
|
|
|
1672
1492
|
<h3 className="font-display text-base font-bold md:text-lg">Revisão final</h3>
|
|
1673
1493
|
<p className="mt-0.5 text-xs text-slate-400">Confira os dados antes de publicar.</p>
|
|
1674
1494
|
</div>
|
|
1675
|
-
<span className="rounded-full border border-line/70 bg-panel/60 px-2.5 py-1 text-[11px] font-semibold text-slate-300">
|
|
1676
|
-
${files.length} stickers
|
|
1677
|
-
</span>
|
|
1495
|
+
<span className="rounded-full border border-line/70 bg-panel/60 px-2.5 py-1 text-[11px] font-semibold text-slate-300"> ${files.length} stickers </span>
|
|
1678
1496
|
</div>
|
|
1679
1497
|
<div className="mt-3 grid gap-1.5 text-sm text-slate-300">
|
|
1680
1498
|
<p className="truncate"><span className="text-slate-400">Nome:</span> <strong>${preview.name}</strong></p>
|
|
@@ -1693,38 +1511,18 @@ function CreatePackApp() {
|
|
|
1693
1511
|
<div className="mt-2 h-2 overflow-hidden rounded-full bg-slate-900/70">
|
|
1694
1512
|
<div className="h-full bg-accent transition-all" style=${{ width: `${uploadProgressPercent}%` }}></div>
|
|
1695
1513
|
</div>
|
|
1696
|
-
<p className="mt-2 text-xs text-slate-400">
|
|
1697
|
-
${publishPhase === 'creating'
|
|
1698
|
-
? 'Criando pack...'
|
|
1699
|
-
: publishPhase === 'uploading'
|
|
1700
|
-
? `${uploadProgressDone}/${uploadProgressTotal || files.length || 0} enviados`
|
|
1701
|
-
: publishPhase === 'processing'
|
|
1702
|
-
? 'Validando consistência e capa do pack...'
|
|
1703
|
-
: publishPhase === 'publishing'
|
|
1704
|
-
? 'Publicando pack no marketplace...'
|
|
1705
|
-
: `${uploadProgressDone}/${uploadProgressTotal || files.length || 0} concluídos`}
|
|
1706
|
-
</p>
|
|
1514
|
+
<p className="mt-2 text-xs text-slate-400">${publishPhase === 'creating' ? 'Criando pack...' : publishPhase === 'uploading' ? `${uploadProgressDone}/${uploadProgressTotal || files.length || 0} enviados` : publishPhase === 'processing' ? 'Validando consistência e capa do pack...' : publishPhase === 'publishing' ? 'Publicando pack no marketplace...' : `${uploadProgressDone}/${uploadProgressTotal || files.length || 0} concluídos`}</p>
|
|
1707
1515
|
</div>
|
|
1708
1516
|
`
|
|
1709
1517
|
: null}
|
|
1710
|
-
|
|
1711
1518
|
${showUploadFailureCard
|
|
1712
1519
|
? html`
|
|
1713
1520
|
<div className="rounded-2xl border border-rose-400/25 bg-rose-400/5 p-3 text-sm">
|
|
1714
|
-
<p className="font-semibold text-rose-200">
|
|
1715
|
-
|
|
1716
|
-
? 'O pack entrou em estado de falha no backend.'
|
|
1717
|
-
: `${failedUploadsCount} sticker(s) falharam no envio.`}
|
|
1718
|
-
</p>
|
|
1719
|
-
<p className="mt-1 text-xs text-rose-200/80">
|
|
1720
|
-
${backendStateFailed
|
|
1721
|
-
? `Use "${publishLabel}" para reparar e concluir a publicação.`
|
|
1722
|
-
: `Toque em "${publishLabel}" para reenviar apenas as falhas.`}
|
|
1723
|
-
</p>
|
|
1521
|
+
<p className="font-semibold text-rose-200">${backendStateFailed ? 'O pack entrou em estado de falha no backend.' : `${failedUploadsCount} sticker(s) falharam no envio.`}</p>
|
|
1522
|
+
<p className="mt-1 text-xs text-rose-200/80">${backendStateFailed ? `Use "${publishLabel}" para reparar e concluir a publicação.` : `Toque em "${publishLabel}" para reenviar apenas as falhas.`}</p>
|
|
1724
1523
|
</div>
|
|
1725
1524
|
`
|
|
1726
1525
|
: null}
|
|
1727
|
-
|
|
1728
1526
|
${publishCompleted
|
|
1729
1527
|
? html`
|
|
1730
1528
|
<div className="rounded-2xl border border-emerald-400/25 bg-emerald-400/5 p-3 text-sm text-emerald-100 md:p-4">
|
|
@@ -1753,98 +1551,37 @@ function CreatePackApp() {
|
|
|
1753
1551
|
|
|
1754
1552
|
<div className="mt-3 lg:hidden">
|
|
1755
1553
|
<div className="rounded-2xl border border-line/70 bg-panel/80 p-3">
|
|
1756
|
-
<button
|
|
1757
|
-
type="button"
|
|
1758
|
-
onClick=${toggleMobilePreview}
|
|
1759
|
-
className="flex h-11 w-full items-center justify-between gap-3 rounded-xl border border-line/70 bg-panelSoft/70 px-3 text-left"
|
|
1760
|
-
aria-expanded=${mobilePreviewOpen ? 'true' : 'false'}
|
|
1761
|
-
>
|
|
1554
|
+
<button type="button" onClick=${toggleMobilePreview} className="flex h-11 w-full items-center justify-between gap-3 rounded-xl border border-line/70 bg-panelSoft/70 px-3 text-left" aria-expanded=${mobilePreviewOpen ? 'true' : 'false'}>
|
|
1762
1555
|
<div>
|
|
1763
1556
|
<p className="text-xs font-semibold uppercase tracking-[.08em] text-slate-400">Preview</p>
|
|
1764
1557
|
<p className="text-sm font-semibold text-slate-100">${preview.name}</p>
|
|
1765
1558
|
</div>
|
|
1766
1559
|
<span className="text-xs font-semibold text-accent">${mobilePreviewOpen ? 'Ocultar' : 'Mostrar'}</span>
|
|
1767
1560
|
</button>
|
|
1768
|
-
${mobilePreviewOpen
|
|
1769
|
-
? html`<div className="mt-3"><${PackPreviewPanel} preview=${preview} quality=${quality} compact=${true} /></div>`
|
|
1770
|
-
: html`<p className="mt-2 text-xs text-slate-400">Toque para visualizar capa, descrição e score do pack.</p>`}
|
|
1561
|
+
${mobilePreviewOpen ? html`<div className="mt-3"><${PackPreviewPanel} preview=${preview} quality=${quality} compact=${true} /></div>` : html`<p className="mt-2 text-xs text-slate-400">Toque para visualizar capa, descrição e score do pack.</p>`}
|
|
1771
1562
|
</div>
|
|
1772
1563
|
</div>
|
|
1773
1564
|
|
|
1774
|
-
${error
|
|
1775
|
-
? html`<div className="mt-3 rounded-2xl border border-rose-400/25 bg-rose-400/5 px-3 py-2.5 text-sm text-rose-200 md:mt-4 md:px-4 md:py-3">${error}</div>`
|
|
1776
|
-
: null}
|
|
1565
|
+
${error ? html`<div className="mt-3 rounded-2xl border border-rose-400/25 bg-rose-400/5 px-3 py-2.5 text-sm text-rose-200 md:mt-4 md:px-4 md:py-3">${error}</div>` : null}
|
|
1777
1566
|
</div>
|
|
1778
1567
|
|
|
1779
1568
|
<div className="fixed inset-x-0 bottom-0 z-30 border-t border-line/70 bg-panel/95 p-3 backdrop-blur md:hidden">
|
|
1780
1569
|
<div className="mx-auto w-full max-w-7xl">
|
|
1781
1570
|
<div className="mb-2 flex items-center justify-between gap-2">
|
|
1782
|
-
<button
|
|
1783
|
-
|
|
1784
|
-
className="h-8 rounded-full border border-line/70 bg-panelSoft/80 px-3 text-xs font-semibold text-slate-200 disabled:opacity-60"
|
|
1785
|
-
onClick=${restartCreateFlow}
|
|
1786
|
-
disabled=${busy}
|
|
1787
|
-
title="Limpar rascunho local"
|
|
1788
|
-
>
|
|
1789
|
-
Recomeçar
|
|
1790
|
-
</button>
|
|
1791
|
-
<button
|
|
1792
|
-
type="button"
|
|
1793
|
-
className="h-8 rounded-full border border-line/70 bg-panelSoft/60 px-3 text-xs font-semibold text-slate-300"
|
|
1794
|
-
onClick=${toggleMobilePreview}
|
|
1795
|
-
aria-expanded=${mobilePreviewOpen ? 'true' : 'false'}
|
|
1796
|
-
>
|
|
1797
|
-
${mobilePreviewOpen ? 'Ocultar preview' : 'Preview'}
|
|
1798
|
-
</button>
|
|
1571
|
+
<button type="button" className="h-8 rounded-full border border-line/70 bg-panelSoft/80 px-3 text-xs font-semibold text-slate-200 disabled:opacity-60" onClick=${restartCreateFlow} disabled=${busy} title="Limpar rascunho local">Recomeçar</button>
|
|
1572
|
+
<button type="button" className="h-8 rounded-full border border-line/70 bg-panelSoft/60 px-3 text-xs font-semibold text-slate-300" onClick=${toggleMobilePreview} aria-expanded=${mobilePreviewOpen ? 'true' : 'false'}>${mobilePreviewOpen ? 'Ocultar preview' : 'Preview'}</button>
|
|
1799
1573
|
</div>
|
|
1800
1574
|
<div className="grid grid-cols-[1fr_1.45fr] gap-2">
|
|
1801
|
-
|
|
1802
|
-
type="button"
|
|
1803
|
-
className="h-11 rounded-xl border border-line/70 bg-panelSoft/80 text-sm font-bold disabled:opacity-60"
|
|
1804
|
-
onClick=${prevStep}
|
|
1805
|
-
disabled=${step === 1 || busy}
|
|
1806
|
-
>
|
|
1807
|
-
Voltar
|
|
1808
|
-
</button>
|
|
1809
|
-
${step < 3
|
|
1810
|
-
? html`
|
|
1811
|
-
<button
|
|
1812
|
-
type="button"
|
|
1813
|
-
className=${`h-11 rounded-xl text-sm font-extrabold disabled:opacity-60 ${mobilePrimaryActionClass}`}
|
|
1814
|
-
onClick=${nextStep}
|
|
1815
|
-
disabled=${busy}
|
|
1816
|
-
>
|
|
1817
|
-
${mobilePrimaryActionLabel}
|
|
1818
|
-
</button>
|
|
1819
|
-
`
|
|
1820
|
-
: html`
|
|
1821
|
-
<button
|
|
1822
|
-
type="button"
|
|
1823
|
-
className=${`h-11 rounded-xl text-sm font-extrabold disabled:opacity-60 ${mobilePrimaryActionClass}`}
|
|
1824
|
-
onClick=${handleFinalStepPrimaryAction}
|
|
1825
|
-
disabled=${finalStepPrimaryDisabled}
|
|
1826
|
-
>
|
|
1827
|
-
${mobilePrimaryActionLabel}
|
|
1828
|
-
</button>
|
|
1829
|
-
`}
|
|
1575
|
+
<button type="button" className="h-11 rounded-xl border border-line/70 bg-panelSoft/80 text-sm font-bold disabled:opacity-60" onClick=${prevStep} disabled=${step === 1 || busy}>Voltar</button>
|
|
1576
|
+
${step < 3 ? html` <button type="button" className=${`h-11 rounded-xl text-sm font-extrabold disabled:opacity-60 ${mobilePrimaryActionClass}`} onClick=${nextStep} disabled=${busy}>${mobilePrimaryActionLabel}</button> ` : html` <button type="button" className=${`h-11 rounded-xl text-sm font-extrabold disabled:opacity-60 ${mobilePrimaryActionClass}`} onClick=${handleFinalStepPrimaryAction} disabled=${finalStepPrimaryDisabled}>${mobilePrimaryActionLabel}</button> `}
|
|
1830
1577
|
</div>
|
|
1831
1578
|
</div>
|
|
1832
1579
|
</div>
|
|
1833
1580
|
|
|
1834
1581
|
<div className="mt-6 hidden items-center justify-end gap-2 px-6 pb-6 md:flex">
|
|
1835
|
-
<button
|
|
1836
|
-
type="button"
|
|
1837
|
-
className="h-10 rounded-xl border border-line/70 bg-panelSoft/80 px-4 text-sm font-bold disabled:opacity-60"
|
|
1838
|
-
onClick=${restartCreateFlow}
|
|
1839
|
-
disabled=${busy}
|
|
1840
|
-
title="Limpar rascunho local e recomeçar"
|
|
1841
|
-
>
|
|
1842
|
-
Recomeçar
|
|
1843
|
-
</button>
|
|
1582
|
+
<button type="button" className="h-10 rounded-xl border border-line/70 bg-panelSoft/80 px-4 text-sm font-bold disabled:opacity-60" onClick=${restartCreateFlow} disabled=${busy} title="Limpar rascunho local e recomeçar">Recomeçar</button>
|
|
1844
1583
|
<button type="button" className="h-11 rounded-xl border border-line/70 bg-panelSoft/80 px-5 text-sm font-bold" onClick=${prevStep} disabled=${step === 1 || busy}>Voltar</button>
|
|
1845
|
-
${step < 3
|
|
1846
|
-
? html`<button type="button" className="h-11 rounded-xl bg-accent px-5 text-sm font-extrabold text-slate-900" onClick=${nextStep} disabled=${busy}>Próximo passo</button>`
|
|
1847
|
-
: html`<button type="button" className="h-11 rounded-xl bg-accent2 px-5 text-sm font-extrabold text-slate-900 disabled:opacity-60" onClick=${handleFinalStepPrimaryAction} disabled=${finalStepPrimaryDisabled}>${finalStepPrimaryLabel}</button>`}
|
|
1584
|
+
${step < 3 ? html`<button type="button" className="h-11 rounded-xl bg-accent px-5 text-sm font-extrabold text-slate-900" onClick=${nextStep} disabled=${busy}>Próximo passo</button>` : html`<button type="button" className="h-11 rounded-xl bg-accent2 px-5 text-sm font-extrabold text-slate-900 disabled:opacity-60" onClick=${handleFinalStepPrimaryAction} disabled=${finalStepPrimaryDisabled}>${finalStepPrimaryLabel}</button>`}
|
|
1848
1585
|
</div>
|
|
1849
1586
|
</div>
|
|
1850
1587
|
`;
|