@greatapps/common 1.1.665 → 1.1.667
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/dist/components/account/ConfigurationsMyAccountModal.mjs +8 -6
- package/dist/components/account/ConfigurationsMyAccountModal.mjs.map +1 -1
- package/dist/components/account/ConfirmDeleteAccountModal.mjs +20 -19
- package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
- package/dist/components/account/ConfirmGlobalPreferencesModal.mjs +10 -11
- package/dist/components/account/ConfirmGlobalPreferencesModal.mjs.map +1 -1
- package/dist/components/account/DeleteAccountModal.mjs +34 -28
- package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
- package/dist/components/account/DisableTwoFactorAuthModal.mjs +7 -5
- package/dist/components/account/DisableTwoFactorAuthModal.mjs.map +1 -1
- package/dist/components/account/IsntPossibleDeleteAccountModal.mjs +8 -9
- package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
- package/dist/components/account/TwoFactorAuthModal.mjs +22 -23
- package/dist/components/account/TwoFactorAuthModal.mjs.map +1 -1
- package/dist/components/account/hooks/useChangeEmailForm.mjs +5 -3
- package/dist/components/account/hooks/useChangeEmailForm.mjs.map +1 -1
- package/dist/components/account/hooks/useChangePasswordForm.mjs +26 -3
- package/dist/components/account/hooks/useChangePasswordForm.mjs.map +1 -1
- package/dist/components/account/hooks/useChangePhoneForm.mjs +18 -9
- package/dist/components/account/hooks/useChangePhoneForm.mjs.map +1 -1
- package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs +26 -3
- package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs.map +1 -1
- package/dist/components/account/hooks/useOtpVerification.mjs +19 -3
- package/dist/components/account/hooks/useOtpVerification.mjs.map +1 -1
- package/dist/components/account/hooks/useTwoFactorAuthForm.mjs +27 -4
- package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
- package/dist/components/account/sections/ChangeEmailModal.mjs +25 -28
- package/dist/components/account/sections/ChangeEmailModal.mjs.map +1 -1
- package/dist/components/account/sections/ChangePasswordSection.mjs +8 -6
- package/dist/components/account/sections/ChangePasswordSection.mjs.map +1 -1
- package/dist/components/account/sections/ChangePhoneModal.mjs +24 -27
- package/dist/components/account/sections/ChangePhoneModal.mjs.map +1 -1
- package/dist/components/account/sections/MyProfileSection.mjs +30 -21
- package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
- package/dist/components/account/sections/PreferencesSection.mjs +41 -23
- package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
- package/dist/components/account/sections/SecuritySection.mjs +11 -9
- package/dist/components/account/sections/SecuritySection.mjs.map +1 -1
- package/dist/components/account/sections/TokenSection.mjs +24 -8
- package/dist/components/account/sections/TokenSection.mjs.map +1 -1
- package/dist/components/embeds/CrispEmbed.mjs +9 -6
- package/dist/components/embeds/CrispEmbed.mjs.map +1 -1
- package/dist/i18n/config.mjs +13 -0
- package/dist/i18n/config.mjs.map +1 -0
- package/dist/i18n/country-language.mjs +74 -0
- package/dist/i18n/country-language.mjs.map +1 -0
- package/dist/i18n/messages/en-us.mjs +211 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -0
- package/dist/i18n/messages/es-es.mjs +211 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -0
- package/dist/i18n/messages/index.mjs +16 -0
- package/dist/i18n/messages/index.mjs.map +1 -0
- package/dist/i18n/messages/pt-br.mjs +216 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -0
- package/dist/i18n/normalize.mjs +12 -0
- package/dist/i18n/normalize.mjs.map +1 -0
- package/dist/i18n/resolve-locale.mjs +42 -0
- package/dist/i18n/resolve-locale.mjs.map +1 -0
- package/dist/i18n.mjs +14 -0
- package/dist/i18n.mjs.map +1 -0
- package/dist/providers/auth.provider.mjs +9 -1
- package/dist/providers/auth.provider.mjs.map +1 -1
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -1
- package/dist/utils/intl/cookie-domain.mjs +17 -0
- package/dist/utils/intl/cookie-domain.mjs.map +1 -0
- package/dist/utils/intl/locale-cookie.mjs +13 -0
- package/dist/utils/intl/locale-cookie.mjs.map +1 -0
- package/dist/utils/intl/locales.mjs +3 -2
- package/dist/utils/intl/locales.mjs.map +1 -1
- package/package.json +7 -1
- package/src/components/account/ConfigurationsMyAccountModal.tsx +10 -6
- package/src/components/account/ConfirmDeleteAccountModal.tsx +22 -13
- package/src/components/account/ConfirmGlobalPreferencesModal.tsx +12 -10
- package/src/components/account/DeleteAccountModal.tsx +34 -24
- package/src/components/account/DisableTwoFactorAuthModal.tsx +11 -6
- package/src/components/account/IsntPossibleDeleteAccountModal.tsx +8 -6
- package/src/components/account/TwoFactorAuthModal.tsx +33 -29
- package/src/components/account/hooks/useChangeEmailForm.tsx +5 -3
- package/src/components/account/hooks/useChangePasswordForm.tsx +20 -6
- package/src/components/account/hooks/useChangePhoneForm.tsx +16 -10
- package/src/components/account/hooks/useDisableTwoFactorAuthForm.tsx +20 -6
- package/src/components/account/hooks/useOtpVerification.tsx +16 -4
- package/src/components/account/hooks/useTwoFactorAuthForm.tsx +21 -7
- package/src/components/account/sections/ChangeEmailModal.tsx +35 -21
- package/src/components/account/sections/ChangePasswordSection.tsx +10 -6
- package/src/components/account/sections/ChangePhoneModal.tsx +50 -22
- package/src/components/account/sections/MyProfileSection.tsx +30 -22
- package/src/components/account/sections/PreferencesSection.tsx +45 -24
- package/src/components/account/sections/SecuritySection.tsx +19 -10
- package/src/components/account/sections/TokenSection.tsx +22 -10
- package/src/components/embeds/CrispEmbed.tsx +12 -8
- package/src/i18n/config.ts +17 -0
- package/src/i18n/country-language.ts +60 -0
- package/src/i18n/messages/en-us.ts +226 -0
- package/src/i18n/messages/es-es.ts +227 -0
- package/src/i18n/messages/index.ts +24 -0
- package/src/i18n/messages/pt-br.ts +236 -0
- package/src/i18n/normalize.ts +15 -0
- package/src/i18n/resolve-locale.ts +77 -0
- package/src/i18n.ts +10 -0
- package/src/providers/auth.provider.tsx +13 -1
- package/src/server.ts +2 -0
- package/src/utils/intl/cookie-domain.ts +29 -0
- package/src/utils/intl/locale-cookie.ts +18 -0
- package/src/utils/intl/locales.ts +3 -2
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { useForm } from 'react-hook-form';
|
|
5
|
+
import { useTranslations, useLocale } from 'next-intl';
|
|
6
|
+
import { useRouter } from 'next/navigation';
|
|
5
7
|
import { toast } from 'sonner';
|
|
6
8
|
import { IconLock } from '@tabler/icons-react';
|
|
7
9
|
import { cn } from '../../../infra/utils/clsx';
|
|
10
|
+
import type { Locale } from '../../../i18n/config';
|
|
11
|
+
import { setLocaleCookie } from '../../../utils/intl/locale-cookie';
|
|
8
12
|
import { Button } from '../../ui/buttons/Button';
|
|
9
13
|
import { Separator } from '../../ui/data-display/Separator';
|
|
10
14
|
import { Toast } from '../../ui/feedback/Toast';
|
|
@@ -13,6 +17,7 @@ import { ComboboxField } from '../../ui/form/ComboboxField';
|
|
|
13
17
|
import { SelectField } from '../../ui/form/SelectField';
|
|
14
18
|
import ConfirmGlobalPreferencesModal from '../ConfirmGlobalPreferencesModal';
|
|
15
19
|
import { useAuth } from '../../../providers/auth.provider';
|
|
20
|
+
import { useWhitelabel } from '../../../providers/whitelabel.provider';
|
|
16
21
|
import { UserProfile } from '../../../modules/users/schema';
|
|
17
22
|
import type { UpdateAccountRequest } from '../../../modules/accounts/types';
|
|
18
23
|
import {
|
|
@@ -41,9 +46,21 @@ interface PreferencesSectionProps {
|
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
49
|
+
// Padrão raiz: um único `translate` chamado pela chave inteira (`common.<modulo>.<chave>`).
|
|
50
|
+
const translate = useTranslations();
|
|
51
|
+
// Exceção: chave dinâmica (option.value) numa lista → namespace escopado.
|
|
52
|
+
const translateLanguage = useTranslations('common.languages');
|
|
53
|
+
const currentLocale = useLocale();
|
|
54
|
+
const router = useRouter();
|
|
44
55
|
const { data: timezones = [] } = useTimezones();
|
|
45
56
|
const { data: currencies = [] } = useCurrencies();
|
|
46
57
|
const { user, account } = useAuth();
|
|
58
|
+
const { whitelabel } = useWhitelabel();
|
|
59
|
+
const languageOptions = LANGUAGE_OPTIONS.map((option) => ({
|
|
60
|
+
...option,
|
|
61
|
+
label: translateLanguage(option.value as Locale),
|
|
62
|
+
displayValue: translateLanguage(option.value as Locale),
|
|
63
|
+
}));
|
|
47
64
|
const isOwner = user?.profile === UserProfile.owner;
|
|
48
65
|
const canEditPreferences = isOwner || user?.profile === UserProfile.admin;
|
|
49
66
|
const updateAccountUser = useUpdateAccountUser();
|
|
@@ -74,7 +91,7 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
74
91
|
companyName: account.name ?? '',
|
|
75
92
|
language: LANGUAGE_OPTIONS.find(
|
|
76
93
|
(o) => o.apiValue === user.language?.toLowerCase()
|
|
77
|
-
)?.value ?? 'pt-
|
|
94
|
+
)?.value ?? 'pt-br',
|
|
78
95
|
currency: account.currency,
|
|
79
96
|
timezone: account.timezone,
|
|
80
97
|
timeFormat: '24h',
|
|
@@ -98,16 +115,20 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
reset(data);
|
|
101
|
-
toast.custom((
|
|
102
|
-
<Toast variant="success" message=
|
|
118
|
+
toast.custom((toastId) => (
|
|
119
|
+
<Toast variant="success" message={translate('common.preferences.savedSuccess')} toastId={toastId} />
|
|
103
120
|
));
|
|
104
121
|
onClose();
|
|
122
|
+
if (data.language !== currentLocale) {
|
|
123
|
+
setLocaleCookie(data.language, whitelabel?.domain);
|
|
124
|
+
router.refresh();
|
|
125
|
+
}
|
|
105
126
|
} catch (error) {
|
|
106
|
-
toast.custom((
|
|
127
|
+
toast.custom((toastId) => (
|
|
107
128
|
<Toast
|
|
108
129
|
variant="error"
|
|
109
|
-
message={error instanceof Error ? error.message : '
|
|
110
|
-
toastId={
|
|
130
|
+
message={error instanceof Error ? error.message : translate('common.preferences.saveError')}
|
|
131
|
+
toastId={toastId}
|
|
111
132
|
/>
|
|
112
133
|
));
|
|
113
134
|
} finally {
|
|
@@ -148,12 +169,12 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
148
169
|
<>
|
|
149
170
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col h-full">
|
|
150
171
|
<div className="overflow-y-auto overscroll-contain px-4 pb-4 lg:p-5 flex flex-col gap-5 lg:gap-6 flex-1">
|
|
151
|
-
<span className="paragraph-medium-semibold text-gray-950">
|
|
172
|
+
<span className="paragraph-medium-semibold text-gray-950">{translate('common.preferences.title')}</span>
|
|
152
173
|
<div className="flex flex-col gap-8">
|
|
153
174
|
<div className="flex flex-col lg:flex-row items-center gap-3">
|
|
154
175
|
<FormField
|
|
155
|
-
label=
|
|
156
|
-
placeholder=
|
|
176
|
+
label={translate('common.preferences.companyName')}
|
|
177
|
+
placeholder={translate('common.preferences.companyNamePlaceholder')}
|
|
157
178
|
classnameContainer={cn('w-full', !isOwner && 'cursor-not-allowed opacity-50')}
|
|
158
179
|
value={watch('companyName')}
|
|
159
180
|
onChange={(e) => setValue('companyName', e.target.value, { shouldDirty: true })}
|
|
@@ -163,18 +184,18 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
163
184
|
|
|
164
185
|
<div className="flex flex-col lg:flex-row items-center gap-3">
|
|
165
186
|
<ComboboxField
|
|
166
|
-
label=
|
|
167
|
-
placeholder=
|
|
168
|
-
searchPlaceholder=
|
|
187
|
+
label={translate('common.preferences.language')}
|
|
188
|
+
placeholder={translate('common.preferences.languagePlaceholder')}
|
|
189
|
+
searchPlaceholder={translate('common.preferences.languageSearch')}
|
|
169
190
|
containerClassName="w-full"
|
|
170
|
-
options={
|
|
191
|
+
options={languageOptions}
|
|
171
192
|
value={watch('language')}
|
|
172
193
|
onChange={(value) => setValue('language', value, { shouldDirty: true })}
|
|
173
194
|
/>
|
|
174
195
|
<ComboboxField
|
|
175
|
-
label=
|
|
176
|
-
placeholder=
|
|
177
|
-
searchPlaceholder=
|
|
196
|
+
label={translate('common.preferences.currency')}
|
|
197
|
+
placeholder={translate('common.preferences.currencyPlaceholder')}
|
|
198
|
+
searchPlaceholder={translate('common.preferences.currencySearch')}
|
|
178
199
|
containerClassName="w-full"
|
|
179
200
|
options={currencies}
|
|
180
201
|
value={watch('currency')}
|
|
@@ -187,12 +208,12 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
187
208
|
<Separator />
|
|
188
209
|
|
|
189
210
|
<div className="flex flex-col gap-4">
|
|
190
|
-
<span className="paragraph-medium-semibold text-gray-950">
|
|
211
|
+
<span className="paragraph-medium-semibold text-gray-950">{translate('common.preferences.timeDisplay')}</span>
|
|
191
212
|
<div className="flex flex-col lg:flex-row items-center gap-3">
|
|
192
213
|
<ComboboxField
|
|
193
|
-
label=
|
|
194
|
-
placeholder=
|
|
195
|
-
searchPlaceholder=
|
|
214
|
+
label={translate('common.preferences.timezone')}
|
|
215
|
+
placeholder={translate('common.preferences.timezonePlaceholder')}
|
|
216
|
+
searchPlaceholder={translate('common.preferences.timezoneSearch')}
|
|
196
217
|
containerClassName="w-full"
|
|
197
218
|
options={timezones}
|
|
198
219
|
value={watch('timezone')}
|
|
@@ -201,8 +222,8 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
201
222
|
disabled={!canEditPreferences}
|
|
202
223
|
/>
|
|
203
224
|
<SelectField
|
|
204
|
-
label=
|
|
205
|
-
placeholder=
|
|
225
|
+
label={translate('common.preferences.timeFormat')}
|
|
226
|
+
placeholder={translate('common.preferences.timeFormatPlaceholder')}
|
|
206
227
|
className="h-10!"
|
|
207
228
|
containerClassName="w-full"
|
|
208
229
|
options={TIME_FORMAT_OPTIONS}
|
|
@@ -230,10 +251,10 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
|
|
|
230
251
|
onClose();
|
|
231
252
|
}}
|
|
232
253
|
>
|
|
233
|
-
|
|
254
|
+
{translate('common.actions.cancel')}
|
|
234
255
|
</Button>
|
|
235
256
|
<Button className="h-10!" type="submit" disabled={isSaving}>
|
|
236
|
-
{isSaving ? '
|
|
257
|
+
{isSaving ? translate('common.actions.saving') : translate('common.actions.saveChanges')}
|
|
237
258
|
</Button>
|
|
238
259
|
</div>
|
|
239
260
|
</form>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { IconAlertTriangle, IconBrandGoogleFilled } from '@tabler/icons-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { toast } from 'sonner';
|
|
5
6
|
import { useQueryClient } from '@tanstack/react-query';
|
|
6
7
|
import { useAuth } from '../../../providers/auth.provider';
|
|
@@ -18,6 +19,7 @@ interface SecuritySectionProps {
|
|
|
18
19
|
export function SecuritySection({
|
|
19
20
|
onDeleteAccount,
|
|
20
21
|
}: SecuritySectionProps) {
|
|
22
|
+
const translate = useTranslations();
|
|
21
23
|
const { user } = useAuth();
|
|
22
24
|
const queryClient = useQueryClient();
|
|
23
25
|
const { mutateAsync: unlinkGoogle, isPending: isUnlinking } = useUnlinkGoogle();
|
|
@@ -32,14 +34,16 @@ export function SecuritySection({
|
|
|
32
34
|
queryClient.invalidateQueries({ queryKey: USER_QUERY_KEY });
|
|
33
35
|
} catch (error) {
|
|
34
36
|
const message =
|
|
35
|
-
error instanceof Error ? error.message : '
|
|
36
|
-
toast.custom((
|
|
37
|
+
error instanceof Error ? error.message : translate('common.account.security.unlinkError');
|
|
38
|
+
toast.custom((toastId) => <Toast variant="error" message={message} toastId={toastId} />);
|
|
37
39
|
}
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
return (
|
|
41
43
|
<div className="h-full overflow-y-auto overscroll-contain px-4 pb-20 lg:p-5 lg:pt-5 flex flex-col gap-5 lg:gap-6 lg:pb-31 lg:overscroll-auto">
|
|
42
|
-
<span className="paragraph-medium-semibold text-gray-950 hidden lg:block">
|
|
44
|
+
<span className="paragraph-medium-semibold text-gray-950 hidden lg:block">
|
|
45
|
+
{translate('common.account.security.title')}
|
|
46
|
+
</span>
|
|
43
47
|
|
|
44
48
|
{hasGoogleLinked && (
|
|
45
49
|
<div className="flex flex-col items-center lg:items-start gap-4">
|
|
@@ -48,10 +52,11 @@ export function SecuritySection({
|
|
|
48
52
|
</div>
|
|
49
53
|
|
|
50
54
|
<div className="flex flex-col gap-2">
|
|
51
|
-
<span className="paragraph-medium-semibold text-gray-950">
|
|
55
|
+
<span className="paragraph-medium-semibold text-gray-950">
|
|
56
|
+
{translate('common.account.security.googleLinkedTitle')}
|
|
57
|
+
</span>
|
|
52
58
|
<span className="paragraph-small-regular text-gray-600">
|
|
53
|
-
|
|
54
|
-
redefinir uma senha de acesso por e-mail.
|
|
59
|
+
{translate('common.account.security.googleLinkedDescription')}
|
|
55
60
|
</span>
|
|
56
61
|
</div>
|
|
57
62
|
|
|
@@ -61,7 +66,9 @@ export function SecuritySection({
|
|
|
61
66
|
className="paragraph-small-semibold text-zinc-700 bg-zinc-100 rounded-lg px-3 py-2 cursor-pointer hover:bg-zinc-200 transition-colors w-fit disabled:opacity-50 disabled:cursor-not-allowed"
|
|
62
67
|
onClick={handleUnlinkGoogle}
|
|
63
68
|
>
|
|
64
|
-
{isUnlinking
|
|
69
|
+
{isUnlinking
|
|
70
|
+
? translate('common.account.security.unlinking')
|
|
71
|
+
: translate('common.account.security.unlinkGoogle')}
|
|
65
72
|
</button>
|
|
66
73
|
</div>
|
|
67
74
|
)}
|
|
@@ -72,9 +79,11 @@ export function SecuritySection({
|
|
|
72
79
|
</div>
|
|
73
80
|
|
|
74
81
|
<div className="flex flex-col gap-2">
|
|
75
|
-
<span className="paragraph-medium-semibold text-gray-950">
|
|
82
|
+
<span className="paragraph-medium-semibold text-gray-950">
|
|
83
|
+
{translate('common.account.deleteAccount.title')}
|
|
84
|
+
</span>
|
|
76
85
|
<span className="paragraph-small-regular text-gray-600">
|
|
77
|
-
|
|
86
|
+
{translate('common.account.security.deleteAccountDescription')}
|
|
78
87
|
</span>
|
|
79
88
|
</div>
|
|
80
89
|
|
|
@@ -84,7 +93,7 @@ export function SecuritySection({
|
|
|
84
93
|
className="paragraph-small-semibold text-red-600 bg-red-50 rounded-lg px-3 py-2 cursor-pointer hover:bg-red-100 transition-colors w-fit"
|
|
85
94
|
onClick={onDeleteAccount}
|
|
86
95
|
>
|
|
87
|
-
|
|
96
|
+
{translate('common.account.security.deleteMyAccount')}
|
|
88
97
|
</button>
|
|
89
98
|
)}
|
|
90
99
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { IconCheck, IconCopy, IconInfoCircle } from '@tabler/icons-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { toast } from 'sonner';
|
|
5
6
|
import { Toast } from '../../ui/feedback/Toast';
|
|
6
7
|
import { Button } from '../../ui/buttons/Button';
|
|
@@ -10,31 +11,42 @@ import { useAccountToken } from '../../../modules/accounts/hooks/use-account-tok
|
|
|
10
11
|
import { useIsDefaultWhitelabel } from '../../../providers/whitelabel.provider';
|
|
11
12
|
|
|
12
13
|
export function TokenSection() {
|
|
14
|
+
const translate = useTranslations();
|
|
13
15
|
const { data: token = '', isLoading } = useAccountToken();
|
|
14
16
|
const isDefaultWl = useIsDefaultWhitelabel();
|
|
15
17
|
|
|
16
18
|
const { isCopied, copy } = useCopyToClipboard({
|
|
17
19
|
onSuccess: () => {
|
|
18
|
-
toast.custom((
|
|
19
|
-
<Toast
|
|
20
|
+
toast.custom((toastId) => (
|
|
21
|
+
<Toast
|
|
22
|
+
variant="success"
|
|
23
|
+
message={translate('common.account.token.copySuccess')}
|
|
24
|
+
toastId={toastId}
|
|
25
|
+
/>
|
|
20
26
|
));
|
|
21
27
|
},
|
|
22
28
|
onError: () => {
|
|
23
|
-
toast.custom((
|
|
24
|
-
<Toast
|
|
29
|
+
toast.custom((toastId) => (
|
|
30
|
+
<Toast
|
|
31
|
+
variant="error"
|
|
32
|
+
message={translate('common.account.token.copyError')}
|
|
33
|
+
toastId={toastId}
|
|
34
|
+
/>
|
|
25
35
|
));
|
|
26
36
|
},
|
|
27
37
|
});
|
|
28
38
|
|
|
29
39
|
return (
|
|
30
40
|
<div className="h-full overflow-y-auto overscroll-contain px-4 pb-20 lg:p-5 lg:pt-5 flex flex-col gap-5 lg:gap-6 lg:pb-31 lg:overscroll-auto">
|
|
31
|
-
<span className="paragraph-medium-semibold text-gray-950">
|
|
41
|
+
<span className="paragraph-medium-semibold text-gray-950">
|
|
42
|
+
{translate('common.account.token.title')}
|
|
43
|
+
</span>
|
|
32
44
|
|
|
33
45
|
<div className="flex flex-col gap-3">
|
|
34
46
|
<div className="flex items-end gap-3">
|
|
35
47
|
<FormField
|
|
36
|
-
label=
|
|
37
|
-
value={isLoading ? '
|
|
48
|
+
label={translate('common.account.token.label')}
|
|
49
|
+
value={isLoading ? translate('common.actions.loading') : token}
|
|
38
50
|
disabled
|
|
39
51
|
readOnly
|
|
40
52
|
classnameContainer="bg-gray-50 cursor-default opacity-100!"
|
|
@@ -48,14 +60,14 @@ export function TokenSection() {
|
|
|
48
60
|
disabled={isLoading || !token}
|
|
49
61
|
>
|
|
50
62
|
{isCopied ? <IconCheck size={16} /> : <IconCopy size={16} />}
|
|
51
|
-
{isCopied ? '
|
|
63
|
+
{isCopied ? translate('common.actions.copied') : translate('common.actions.copy')}
|
|
52
64
|
</Button>
|
|
53
65
|
</div>
|
|
54
66
|
|
|
55
67
|
<div className={`flex items-center gap-3 p-4 rounded-lg ${isDefaultWl ? 'bg-cyan-50' : 'bg-zinc-50'}`}>
|
|
56
68
|
<IconInfoCircle className="size-4 text-zinc-950 shrink-0" />
|
|
57
69
|
<span className="paragraph-small-regular text-zinc-950 max-w-[420px]">
|
|
58
|
-
|
|
70
|
+
{translate('common.account.token.info')}
|
|
59
71
|
</span>
|
|
60
72
|
</div>
|
|
61
73
|
|
|
@@ -65,7 +77,7 @@ export function TokenSection() {
|
|
|
65
77
|
rel="noopener noreferrer"
|
|
66
78
|
className="paragraph-small-medium text-gray-500 underline hover:text-gray-700 transition-colors w-fit"
|
|
67
79
|
>
|
|
68
|
-
|
|
80
|
+
{translate('common.account.token.docsLink')}
|
|
69
81
|
</a>
|
|
70
82
|
</div>
|
|
71
83
|
</div>
|
|
@@ -40,6 +40,10 @@ function crispSessionData(key: string, value: string) {
|
|
|
40
40
|
safeCrispPush(['set', 'session:data', [[[key, value]]]]);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function crispEvent(name: string, data?: Record<string, unknown>, color = 'blue') {
|
|
44
|
+
safeCrispPush(['set', 'session:event', [[[name, data ?? {}, color]]]]);
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
// ── Public API ─────────────────────────────────────────────────
|
|
44
48
|
|
|
45
49
|
export function openCrispHelpdesk() {
|
|
@@ -56,8 +60,8 @@ export function showCrisp() {
|
|
|
56
60
|
|
|
57
61
|
const FREE_PLAN_ID = 5;
|
|
58
62
|
const FREE_PLAN_SIGNUP_QUERY_FLAG = 'createNew';
|
|
59
|
-
const
|
|
60
|
-
const
|
|
63
|
+
const FREE_PLAN_SIGNUP_EVENT = 'cadastro_plano_free';
|
|
64
|
+
const FREE_PLAN_SIGNUP_FIRED_KEY = 'crisp:free-plan-signup:fired';
|
|
61
65
|
|
|
62
66
|
export function updateCrispUser(data: {
|
|
63
67
|
email?: string;
|
|
@@ -173,17 +177,17 @@ export function CrispEmbed() {
|
|
|
173
177
|
} catch {}
|
|
174
178
|
}, []);
|
|
175
179
|
|
|
176
|
-
//
|
|
180
|
+
// Dispara user event de cadastro free para o trigger do Crisp.
|
|
177
181
|
// A mensagem em si é configurada lá no Crisp (Message Trigger com condição
|
|
178
|
-
//
|
|
182
|
+
// "User Event = cadastro_plano_free").
|
|
179
183
|
useEffect(() => {
|
|
180
184
|
if (!user || !shouldLoadCrisp || !isFreshSignup) return;
|
|
181
185
|
if (subscription?.id_plan !== FREE_PLAN_ID) return;
|
|
182
186
|
try {
|
|
183
|
-
const
|
|
184
|
-
if (window.localStorage.getItem(
|
|
185
|
-
|
|
186
|
-
window.localStorage.setItem(
|
|
187
|
+
const firedKey = `${FREE_PLAN_SIGNUP_FIRED_KEY}:${user.id}`;
|
|
188
|
+
if (window.localStorage.getItem(firedKey)) return;
|
|
189
|
+
crispEvent(FREE_PLAN_SIGNUP_EVENT, { id_plan: FREE_PLAN_ID });
|
|
190
|
+
window.localStorage.setItem(firedKey, '1');
|
|
187
191
|
} catch {}
|
|
188
192
|
}, [user, shouldLoadCrisp, isFreshSignup, subscription?.id_plan]);
|
|
189
193
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locales suportados pela plataforma GreatApps.
|
|
3
|
+
* Identificadores no padrão `idioma-REGIÃO` em minúsculas: `pt-br`, `en-us`, `es-es`.
|
|
4
|
+
*/
|
|
5
|
+
export const LOCALES = ['pt-br', 'en-us', 'es-es'] as const;
|
|
6
|
+
|
|
7
|
+
export type Locale = (typeof LOCALES)[number];
|
|
8
|
+
|
|
9
|
+
/** Idioma padrão — fallback final (o produto nasce em pt-BR). */
|
|
10
|
+
export const DEFAULT_LOCALE: Locale = 'pt-br';
|
|
11
|
+
|
|
12
|
+
/** Nome do cookie onde o locale resolvido é persistido (convenção next-intl). */
|
|
13
|
+
export const LOCALE_COOKIE = 'NEXT_LOCALE';
|
|
14
|
+
|
|
15
|
+
export function isLocale(value: unknown): value is Locale {
|
|
16
|
+
return typeof value === 'string' && (LOCALES as readonly string[]).includes(value);
|
|
17
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Locale } from './config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Países de língua portuguesa (ISO 3166-1 alpha-2).
|
|
5
|
+
*/
|
|
6
|
+
const PORTUGUESE_COUNTRIES = new Set<string>([
|
|
7
|
+
'BR', // Brasil
|
|
8
|
+
'PT', // Portugal
|
|
9
|
+
'AO', // Angola
|
|
10
|
+
'MZ', // Moçambique
|
|
11
|
+
'CV', // Cabo Verde
|
|
12
|
+
'GW', // Guiné-Bissau
|
|
13
|
+
'ST', // São Tomé e Príncipe
|
|
14
|
+
'TL', // Timor-Leste
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Países de língua espanhola (ISO 3166-1 alpha-2).
|
|
19
|
+
*/
|
|
20
|
+
const SPANISH_COUNTRIES = new Set<string>([
|
|
21
|
+
'ES', // Espanha
|
|
22
|
+
'MX', // México
|
|
23
|
+
'AR', // Argentina
|
|
24
|
+
'CO', // Colômbia
|
|
25
|
+
'PE', // Peru
|
|
26
|
+
'VE', // Venezuela
|
|
27
|
+
'CL', // Chile
|
|
28
|
+
'EC', // Equador
|
|
29
|
+
'GT', // Guatemala
|
|
30
|
+
'CU', // Cuba
|
|
31
|
+
'BO', // Bolívia
|
|
32
|
+
'DO', // República Dominicana
|
|
33
|
+
'HN', // Honduras
|
|
34
|
+
'PY', // Paraguai
|
|
35
|
+
'SV', // El Salvador
|
|
36
|
+
'NI', // Nicarágua
|
|
37
|
+
'CR', // Costa Rica
|
|
38
|
+
'PA', // Panamá
|
|
39
|
+
'UY', // Uruguai
|
|
40
|
+
'PR', // Porto Rico
|
|
41
|
+
'GQ', // Guiné Equatorial
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Infere o idioma a partir do código de país (ex.: header `CF-IPCountry` da Cloudflare
|
|
46
|
+
* ou `session.location.country` do JWT):
|
|
47
|
+
* - país lusófono → `pt`
|
|
48
|
+
* - país hispanófono → `es`
|
|
49
|
+
* - demais países → `en`
|
|
50
|
+
*
|
|
51
|
+
* Retorna `null` quando o país é desconhecido/ausente (deixa o fallback de locale decidir).
|
|
52
|
+
*/
|
|
53
|
+
export function localeFromCountry(country?: string | null): Locale | null {
|
|
54
|
+
if (!country) return null;
|
|
55
|
+
const code = country.trim().toUpperCase();
|
|
56
|
+
if (code === 'XX' || code === 'T1') return null; // Cloudflare: desconhecido / Tor
|
|
57
|
+
if (PORTUGUESE_COUNTRIES.has(code)) return 'pt-br';
|
|
58
|
+
if (SPANISH_COUNTRIES.has(code)) return 'es-es';
|
|
59
|
+
return 'en-us';
|
|
60
|
+
}
|