@kaikybrofc/omnizap-system 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -20
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +27 -5
- package/database/index.js +1 -0
- package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
- package/package.json +1 -1
- package/public/index.html +42 -52
- package/public/js/apps/homeApp.js +184 -31
- package/public/js/apps/stickersApp.js +0 -28
- package/public/js/apps/userApp.js +217 -1
- package/public/licenca/index.html +98 -2
- package/public/termos-de-uso/index.html +245 -25
- package/public/user/index.html +181 -1
- package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
- package/server/controllers/stickerCatalogController.js +231 -628
package/README.md
CHANGED
|
@@ -51,31 +51,29 @@ Conteúdo incluído no snapshot:
|
|
|
51
51
|
Atualização em cache: **30 minutos** por padrão (`README_SUMMARY_CACHE_SECONDS=1800`).
|
|
52
52
|
|
|
53
53
|
<!-- README_SNAPSHOT:START -->
|
|
54
|
-
|
|
55
54
|
### Snapshot do Sistema
|
|
56
55
|
|
|
57
|
-
> Atualizado em `2026-02-28T21:
|
|
56
|
+
> Atualizado em `2026-02-28T21:56:21.021Z` | cache `1800s`
|
|
58
57
|
|
|
59
|
-
| Métrica
|
|
60
|
-
|
|
|
61
|
-
| Usuários (lid_map)
|
|
62
|
-
| Grupos
|
|
63
|
-
| Packs
|
|
64
|
-
| Stickers
|
|
65
|
-
| Mensagens registradas | 444.
|
|
58
|
+
| Métrica | Valor |
|
|
59
|
+
| --- | ---: |
|
|
60
|
+
| Usuários (lid_map) | 5.515 |
|
|
61
|
+
| Grupos | 116 |
|
|
62
|
+
| Packs | 301 |
|
|
63
|
+
| Stickers | 7.170 |
|
|
64
|
+
| Mensagens registradas | 444.453 |
|
|
66
65
|
|
|
67
66
|
#### Tipos de mensagem mais usados (amostra: 25.000)
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `documento` | 6 |
|
|
67
|
+
| Tipo | Total |
|
|
68
|
+
| --- | ---: |
|
|
69
|
+
| `texto` | 16.074 |
|
|
70
|
+
| `figurinha` | 4.780 |
|
|
71
|
+
| `reacao` | 1.546 |
|
|
72
|
+
| `imagem` | 1.339 |
|
|
73
|
+
| `outros` | 850 |
|
|
74
|
+
| `video` | 215 |
|
|
75
|
+
| `audio` | 190 |
|
|
76
|
+
| `documento` | 6 |
|
|
79
77
|
|
|
80
78
|
<details><summary>Comandos disponíveis (62)</summary>
|
|
81
79
|
|
|
@@ -19,6 +19,9 @@ const CONSUMER_ENABLED = parseEnvBool(process.env.STICKER_DOMAIN_EVENT_CONSUMER_
|
|
|
19
19
|
const STARTUP_DELAY_MS = Math.max(1_000, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_STARTUP_DELAY_MS) || 8_000);
|
|
20
20
|
const POLLER_INTERVAL_MS = Math.max(1_000, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_POLLER_INTERVAL_MS) || 2_000);
|
|
21
21
|
const RETRY_DELAY_SECONDS = Math.max(5, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_RETRY_DELAY_SECONDS) || 45));
|
|
22
|
+
const CLASSIFICATION_COALESCE_WINDOW_SECONDS = Math.max(30, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CLASSIFICATION_COALESCE_WINDOW_SECONDS) || 60));
|
|
23
|
+
const CURATION_COALESCE_WINDOW_SECONDS = Math.max(30, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CURATION_COALESCE_WINDOW_SECONDS) || 60));
|
|
24
|
+
const REBUILD_COALESCE_WINDOW_SECONDS = Math.max(30, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_REBUILD_COALESCE_WINDOW_SECONDS) || 120));
|
|
22
25
|
const CONSUMER_COHORT_KEY = String(process.env.STICKER_DOMAIN_EVENT_CONSUMER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim() || 'consumer';
|
|
23
26
|
|
|
24
27
|
let startupHandle = null;
|
|
@@ -47,6 +50,18 @@ const enqueueTaskSafely = async ({ taskType, payload, priority, idempotencyKey }
|
|
|
47
50
|
});
|
|
48
51
|
};
|
|
49
52
|
|
|
53
|
+
const toUnixSeconds = (value) => {
|
|
54
|
+
if (!value) return Math.floor(Date.now() / 1000);
|
|
55
|
+
const numeric = Date.parse(value);
|
|
56
|
+
if (!Number.isFinite(numeric)) return Math.floor(Date.now() / 1000);
|
|
57
|
+
return Math.floor(numeric / 1000);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const toWindowBucket = (value, windowSeconds) => {
|
|
61
|
+
const safeWindow = Math.max(1, Math.floor(windowSeconds || 1));
|
|
62
|
+
return Math.floor(toUnixSeconds(value) / safeWindow);
|
|
63
|
+
};
|
|
64
|
+
|
|
50
65
|
const handleDomainEvent = async (event) => {
|
|
51
66
|
const eventType = String(event?.event_type || '')
|
|
52
67
|
.trim()
|
|
@@ -55,16 +70,18 @@ const handleDomainEvent = async (event) => {
|
|
|
55
70
|
const payload = event?.payload && typeof event.payload === 'object' ? event.payload : {};
|
|
56
71
|
|
|
57
72
|
if (eventType === STICKER_DOMAIN_EVENTS.STICKER_ASSET_CREATED) {
|
|
73
|
+
const coalesceBucket = toWindowBucket(event?.created_at, CLASSIFICATION_COALESCE_WINDOW_SECONDS);
|
|
58
74
|
await enqueueTaskSafely({
|
|
59
75
|
taskType: 'classification_cycle',
|
|
60
|
-
payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId },
|
|
76
|
+
payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, coalesced: true },
|
|
61
77
|
priority: 80,
|
|
62
|
-
idempotencyKey: `evt:${
|
|
78
|
+
idempotencyKey: `evt:${eventType}:${coalesceBucket}:classification_cycle`,
|
|
63
79
|
});
|
|
64
80
|
return;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
if (eventType === STICKER_DOMAIN_EVENTS.STICKER_CLASSIFIED) {
|
|
84
|
+
const coalesceBucket = toWindowBucket(event?.created_at, CURATION_COALESCE_WINDOW_SECONDS);
|
|
68
85
|
const assetId = String(payload?.asset_id || aggregateId || '').trim();
|
|
69
86
|
const relatedPackIds = assetId ? await listPackIdsByStickerId(assetId).catch(() => []) : [];
|
|
70
87
|
if (relatedPackIds.length) {
|
|
@@ -77,23 +94,28 @@ const handleDomainEvent = async (event) => {
|
|
|
77
94
|
event_type: eventType,
|
|
78
95
|
aggregate_id: aggregateId,
|
|
79
96
|
related_pack_ids: relatedPackIds,
|
|
97
|
+
coalesced: true,
|
|
80
98
|
},
|
|
81
99
|
priority: 65,
|
|
82
|
-
idempotencyKey: `evt:${
|
|
100
|
+
idempotencyKey: `evt:${eventType}:${coalesceBucket}:curation_cycle`,
|
|
83
101
|
});
|
|
84
102
|
return;
|
|
85
103
|
}
|
|
86
104
|
|
|
87
105
|
if (eventType === STICKER_DOMAIN_EVENTS.PACK_UPDATED) {
|
|
106
|
+
const coalesceBucket = toWindowBucket(event?.created_at, REBUILD_COALESCE_WINDOW_SECONDS);
|
|
88
107
|
const packId = String(payload?.pack_id || aggregateId || '').trim();
|
|
89
108
|
if (packId) {
|
|
90
109
|
enqueuePackScoreSnapshotRefresh([packId]);
|
|
91
110
|
}
|
|
111
|
+
const rebuildIdempotency = packId
|
|
112
|
+
? `evt:${eventType}:${packId}:${coalesceBucket}:rebuild_cycle`
|
|
113
|
+
: `evt:${eventType}:${coalesceBucket}:rebuild_cycle`;
|
|
92
114
|
await enqueueTaskSafely({
|
|
93
115
|
taskType: 'rebuild_cycle',
|
|
94
|
-
payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, pack_id: packId || null },
|
|
116
|
+
payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, pack_id: packId || null, coalesced: true },
|
|
95
117
|
priority: 60,
|
|
96
|
-
idempotencyKey:
|
|
118
|
+
idempotencyKey: rebuildIdempotency,
|
|
97
119
|
});
|
|
98
120
|
return;
|
|
99
121
|
}
|
package/database/index.js
CHANGED
|
@@ -104,6 +104,7 @@ export const TABLES = {
|
|
|
104
104
|
SEMANTIC_THEME_SUGGESTION_CACHE: 'semantic_theme_suggestion_cache',
|
|
105
105
|
STICKER_PACK_ENGAGEMENT: 'sticker_pack_engagement',
|
|
106
106
|
STICKER_PACK_INTERACTION_EVENT: 'sticker_pack_interaction_event',
|
|
107
|
+
WEB_VISIT_EVENT: 'web_visit_event',
|
|
107
108
|
STICKER_PACK_SCORE_SNAPSHOT: 'sticker_pack_score_snapshot',
|
|
108
109
|
STICKER_ASSET_REPROCESS_QUEUE: 'sticker_asset_reprocess_queue',
|
|
109
110
|
STICKER_WORKER_TASK_QUEUE: 'sticker_worker_task_queue',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS web_visit_event (
|
|
2
|
+
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|
3
|
+
visitor_key VARCHAR(80) NOT NULL,
|
|
4
|
+
session_key VARCHAR(80) NOT NULL,
|
|
5
|
+
page_path VARCHAR(255) NOT NULL,
|
|
6
|
+
referrer VARCHAR(1024) NULL,
|
|
7
|
+
user_agent VARCHAR(512) NULL,
|
|
8
|
+
source VARCHAR(32) NOT NULL DEFAULT 'web',
|
|
9
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
10
|
+
INDEX idx_web_visit_created_at (created_at),
|
|
11
|
+
INDEX idx_web_visit_page_created (page_path, created_at),
|
|
12
|
+
INDEX idx_web_visit_visitor_created (visitor_key, created_at),
|
|
13
|
+
INDEX idx_web_visit_session_created (session_key, created_at),
|
|
14
|
+
INDEX idx_web_visit_source_created (source, created_at)
|
|
15
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -184,6 +184,32 @@
|
|
|
184
184
|
cursor: pointer;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
.nav-toggle.nav-toggle-user {
|
|
188
|
+
padding: 0;
|
|
189
|
+
border-color: #3f5f8f;
|
|
190
|
+
background: linear-gradient(120deg, #132544d6, #10203ad6);
|
|
191
|
+
box-shadow: 0 0 0 2px #1a2e4b;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.nav-toggle.nav-toggle-login {
|
|
196
|
+
width: auto;
|
|
197
|
+
min-width: 92px;
|
|
198
|
+
padding: 0 14px;
|
|
199
|
+
border-color: #38598b;
|
|
200
|
+
background: linear-gradient(120deg, #132544d6, #10203ad6);
|
|
201
|
+
box-shadow: 0 0 0 2px #1a2e4b;
|
|
202
|
+
font-size: 14px;
|
|
203
|
+
font-weight: 700;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.nav-toggle-photo {
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100%;
|
|
209
|
+
object-fit: cover;
|
|
210
|
+
display: block;
|
|
211
|
+
}
|
|
212
|
+
|
|
187
213
|
.btn {
|
|
188
214
|
text-decoration: none;
|
|
189
215
|
color: var(--text);
|
|
@@ -672,26 +698,25 @@
|
|
|
672
698
|
background: #101a2f;
|
|
673
699
|
}
|
|
674
700
|
|
|
675
|
-
.nav-user-chip {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
max-width: 270px;
|
|
701
|
+
.btn.nav-user-chip {
|
|
702
|
+
width: 40px;
|
|
703
|
+
height: 40px;
|
|
704
|
+
padding: 0;
|
|
705
|
+
border-radius: 999px;
|
|
681
706
|
border-color: #3f5f8f;
|
|
682
707
|
background: linear-gradient(120deg, #132544d6, #10203ad6);
|
|
708
|
+
box-shadow: 0 0 0 2px #1a2e4b;
|
|
709
|
+
justify-content: center;
|
|
710
|
+
overflow: hidden;
|
|
683
711
|
}
|
|
684
712
|
|
|
685
713
|
.nav-user-avatar-bubble {
|
|
686
714
|
display: inline-flex;
|
|
687
|
-
width:
|
|
688
|
-
height:
|
|
715
|
+
width: 100%;
|
|
716
|
+
height: 100%;
|
|
689
717
|
border-radius: 999px;
|
|
690
|
-
border:
|
|
718
|
+
border: 0;
|
|
691
719
|
background: #132340;
|
|
692
|
-
box-shadow:
|
|
693
|
-
0 0 0 2px #1a2e4b,
|
|
694
|
-
0 0 16px #6aaaf236;
|
|
695
720
|
overflow: hidden;
|
|
696
721
|
flex: 0 0 auto;
|
|
697
722
|
}
|
|
@@ -703,32 +728,6 @@
|
|
|
703
728
|
display: block;
|
|
704
729
|
}
|
|
705
730
|
|
|
706
|
-
.nav-user-name-bubble {
|
|
707
|
-
display: inline-flex;
|
|
708
|
-
align-items: center;
|
|
709
|
-
gap: 6px;
|
|
710
|
-
min-width: 0;
|
|
711
|
-
padding: 4px 8px;
|
|
712
|
-
border-radius: 999px;
|
|
713
|
-
border: 1px solid #34547f;
|
|
714
|
-
background: #12213ac9;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
.nav-user-icon {
|
|
718
|
-
font-size: 12px;
|
|
719
|
-
color: #9bc6f5;
|
|
720
|
-
flex: 0 0 auto;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
.nav-user-name {
|
|
724
|
-
font-size: 12px;
|
|
725
|
-
font-weight: 700;
|
|
726
|
-
color: #dbe8fb;
|
|
727
|
-
white-space: nowrap;
|
|
728
|
-
overflow: hidden;
|
|
729
|
-
text-overflow: ellipsis;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
731
|
.wpp-float {
|
|
733
732
|
position: fixed;
|
|
734
733
|
right: 16px;
|
|
@@ -805,6 +804,10 @@
|
|
|
805
804
|
.btn {
|
|
806
805
|
width: 100%;
|
|
807
806
|
}
|
|
807
|
+
|
|
808
|
+
.btn.nav-mobile-hidden {
|
|
809
|
+
display: none;
|
|
810
|
+
}
|
|
808
811
|
.proof-grid {
|
|
809
812
|
grid-template-columns: 1fr;
|
|
810
813
|
}
|
|
@@ -817,9 +820,6 @@
|
|
|
817
820
|
<path d="M3 4.5A2.5 2.5 0 0 1 5.5 2h8A2.5 2.5 0 0 1 16 4.5V8a1 1 0 1 1-2 0V4.5a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v15a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V16a1 1 0 1 1 2 0v3.5a2.5 2.5 0 0 1-2.5 2.5h-8A2.5 2.5 0 0 1 3 19.5z" />
|
|
818
821
|
<path d="M19.293 8.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L20.586 13H9a1 1 0 1 1 0-2h11.586l-1.293-1.293a1 1 0 0 1 0-1.414z" />
|
|
819
822
|
</symbol>
|
|
820
|
-
<symbol id="icon-user" viewBox="0 0 24 24">
|
|
821
|
-
<path d="M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10zM4 20a8 8 0 1 1 16 0z" />
|
|
822
|
-
</symbol>
|
|
823
823
|
<symbol id="icon-sparkles" viewBox="0 0 24 24">
|
|
824
824
|
<path d="m11.35 2.23 1.3.01 1.58 4.23 4.24 1.59v1.29l-4.24 1.59-1.58 4.23h-1.3l-1.58-4.23-4.24-1.59V8.06l4.24-1.59zM18.17 10l.83 2.03L21 12.86l-2 .83-.83 2.03-.83-2.03-2-.83 2-.83zM5 13.2l1.02 2.43 2.42 1.03-2.42 1.03L5 20.12l-1.02-2.43-2.42-1.03 2.42-1.03z" />
|
|
825
825
|
</symbol>
|
|
@@ -875,11 +875,6 @@
|
|
|
875
875
|
><svg viewBox="0 0 24 24"><use href="#icon-sparkles"></use></svg></span
|
|
876
876
|
>Ver Recursos</a
|
|
877
877
|
>
|
|
878
|
-
<a id="hero-login-cta" class="btn" href="/login/"
|
|
879
|
-
><span class="icon" aria-hidden="true"
|
|
880
|
-
><svg viewBox="0 0 24 24"><use href="#icon-user"></use></svg></span
|
|
881
|
-
>Entrar</a
|
|
882
|
-
>
|
|
883
878
|
</div>
|
|
884
879
|
|
|
885
880
|
<div class="proof-grid" aria-label="Prova social">
|
|
@@ -1114,11 +1109,6 @@
|
|
|
1114
1109
|
><svg viewBox="0 0 24 24"><use href="#icon-whatsapp"></use></svg></span
|
|
1115
1110
|
>Adicionar Bot Agora</a
|
|
1116
1111
|
>
|
|
1117
|
-
<a id="final-login-cta" class="btn" href="/login/"
|
|
1118
|
-
><span class="icon" aria-hidden="true"
|
|
1119
|
-
><svg viewBox="0 0 24 24"><use href="#icon-user"></use></svg></span
|
|
1120
|
-
>Entrar</a
|
|
1121
|
-
>
|
|
1122
1112
|
</div>
|
|
1123
1113
|
</section>
|
|
1124
1114
|
</main>
|
|
@@ -1142,6 +1132,6 @@
|
|
|
1142
1132
|
><svg viewBox="0 0 24 24"><use href="#icon-whatsapp"></use></svg></span
|
|
1143
1133
|
></a>
|
|
1144
1134
|
|
|
1145
|
-
<script type="module" src="/js/apps/homeApp.js?v=20260228-home-bootstrap-
|
|
1135
|
+
<script type="module" src="/js/apps/homeApp.js?v=20260228-home-bootstrap-v10"></script>
|
|
1146
1136
|
</body>
|
|
1147
1137
|
</html>
|
|
@@ -137,7 +137,21 @@ const initNavToggle = () => {
|
|
|
137
137
|
toggle.setAttribute('aria-expanded', 'false');
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
-
const onClick = () => {
|
|
140
|
+
const onClick = (event) => {
|
|
141
|
+
if (toggle.classList.contains('nav-toggle-login')) {
|
|
142
|
+
if (event) event.preventDefault();
|
|
143
|
+
closeMenu();
|
|
144
|
+
const loginUrl = String(toggle.dataset.loginUrl || '/login/').trim() || '/login/';
|
|
145
|
+
window.location.assign(loginUrl);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (toggle.classList.contains('nav-toggle-user')) {
|
|
149
|
+
if (event) event.preventDefault();
|
|
150
|
+
closeMenu();
|
|
151
|
+
const profileUrl = String(toggle.dataset.profileUrl || '/user/').trim() || '/user/';
|
|
152
|
+
window.location.assign(profileUrl);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
141
155
|
const isOpen = nav.classList.toggle('open');
|
|
142
156
|
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
143
157
|
};
|
|
@@ -163,16 +177,138 @@ const initNavToggle = () => {
|
|
|
163
177
|
|
|
164
178
|
const initAuthSession = () => {
|
|
165
179
|
const authLink = document.getElementById('nav-auth-link');
|
|
166
|
-
const
|
|
167
|
-
const
|
|
180
|
+
const schedulerLink = document.getElementById('nav-scheduler-link');
|
|
181
|
+
const navToggle = document.getElementById('nav-toggle');
|
|
168
182
|
if (!authLink) return null;
|
|
169
183
|
|
|
184
|
+
const mobileQuery = typeof window.matchMedia === 'function' ? window.matchMedia('(max-width: 920px)') : null;
|
|
185
|
+
let currentSessionData = null;
|
|
186
|
+
let isAuthenticated = false;
|
|
187
|
+
const isMobileViewport = () => {
|
|
188
|
+
const byMedia = Boolean(mobileQuery?.matches);
|
|
189
|
+
const viewportWidth = Math.max(Number(window.innerWidth || 0), Number(document.documentElement?.clientWidth || 0));
|
|
190
|
+
return byMedia || (viewportWidth > 0 && viewportWidth <= 920);
|
|
191
|
+
};
|
|
192
|
+
const schedulerDefaultHref = String(schedulerLink?.getAttribute('href') || '#beneficios').trim() || '#beneficios';
|
|
193
|
+
const schedulerDefaultLabel = String(schedulerLink?.textContent || 'Benefícios').trim() || 'Benefícios';
|
|
194
|
+
const navToggleDefaultLabel = String(navToggle?.textContent || '☰').trim() || '☰';
|
|
195
|
+
|
|
170
196
|
const clearChildren = (node) => {
|
|
171
197
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
172
198
|
};
|
|
173
199
|
|
|
200
|
+
const setSchedulerDefaultState = () => {
|
|
201
|
+
if (!schedulerLink) return;
|
|
202
|
+
schedulerLink.classList.remove('nav-user-chip');
|
|
203
|
+
schedulerLink.href = schedulerDefaultHref;
|
|
204
|
+
schedulerLink.removeAttribute('title');
|
|
205
|
+
schedulerLink.removeAttribute('aria-label');
|
|
206
|
+
clearChildren(schedulerLink);
|
|
207
|
+
schedulerLink.append(document.createTextNode(schedulerDefaultLabel));
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const setToggleDefaultState = () => {
|
|
211
|
+
if (!navToggle) return;
|
|
212
|
+
navToggle.classList.remove('nav-toggle-user', 'nav-toggle-login');
|
|
213
|
+
delete navToggle.dataset.profileUrl;
|
|
214
|
+
delete navToggle.dataset.loginUrl;
|
|
215
|
+
navToggle.setAttribute('aria-label', 'Abrir menu');
|
|
216
|
+
navToggle.setAttribute('aria-controls', 'main-nav');
|
|
217
|
+
navToggle.setAttribute('aria-expanded', 'false');
|
|
218
|
+
clearChildren(navToggle);
|
|
219
|
+
navToggle.append(document.createTextNode(navToggleDefaultLabel));
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const setToggleLoginState = () => {
|
|
223
|
+
if (!navToggle) return;
|
|
224
|
+
navToggle.classList.remove('nav-toggle-user');
|
|
225
|
+
navToggle.classList.add('nav-toggle-login');
|
|
226
|
+
delete navToggle.dataset.profileUrl;
|
|
227
|
+
navToggle.dataset.loginUrl = '/login/';
|
|
228
|
+
navToggle.setAttribute('aria-label', 'Entrar');
|
|
229
|
+
navToggle.removeAttribute('aria-controls');
|
|
230
|
+
navToggle.removeAttribute('aria-expanded');
|
|
231
|
+
clearChildren(navToggle);
|
|
232
|
+
navToggle.append(document.createTextNode('Entrar'));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const setSchedulerAsUserBubble = (sessionData) => {
|
|
236
|
+
if (!schedulerLink) return;
|
|
237
|
+
|
|
238
|
+
const profile = sessionData?.user || {};
|
|
239
|
+
const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
|
|
240
|
+
const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
|
|
241
|
+
|
|
242
|
+
schedulerLink.classList.add('nav-user-chip');
|
|
243
|
+
schedulerLink.href = '/user/';
|
|
244
|
+
schedulerLink.title = `${resolvedName} (sessão ativa)`;
|
|
245
|
+
schedulerLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
|
|
246
|
+
clearChildren(schedulerLink);
|
|
247
|
+
|
|
248
|
+
const avatarBubble = document.createElement('span');
|
|
249
|
+
avatarBubble.className = 'nav-user-avatar-bubble';
|
|
250
|
+
|
|
251
|
+
const photo = document.createElement('img');
|
|
252
|
+
photo.className = 'nav-user-photo';
|
|
253
|
+
photo.src = resolvedPhoto;
|
|
254
|
+
photo.alt = `Foto de ${resolvedName}`;
|
|
255
|
+
photo.loading = 'lazy';
|
|
256
|
+
photo.decoding = 'async';
|
|
257
|
+
photo.width = 34;
|
|
258
|
+
photo.height = 34;
|
|
259
|
+
photo.onerror = () => {
|
|
260
|
+
photo.src = FALLBACK_THUMB_URL;
|
|
261
|
+
};
|
|
262
|
+
avatarBubble.appendChild(photo);
|
|
263
|
+
schedulerLink.append(avatarBubble);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const setToggleAsUserBubble = (sessionData) => {
|
|
267
|
+
if (!navToggle) return;
|
|
268
|
+
|
|
269
|
+
const profile = sessionData?.user || {};
|
|
270
|
+
const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
|
|
271
|
+
const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
|
|
272
|
+
|
|
273
|
+
navToggle.classList.remove('nav-toggle-login');
|
|
274
|
+
navToggle.classList.add('nav-toggle-user');
|
|
275
|
+
navToggle.dataset.profileUrl = '/user/';
|
|
276
|
+
delete navToggle.dataset.loginUrl;
|
|
277
|
+
navToggle.setAttribute('aria-label', `Abrir perfil de ${resolvedName}`);
|
|
278
|
+
navToggle.removeAttribute('aria-controls');
|
|
279
|
+
navToggle.removeAttribute('aria-expanded');
|
|
280
|
+
clearChildren(navToggle);
|
|
281
|
+
|
|
282
|
+
const photo = document.createElement('img');
|
|
283
|
+
photo.className = 'nav-toggle-photo';
|
|
284
|
+
photo.src = resolvedPhoto;
|
|
285
|
+
photo.alt = `Foto de ${resolvedName}`;
|
|
286
|
+
photo.loading = 'lazy';
|
|
287
|
+
photo.decoding = 'async';
|
|
288
|
+
photo.width = 40;
|
|
289
|
+
photo.height = 40;
|
|
290
|
+
photo.onerror = () => {
|
|
291
|
+
photo.src = FALLBACK_THUMB_URL;
|
|
292
|
+
};
|
|
293
|
+
navToggle.append(photo);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const applyLoggedOutLayout = () => {
|
|
297
|
+
setSchedulerDefaultState();
|
|
298
|
+
if (isMobileViewport()) {
|
|
299
|
+
authLink.classList.add('nav-mobile-hidden');
|
|
300
|
+
setToggleLoginState();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
authLink.classList.remove('nav-mobile-hidden');
|
|
304
|
+
setToggleDefaultState();
|
|
305
|
+
};
|
|
306
|
+
|
|
174
307
|
const setLoginState = () => {
|
|
308
|
+
currentSessionData = null;
|
|
309
|
+
isAuthenticated = false;
|
|
175
310
|
authLink.classList.remove('nav-user-chip');
|
|
311
|
+
authLink.classList.remove('nav-mobile-hidden');
|
|
176
312
|
authLink.href = '/login/';
|
|
177
313
|
authLink.removeAttribute('title');
|
|
178
314
|
authLink.removeAttribute('aria-label');
|
|
@@ -181,18 +317,13 @@ const initAuthSession = () => {
|
|
|
181
317
|
const icon = createIcon('icon-login');
|
|
182
318
|
|
|
183
319
|
authLink.append(icon, document.createTextNode('Entrar'));
|
|
184
|
-
|
|
185
|
-
if (heroLoginCta) {
|
|
186
|
-
heroLoginCta.hidden = false;
|
|
187
|
-
heroLoginCta.removeAttribute('aria-hidden');
|
|
188
|
-
}
|
|
189
|
-
if (finalLoginCta) {
|
|
190
|
-
finalLoginCta.hidden = false;
|
|
191
|
-
finalLoginCta.removeAttribute('aria-hidden');
|
|
192
|
-
}
|
|
320
|
+
applyLoggedOutLayout();
|
|
193
321
|
};
|
|
194
322
|
|
|
195
|
-
const
|
|
323
|
+
const applyLoggedLayout = () => {
|
|
324
|
+
if (!currentSessionData) return;
|
|
325
|
+
|
|
326
|
+
const sessionData = currentSessionData;
|
|
196
327
|
const profile = sessionData?.user || {};
|
|
197
328
|
const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
|
|
198
329
|
const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
|
|
@@ -218,30 +349,42 @@ const initAuthSession = () => {
|
|
|
218
349
|
photo.src = FALLBACK_THUMB_URL;
|
|
219
350
|
};
|
|
220
351
|
avatarBubble.appendChild(photo);
|
|
352
|
+
authLink.append(avatarBubble);
|
|
221
353
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
354
|
+
if (isMobileViewport()) {
|
|
355
|
+
setSchedulerAsUserBubble(sessionData);
|
|
356
|
+
setToggleAsUserBubble(sessionData);
|
|
357
|
+
authLink.classList.add('nav-mobile-hidden');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
226
360
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
361
|
+
authLink.classList.remove('nav-mobile-hidden');
|
|
362
|
+
setSchedulerDefaultState();
|
|
363
|
+
setToggleDefaultState();
|
|
364
|
+
};
|
|
230
365
|
|
|
231
|
-
|
|
232
|
-
|
|
366
|
+
const setLoggedState = (sessionData) => {
|
|
367
|
+
currentSessionData = sessionData || null;
|
|
368
|
+
isAuthenticated = true;
|
|
369
|
+
applyLoggedLayout();
|
|
370
|
+
};
|
|
233
371
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (finalLoginCta) {
|
|
239
|
-
finalLoginCta.hidden = true;
|
|
240
|
-
finalLoginCta.setAttribute('aria-hidden', 'true');
|
|
372
|
+
const onViewportChange = () => {
|
|
373
|
+
if (isAuthenticated) {
|
|
374
|
+
applyLoggedLayout();
|
|
375
|
+
return;
|
|
241
376
|
}
|
|
377
|
+
applyLoggedOutLayout();
|
|
242
378
|
};
|
|
243
379
|
|
|
244
|
-
|
|
380
|
+
if (mobileQuery && typeof mobileQuery.addEventListener === 'function') {
|
|
381
|
+
mobileQuery.addEventListener('change', onViewportChange);
|
|
382
|
+
} else if (mobileQuery && typeof mobileQuery.addListener === 'function') {
|
|
383
|
+
mobileQuery.addListener(onViewportChange);
|
|
384
|
+
}
|
|
385
|
+
window.addEventListener('resize', onViewportChange);
|
|
386
|
+
|
|
387
|
+
const stopBootstrap = runAfterLoadIdle(
|
|
245
388
|
() => {
|
|
246
389
|
fetchHomeBootstrapPayload()
|
|
247
390
|
.then((bootstrapData) => {
|
|
@@ -258,6 +401,16 @@ const initAuthSession = () => {
|
|
|
258
401
|
},
|
|
259
402
|
{ delayMs: 520, timeoutMs: 1200 },
|
|
260
403
|
);
|
|
404
|
+
|
|
405
|
+
return () => {
|
|
406
|
+
stopBootstrap();
|
|
407
|
+
window.removeEventListener('resize', onViewportChange);
|
|
408
|
+
if (mobileQuery && typeof mobileQuery.removeEventListener === 'function') {
|
|
409
|
+
mobileQuery.removeEventListener('change', onViewportChange);
|
|
410
|
+
} else if (mobileQuery && typeof mobileQuery.removeListener === 'function') {
|
|
411
|
+
mobileQuery.removeListener(onViewportChange);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
261
414
|
};
|
|
262
415
|
|
|
263
416
|
const initAddBotCtas = () => {
|
|
@@ -286,7 +439,7 @@ const initAddBotCtas = () => {
|
|
|
286
439
|
() => {
|
|
287
440
|
fetchHomeBootstrapPayload()
|
|
288
441
|
.then((bootstrapData) => {
|
|
289
|
-
const url = String(bootstrapData?.
|
|
442
|
+
const url = String(bootstrapData?.bot_contact?.urls?.menu || '').trim();
|
|
290
443
|
const applied = applyLink(url);
|
|
291
444
|
if (!applied && floatButton) {
|
|
292
445
|
floatButton.hidden = true;
|
|
@@ -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">
|