@kaikybrofc/omnizap-system 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +13 -13
  2. package/app/controllers/messageController.js +473 -255
  3. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  4. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +25 -5
  5. package/app/observability/metrics.js +6 -3
  6. package/app/services/googleWebLinkService.js +77 -0
  7. package/database/index.js +3 -0
  8. package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
  9. package/database/migrations/20260301_0028_message_analysis_event.sql +32 -0
  10. package/database/migrations/20260301_0029_admin_action_audit.sql +16 -0
  11. package/package.json +1 -1
  12. package/public/index.html +53 -59
  13. package/public/js/apps/homeApp.js +259 -61
  14. package/public/js/apps/loginApp.js +184 -29
  15. package/public/js/apps/stickersAdminApp.js +3 -9
  16. package/public/js/apps/stickersApp.js +0 -28
  17. package/public/js/apps/userApp.js +1160 -14
  18. package/public/js/apps/userProfileApp.js +244 -0
  19. package/public/licenca/index.html +98 -2
  20. package/public/login/index.html +430 -100
  21. package/public/termos-de-uso/index.html +245 -25
  22. package/public/user/index.html +3 -1
  23. package/public/user/systemadm/index.html +774 -0
  24. package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
  25. package/server/controllers/stickerCatalog/nonCatalogHandlers.js +208 -0
  26. package/server/controllers/stickerCatalogController.js +1350 -924
  27. package/server/controllers/systemAdminController.js +141 -0
  28. package/server/controllers/userController.js +87 -0
  29. package/server/http/httpServer.js +72 -32
  30. package/server/middleware/cachePolicy.js +24 -0
  31. package/server/middleware/cachePolicyHelpers.js +2 -0
  32. package/server/middleware/rateLimit.js +82 -0
  33. package/server/middleware/requestLogger.js +16 -0
  34. package/server/middleware/requireAdminAuth.js +42 -0
  35. package/server/middleware/securityHeaders.js +6 -0
  36. package/server/routes/admin/systemAdminRouter.js +56 -0
  37. package/server/routes/health/healthRouter.js +41 -0
  38. package/server/routes/indexRouter.js +203 -0
  39. package/server/routes/metrics/metricsRouter.js +13 -0
  40. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +44 -0
  41. package/server/routes/stickerCatalog/stickerApiRouter.js +84 -0
  42. package/server/routes/stickerCatalog/stickerDataRouter.js +140 -0
  43. package/server/routes/stickerCatalog/stickerSiteRouter.js +43 -0
  44. package/server/routes/user/userRouter.js +56 -0
  45. package/server/utils/safePath.js +26 -0
  46. package/server/routes/metricsRoute.js +0 -7
  47. package/server/routes/stickerCatalogRoute.js +0 -20
@@ -2,6 +2,12 @@
2
2
 
3
3
  const GOOGLE_GSI_SCRIPT_SRC = 'https://accounts.google.com/gsi/client';
4
4
  const DEFAULT_API_BASE_PATH = '/api/sticker-packs';
5
+ const LOGIN_CONSENT_STORAGE_KEY = 'omnizap_login_terms_consent_v1';
6
+ const LOGIN_CONSENT_HINT = 'Aceite os Termos de Uso e a Politica de Privacidade para continuar.';
7
+ const DEFAULT_SUCCESS_CHAT_LABEL = 'Abrir WhatsApp do bot';
8
+ const DEFAULT_SUCCESS_HOME_LABEL = 'Ir para o painel';
9
+ const ALREADY_LOGGED_STATUS_TEXT = 'Voce ja esta logado neste navegador.';
10
+ const ALREADY_LOGGED_HINT_TEXT = 'Nao e necessario fazer login novamente. Escolha uma opcao abaixo.';
5
11
 
6
12
  const root = document.getElementById('login-app-root');
7
13
 
@@ -13,7 +19,14 @@ if (root) {
13
19
  googleArea: document.getElementById('google-login-area'),
14
20
  googleButton: document.querySelector('[data-google-login-button]'),
15
21
  googleState: document.getElementById('google-login-state'),
22
+ consentBox: document.getElementById('login-consent-box'),
23
+ consentCheckbox: document.getElementById('login-consent-checkbox'),
24
+ consentError: document.getElementById('login-consent-error'),
25
+ alreadyLoggedBanner: document.getElementById('already-logged-banner'),
26
+ alreadyLoggedTitle: document.getElementById('already-logged-title'),
27
+ alreadyLoggedDetail: document.getElementById('already-logged-detail'),
16
28
  summary: document.getElementById('login-summary'),
29
+ summaryTitle: document.getElementById('login-summary-title'),
17
30
  summaryOwner: document.getElementById('login-summary-owner'),
18
31
  whatsappCta: document.getElementById('whatsapp-cta'),
19
32
  whatsappCtaLink: document.getElementById('whatsapp-cta-link'),
@@ -21,6 +34,7 @@ if (root) {
21
34
  successActions: document.getElementById('login-success-actions'),
22
35
  successChat: document.getElementById('login-success-chat'),
23
36
  successHome: document.getElementById('login-success-home'),
37
+ successCelebration: document.getElementById('login-success-celebration'),
24
38
  };
25
39
 
26
40
  const state = {
@@ -30,6 +44,8 @@ if (root) {
30
44
  googleReady: false,
31
45
  busy: false,
32
46
  authenticated: false,
47
+ consentAccepted: false,
48
+ successAnimationTimer: 0,
33
49
  botPhone: '',
34
50
  sessionOwnerPhone: '',
35
51
  hint: readWhatsAppHintFromUrl(window.location.search),
@@ -52,13 +68,67 @@ if (root) {
52
68
  if (safe) ui.error.textContent = safe;
53
69
  };
54
70
 
71
+ const showConsentError = (message) => {
72
+ if (!ui.consentError) return;
73
+ const safe = String(message || '').trim();
74
+ ui.consentError.hidden = !safe;
75
+ if (safe) ui.consentError.textContent = safe;
76
+ };
77
+
78
+ const persistConsentState = (accepted) => {
79
+ try {
80
+ if (accepted) {
81
+ window.localStorage.setItem(LOGIN_CONSENT_STORAGE_KEY, '1');
82
+ } else {
83
+ window.localStorage.removeItem(LOGIN_CONSENT_STORAGE_KEY);
84
+ }
85
+ } catch {}
86
+ };
87
+
88
+ const readConsentState = () => {
89
+ try {
90
+ return window.localStorage.getItem(LOGIN_CONSENT_STORAGE_KEY) === '1';
91
+ } catch {
92
+ return false;
93
+ }
94
+ };
95
+
96
+ const hideSuccessCelebration = () => {
97
+ if (!ui.successCelebration) return;
98
+ ui.successCelebration.classList.remove('is-visible');
99
+ window.setTimeout(() => {
100
+ if (!ui.successCelebration?.classList.contains('is-visible')) {
101
+ ui.successCelebration.hidden = true;
102
+ }
103
+ }, 320);
104
+ };
105
+
106
+ const playSuccessCelebration = () => {
107
+ if (!ui.successCelebration) return;
108
+ if (state.successAnimationTimer) {
109
+ window.clearTimeout(state.successAnimationTimer);
110
+ state.successAnimationTimer = 0;
111
+ }
112
+
113
+ ui.successCelebration.hidden = false;
114
+ ui.successCelebration.classList.remove('is-visible');
115
+ void ui.successCelebration.offsetWidth;
116
+ ui.successCelebration.classList.add('is-visible');
117
+
118
+ state.successAnimationTimer = window.setTimeout(() => {
119
+ hideSuccessCelebration();
120
+ state.successAnimationTimer = 0;
121
+ }, 2300);
122
+ };
123
+
55
124
  const setBusy = (value) => {
56
125
  state.busy = Boolean(value);
57
126
  if (ui.googleButton) {
58
- ui.googleButton.style.opacity = state.busy ? '0.55' : '1';
59
- ui.googleButton.style.pointerEvents = state.busy ? 'none' : 'auto';
127
+ const canInteract = !state.busy && state.consentAccepted;
128
+ ui.googleButton.style.opacity = canInteract ? '1' : '0.55';
129
+ ui.googleButton.style.pointerEvents = canInteract ? 'auto' : 'none';
60
130
  }
61
- setText(ui.googleState, state.busy ? 'Finalizando login...' : state.googleReady ? '' : 'Carregando login Google...');
131
+ setText(ui.googleState, state.busy ? 'Finalizando login...' : state.consentAccepted ? (state.googleReady ? '' : 'Carregando login Google...') : LOGIN_CONSENT_HINT);
62
132
  };
63
133
 
64
134
  const formatPhone = (digits) => {
@@ -102,20 +172,67 @@ if (root) {
102
172
 
103
173
  const canUseGoogleLogin = () => Boolean(state.hint.phone);
104
174
 
105
- const renderSuccessActions = (sessionData) => {
175
+ const renderSuccessActions = (sessionData, options = {}) => {
106
176
  if (!ui.successActions) return;
107
177
  const authenticated = Boolean(sessionData?.authenticated);
108
178
  ui.successActions.hidden = !authenticated;
109
179
  if (!authenticated) return;
110
180
 
181
+ const chatLabel = String(options.chatLabel || DEFAULT_SUCCESS_CHAT_LABEL);
182
+ const homeLabel = String(options.homeLabel || DEFAULT_SUCCESS_HOME_LABEL);
183
+ const homeHref = String(options.homeHref || '/user/');
184
+
111
185
  if (ui.successChat) {
112
186
  ui.successChat.href = buildWhatsappMenuUrl(state.botPhone);
187
+ setText(ui.successChat, chatLabel);
113
188
  }
114
189
  if (ui.successHome) {
115
- ui.successHome.href = '/';
190
+ ui.successHome.href = homeHref;
191
+ setText(ui.successHome, homeLabel);
116
192
  }
117
193
  };
118
194
 
195
+ const renderAlreadyLoggedBanner = ({ visible = false, ownerPhone = '' } = {}) => {
196
+ if (!ui.alreadyLoggedBanner) return;
197
+ ui.alreadyLoggedBanner.hidden = !visible;
198
+ if (!visible) return;
199
+ setText(ui.alreadyLoggedTitle, 'Voce ja esta logado neste navegador.');
200
+ if (ownerPhone) {
201
+ setText(ui.alreadyLoggedDetail, `Sessao ativa para +${formatPhone(ownerPhone)}. Nao e necessario fazer login novamente.`);
202
+ return;
203
+ }
204
+ setText(ui.alreadyLoggedDetail, ALREADY_LOGGED_HINT_TEXT);
205
+ };
206
+
207
+ const renderAlreadyLoggedInState = (sessionData) => {
208
+ const ownerPhone = String(sessionData?.owner_phone || '').trim();
209
+ state.authenticated = true;
210
+ state.sessionOwnerPhone = ownerPhone;
211
+
212
+ setText(ui.status, ALREADY_LOGGED_STATUS_TEXT);
213
+ if (ownerPhone) {
214
+ setText(ui.hint, `Sessao ativa para +${formatPhone(ownerPhone)}.`);
215
+ } else {
216
+ setText(ui.hint, ALREADY_LOGGED_HINT_TEXT);
217
+ }
218
+
219
+ showError('');
220
+ showConsentError('');
221
+ renderAlreadyLoggedBanner({ visible: true, ownerPhone });
222
+ hideSuccessCelebration();
223
+ if (ui.googleArea) ui.googleArea.hidden = true;
224
+ if (ui.summary) ui.summary.hidden = true;
225
+ if (ui.whatsappCta) ui.whatsappCta.hidden = true;
226
+ renderSuccessActions(
227
+ { authenticated: true },
228
+ {
229
+ chatLabel: 'Abrir WPP no bot',
230
+ homeLabel: 'Voltar para tela inicial',
231
+ homeHref: '/',
232
+ },
233
+ );
234
+ };
235
+
119
236
  const renderWhatsAppCta = () => {
120
237
  if (!ui.whatsappCta || !ui.whatsappCtaLink) return;
121
238
 
@@ -146,19 +263,20 @@ if (root) {
146
263
 
147
264
  const renderHint = () => {
148
265
  if (!state.hint.hasPayload) {
149
- setText(ui.hint, 'Voce abriu esta pagina direto. Por seguranca, gere seu link no WhatsApp clicando no botao abaixo e enviando "iniciar".');
266
+ setText(ui.hint, 'Abra o link que o bot envia no WhatsApp para liberar o login neste navegador.');
150
267
  return;
151
268
  }
152
269
 
153
270
  if (!state.hint.phone) {
154
- setText(ui.hint, 'Este link nao trouxe um numero de WhatsApp valido. Ele pode ter sido alterado ou expirado. Gere um novo link enviando "iniciar".');
271
+ setText(ui.hint, 'Este link nao tem um numero valido. Gere um novo enviando "iniciar" no bot.');
155
272
  return;
156
273
  }
157
274
 
158
- setText(ui.hint, `Numero detectado para vinculo: +${formatPhone(state.hint.phone)}.`);
275
+ setText(ui.hint, `Numero detectado: +${formatPhone(state.hint.phone)}.`);
159
276
  };
160
277
 
161
278
  const renderSessionSummary = (sessionData) => {
279
+ renderAlreadyLoggedBanner({ visible: false });
162
280
  if (!canUseGoogleLogin()) {
163
281
  state.authenticated = false;
164
282
  state.sessionOwnerPhone = '';
@@ -181,18 +299,24 @@ if (root) {
181
299
  const ownerPhone = String(sessionData?.owner_phone || '').trim();
182
300
  state.sessionOwnerPhone = ownerPhone;
183
301
  if (ownerPhone) {
184
- setText(ui.summaryOwner, `WhatsApp vinculado: +${formatPhone(ownerPhone)}`);
302
+ if (ui.summary) ui.summary.dataset.state = 'ok';
303
+ setText(ui.summaryTitle, 'WhatsApp conectado');
304
+ setText(ui.summaryOwner, `+${formatPhone(ownerPhone)}`);
185
305
  renderWhatsAppCta();
186
306
  return;
187
307
  }
188
308
 
189
309
  if (state.hint.phone) {
190
- setText(ui.summaryOwner, `Numero detectado: +${formatPhone(state.hint.phone)}. Clique no botao do Google para concluir o vinculo.`);
310
+ if (ui.summary) ui.summary.dataset.state = 'pending';
311
+ setText(ui.summaryTitle, 'Vinculo em andamento');
312
+ setText(ui.summaryOwner, `Numero detectado: +${formatPhone(state.hint.phone)}`);
191
313
  renderWhatsAppCta();
192
314
  return;
193
315
  }
194
316
 
195
- setText(ui.summaryOwner, 'Conta Google ativa. Vincule o WhatsApp abrindo o link pelo "iniciar".');
317
+ if (ui.summary) ui.summary.dataset.state = 'ok';
318
+ setText(ui.summaryTitle, 'Conta ativa');
319
+ setText(ui.summaryOwner, 'Abra o link do WhatsApp para concluir o vinculo.');
196
320
  renderWhatsAppCta();
197
321
  };
198
322
 
@@ -231,7 +355,25 @@ if (root) {
231
355
  return payload;
232
356
  };
233
357
 
358
+ const syncConsentState = () => {
359
+ state.consentAccepted = Boolean(ui.consentCheckbox?.checked);
360
+ persistConsentState(state.consentAccepted);
361
+ if (ui.consentBox) {
362
+ ui.consentBox.classList.toggle('is-checked', state.consentAccepted);
363
+ }
364
+ if (state.consentAccepted) {
365
+ showConsentError('');
366
+ }
367
+ setBusy(state.busy);
368
+ };
369
+
234
370
  const handleGoogleCredential = async (credential) => {
371
+ if (!state.consentAccepted) {
372
+ showConsentError(LOGIN_CONSENT_HINT);
373
+ setBusy(false);
374
+ return;
375
+ }
376
+
235
377
  const token = String(credential || '').trim();
236
378
  if (!token) {
237
379
  showError('Falha ao receber token do Google. Tente novamente.');
@@ -252,13 +394,15 @@ if (root) {
252
394
  if (!sessionData?.authenticated) {
253
395
  throw new Error('Nao foi possivel criar a sessao Google.');
254
396
  }
255
- setText(ui.status, 'Login concluido. Seu acesso foi vinculado com sucesso.');
397
+ setText(ui.status, 'Conta Google detectada');
256
398
  renderSessionSummary(sessionData);
257
399
  setText(ui.googleState, 'Login Google ativo.');
400
+ playSuccessCelebration();
258
401
  } catch (error) {
259
402
  showError(error?.message || 'Falha ao concluir login Google.');
260
- setText(ui.status, 'Nao foi possivel concluir o login agora.');
403
+ setText(ui.status, 'Falha ao validar conta Google');
261
404
  renderSessionSummary(null);
405
+ hideSuccessCelebration();
262
406
  } finally {
263
407
  setBusy(false);
264
408
  }
@@ -314,18 +458,18 @@ if (root) {
314
458
  const buttonWidth = Math.max(180, Math.min(320, measuredWidth || 320));
315
459
  accounts.renderButton(ui.googleButton, {
316
460
  type: 'standard',
317
- theme: 'filled_black',
461
+ theme: 'outline',
318
462
  size: 'large',
319
- text: 'signin_with',
463
+ text: 'continue_with',
320
464
  shape: 'pill',
321
465
  width: buttonWidth,
322
466
  });
323
467
 
324
468
  state.googleReady = true;
325
- setText(ui.googleState, '');
469
+ setBusy(state.busy);
326
470
  } catch (error) {
327
471
  state.googleReady = false;
328
- setText(ui.googleState, '');
472
+ setBusy(state.busy);
329
473
  showError(error?.message || 'Falha ao carregar login Google.');
330
474
  }
331
475
  };
@@ -382,24 +526,27 @@ if (root) {
382
526
  };
383
527
 
384
528
  const loadCurrentSession = async () => {
385
- if (!canUseGoogleLogin()) {
386
- renderSessionSummary(null);
387
- return;
388
- }
389
529
  try {
390
530
  const payload = await fetchJson(sessionApiPath, { method: 'GET' });
391
531
  const sessionData = payload?.data || {};
392
532
  if (sessionData?.authenticated) {
393
- setText(ui.status, 'Sessao Google ativa neste navegador.');
394
- renderSessionSummary(sessionData);
533
+ renderAlreadyLoggedInState(sessionData);
534
+ return true;
535
+ }
536
+ if (canUseGoogleLogin()) {
537
+ setText(ui.status, 'Entre com Google para continuar');
395
538
  } else {
396
- setText(ui.status, 'Use o login Google abaixo para entrar no OmniZap.');
397
- renderSessionSummary(null);
539
+ state.authenticated = false;
540
+ state.sessionOwnerPhone = '';
398
541
  }
542
+ renderSessionSummary(null);
399
543
  } catch {
400
- setText(ui.status, 'Nao foi possivel validar sua sessao atual.');
544
+ if (canUseGoogleLogin()) {
545
+ setText(ui.status, 'Nao foi possivel validar sua sessao');
546
+ }
401
547
  renderSessionSummary(null);
402
548
  }
549
+ return false;
403
550
  };
404
551
 
405
552
  const renderGoogleLoginGate = () => {
@@ -408,7 +555,10 @@ if (root) {
408
555
  ui.googleArea.hidden = !allowed;
409
556
  }
410
557
  if (!allowed) {
411
- setText(ui.status, 'Para fazer login, abra esta pagina pelo link do WhatsApp (envie "iniciar" no bot).');
558
+ renderAlreadyLoggedBanner({ visible: false });
559
+ }
560
+ if (!allowed) {
561
+ setText(ui.status, 'Abra o link enviado no WhatsApp para continuar');
412
562
  setText(ui.googleState, '');
413
563
  if (ui.summary) ui.summary.hidden = true;
414
564
  renderSuccessActions(null);
@@ -418,6 +568,11 @@ if (root) {
418
568
  };
419
569
 
420
570
  const init = async () => {
571
+ if (ui.consentCheckbox) {
572
+ ui.consentCheckbox.checked = readConsentState();
573
+ ui.consentCheckbox.addEventListener('change', syncConsentState);
574
+ }
575
+ syncConsentState();
421
576
  renderHint();
422
577
  if (ui.whatsappCta && state.hint.phone) {
423
578
  ui.whatsappCta.hidden = true;
@@ -426,9 +581,9 @@ if (root) {
426
581
  renderSuccessActions(null);
427
582
  const allowGoogleLogin = renderGoogleLoginGate();
428
583
  await loadBotPhone();
429
- if (!allowGoogleLogin) return;
584
+ const alreadyLogged = await loadCurrentSession();
585
+ if (alreadyLogged || !allowGoogleLogin) return;
430
586
  await loadConfig();
431
- await loadCurrentSession();
432
587
  await mountGoogleButton();
433
588
  };
434
589
 
@@ -890,7 +890,7 @@ function renderSystemTab() {
890
890
  <h3 class="panel-title">Autenticacao admin</h3>
891
891
  </div>
892
892
  <div class="kv-grid">
893
- <div class="kv-item"><span class="kv-key">Admin email</span><span class="kv-value break-all">${escapeHtml(state.adminStatus?.admin_email || '-')}</span></div>
893
+ <div class="kv-item"><span class="kv-key">Login provider</span><span class="kv-value">${escapeHtml(state.adminStatus?.session?.authenticated ? 'Google + senha' : 'Google')}</span></div>
894
894
  <div class="kv-item"><span class="kv-key">Google session</span><span class="kv-value">${authGoogle?.authenticated ? 'Ativa' : 'Inativa'}</span></div>
895
895
  <div class="kv-item"><span class="kv-key">Elegivel</span><span class="kv-value">${canUnlockAdmin() ? 'Sim' : 'Nao'}</span></div>
896
896
  <div class="kv-item"><span class="kv-key">Seu papel</span><span class="kv-value">${escapeHtml(getAdminRole() || 'owner')}</span></div>
@@ -1060,7 +1060,6 @@ function renderUnlockView() {
1060
1060
  const googleSession = google?.user ? google : { authenticated: false };
1061
1061
  const localGoogleCache = readLocalGoogleAuthCache();
1062
1062
  const hasLocalCache = Boolean(localGoogleCache?.user?.sub);
1063
- const adminEmail = String(adminStatus?.admin_email || 'ADM_EMAIL');
1064
1063
  const googleConfigEnabled = Boolean(state.googleAuthConfig?.enabled && state.googleAuthConfig?.clientId);
1065
1064
 
1066
1065
  return `
@@ -1087,7 +1086,7 @@ function renderUnlockView() {
1087
1086
  <div class="account-box">
1088
1087
  <p class="row-title">${escapeHtml(googleSession.user?.name || 'Conta Google')}</p>
1089
1088
  <p class="row-sub break-all">${escapeHtml(googleSession.user?.email || '')}</p>
1090
- <p class="row-meta">${canUnlockAdmin() ? 'Conta elegivel para admin.' : 'Conta Google logada nao bate com ADM_EMAIL.'}</p>
1089
+ <p class="row-meta">${canUnlockAdmin() ? 'Conta elegivel para admin.' : 'Conta Google logada nao esta elegivel para admin.'}</p>
1091
1090
  </div>
1092
1091
  `
1093
1092
  : `
@@ -1117,7 +1116,7 @@ function renderUnlockView() {
1117
1116
  <form data-form="admin-unlock" class="form-grid">
1118
1117
  <input class="search-input" type="password" name="password" placeholder="Digite a senha do painel" autocomplete="current-password" />
1119
1118
  <button class="primary-btn" type="submit" ${state.busy ? 'disabled' : ''}>${state.busy ? 'Validando...' : 'Desbloquear Painel'}</button>
1120
- ${!canUnlockAdmin() ? `<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (${escapeHtml(adminEmail)} ou moderador autorizado).</p>` : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
1119
+ ${!canUnlockAdmin() ? '<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (owner ou moderador autorizado).</p>' : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
1121
1120
  </form>
1122
1121
  </article>
1123
1122
  </div>
@@ -1318,8 +1317,6 @@ async function unlockAdmin(password) {
1318
1317
  async () => {
1319
1318
  if (!canUnlockAdmin()) {
1320
1319
  const google = state.adminStatus?.google || {};
1321
- const adminEmail = String(state.adminStatus?.admin_email || '').trim();
1322
- const loggedEmail = String(google?.user?.email || '').trim();
1323
1320
  if (!google?.authenticated) {
1324
1321
  const local = readLocalGoogleAuthCache();
1325
1322
  if (local?.user?.email) {
@@ -1327,9 +1324,6 @@ async function unlockAdmin(password) {
1327
1324
  }
1328
1325
  throw new Error('Faca login Google no site com o email admin antes de digitar a senha.');
1329
1326
  }
1330
- if (loggedEmail && adminEmail && loggedEmail.toLowerCase() !== adminEmail.toLowerCase()) {
1331
- throw new Error(`Email logado (${loggedEmail}) e diferente do ADM_EMAIL (${adminEmail}).`);
1332
- }
1333
1327
  throw new Error('Conta Google atual nao esta elegivel para desbloquear o painel.');
1334
1328
  }
1335
1329
 
@@ -1729,7 +1729,6 @@ function PackPage({ pack, relatedPacks, onBack, onOpenRelated, onLike, onDislike
1729
1729
  const packLockedByNsfw = isPackMarkedNsfw(pack) && !hasNsfwAccess;
1730
1730
  const cover = packLockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : pack?.cover_url || items?.[0]?.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL;
1731
1731
  const whatsappUrl = String(pack?.whatsapp?.url || '').trim();
1732
- const packApiPath = `/api/sticker-packs/${encodeURIComponent(String(pack?.pack_key || '').trim())}`;
1733
1732
  const engagement = getPackEngagement(pack);
1734
1733
  const hasReactionRequest = Boolean(reactionLoading);
1735
1734
  const [previewIndex, setPreviewIndex] = useState(-1);
@@ -1779,23 +1778,6 @@ function PackPage({ pack, relatedPacks, onBack, onOpenRelated, onLike, onDislike
1779
1778
  </div>
1780
1779
 
1781
1780
  ${pack?.description ? html`<p className="text-sm leading-6 text-slate-300">${pack.description}</p>` : null}
1782
-
1783
- <section className="rounded-xl border border-cyan-500/25 bg-cyan-500/5 p-3">
1784
- <p className="text-[11px] uppercase tracking-wide text-cyan-200">Use este pack no seu bot</p>
1785
- <p className="mt-1 text-sm text-slate-200">Este pack faz parte do módulo de stickers da plataforma OmniZap. Você pode consumir por API e conectar ao seu fluxo de automação WhatsApp.</p>
1786
- <pre className="mt-2 overflow-auto rounded-lg border border-slate-800 bg-slate-950/60 p-2 text-[11px] text-slate-300">
1787
- GET ${packApiPath}
1788
- POST ${packApiPath}/open
1789
- POST ${packApiPath}/like
1790
- </pre
1791
- >
1792
- <div className="mt-2 flex flex-wrap gap-2">
1793
- <a href="/api-docs/" className="inline-flex h-8 items-center rounded-lg border border-cyan-500/35 bg-cyan-500/10 px-3 text-[11px] font-semibold text-cyan-100 hover:bg-cyan-500/20"> Ver API e exemplos </a>
1794
- <a href="/" className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/70 px-3 text-[11px] text-slate-200 hover:bg-slate-800"> Plataforma OmniZap </a>
1795
- <a href="/stickers/" className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/70 px-3 text-[11px] text-slate-200 hover:bg-slate-800"> Voltar ao catálogo </a>
1796
- </div>
1797
- </section>
1798
-
1799
1781
  ${tags.length
1800
1782
  ? html`
1801
1783
  <div className="space-y-1">
@@ -3765,16 +3747,6 @@ function StickersApp() {
3765
3747
  : currentPackKey
3766
3748
  ? html` ${packLoading ? html`<${PackPageSkeleton} />` : html`<${PackPage} pack=${currentPack} relatedPacks=${relatedPacks} onBack=${goCatalog} onOpenRelated=${openPack} onLike=${handleLike} onDislike=${handleDislike} onTagClick=${openCatalogTagFilter} reactionLoading=${reactionLoading} reactionNotice=${reactionNotice} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`} `
3767
3749
  : html`
3768
- <section className="rounded-2xl border border-slate-800 bg-slate-900/80 p-3 sm:p-4">
3769
- <p className="text-[11px] uppercase tracking-wide text-slate-400">Módulo de stickers da plataforma OmniZap</p>
3770
- <h1 className="mt-1 text-lg sm:text-xl font-extrabold tracking-tight text-slate-100">Catálogo de stickers integrável via API</h1>
3771
- <p className="mt-1 text-sm text-slate-300">Os stickers são uma feature do OmniZap para bots e automação WhatsApp. Explore packs públicos e integre no seu sistema com endpoints documentados.</p>
3772
- <div className="mt-2 flex flex-wrap gap-2">
3773
- <a href="/api-docs/" className="inline-flex h-8 items-center rounded-lg border border-cyan-500/35 bg-cyan-500/10 px-3 text-[11px] font-semibold text-cyan-100 hover:bg-cyan-500/20"> Área de Desenvolvedor </a>
3774
- <a href="/" className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/70 px-3 text-[11px] text-slate-200 hover:bg-slate-800"> Página principal OmniZap </a>
3775
- </div>
3776
- </section>
3777
-
3778
3750
  <div className="lg:grid lg:grid-cols-[220px_minmax(0,1fr)] lg:gap-4">
3779
3751
  <aside className="hidden lg:block">
3780
3752
  <div className="sticky top-[72px] space-y-2.5 rounded-2xl border border-slate-800 bg-slate-900/80 p-2.5">