@kaikybrofc/omnizap-system 2.2.3 → 2.2.4

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.
@@ -0,0 +1,244 @@
1
+ /* global document, window, fetch, URLSearchParams */
2
+
3
+ const DEFAULT_API_BASE_PATH = '/api/sticker-packs';
4
+ const DEFAULT_STICKERS_PATH = '/stickers';
5
+ const DEFAULT_LOGIN_PATH = '/login';
6
+ const FALLBACK_AVATAR = 'https://iili.io/FC3FABe.jpg';
7
+
8
+ const root = document.getElementById('user-app-root');
9
+
10
+ if (root) {
11
+ const ui = {
12
+ status: document.getElementById('user-status'),
13
+ error: document.getElementById('user-error'),
14
+ profile: document.getElementById('user-profile'),
15
+ avatar: document.getElementById('user-avatar'),
16
+ name: document.getElementById('user-name'),
17
+ email: document.getElementById('user-email'),
18
+ whatsapp: document.getElementById('user-whatsapp'),
19
+ grid: document.getElementById('user-grid'),
20
+ metricPacks: document.getElementById('metric-packs'),
21
+ metricStickers: document.getElementById('metric-stickers'),
22
+ metricDownloads: document.getElementById('metric-downloads'),
23
+ metricLikes: document.getElementById('metric-likes'),
24
+ summary: document.getElementById('user-summary'),
25
+ ownerJid: document.getElementById('user-owner-jid'),
26
+ googleSub: document.getElementById('user-google-sub'),
27
+ expiresAt: document.getElementById('user-expires-at'),
28
+ actions: document.getElementById('user-actions'),
29
+ chatLink: document.getElementById('user-chat-link'),
30
+ logoutBtn: document.getElementById('user-logout-btn'),
31
+ manageHeadLink: document.getElementById('user-manage-head-link'),
32
+ manageMainLink: document.getElementById('user-manage-main-link'),
33
+ currentYear: document.getElementById('user-current-year'),
34
+ };
35
+
36
+ const state = {
37
+ apiBasePath: String(root.dataset.apiBasePath || DEFAULT_API_BASE_PATH).trim() || DEFAULT_API_BASE_PATH,
38
+ stickersPath: String(root.dataset.stickersPath || DEFAULT_STICKERS_PATH).trim() || DEFAULT_STICKERS_PATH,
39
+ loginPath: String(root.dataset.loginPath || DEFAULT_LOGIN_PATH).trim() || DEFAULT_LOGIN_PATH,
40
+ botPhone: '',
41
+ };
42
+
43
+ const sessionApiPath = `${state.apiBasePath}/auth/google/session`;
44
+ const myProfileApiPath = `${state.apiBasePath}/me`;
45
+ const botContactApiPath = `${state.apiBasePath}/bot-contact`;
46
+
47
+ const setText = (el, value) => {
48
+ if (!el) return;
49
+ el.textContent = String(value || '');
50
+ };
51
+
52
+ const showError = (message) => {
53
+ if (!ui.error) return;
54
+ const safeMessage = String(message || '').trim();
55
+ ui.error.hidden = !safeMessage;
56
+ if (safeMessage) ui.error.textContent = safeMessage;
57
+ };
58
+
59
+ const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
60
+
61
+ const formatPhone = (digits) => {
62
+ const value = normalizeDigits(digits);
63
+ if (!value) return '';
64
+ if (value.length <= 4) return value;
65
+ return `${value.slice(0, 2)} ${value.slice(2, -4)}-${value.slice(-4)}`.trim();
66
+ };
67
+
68
+ const formatNumber = (value) =>
69
+ new Intl.NumberFormat('pt-BR', {
70
+ maximumFractionDigits: 0,
71
+ }).format(Math.max(0, Number(value || 0)));
72
+
73
+ const formatDateTime = (value) => {
74
+ const ms = Date.parse(String(value || ''));
75
+ if (!Number.isFinite(ms)) return 'n/d';
76
+ return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(ms));
77
+ };
78
+
79
+ const buildLoginRedirectUrl = () => {
80
+ const loginUrl = new URL(state.loginPath, window.location.origin);
81
+ loginUrl.searchParams.set('next', '/user/');
82
+ return `${loginUrl.pathname}${loginUrl.search}`;
83
+ };
84
+
85
+ const buildWhatsAppMenuUrl = (phoneDigits) => {
86
+ const params = new URLSearchParams({
87
+ text: '/menu',
88
+ type: 'custom_url',
89
+ app_absent: '0',
90
+ });
91
+ const digits = normalizeDigits(phoneDigits);
92
+ if (digits) params.set('phone', digits);
93
+ return `https://api.whatsapp.com/send/?${params.toString()}`;
94
+ };
95
+
96
+ const fetchJson = async (url, init = {}) => {
97
+ const response = await fetch(url, {
98
+ credentials: 'include',
99
+ ...init,
100
+ });
101
+ let payload = null;
102
+ try {
103
+ payload = await response.json();
104
+ } catch {
105
+ payload = null;
106
+ }
107
+
108
+ if (!response.ok) {
109
+ const err = new Error(payload?.error || `Falha HTTP ${response.status}`);
110
+ err.statusCode = response.status;
111
+ throw err;
112
+ }
113
+ return payload || {};
114
+ };
115
+
116
+ const redirectToLogin = () => {
117
+ window.location.assign(buildLoginRedirectUrl());
118
+ };
119
+
120
+ const loadBotPhone = async () => {
121
+ try {
122
+ const payload = await fetchJson(botContactApiPath, { method: 'GET' });
123
+ state.botPhone = normalizeDigits(payload?.data?.phone || '');
124
+ } catch {
125
+ state.botPhone = '';
126
+ }
127
+ if (ui.chatLink) ui.chatLink.href = buildWhatsAppMenuUrl(state.botPhone);
128
+ };
129
+
130
+ const renderSession = (sessionData) => {
131
+ const user = sessionData?.user || {};
132
+ const ownerPhone = String(sessionData?.owner_phone || '').trim();
133
+ const ownerJid = String(sessionData?.owner_jid || '').trim();
134
+
135
+ setText(ui.name, user?.name || 'Conta Google');
136
+ setText(ui.email, user?.email || 'Email não disponível');
137
+ if (ownerPhone) {
138
+ setText(ui.whatsapp, `WhatsApp vinculado: +${formatPhone(ownerPhone)}`);
139
+ } else if (ownerJid) {
140
+ setText(ui.whatsapp, `WhatsApp vinculado via owner: ${ownerJid}`);
141
+ } else {
142
+ setText(ui.whatsapp, 'WhatsApp não vinculado.');
143
+ }
144
+
145
+ if (ui.avatar) {
146
+ const picture = String(user?.picture || '').trim() || FALLBACK_AVATAR;
147
+ ui.avatar.src = picture;
148
+ ui.avatar.onerror = () => {
149
+ ui.avatar.src = FALLBACK_AVATAR;
150
+ };
151
+ }
152
+
153
+ setText(ui.ownerJid, ownerJid || 'n/d');
154
+ setText(ui.googleSub, String(user?.sub || '').trim() || 'n/d');
155
+ setText(ui.expiresAt, formatDateTime(sessionData?.expires_at));
156
+
157
+ if (ui.profile) ui.profile.hidden = false;
158
+ if (ui.summary) ui.summary.hidden = false;
159
+ if (ui.actions) ui.actions.hidden = false;
160
+ };
161
+
162
+ const renderPackMetrics = (payload) => {
163
+ const data = payload?.data || {};
164
+ const packs = Array.isArray(data?.packs) ? data.packs : [];
165
+ const stats = data?.stats && typeof data.stats === 'object' ? data.stats : {};
166
+
167
+ let stickers = 0;
168
+ let downloads = 0;
169
+ let likes = 0;
170
+
171
+ for (const pack of packs) {
172
+ stickers += Number(pack?.sticker_count || 0);
173
+ downloads += Number(pack?.engagement?.open_count || 0);
174
+ likes += Number(pack?.engagement?.like_count || 0);
175
+ }
176
+
177
+ setText(ui.metricPacks, formatNumber(stats.total || packs.length));
178
+ setText(ui.metricStickers, formatNumber(stickers));
179
+ setText(ui.metricDownloads, formatNumber(downloads));
180
+ setText(ui.metricLikes, formatNumber(likes));
181
+ if (ui.grid) ui.grid.hidden = false;
182
+ };
183
+
184
+ const handleLogout = async () => {
185
+ if (!ui.logoutBtn) return;
186
+ ui.logoutBtn.disabled = true;
187
+ ui.logoutBtn.textContent = 'Encerrando...';
188
+ try {
189
+ await fetchJson(sessionApiPath, { method: 'DELETE' });
190
+ } catch {
191
+ // clear local navigation even if request fails
192
+ }
193
+ window.location.assign(`${state.loginPath}/`);
194
+ };
195
+
196
+ const init = async () => {
197
+ const manageHref = `${state.stickersPath.replace(/\/+$/, '') || DEFAULT_STICKERS_PATH}/perfil`;
198
+ if (ui.manageHeadLink) ui.manageHeadLink.href = manageHref;
199
+ if (ui.manageMainLink) ui.manageMainLink.href = manageHref;
200
+ if (ui.currentYear) ui.currentYear.textContent = String(new Date().getFullYear());
201
+
202
+ setText(ui.status, 'Validando sua sessão...');
203
+ showError('');
204
+
205
+ let sessionData = null;
206
+ try {
207
+ const sessionPayload = await fetchJson(sessionApiPath, { method: 'GET' });
208
+ sessionData = sessionPayload?.data || {};
209
+ if (!sessionData?.authenticated || !sessionData?.user?.sub) {
210
+ redirectToLogin();
211
+ return;
212
+ }
213
+ } catch (error) {
214
+ showError(error?.message || 'Falha ao validar sessão.');
215
+ setText(ui.status, 'Não foi possível validar sua sessão agora.');
216
+ return;
217
+ }
218
+
219
+ renderSession(sessionData);
220
+ await loadBotPhone();
221
+
222
+ try {
223
+ const myProfilePayload = await fetchJson(myProfileApiPath, { method: 'GET' });
224
+ const sessionOk = Boolean(myProfilePayload?.data?.session?.authenticated);
225
+ if (!sessionOk) {
226
+ redirectToLogin();
227
+ return;
228
+ }
229
+ renderPackMetrics(myProfilePayload);
230
+ setText(ui.status, 'Sessão ativa. Dados da sua conta carregados com sucesso.');
231
+ } catch (error) {
232
+ showError(error?.message || 'Falha ao carregar dados da conta.');
233
+ setText(ui.status, 'Sessão ativa, mas não foi possível carregar todos os dados.');
234
+ }
235
+ };
236
+
237
+ if (ui.logoutBtn) {
238
+ ui.logoutBtn.addEventListener('click', () => {
239
+ void handleLogout();
240
+ });
241
+ }
242
+
243
+ void init();
244
+ }
@@ -9,6 +9,7 @@ if (typeof document !== 'undefined' && !window.__omnizapTwindReady) {
9
9
 
10
10
  export {
11
11
  default as React,
12
+ useCallback,
12
13
  useEffect,
13
14
  useMemo,
14
15
  useRef,
@@ -0,0 +1,333 @@
1
+ <!doctype html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>OmniZap System | Login</title>
7
+ <meta
8
+ name="description"
9
+ content="Login OmniZap System com Google para vincular sua conta ao numero do WhatsApp."
10
+ />
11
+ <meta name="robots" content="index, follow" />
12
+ <meta name="theme-color" content="#0b1020" />
13
+ <link rel="canonical" href="https://omnizap.shop/login/" />
14
+ <link rel="icon" type="image/jpeg" href="https://iili.io/FC3FABe.jpg" />
15
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
17
+ <link
18
+ href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Sora:wght@500;600;700&display=swap"
19
+ rel="stylesheet"
20
+ />
21
+ <script>
22
+ (() => {
23
+ try {
24
+ const params = new URLSearchParams(window.location.search || '');
25
+ const phone = String(params.get('wa') || '').replace(/\D+/g, '');
26
+ if (phone) {
27
+ document.documentElement.setAttribute('data-login-has-wa', '1');
28
+ }
29
+ } catch {}
30
+ })();
31
+ </script>
32
+ <style>
33
+ :root {
34
+ --bg: #0b1020;
35
+ --bg-2: #121a2f;
36
+ --card: #121f39cf;
37
+ --line: #2f466f;
38
+ --text: #e6edf7;
39
+ --muted: #9bb0cf;
40
+ --primary: #22c55e;
41
+ --error: #f87171;
42
+ --radius: 18px;
43
+ }
44
+
45
+ * {
46
+ box-sizing: border-box;
47
+ }
48
+
49
+ html,
50
+ body {
51
+ margin: 0;
52
+ padding: 0;
53
+ min-height: 100%;
54
+ }
55
+
56
+ body {
57
+ color: var(--text);
58
+ font-family: "Manrope", system-ui, sans-serif;
59
+ background:
60
+ radial-gradient(55rem 22rem at -10% -10%, #1fce6f30, transparent 60%),
61
+ radial-gradient(55rem 22rem at 110% -8%, #43b8ff22, transparent 56%),
62
+ linear-gradient(160deg, var(--bg), var(--bg-2));
63
+ min-height: 100vh;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ padding: 24px 14px;
68
+ }
69
+
70
+ .page {
71
+ width: min(760px, 100%);
72
+ border: 1px solid #314b74;
73
+ border-radius: 24px;
74
+ background: linear-gradient(150deg, #0f1a31e8, #0f1b34ee);
75
+ box-shadow: 0 18px 44px #0209166e, inset 0 1px 0 #95c1ff1a;
76
+ overflow: hidden;
77
+ }
78
+
79
+ .head {
80
+ border-bottom: 1px solid #2a3f63;
81
+ padding: 20px 22px;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: flex-start;
85
+ gap: 14px;
86
+ flex-wrap: wrap;
87
+ }
88
+
89
+ .brand {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ gap: 10px;
93
+ font-family: "Sora", sans-serif;
94
+ font-weight: 700;
95
+ letter-spacing: 0.2px;
96
+ text-decoration: none;
97
+ color: var(--text);
98
+ }
99
+
100
+ .brand img {
101
+ width: 30px;
102
+ height: 30px;
103
+ border-radius: 50%;
104
+ border: 1px solid #33507e;
105
+ object-fit: cover;
106
+ }
107
+
108
+ .btn {
109
+ border: 1px solid var(--line);
110
+ border-radius: 12px;
111
+ background: #101a2f;
112
+ color: var(--text);
113
+ text-decoration: none;
114
+ padding: 9px 12px;
115
+ font-size: 14px;
116
+ font-weight: 600;
117
+ transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease;
118
+ cursor: pointer;
119
+ }
120
+
121
+ .btn:hover {
122
+ transform: translateY(-1px);
123
+ border-color: #4e6ea1;
124
+ box-shadow: 0 10px 20px #02091650;
125
+ }
126
+
127
+ .btn.primary {
128
+ border-color: transparent;
129
+ background: linear-gradient(90deg, var(--primary), #16a34a);
130
+ color: #052412;
131
+ box-shadow: 0 10px 22px #22c55e30;
132
+ }
133
+
134
+ .content {
135
+ padding: 24px 22px;
136
+ display: grid;
137
+ gap: 14px;
138
+ }
139
+
140
+ h1 {
141
+ margin: 0;
142
+ font-family: "Sora", sans-serif;
143
+ font-size: clamp(26px, 3.6vw, 36px);
144
+ line-height: 1.08;
145
+ letter-spacing: -0.02em;
146
+ background: linear-gradient(90deg, #eef5ff 0%, #8fd7ff 46%, #6ce8b5 100%);
147
+ -webkit-background-clip: text;
148
+ background-clip: text;
149
+ color: transparent;
150
+ }
151
+
152
+ .lead {
153
+ margin: 0;
154
+ color: #bfd1ea;
155
+ line-height: 1.6;
156
+ font-size: 16px;
157
+ }
158
+
159
+ .card {
160
+ border: 1px solid #334f7bc7;
161
+ border-radius: var(--radius);
162
+ background: var(--card);
163
+ padding: 16px;
164
+ display: grid;
165
+ gap: 10px;
166
+ backdrop-filter: blur(8px);
167
+ }
168
+
169
+ .status {
170
+ margin: 0;
171
+ color: #dce8fa;
172
+ font-size: 15px;
173
+ line-height: 1.5;
174
+ }
175
+
176
+ .hint {
177
+ margin: 0;
178
+ color: var(--muted);
179
+ font-size: 14px;
180
+ line-height: 1.5;
181
+ }
182
+
183
+ .error {
184
+ margin: 0;
185
+ border: 1px solid #a74949;
186
+ border-radius: 12px;
187
+ background: #3b181899;
188
+ color: #ffd7d7;
189
+ padding: 9px 10px;
190
+ font-size: 14px;
191
+ }
192
+
193
+ .google-area {
194
+ border: 1px dashed #3c5b8d;
195
+ border-radius: 14px;
196
+ background: #0f1930a3;
197
+ padding: 12px;
198
+ display: grid;
199
+ gap: 10px;
200
+ align-items: stretch;
201
+ }
202
+
203
+ html:not([data-login-has-wa="1"]) #google-login-area {
204
+ display: none !important;
205
+ }
206
+
207
+ [data-google-login-button] {
208
+ width: 100%;
209
+ min-height: 40px;
210
+ }
211
+
212
+ .google-state {
213
+ margin: 0;
214
+ color: var(--muted);
215
+ font-size: 13px;
216
+ }
217
+
218
+ .summary {
219
+ border: 1px solid #2b8b5d9c;
220
+ border-radius: 12px;
221
+ background: #112d2094;
222
+ padding: 10px;
223
+ display: grid;
224
+ gap: 6px;
225
+ color: #dfffe8;
226
+ font-size: 14px;
227
+ }
228
+
229
+ .whatsapp-cta {
230
+ border: 1px solid #2d7d57a8;
231
+ border-radius: 14px;
232
+ background: #1032248f;
233
+ padding: 12px;
234
+ display: grid;
235
+ gap: 10px;
236
+ }
237
+
238
+ html[data-login-has-wa="1"] #whatsapp-cta {
239
+ display: none !important;
240
+ }
241
+
242
+ .whatsapp-cta p {
243
+ margin: 0;
244
+ color: #d5ffdf;
245
+ font-size: 14px;
246
+ line-height: 1.5;
247
+ }
248
+
249
+ .btn.whatsapp {
250
+ border-color: #1e9a5f;
251
+ background: linear-gradient(90deg, #1fc86a, #16a34a);
252
+ color: #04230f;
253
+ text-align: center;
254
+ }
255
+
256
+ .whatsapp-meta {
257
+ margin: 0;
258
+ color: #b7e9ca;
259
+ font-size: 13px;
260
+ }
261
+
262
+ .actions {
263
+ display: flex;
264
+ flex-wrap: wrap;
265
+ gap: 8px;
266
+ }
267
+
268
+ @media (max-width: 620px) {
269
+ .head {
270
+ padding: 16px;
271
+ }
272
+
273
+ .content {
274
+ padding: 18px 16px;
275
+ }
276
+
277
+ .actions .btn {
278
+ width: 100%;
279
+ text-align: center;
280
+ }
281
+ }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <main id="login-app-root" class="page" data-api-base-path="/api/sticker-packs">
286
+ <header class="head">
287
+ <a href="/" class="brand">
288
+ <img src="https://iili.io/FC3FABe.jpg" alt="OmniZap" loading="lazy" decoding="async" />
289
+ <span>OmniZap System</span>
290
+ </a>
291
+ </header>
292
+
293
+ <section class="content">
294
+ <h1>Login da Plataforma</h1>
295
+ <p class="lead">
296
+ Entre com Google para vincular sua conta ao seu numero do WhatsApp e liberar os recursos web do OmniZap.
297
+ </p>
298
+
299
+ <article class="card">
300
+ <p id="login-status" class="status">Carregando estado de login...</p>
301
+ <p id="login-hint" class="hint"></p>
302
+ <p id="login-error" class="error" hidden></p>
303
+
304
+ <div id="google-login-area" class="google-area" hidden>
305
+ <div data-google-login-button></div>
306
+ <p id="google-login-state" class="google-state">Carregando login Google...</p>
307
+ </div>
308
+
309
+ <div id="login-summary" class="summary" hidden>
310
+ <div id="login-summary-owner">WhatsApp vinculado:</div>
311
+ </div>
312
+
313
+ <div id="whatsapp-cta" class="whatsapp-cta" hidden>
314
+ <p>Abriu esta página direto? Inicie a conversa no WhatsApp para gerar o link com seu número automaticamente.</p>
315
+ <a id="whatsapp-cta-link" class="btn whatsapp" href="https://wa.me/?text=iniciar" target="_blank" rel="noreferrer noopener">
316
+ Abrir WhatsApp e enviar "iniciar"
317
+ </a>
318
+ <p id="whatsapp-cta-meta" class="whatsapp-meta"></p>
319
+ </div>
320
+
321
+ <div id="login-success-actions" class="actions" hidden>
322
+ <a id="login-success-chat" class="btn primary" href="https://wa.me/?text=%2Fmenu" target="_blank" rel="noreferrer noopener">
323
+ Voltar ao chat do bot (/menu)
324
+ </a>
325
+ <a id="login-success-home" class="btn" href="/">Voltar para Home</a>
326
+ </div>
327
+ </article>
328
+ </section>
329
+ </main>
330
+
331
+ <script type="module" src="/js/apps/loginApp.js?v=20260228f"></script>
332
+ </body>
333
+ </html>
@@ -40,8 +40,9 @@
40
40
  id="create-pack-react-root"
41
41
  data-api-base-path="/api/sticker-packs"
42
42
  data-web-path="/stickers"
43
+ data-login-path="__STICKER_LOGIN_WEB_PATH__"
43
44
  ></div>
44
45
 
45
- <script type="module" src="/js/apps/createPackApp.js?v=20260226-create-pack-cta-open-created-pack1"></script>
46
+ <script type="module" src="/js/apps/createPackApp.js?v=20260228-create-pack-login-required1"></script>
46
47
  </body>
47
48
  </html>
@@ -38,11 +38,12 @@
38
38
  data-web-path="/stickers"
39
39
  data-api-base-path="/api/sticker-packs"
40
40
  data-orphan-api-path="/api/sticker-packs/orphan-stickers"
41
+ data-login-path="__STICKER_LOGIN_WEB_PATH__"
41
42
  data-default-limit="24"
42
43
  data-default-orphan-limit="24"
43
44
  data-initial-pack-key=""
44
45
  ></div>
45
46
 
46
- <script type="module" src="/js/apps/stickersApp.js?v=20260227-ui-hotfix-v5"></script>
47
+ <script type="module" src="/js/apps/stickersApp.js?v=20260228-login-redirect-my-packs1"></script>
47
48
  </body>
48
49
  </html>