@kaikybrofc/omnizap-system 2.2.2 → 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.
- package/.env.example +8 -0
- package/README.md +42 -0
- package/app/controllers/messageController.js +133 -1
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +582 -156
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +19 -1
- package/app/services/lidMapService.js +4 -1
- package/app/services/whatsappLoginLinkService.js +232 -0
- package/database/migrations/20260228_0021_sticker_web_google_owner_phone.sql +83 -0
- package/package.json +2 -1
- package/public/index.html +101 -10
- package/public/js/apps/createPackApp.js +59 -272
- package/public/js/apps/homeApp.js +106 -0
- package/public/js/apps/loginApp.js +459 -0
- package/public/js/apps/stickersApp.js +34 -37
- package/public/js/apps/userApp.js +244 -0
- package/public/js/runtime/react-runtime.js +1 -0
- package/public/login/index.html +333 -0
- package/public/stickers/create/index.html +2 -1
- package/public/stickers/index.html +2 -1
- package/public/user/index.html +367 -0
- package/scripts/cache-bust.mjs +65 -11
- package/scripts/release.sh +17 -0
- package/scripts/sync-readme-snapshot.mjs +71 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { React, createRoot, useMemo, useState, useEffect,
|
|
1
|
+
import { React, createRoot, useMemo, useState, useEffect, useCallback } from '../runtime/react-runtime.js?v=20260228-runtime-usecallback1';
|
|
2
2
|
import htm from 'https://esm.sh/htm@3.1.1';
|
|
3
3
|
|
|
4
4
|
const html = htm.bind(React.createElement);
|
|
@@ -9,7 +9,6 @@ const GOOGLE_AUTH_CACHE_KEY = 'omnizap_google_web_auth_cache_v1';
|
|
|
9
9
|
const GOOGLE_AUTH_CACHE_MAX_STALE_MS = 8 * 24 * 60 * 60 * 1000;
|
|
10
10
|
const MAX_MANUAL_TAGS = 8;
|
|
11
11
|
const DEFAULT_SUGGESTED_TAGS = ['anime', 'meme', 'game', 'texto', 'nsfw', 'dark', 'cartoon', 'foto-real', 'cyberpunk'];
|
|
12
|
-
const GOOGLE_GSI_SCRIPT_SRC = 'https://accounts.google.com/gsi/client';
|
|
13
12
|
const PACK_STATUS_PUBLISHED = 'published';
|
|
14
13
|
const FIXED_UPLOAD_QUEUE_CONCURRENCY = 3;
|
|
15
14
|
const UPLOAD_AUTO_RETRY_ATTEMPTS = 2;
|
|
@@ -51,11 +50,6 @@ const sanitizePackName = (value, maxLength = 120) =>
|
|
|
51
50
|
.slice(0, maxLength);
|
|
52
51
|
|
|
53
52
|
const toBytesLabel = (bytes) => `${Math.round(Number(bytes || 0) / 1024)} KB`;
|
|
54
|
-
const normalizePhoneDigits = (value) => String(value || '').replace(/\D+/g, '');
|
|
55
|
-
const isValidPhone = (value) => {
|
|
56
|
-
const digits = normalizePhoneDigits(value);
|
|
57
|
-
return digits.length >= 10 && digits.length <= 15;
|
|
58
|
-
};
|
|
59
53
|
const normalizeTag = (value) =>
|
|
60
54
|
String(value || '')
|
|
61
55
|
.trim()
|
|
@@ -80,19 +74,6 @@ const mergeTags = (...groups) => {
|
|
|
80
74
|
return ordered;
|
|
81
75
|
};
|
|
82
76
|
|
|
83
|
-
const decodeJwtPayload = (jwt) => {
|
|
84
|
-
const parts = String(jwt || '').split('.');
|
|
85
|
-
if (parts.length < 2) return null;
|
|
86
|
-
try {
|
|
87
|
-
const normalized = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
88
|
-
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
|
|
89
|
-
const decoded = atob(padded);
|
|
90
|
-
return JSON.parse(decoded);
|
|
91
|
-
} catch {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
77
|
const normalizeGoogleAuthState = (value) => {
|
|
97
78
|
const user = value?.user && typeof value.user === 'object' ? value.user : null;
|
|
98
79
|
const sub = String(user?.sub || '').trim();
|
|
@@ -163,31 +144,6 @@ const clearGoogleAuthCache = () => {
|
|
|
163
144
|
}
|
|
164
145
|
};
|
|
165
146
|
|
|
166
|
-
const loadScript = (src) =>
|
|
167
|
-
new Promise((resolve, reject) => {
|
|
168
|
-
const existing = document.querySelector(`script[src="${src}"]`);
|
|
169
|
-
if (existing) {
|
|
170
|
-
if (existing.dataset.loaded === '1') {
|
|
171
|
-
resolve();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
existing.addEventListener('load', () => resolve(), { once: true });
|
|
175
|
-
existing.addEventListener('error', () => reject(new Error(`Falha ao carregar script: ${src}`)), { once: true });
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const script = document.createElement('script');
|
|
180
|
-
script.src = src;
|
|
181
|
-
script.async = true;
|
|
182
|
-
script.defer = true;
|
|
183
|
-
script.addEventListener('load', () => {
|
|
184
|
-
script.dataset.loaded = '1';
|
|
185
|
-
resolve();
|
|
186
|
-
});
|
|
187
|
-
script.addEventListener('error', () => reject(new Error(`Falha ao carregar script: ${src}`)));
|
|
188
|
-
document.head.appendChild(script);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
147
|
const fetchJson = async (url, options = {}) => {
|
|
192
148
|
const response = await fetch(url, { credentials: 'same-origin', ...options });
|
|
193
149
|
const payload = await response.json().catch(() => ({}));
|
|
@@ -481,6 +437,7 @@ function CreatePackApp() {
|
|
|
481
437
|
const root = document.getElementById('create-pack-react-root');
|
|
482
438
|
const apiBasePath = root?.dataset?.apiBasePath || '/api/sticker-packs';
|
|
483
439
|
const webPath = root?.dataset?.webPath || '/stickers';
|
|
440
|
+
const loginPath = root?.dataset?.loginPath || '/login';
|
|
484
441
|
const googleSessionApiPath = `${apiBasePath}/auth/google/session`;
|
|
485
442
|
|
|
486
443
|
const [step, setStep] = useState(1);
|
|
@@ -492,13 +449,9 @@ function CreatePackApp() {
|
|
|
492
449
|
const [tags, setTags] = useState([]);
|
|
493
450
|
const [tagInput, setTagInput] = useState('');
|
|
494
451
|
const [suggestedTags, setSuggestedTags] = useState(DEFAULT_SUGGESTED_TAGS);
|
|
495
|
-
const [accountId, setAccountId] = useState('');
|
|
496
|
-
const [googleAuthConfig, setGoogleAuthConfig] = useState({ enabled: false, required: false, clientId: '' });
|
|
497
452
|
const [googleAuth, setGoogleAuth] = useState(() => readGoogleAuthCache() || { user: null, expiresAt: '' });
|
|
498
|
-
const [googleAuthUiReady, setGoogleAuthUiReady] = useState(false);
|
|
499
|
-
const [googleAuthError, setGoogleAuthError] = useState('');
|
|
500
|
-
const [googleAuthBusy, setGoogleAuthBusy] = useState(false);
|
|
501
453
|
const [googleSessionChecked, setGoogleSessionChecked] = useState(false);
|
|
454
|
+
const [authRedirecting, setAuthRedirecting] = useState(false);
|
|
502
455
|
const [files, setFiles] = useState([]);
|
|
503
456
|
const [coverId, setCoverId] = useState('');
|
|
504
457
|
const [dragActive, setDragActive] = useState(false);
|
|
@@ -514,17 +467,31 @@ function CreatePackApp() {
|
|
|
514
467
|
const [backendPublishState, setBackendPublishState] = useState(null);
|
|
515
468
|
const [draftLoaded, setDraftLoaded] = useState(false);
|
|
516
469
|
const [mobilePreviewOpen, setMobilePreviewOpen] = useState(false);
|
|
517
|
-
const googleButtonRef = useRef(null);
|
|
518
|
-
const googleLoginEnabled = Boolean(googleAuthConfig.enabled && googleAuthConfig.clientId);
|
|
519
|
-
const googleLoginRequired = Boolean(googleAuthConfig.required);
|
|
520
470
|
const hasGoogleLogin = Boolean(googleAuth.user?.sub);
|
|
521
|
-
const
|
|
471
|
+
const buildLoginRedirectUrl = useCallback(() => {
|
|
472
|
+
const nextPath = `${window.location.pathname || '/'}${window.location.search || ''}`;
|
|
473
|
+
const loginUrl = new URL(loginPath, window.location.origin);
|
|
474
|
+
loginUrl.searchParams.set('next', nextPath);
|
|
475
|
+
return `${loginUrl.pathname}${loginUrl.search}`;
|
|
476
|
+
}, [loginPath]);
|
|
477
|
+
const redirectToLogin = useCallback(
|
|
478
|
+
(reason = 'Sessão ausente. Redirecionando para login...') => {
|
|
479
|
+
if (authRedirecting) return;
|
|
480
|
+
setAuthRedirecting(true);
|
|
481
|
+
setStatus(reason);
|
|
482
|
+
setError('');
|
|
483
|
+
window.location.assign(buildLoginRedirectUrl());
|
|
484
|
+
},
|
|
485
|
+
[authRedirecting, buildLoginRedirectUrl],
|
|
486
|
+
);
|
|
522
487
|
|
|
523
488
|
const canStep2 = useMemo(
|
|
524
489
|
() =>
|
|
525
490
|
sanitizePackName(name, limits.pack_name_max_length).length > 0 &&
|
|
526
|
-
|
|
527
|
-
|
|
491
|
+
hasGoogleLogin &&
|
|
492
|
+
googleSessionChecked &&
|
|
493
|
+
!authRedirecting,
|
|
494
|
+
[name, limits.pack_name_max_length, hasGoogleLogin, googleSessionChecked, authRedirecting],
|
|
528
495
|
);
|
|
529
496
|
const canStep3 = useMemo(() => files.length > 0, [files.length]);
|
|
530
497
|
const publishReady = canStep2 && canStep3 && !busy;
|
|
@@ -632,16 +599,10 @@ function CreatePackApp() {
|
|
|
632
599
|
const payload = await fetchJson(`${apiBasePath}/create-config`);
|
|
633
600
|
const apiLimits = payload?.data?.limits || {};
|
|
634
601
|
const apiSuggestions = payload?.data?.rules?.suggested_tags;
|
|
635
|
-
const apiGoogleAuth = payload?.data?.auth?.google || {};
|
|
636
602
|
setLimits((prev) => ({ ...prev, ...apiLimits }));
|
|
637
603
|
if (Array.isArray(apiSuggestions) && apiSuggestions.length) {
|
|
638
604
|
setSuggestedTags(mergeTags(apiSuggestions).slice(0, 20));
|
|
639
605
|
}
|
|
640
|
-
setGoogleAuthConfig({
|
|
641
|
-
enabled: Boolean(apiGoogleAuth?.enabled),
|
|
642
|
-
required: Boolean(apiGoogleAuth?.required),
|
|
643
|
-
clientId: String(apiGoogleAuth?.client_id || '').trim(),
|
|
644
|
-
});
|
|
645
606
|
} catch {
|
|
646
607
|
// keep default
|
|
647
608
|
}
|
|
@@ -650,10 +611,6 @@ function CreatePackApp() {
|
|
|
650
611
|
}, [apiBasePath]);
|
|
651
612
|
|
|
652
613
|
useEffect(() => {
|
|
653
|
-
if (!googleLoginEnabled) {
|
|
654
|
-
setGoogleSessionChecked(true);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
614
|
let cancelled = false;
|
|
658
615
|
setGoogleSessionChecked(false);
|
|
659
616
|
|
|
@@ -661,9 +618,10 @@ function CreatePackApp() {
|
|
|
661
618
|
.then((payload) => {
|
|
662
619
|
if (cancelled) return;
|
|
663
620
|
const sessionData = payload?.data || {};
|
|
664
|
-
if (!sessionData?.authenticated || !sessionData?.user?.sub) {
|
|
621
|
+
if (!sessionData?.authenticated || !sessionData?.user?.sub || !sessionData?.owner_jid) {
|
|
665
622
|
setGoogleAuth({ user: null, expiresAt: '' });
|
|
666
623
|
clearGoogleAuthCache();
|
|
624
|
+
redirectToLogin('Sessão inválida ou expirada. Redirecionando para login...');
|
|
667
625
|
return;
|
|
668
626
|
}
|
|
669
627
|
const nextAuth = {
|
|
@@ -677,10 +635,10 @@ function CreatePackApp() {
|
|
|
677
635
|
};
|
|
678
636
|
setGoogleAuth(nextAuth);
|
|
679
637
|
writeGoogleAuthCache(nextAuth);
|
|
680
|
-
setGoogleAuthError('');
|
|
681
638
|
})
|
|
682
639
|
.catch(() => {
|
|
683
|
-
|
|
640
|
+
if (cancelled) return;
|
|
641
|
+
setError('Não foi possível validar sua sessão agora. Recarregue a página.');
|
|
684
642
|
})
|
|
685
643
|
.finally(() => {
|
|
686
644
|
if (cancelled) return;
|
|
@@ -690,110 +648,7 @@ function CreatePackApp() {
|
|
|
690
648
|
return () => {
|
|
691
649
|
cancelled = true;
|
|
692
650
|
};
|
|
693
|
-
}, [
|
|
694
|
-
|
|
695
|
-
useEffect(() => {
|
|
696
|
-
const clearGoogleButton = () => {
|
|
697
|
-
if (googleButtonRef.current) googleButtonRef.current.innerHTML = '';
|
|
698
|
-
try {
|
|
699
|
-
window.google?.accounts?.id?.cancel?.();
|
|
700
|
-
} catch {
|
|
701
|
-
// ignore sdk errors
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
if (!shouldRenderGoogleButton) {
|
|
706
|
-
clearGoogleButton();
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
if (!googleButtonRef.current) return;
|
|
710
|
-
|
|
711
|
-
let cancelled = false;
|
|
712
|
-
setGoogleAuthUiReady(false);
|
|
713
|
-
setGoogleAuthError('');
|
|
714
|
-
|
|
715
|
-
loadScript(GOOGLE_GSI_SCRIPT_SRC)
|
|
716
|
-
.then(() => {
|
|
717
|
-
if (cancelled) return;
|
|
718
|
-
const accounts = window.google?.accounts?.id;
|
|
719
|
-
if (!accounts) throw new Error('SDK do Google não disponível.');
|
|
720
|
-
|
|
721
|
-
accounts.initialize({
|
|
722
|
-
client_id: googleAuthConfig.clientId,
|
|
723
|
-
callback: (response) => {
|
|
724
|
-
const credential = String(response?.credential || '').trim();
|
|
725
|
-
const claims = decodeJwtPayload(credential);
|
|
726
|
-
if (!credential || !claims?.sub) {
|
|
727
|
-
setGoogleAuthError('Falha ao concluir login Google.');
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
setGoogleAuthBusy(true);
|
|
731
|
-
fetchJson(googleSessionApiPath, {
|
|
732
|
-
method: 'POST',
|
|
733
|
-
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
734
|
-
body: JSON.stringify({ google_id_token: credential }),
|
|
735
|
-
})
|
|
736
|
-
.then((sessionPayload) => {
|
|
737
|
-
const sessionData = sessionPayload?.data || {};
|
|
738
|
-
if (!sessionData?.authenticated || !sessionData?.user?.sub) {
|
|
739
|
-
throw new Error('Sessão Google não foi criada.');
|
|
740
|
-
}
|
|
741
|
-
setGoogleAuth({
|
|
742
|
-
user: {
|
|
743
|
-
sub: String(sessionData.user.sub || claims.sub || ''),
|
|
744
|
-
email: String(sessionData.user.email || claims.email || ''),
|
|
745
|
-
name: String(sessionData.user.name || claims.name || claims.given_name || 'Conta Google'),
|
|
746
|
-
picture: String(sessionData.user.picture || claims.picture || ''),
|
|
747
|
-
},
|
|
748
|
-
expiresAt: String(sessionData.expires_at || ''),
|
|
749
|
-
});
|
|
750
|
-
writeGoogleAuthCache({
|
|
751
|
-
user: {
|
|
752
|
-
sub: String(sessionData.user.sub || claims.sub || ''),
|
|
753
|
-
email: String(sessionData.user.email || claims.email || ''),
|
|
754
|
-
name: String(sessionData.user.name || claims.name || claims.given_name || 'Conta Google'),
|
|
755
|
-
picture: String(sessionData.user.picture || claims.picture || ''),
|
|
756
|
-
},
|
|
757
|
-
expiresAt: String(sessionData.expires_at || ''),
|
|
758
|
-
});
|
|
759
|
-
setGoogleAuthError('');
|
|
760
|
-
setError('');
|
|
761
|
-
})
|
|
762
|
-
.catch((sessionError) => {
|
|
763
|
-
setGoogleAuthError(sessionError?.message || 'Falha ao salvar sessão Google.');
|
|
764
|
-
})
|
|
765
|
-
.finally(() => setGoogleAuthBusy(false));
|
|
766
|
-
},
|
|
767
|
-
auto_select: false,
|
|
768
|
-
cancel_on_tap_outside: true,
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
if (googleButtonRef.current) {
|
|
772
|
-
googleButtonRef.current.innerHTML = '';
|
|
773
|
-
const measuredWidth = Math.floor(Number(googleButtonRef.current.clientWidth || 0));
|
|
774
|
-
const buttonWidth = Math.max(180, Math.min(320, measuredWidth || 320));
|
|
775
|
-
accounts.renderButton(googleButtonRef.current, {
|
|
776
|
-
type: 'standard',
|
|
777
|
-
theme: 'filled_black',
|
|
778
|
-
size: 'large',
|
|
779
|
-
text: 'signin_with',
|
|
780
|
-
shape: 'pill',
|
|
781
|
-
logo_alignment: 'left',
|
|
782
|
-
width: buttonWidth,
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
setGoogleAuthUiReady(true);
|
|
786
|
-
})
|
|
787
|
-
.catch((sdkError) => {
|
|
788
|
-
if (cancelled) return;
|
|
789
|
-
setGoogleAuthError(sdkError?.message || 'Falha ao carregar login Google.');
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
return () => {
|
|
793
|
-
cancelled = true;
|
|
794
|
-
clearGoogleButton();
|
|
795
|
-
};
|
|
796
|
-
}, [shouldRenderGoogleButton, googleAuthConfig.clientId, googleSessionApiPath]);
|
|
651
|
+
}, [googleSessionApiPath, redirectToLogin]);
|
|
797
652
|
|
|
798
653
|
useEffect(() => {
|
|
799
654
|
try {
|
|
@@ -814,7 +669,6 @@ function CreatePackApp() {
|
|
|
814
669
|
if (typeof parsed.description === 'string') setDescription(parsed.description);
|
|
815
670
|
if (typeof parsed.publisher === 'string') setPublisher(parsed.publisher);
|
|
816
671
|
if (typeof parsed.visibility === 'string') setVisibility(parsed.visibility);
|
|
817
|
-
if (typeof parsed.accountId === 'string') setAccountId(parsed.accountId);
|
|
818
672
|
if (Array.isArray(parsed.tags)) setTags(mergeTags(parsed.tags).slice(0, MAX_MANUAL_TAGS));
|
|
819
673
|
const parsedStep = Number.isFinite(Number(parsed.step)) ? Math.max(1, Math.min(3, Number(parsed.step))) : 1;
|
|
820
674
|
|
|
@@ -885,7 +739,6 @@ function CreatePackApp() {
|
|
|
885
739
|
description,
|
|
886
740
|
publisher,
|
|
887
741
|
visibility,
|
|
888
|
-
accountId,
|
|
889
742
|
tags,
|
|
890
743
|
coverId,
|
|
891
744
|
activeSession: activeSession?.packKey && activeSession?.editToken ? activeSession : null,
|
|
@@ -911,7 +764,7 @@ function CreatePackApp() {
|
|
|
911
764
|
} catch {
|
|
912
765
|
// ignore storage errors
|
|
913
766
|
}
|
|
914
|
-
}, [draftLoaded, busy, step, name, description, publisher, visibility,
|
|
767
|
+
}, [draftLoaded, busy, step, name, description, publisher, visibility, tags, coverId, files, activeSession]);
|
|
915
768
|
|
|
916
769
|
useEffect(() => {
|
|
917
770
|
if (!draftLoaded) return;
|
|
@@ -1137,13 +990,8 @@ function CreatePackApp() {
|
|
|
1137
990
|
setStep(1);
|
|
1138
991
|
return;
|
|
1139
992
|
}
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1142
|
-
setStep(1);
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
if (!googleLoginRequired && !googleLoginEnabled && !isValidPhone(accountId)) {
|
|
1146
|
-
setError('Informe seu número de celular com DDD para publicar.');
|
|
993
|
+
if (!hasGoogleLogin) {
|
|
994
|
+
redirectToLogin('Sua sessão expirou. Redirecionando para login...');
|
|
1147
995
|
setStep(1);
|
|
1148
996
|
return;
|
|
1149
997
|
}
|
|
@@ -1186,11 +1034,13 @@ function CreatePackApp() {
|
|
|
1186
1034
|
description: finalDescription,
|
|
1187
1035
|
tags,
|
|
1188
1036
|
visibility,
|
|
1189
|
-
owner_jid: hasGoogleLogin ? '' : clampText(accountId, 64),
|
|
1190
1037
|
}),
|
|
1191
1038
|
});
|
|
1192
1039
|
|
|
1193
1040
|
const createPayload = await createResponse.json().catch(() => ({}));
|
|
1041
|
+
if (createResponse.status === 401 || createResponse.status === 403) {
|
|
1042
|
+
redirectToLogin('Sua sessão expirou. Redirecionando para login...');
|
|
1043
|
+
}
|
|
1194
1044
|
if (!createResponse.ok) throw new Error(createPayload?.error || 'Não foi possível criar o pack.');
|
|
1195
1045
|
|
|
1196
1046
|
const created = createPayload?.data || {};
|
|
@@ -1466,13 +1316,11 @@ function CreatePackApp() {
|
|
|
1466
1316
|
setError('Defina um nome para avançar.');
|
|
1467
1317
|
return;
|
|
1468
1318
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
: 'Informe seu número de celular com DDD para avançar.',
|
|
1475
|
-
);
|
|
1319
|
+
if (!googleSessionChecked) {
|
|
1320
|
+
setError('Validando sua sessão. Aguarde alguns segundos...');
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
redirectToLogin('Sessão não encontrada. Redirecionando para login...');
|
|
1476
1324
|
return;
|
|
1477
1325
|
}
|
|
1478
1326
|
if (step === 2 && !canStep3) {
|
|
@@ -1488,23 +1336,6 @@ function CreatePackApp() {
|
|
|
1488
1336
|
setStep((prev) => Math.max(1, prev - 1));
|
|
1489
1337
|
};
|
|
1490
1338
|
|
|
1491
|
-
const disconnectGoogleLogin = () => {
|
|
1492
|
-
try {
|
|
1493
|
-
window.google?.accounts?.id?.disableAutoSelect?.();
|
|
1494
|
-
} catch {
|
|
1495
|
-
// ignore sdk errors
|
|
1496
|
-
}
|
|
1497
|
-
setGoogleAuthBusy(true);
|
|
1498
|
-
fetchJson(googleSessionApiPath, { method: 'DELETE' })
|
|
1499
|
-
.catch(() => null)
|
|
1500
|
-
.finally(() => {
|
|
1501
|
-
clearGoogleAuthCache();
|
|
1502
|
-
setGoogleAuth({ user: null, expiresAt: '' });
|
|
1503
|
-
setGoogleAuthBusy(false);
|
|
1504
|
-
});
|
|
1505
|
-
setGoogleAuthError('');
|
|
1506
|
-
};
|
|
1507
|
-
|
|
1508
1339
|
const restartCreateFlow = () => {
|
|
1509
1340
|
if (busy) return;
|
|
1510
1341
|
const confirmed = window.confirm('Recomeçar a criação? Isso vai limpar o rascunho local e o progresso salvo neste dispositivo.');
|
|
@@ -1518,7 +1349,6 @@ function CreatePackApp() {
|
|
|
1518
1349
|
setVisibility('public');
|
|
1519
1350
|
setTags([]);
|
|
1520
1351
|
setTagInput('');
|
|
1521
|
-
setAccountId('');
|
|
1522
1352
|
setFiles([]);
|
|
1523
1353
|
setCoverId('');
|
|
1524
1354
|
setDragActive(false);
|
|
@@ -1671,69 +1501,26 @@ function CreatePackApp() {
|
|
|
1671
1501
|
hint="Como seu nome será exibido no catálogo."
|
|
1672
1502
|
onChange=${(e) => setPublisher(clampInputText(e.target.value, limits.publisher_max_length))}
|
|
1673
1503
|
/>
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
${hasGoogleLogin
|
|
1695
|
-
? html`
|
|
1696
|
-
<div className="mt-3 flex items-center justify-between gap-2 rounded-xl border border-line/70 bg-panelSoft/80 p-2.5 md:gap-3 md:p-3">
|
|
1697
|
-
<div className="min-w-0">
|
|
1698
|
-
<p className="truncate text-sm font-semibold text-slate-100">${googleAuth.user?.name || 'Conta Google'}</p>
|
|
1699
|
-
<p className="truncate text-xs text-slate-400">${googleAuth.user?.email || ''}</p>
|
|
1700
|
-
</div>
|
|
1701
|
-
<button type="button" onClick=${disconnectGoogleLogin} className="h-10 rounded-lg border border-line/70 px-3 text-xs font-semibold text-slate-200">
|
|
1702
|
-
Trocar conta
|
|
1703
|
-
</button>
|
|
1704
|
-
</div>
|
|
1705
|
-
`
|
|
1706
|
-
: html`
|
|
1707
|
-
<div className="mt-3">
|
|
1708
|
-
<div ref=${googleButtonRef} className="min-h-[42px] w-full max-w-full overflow-hidden"></div>
|
|
1709
|
-
${!googleSessionChecked
|
|
1710
|
-
? html`<p className="mt-2 text-xs text-slate-400">Verificando sessão Google...</p>`
|
|
1711
|
-
: googleAuthBusy
|
|
1712
|
-
? html`<p className="mt-2 text-xs text-slate-400">Conectando conta Google...</p>`
|
|
1713
|
-
: !googleAuthUiReady && !googleAuthError
|
|
1714
|
-
? html`<p className="mt-2 text-xs text-slate-400">Carregando login Google...</p>`
|
|
1715
|
-
: null}
|
|
1716
|
-
${googleSessionChecked && !googleAuthBusy && !shouldRenderGoogleButton && !googleAuthError
|
|
1717
|
-
? html`<p className="mt-2 text-xs text-slate-400">Login Google indisponível no momento. Tente recarregar a página.</p>`
|
|
1718
|
-
: null}
|
|
1719
|
-
</div>
|
|
1720
|
-
`}
|
|
1721
|
-
|
|
1722
|
-
${googleAuthError ? html`<p className="mt-2 text-xs text-rose-300">${googleAuthError}</p>` : null}
|
|
1723
|
-
</div>
|
|
1724
|
-
`
|
|
1725
|
-
: html`
|
|
1726
|
-
<${FloatingField}
|
|
1727
|
-
label="Celular (WhatsApp)"
|
|
1728
|
-
value=${accountId}
|
|
1729
|
-
maxLength=${32}
|
|
1730
|
-
hint="Obrigatório para vincular o pack ao criador. Ex: 5511999998888"
|
|
1731
|
-
onChange=${(e) => setAccountId(String(e.target.value || '').replace(/[^\d+()\-\s]/g, '').slice(0, 32))}
|
|
1732
|
-
/>
|
|
1733
|
-
${accountId && !isValidPhone(accountId)
|
|
1734
|
-
? html`<p className="text-xs text-rose-300">Informe um número válido com DDD (10 a 15 dígitos).</p>`
|
|
1735
|
-
: null}
|
|
1736
|
-
`}
|
|
1504
|
+
<div className="rounded-2xl border border-line/70 bg-panel/70 p-3 md:p-4">
|
|
1505
|
+
<div className="flex items-start justify-between gap-3">
|
|
1506
|
+
<div>
|
|
1507
|
+
<p className="text-xs font-semibold uppercase tracking-[.08em] text-slate-400">Sessão da conta</p>
|
|
1508
|
+
<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>
|
|
1514
|
+
</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>`}
|
|
1518
|
+
</div>
|
|
1519
|
+
<div className="mt-3 rounded-xl border border-line/70 bg-panelSoft/80 p-2.5 md:p-3">
|
|
1520
|
+
<p className="truncate text-sm font-semibold text-slate-100">${googleAuth.user?.name || 'Conta Google'}</p>
|
|
1521
|
+
<p className="truncate text-xs text-slate-400">${googleAuth.user?.email || 'Sessão será validada automaticamente.'}</p>
|
|
1522
|
+
</div>
|
|
1523
|
+
</div>
|
|
1737
1524
|
<label className="block">
|
|
1738
1525
|
<span className="mb-2 inline-block text-xs font-semibold text-slate-300">Tags do pack</span>
|
|
1739
1526
|
<div className="rounded-2xl border border-line/70 bg-panelSoft/80 px-3 py-3">
|
|
@@ -231,6 +231,112 @@ function HomeEffects() {
|
|
|
231
231
|
return () => toggle.removeEventListener('click', handleClick);
|
|
232
232
|
}, []);
|
|
233
233
|
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
const authLink = document.getElementById('nav-auth-link');
|
|
236
|
+
const schedulerLink = document.getElementById('nav-scheduler-link');
|
|
237
|
+
const heroLoginCta = document.getElementById('hero-login-cta');
|
|
238
|
+
if (!authLink) return;
|
|
239
|
+
|
|
240
|
+
const fallbackAvatar = 'https://iili.io/FC3FABe.jpg';
|
|
241
|
+
const clearChildren = (node) => {
|
|
242
|
+
while (node.firstChild) {
|
|
243
|
+
node.removeChild(node.firstChild);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const showLoginButton = () => {
|
|
248
|
+
document.body.classList.remove('home-authenticated');
|
|
249
|
+
authLink.classList.remove('nav-user-chip');
|
|
250
|
+
authLink.href = '/login/';
|
|
251
|
+
authLink.removeAttribute('title');
|
|
252
|
+
authLink.removeAttribute('aria-label');
|
|
253
|
+
clearChildren(authLink);
|
|
254
|
+
|
|
255
|
+
if (schedulerLink) {
|
|
256
|
+
schedulerLink.hidden = false;
|
|
257
|
+
schedulerLink.removeAttribute('aria-hidden');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (heroLoginCta) {
|
|
261
|
+
heroLoginCta.hidden = false;
|
|
262
|
+
heroLoginCta.removeAttribute('aria-hidden');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const icon = document.createElement('i');
|
|
266
|
+
icon.className = 'fa-solid fa-right-to-bracket icon-inline';
|
|
267
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
268
|
+
authLink.append(icon, document.createTextNode('Login'));
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const showLoggedUser = (sessionData) => {
|
|
272
|
+
const profile = sessionData?.user || {};
|
|
273
|
+
const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
|
|
274
|
+
const resolvedPhoto = String(profile?.picture || '').trim() || fallbackAvatar;
|
|
275
|
+
|
|
276
|
+
document.body.classList.add('home-authenticated');
|
|
277
|
+
authLink.classList.add('nav-user-chip');
|
|
278
|
+
authLink.href = '/user/';
|
|
279
|
+
authLink.title = `${resolvedName} (sessão ativa)`;
|
|
280
|
+
authLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
|
|
281
|
+
clearChildren(authLink);
|
|
282
|
+
|
|
283
|
+
if (schedulerLink) {
|
|
284
|
+
schedulerLink.hidden = true;
|
|
285
|
+
schedulerLink.setAttribute('aria-hidden', 'true');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (heroLoginCta) {
|
|
289
|
+
heroLoginCta.hidden = true;
|
|
290
|
+
heroLoginCta.setAttribute('aria-hidden', 'true');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const avatarBubble = document.createElement('span');
|
|
294
|
+
avatarBubble.className = 'nav-user-avatar-bubble';
|
|
295
|
+
|
|
296
|
+
const photo = document.createElement('img');
|
|
297
|
+
photo.className = 'nav-user-photo';
|
|
298
|
+
photo.src = resolvedPhoto;
|
|
299
|
+
photo.alt = `Foto de ${resolvedName}`;
|
|
300
|
+
photo.loading = 'lazy';
|
|
301
|
+
photo.decoding = 'async';
|
|
302
|
+
photo.onerror = () => {
|
|
303
|
+
photo.src = fallbackAvatar;
|
|
304
|
+
};
|
|
305
|
+
avatarBubble.appendChild(photo);
|
|
306
|
+
|
|
307
|
+
const nameBubble = document.createElement('span');
|
|
308
|
+
nameBubble.className = 'nav-user-name-bubble';
|
|
309
|
+
|
|
310
|
+
const icon = document.createElement('i');
|
|
311
|
+
icon.className = 'fa-solid fa-user nav-user-icon';
|
|
312
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
313
|
+
|
|
314
|
+
const name = document.createElement('span');
|
|
315
|
+
name.className = 'nav-user-name';
|
|
316
|
+
name.textContent = resolvedName;
|
|
317
|
+
|
|
318
|
+
nameBubble.append(icon, name);
|
|
319
|
+
authLink.append(avatarBubble, nameBubble);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
fetch('/api/sticker-packs/auth/google/session', { credentials: 'include' })
|
|
323
|
+
.then((response) => {
|
|
324
|
+
if (!response.ok) throw new Error('Sessão indisponível');
|
|
325
|
+
return response.json();
|
|
326
|
+
})
|
|
327
|
+
.then((payload) => {
|
|
328
|
+
const sessionData = payload?.data || {};
|
|
329
|
+
if (!sessionData?.authenticated || !sessionData?.user?.sub) {
|
|
330
|
+
showLoginButton();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
showLoggedUser(sessionData);
|
|
334
|
+
})
|
|
335
|
+
.catch(() => {
|
|
336
|
+
showLoginButton();
|
|
337
|
+
});
|
|
338
|
+
}, []);
|
|
339
|
+
|
|
234
340
|
useEffect(() => {
|
|
235
341
|
const wppButton = document.getElementById('wpp-float');
|
|
236
342
|
if (!wppButton) return;
|