From 5d0744f4dfac88278990fe4f2eacd8ec27045e46 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:23 -0300 Subject: [PATCH 01/16] [feat] :sparkles: add security events endpoint mapping (#288) --- src/app/api/hooks/api-resolver.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app/api/hooks/api-resolver.ts b/src/app/api/hooks/api-resolver.ts index 1dd10fe..fa833b1 100644 --- a/src/app/api/hooks/api-resolver.ts +++ b/src/app/api/hooks/api-resolver.ts @@ -24,15 +24,15 @@ axiosInstance.interceptors.request.use((config) => { return config }) axiosInstance.interceptors.response.use( - (res) => res, - (error) => { - if (error.response?.status === 401) { - const errorCode = error.response?.data?.code - if (typeof window !== 'undefined' && errorCode === 'NOT_AUTHORIZED') { - window.dispatchEvent(new Event('auth:unauthorized')) - } - return Promise.reject(error) - } + (res) => res, + (error) => { + if (error.response?.status === 401) { + const errorCode = error.response?.data?.code + if (typeof window !== 'undefined' && errorCode === 'NOT_AUTHORIZED') { + window.dispatchEvent(new Event('auth:unauthorized')) + } + return Promise.reject(error) + } console.error('[API Error]', error.message) return Promise.reject(error) } @@ -113,6 +113,7 @@ export const endpoints = { questions: (id: string) => getFullUIEndpoint(`user/${id}/security/questions`), recoveryQuestions: (id: string) => getFullUIEndpoint(`user/${id}/security/recovery-questions`), + events: (id: string) => getFullUIEndpoint(`user/${id}/security/events`), pin: (id: string) => getFullUIEndpoint(`user/${id}/security/pin`), resetPin: (id: string) => getFullUIEndpoint(`user/${id}/security/pin/reset`) }, From c96a9442dcd33a77fea3dd0b8459a4dd2b9b41a1 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:23 -0300 Subject: [PATCH 02/16] [feat] :sparkles: add security events hook and types (#288) --- src/app/api/hooks/use-security.ts | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/app/api/hooks/use-security.ts b/src/app/api/hooks/use-security.ts index f2a11e2..31f8988 100644 --- a/src/app/api/hooks/use-security.ts +++ b/src/app/api/hooks/use-security.ts @@ -32,6 +32,31 @@ export type SecurityMutationResponse = | { ok: true; data: Record } | { ok: false; message: string } +export type SecurityEventChannel = 'frontend' | 'bot' | string + +export type SecurityEvent = { + id?: string + userId?: string + channelUserId?: string + channel: SecurityEventChannel + eventType: string + message?: string + details?: Record | null + createdAt: string +} + +export type SecurityEventsResponse = + | { ok: true; data: { events: SecurityEvent[] } } + | { ok: false; message: string } + +export type SecurityEventsParams = { + channel?: SecurityEventChannel + eventTypes?: string[] + search?: string + page?: number + pageSize?: number +} + // ---------------------------------------------------------------------- export function useSecurityStatus(userId?: string) { @@ -63,6 +88,20 @@ export function useSecurityQuestionsCatalog(userId?: string) { } } +export function useSecurityEvents(userId?: string, params?: SecurityEventsParams) { + const query = buildSecurityEventsQuery(params) + const endpoint = userId + ? `${endpoints.dashboard.user.security.events(userId)}${query ? `?${query}` : ''}` + : null + return useGetCommon(endpoint, userId ? { headers: getAuthorizationHeader() } : {}) as { + data?: SecurityEventsResponse + isLoading: boolean + error: unknown + isValidating: boolean + empty: boolean + } +} + export async function setRecoveryQuestions( userId: string, questions: Array<{ questionId: string; answer: string }>, @@ -129,6 +168,17 @@ export async function resetPin( } } +function buildSecurityEventsQuery(params?: SecurityEventsParams) { + if (!params) return '' + const query = new URLSearchParams() + if (params.channel) query.set('channel', params.channel) + if (params.search) query.set('search', params.search) + if (params.eventTypes?.length) query.set('eventTypes', params.eventTypes.join(',')) + if (typeof params.page === 'number') query.set('page', String(params.page)) + if (typeof params.pageSize === 'number') query.set('pageSize', String(params.pageSize)) + return query.toString() +} + function extractErrorMessage(error: unknown): string { if (typeof error !== 'object' || error === null) { return 'Request failed' From 0f30a44f4c933ec19eeb62df1f200e593770912e Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:23 -0300 Subject: [PATCH 03/16] [feat] :sparkles: add security events backend service (#288) --- .../api/services/security/security-service.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/app/api/services/security/security-service.ts b/src/app/api/services/security/security-service.ts index f154e02..c017deb 100644 --- a/src/app/api/services/security/security-service.ts +++ b/src/app/api/services/security/security-service.ts @@ -40,6 +40,17 @@ export type SecurityQuestion = { text: string } +export type SecurityEvent = { + id?: string + userId?: string + channelUserId?: string + channel: string + eventType: string + message?: string + details?: Record | null + createdAt: string +} + export type SecurityRecoveryQuestionsResult = { recoveryQuestionsSet: boolean recoveryQuestionIds: string[] @@ -51,6 +62,14 @@ export type SecurityPinResult = { lastSetAt?: string } +export type SecurityEventsFilters = { + channel?: string + eventTypes?: string[] + search?: string + page?: number + pageSize?: number +} + // ---------------------------------------------------------------------- const backendHeaders = { @@ -126,6 +145,53 @@ export async function getSecurityQuestionsCatalog( } } +export async function getSecurityEvents( + phoneNumber: string, + _filters?: SecurityEventsFilters +): Promise> { + const response = await axios.post< + BackendResponse<{ + events: Array<{ + _id?: string + user_id?: string + channel_user_id?: string + channel?: string + event_type?: string + message?: string + details?: Record | null + metadata?: Record | null + created_at?: string + }> + }> + >( + `${BACKEND_API_URL}/get_security_events/`, + { channel_user_id: phoneNumber }, + { headers: backendHeaders } + ) + + if (response.data.status !== 'success') { + return { ok: false, message: normalizeError(response.data) } + } + + const events = (response.data.data.events ?? []).map((event) => ({ + id: event._id, + userId: event.user_id, + channelUserId: event.channel_user_id, + channel: event.channel ?? 'unknown', + eventType: event.event_type ?? 'unknown', + message: event.message, + details: event.details ?? event.metadata ?? null, + createdAt: event.created_at ?? '' + })) + + return { + ok: true, + data: { + events + } + } +} + export async function setSecurityRecoveryQuestions( phoneNumber: string, questions: Array<{ questionId: string; answer: string }> From 6232926d0d62b546957ca40787d7181c1d361584 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:24 -0300 Subject: [PATCH 04/16] [feat] :sparkles: add security events API route (#288) --- .../api/v1/user/[id]/security/events/route.ts | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/app/api/v1/user/[id]/security/events/route.ts diff --git a/src/app/api/v1/user/[id]/security/events/route.ts b/src/app/api/v1/user/[id]/security/events/route.ts new file mode 100644 index 0000000..a2665bc --- /dev/null +++ b/src/app/api/v1/user/[id]/security/events/route.ts @@ -0,0 +1,84 @@ +import { type NextRequest, NextResponse } from 'next/server' + +import { getSecurityEvents } from 'src/app/api/services/security/security-service' +import { validateRequestSecurity } from 'src/app/api/middleware/validators/base-security-validator' +import { validateUserCommonsInputs } from 'src/app/api/middleware/validators/user-common-inputs-validator' +import { getUserById } from 'src/app/api/services/db/chatterpay-db-service' + +// ---------------------------------------------------------------------- + +type IParams = { + id: string +} + +export async function GET(req: NextRequest, { params }: { params: IParams }) { + const userValidationResult = await validateUserCommonsInputs(req, params.id) + if (userValidationResult instanceof NextResponse) { + userValidationResult.headers.set('Cache-Control', 'no-store') + return userValidationResult + } + + const { userId } = userValidationResult + + const securityCheckResult = await validateRequestSecurity(req, userId) + if (securityCheckResult instanceof NextResponse) { + securityCheckResult.headers.set('Cache-Control', 'no-store') + return securityCheckResult + } + + try { + const user = await getUserById(userId) + if (!user) { + return NextResponse.json( + { ok: false, message: 'User not found' }, + { + status: 404, + headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' } + } + ) + } + + const phoneNumber = user.phone_number + if (!phoneNumber || !phoneNumber.trim()) { + return NextResponse.json( + { ok: false, message: 'Missing phoneNumber in session' }, + { + status: 400, + headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' } + } + ) + } + + const { searchParams } = new URL(req.url) + const channel = searchParams.get('channel') ?? undefined + const search = searchParams.get('search') ?? undefined + const eventTypesParam = searchParams.get('eventTypes') + const eventTypes = eventTypesParam + ? eventTypesParam + .split(',') + .map((value) => value.trim()) + .filter(Boolean) + : undefined + const page = searchParams.get('page') + const pageSize = searchParams.get('pageSize') + const pageNumber = page ? Number.parseInt(page, 10) : undefined + const pageSizeNumber = pageSize ? Number.parseInt(pageSize, 10) : undefined + + const result = await getSecurityEvents(phoneNumber, { + channel, + search, + eventTypes, + page: Number.isNaN(pageNumber ?? Number.NaN) ? undefined : pageNumber, + pageSize: Number.isNaN(pageSizeNumber ?? Number.NaN) ? undefined : pageSizeNumber + }) + + return NextResponse.json(result, { + headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' } + }) + } catch { + return NextResponse.json( + { ok: false, message: 'Error getting security events' }, + { status: 400, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' } } + ) + } +} From 104dbfa87afed31d367937f222d6958b14839d0c Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:24 -0300 Subject: [PATCH 05/16] [feat] :sparkles: add security events dashboard page (#288) --- src/app/dashboard/user/security/events/page.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/app/dashboard/user/security/events/page.tsx diff --git a/src/app/dashboard/user/security/events/page.tsx b/src/app/dashboard/user/security/events/page.tsx new file mode 100644 index 0000000..16087b4 --- /dev/null +++ b/src/app/dashboard/user/security/events/page.tsx @@ -0,0 +1,11 @@ +import { SecurityEventsView } from 'src/sections/account/view' + +// ---------------------------------------------------------------------- + +export const metadata = { + title: 'Dashboard - Security Events' +} + +export default function SecurityEventsPage() { + return +} From 020211adc36193d5a1bf64ab5bbbce2a4587c174 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:24 -0300 Subject: [PATCH 06/16] [feat] :sparkles: add security events route path (#288) --- src/routes/paths.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/paths.ts b/src/routes/paths.ts index ea4aeaa..76031bf 100644 --- a/src/routes/paths.ts +++ b/src/routes/paths.ts @@ -54,6 +54,7 @@ export const paths = { security: `${ROOTS.DASHBOARD}/user/security`, securityStatus: `${ROOTS.DASHBOARD}/user/security/status`, securityRecovery: `${ROOTS.DASHBOARD}/user/security/recovery-questions`, + securityEvents: `${ROOTS.DASHBOARD}/user/security/events`, securityPin: `${ROOTS.DASHBOARD}/user/security/pin` } } From 5f62a2e548e4a79f99afe17160df61f29b3d4627 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:25 -0300 Subject: [PATCH 07/16] [feat] :sparkles: add security events entry to security list (#288) --- src/sections/account/security-list.tsx | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/sections/account/security-list.tsx b/src/sections/account/security-list.tsx index 7d520ec..5af6555 100644 --- a/src/sections/account/security-list.tsx +++ b/src/sections/account/security-list.tsx @@ -39,6 +39,8 @@ export default function SecurityList() { ? { color: 'success.main', icon: 'eva:checkmark-fill' } : { color: 'warning.main', icon: 'eva:alert-circle-fill' } + const eventsBadge = { color: 'info.main', icon: 'eva:info-fill' } + return ( @@ -131,6 +133,37 @@ export default function SecurityList() { + + + + + + + + + + + + + From f4ee4f6cadda5365d0bc4f1eb0b0b7abb4393687 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:25 -0300 Subject: [PATCH 08/16] [feat] :label: define security event types (#288) --- src/sections/account/security-types.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sections/account/security-types.ts b/src/sections/account/security-types.ts index eaa5f86..4a7a091 100644 --- a/src/sections/account/security-types.ts +++ b/src/sections/account/security-types.ts @@ -10,3 +10,15 @@ export type SecurityQuestionAnswerInput = { } export type SecurityQuestionAnswerMap = Record + +export const SECURITY_EVENT_TYPES = [ + 'QUESTIONS_UPDATED', + 'PIN_RESET', + 'PIN_BLOCKED', + 'PIN_VERIFY_FAILED', + 'PIN_RESET_FAILED', + 'QUESTIONS_SET', + 'PIN_SET' +] as const + +export type SecurityEventType = (typeof SECURITY_EVENT_TYPES)[number] | string From 3ee93e646a7a27092acb6f2aabe3cb5d75a1d732 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:25 -0300 Subject: [PATCH 09/16] [feat] :sparkles: add security events table with filters (#288) --- src/sections/account/security-events.tsx | 371 +++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 src/sections/account/security-events.tsx diff --git a/src/sections/account/security-events.tsx b/src/sections/account/security-events.tsx new file mode 100644 index 0000000..300d544 --- /dev/null +++ b/src/sections/account/security-events.tsx @@ -0,0 +1,371 @@ +'use client' + +import NProgress from 'nprogress' +import { useEffect, useMemo, useState } from 'react' + +import Alert from '@mui/material/Alert' +import Box from '@mui/material/Box' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import Chip from '@mui/material/Chip' +import FormControl from '@mui/material/FormControl' +import InputLabel from '@mui/material/InputLabel' +import MenuItem from '@mui/material/MenuItem' +import Select from '@mui/material/Select' +import Stack from '@mui/material/Stack' +import Table from '@mui/material/Table' +import TableBody from '@mui/material/TableBody' +import TableCell from '@mui/material/TableCell' +import TableContainer from '@mui/material/TableContainer' +import TableRow from '@mui/material/TableRow' +import TextField from '@mui/material/TextField' +import Tooltip from '@mui/material/Tooltip' +import Typography from '@mui/material/Typography' +import IconButton from '@mui/material/IconButton' + +import { useSecurityEvents } from 'src/app/api/hooks/use-security' +import { useAuthContext } from 'src/auth/hooks' +import EmptyContent from 'src/components/empty-content' +import Iconify from 'src/components/iconify' +import Scrollbar from 'src/components/scrollbar' +import { + TableEmptyRows, + TableHeadCustom, + TableNoData, + TablePaginationCustom, + emptyRows, + useTable +} from 'src/components/table' +import { useTranslate } from 'src/locales' + +import { SECURITY_EVENT_TYPES } from './security-types' + +// ---------------------------------------------------------------------- + +const CHANNEL_OPTIONS = ['all', 'frontend', 'bot'] as const + +const TABLE_HEAD = [ + { id: 'createdAt', label: 'security.events.table.date' }, + { id: 'eventType', label: 'security.events.table.type' }, + { id: 'channel', label: 'security.events.table.channel' }, + { id: 'details', label: 'security.events.table.details' } +] + +export default function SecurityEvents() { + const { t, i18n } = useTranslate() + const { user } = useAuthContext() + const table = useTable({ defaultOrder: 'desc', defaultOrderBy: 'createdAt' }) + + const [channel, setChannel] = useState('all') + const [eventTypes, setEventTypes] = useState([]) + const [search, setSearch] = useState('') + + const hasActiveFilters = channel !== 'all' || eventTypes.length > 0 || search.trim().length > 0 + + const handleClearFilters = () => { + setChannel('all') + setEventTypes([]) + setSearch('') + } + + const { + data: eventsResponse, + isLoading, + isValidating + } = useSecurityEvents(user?.id, { + channel: channel !== 'all' ? channel : undefined, + eventTypes: eventTypes.length ? eventTypes : undefined, + search: search.trim() ? search.trim() : undefined + }) + + const dateFormatter = useMemo(() => { + try { + return new Intl.DateTimeFormat(i18n.language, { + dateStyle: 'medium', + timeStyle: 'short' + }) + } catch { + return new Intl.DateTimeFormat('en', { dateStyle: 'medium', timeStyle: 'short' }) + } + }, [i18n.language]) + + const formatEventDate = (value: string) => { + const date = new Date(value) + if (Number.isNaN(date.getTime())) { + return value + } + return dateFormatter.format(date) + } + + useEffect(() => { + let timer: ReturnType | null = null + if (isLoading || isValidating) { + timer = setTimeout(() => { + NProgress.start() + }, 150) + } else { + NProgress.done() + } + + return () => { + if (timer) clearTimeout(timer) + } + }, [isLoading, isValidating]) + + const events = eventsResponse?.ok ? eventsResponse.data.events : [] + const totalEventsCount = events.length + + const eventTypeOptions = useMemo(() => { + const unique = new Set(SECURITY_EVENT_TYPES) + events.forEach((event) => { + unique.add(event.eventType) + }) + return Array.from(unique) + }, [events]) + + const filteredEvents = useMemo(() => { + const query = search.trim().toLowerCase() + return events.filter((event) => { + if (channel !== 'all' && event.channel !== channel) return false + if (eventTypes.length && !eventTypes.includes(event.eventType)) return false + if (!query) return true + + const details = event.details ? JSON.stringify(event.details).toLowerCase() : '' + const message = event.message?.toLowerCase() ?? '' + return ( + message.includes(query) || + event.eventType.toLowerCase().includes(query) || + event.channel.toLowerCase().includes(query) || + details.includes(query) + ) + }) + }, [channel, eventTypes, events, search]) + + const sortedEvents = useMemo(() => { + const sorted = [...filteredEvents] + const direction = table.order === 'asc' ? 1 : -1 + + const getValue = (event: (typeof filteredEvents)[number]) => { + switch (table.orderBy) { + case 'eventType': + return event.eventType + case 'channel': + return event.channel + case 'details': + return event.details ? JSON.stringify(event.details) : '' + default: + return event.createdAt + } + } + + return sorted.sort((a, b) => { + const aValue = getValue(a) + const bValue = getValue(b) + if (table.orderBy === 'createdAt') { + const aTime = aValue ? new Date(aValue).getTime() : 0 + const bTime = bValue ? new Date(bValue).getTime() : 0 + return (aTime - bTime) * direction + } + return String(aValue).localeCompare(String(bValue)) * direction + }) + }, [filteredEvents, table.order, table.orderBy]) + + const pagedEvents = useMemo(() => { + const start = table.page * table.rowsPerPage + const end = start + table.rowsPerPage + return sortedEvents.slice(start, end) + }, [sortedEvents, table.page, table.rowsPerPage]) + + useEffect(() => { + table.onResetPage() + }, [channel, eventTypes, search, table.onResetPage]) + + const emptyOnFilter = !isLoading && sortedEvents.length === 0 + + const formatEventType = (eventType: string) => { + const label = t(`security.events.types.${eventType}`) + if (label.includes('security.events.types.')) { + return eventType.replace(/_/g, ' ') + } + return label + } + + const formatChannel = (value: string) => { + const label = t(`security.events.channels.${value}`) + if (label.includes('security.events.channels.')) { + return value + } + return label + } + + const renderDetails = (details?: Record | null) => { + if (!details || Object.keys(details).length === 0) { + return t('common.nodata') + } + + const text = JSON.stringify(details) + if (text.length <= 120) return text + return `${text.slice(0, 117)}...` + } + + return ( + + + + + {t('security.events.contextTitle')} + + {t('security.events.contextDescription')} + + {eventsResponse?.ok && ( + + {t('security.events.total').replace('{COUNT}', String(totalEventsCount))} + + )} + + + + + {t('security.events.filters.channel')} + + + + + {t('security.events.filters.eventType')} + + + + + setSearch(event.target.value)} + sx={{ minWidth: 220 }} + /> + + + + + + + + + + + {eventsResponse && !eventsResponse.ok && ( + {t('security.events.errors.generic')} + )} + + {emptyOnFilter ? ( + + ) : ( + <> + + + + ({ + ...item, + label: t(item.label) + }))} + order={table.order} + orderBy={table.orderBy} + onSort={table.onSort} + sx={{ '& th': { px: 3 } }} + /> + + + {pagedEvents.map((event) => ( + + + {event.createdAt + ? formatEventDate(event.createdAt) + : t('common.nodata')} + + + + + + + + + {renderDetails(event.details)} + + + ))} + + + + + +
+
+
+ + + + )} +
+
+
+ ) +} From f208b69a5feb9fa49d64583c669c22714139d245 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:26 -0300 Subject: [PATCH 10/16] [feat] :sparkles: add security events view layout (#288) --- .../account/view/security-events-view.tsx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/sections/account/view/security-events-view.tsx diff --git a/src/sections/account/view/security-events-view.tsx b/src/sections/account/view/security-events-view.tsx new file mode 100644 index 0000000..484cd20 --- /dev/null +++ b/src/sections/account/view/security-events-view.tsx @@ -0,0 +1,45 @@ +'use client' + +import Container from '@mui/material/Container' +import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' + +import { paths } from 'src/routes/paths' +import { useTranslate } from 'src/locales' + +import { useSettingsContext } from 'src/components/settings' +import CustomBreadcrumbs from 'src/components/custom-breadcrumbs' + +import SecurityEvents from '../security-events' + +// ---------------------------------------------------------------------- + +export default function SecurityEventsView() { + const settings = useSettingsContext() + const { t } = useTranslate() + + return ( + + + + + {t('security.events.title')} + + {t('security.events.description')} + + + + + + ) +} From 3bbf33c2dea54cf6774e443d39caf46c6491198d Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:26 -0300 Subject: [PATCH 11/16] [feat] :sparkles: export security events view (#288) --- src/sections/account/view/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sections/account/view/index.ts b/src/sections/account/view/index.ts index 9ab0298..0d9c255 100644 --- a/src/sections/account/view/index.ts +++ b/src/sections/account/view/index.ts @@ -7,6 +7,7 @@ export { default as ReferralsView } from './referrals-view' export { default as SecurityListView } from './security-list-view' export { default as SecurityStatusView } from './security-status-view' export { default as SecurityRecoveryView } from './security-recovery-view' +export { default as SecurityEventsView } from './security-events-view' export { default as SecurityPinView } from './security-pin-view' export { default as ReferralsReferredCodeView } from './referrals-referred-code-view' export { default as ProfileNameView } from './profile-name-view' From 59d4cc96ad161fce8653475d3b6cc6446431deda Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:26 -0300 Subject: [PATCH 12/16] [feat] :globe_with_meridians: add security events translations (en) (#288) --- src/locales/langs/en.json | 46 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/locales/langs/en.json b/src/locales/langs/en.json index 851acee..81b82fd 100644 --- a/src/locales/langs/en.json +++ b/src/locales/langs/en.json @@ -65,7 +65,7 @@ }, "security": { "title": "Security", - "description": "PIN, recovery questions, and status.", + "description": "Status, PIN, recovery questions, and events.", "cta": "Manage security", "badge": { "configured": "Configured", @@ -113,7 +113,9 @@ "recoveryQuestions": "Recovery questions", "recoveryQuestionsDescription": "Update your security questions and answers.", "pinManagement": "Security PIN", - "pinManagementDescription": "Set or reset your security PIN." + "pinManagementDescription": "Set or reset your security PIN.", + "events": "Security events", + "eventsDescription": "Review security activity and changes." } } }, @@ -476,6 +478,46 @@ "reset_required": "Reset required" } }, + "events": { + "title": "Security events", + "description": "Track security-related activity on your account.", + "contextTitle": "Event history", + "contextDescription": "Security events: PIN changes, blocks, security questions updates, and related activity.", + "total": "Total events: {COUNT}", + "filters": { + "channel": "Channel", + "eventType": "Event type", + "search": "Search", + "clear": "Clear filters" + }, + "channels": { + "all": "All channels", + "frontend": "Frontend", + "bot": "Bot" + }, + "table": { + "date": "Date", + "type": "Type", + "channel": "Channel", + "details": "Details" + }, + "empty": { + "title": "No events", + "description": "No security events match the current filters." + }, + "errors": { + "generic": "Unable to load security events. Try again later." + }, + "types": { + "QUESTIONS_UPDATED": "Questions updated", + "PIN_RESET": "PIN reset", + "PIN_BLOCKED": "PIN blocked", + "PIN_VERIFY_FAILED": "PIN verification failed", + "PIN_RESET_FAILED": "PIN reset failed", + "QUESTIONS_SET": "Questions set", + "PIN_SET": "PIN set" + } + }, "recovery": { "title": "Recovery questions", "description": "Set or update your recovery questions and answers.", From f1c5d79306ab51136a25a90c5372acce8e111f83 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:27 -0300 Subject: [PATCH 13/16] [feat] :globe_with_meridians: add security events translations (pt-BR) (#288) --- src/locales/langs/br.json | 47 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/locales/langs/br.json b/src/locales/langs/br.json index f9fc1c4..6d0e627 100644 --- a/src/locales/langs/br.json +++ b/src/locales/langs/br.json @@ -63,7 +63,7 @@ }, "security": { "title": "Seguranca", - "description": "PIN, perguntas de recuperacao e status." + "description": "Status, PIN, perguntas de recuperacao e eventos." }, "referrals": { "title": "Indicacoes", @@ -101,7 +101,9 @@ "recoveryQuestions": "Perguntas de recuperacao", "recoveryQuestionsDescription": "Atualizar suas perguntas e respostas de seguranca.", "pinManagement": "PIN de seguranca", - "pinManagementDescription": "Configurar ou redefinir seu PIN de seguranca." + "pinManagementDescription": "Configurar ou redefinir seu PIN de seguranca.", + "events": "Eventos de seguranca", + "eventsDescription": "Veja a atividade e as mudancas de seguranca." } } }, @@ -465,6 +467,47 @@ "reset_required": "Requer redefinicao" } }, + "events": { + "title": "Eventos de seguranca", + "description": "Acompanhe a atividade de seguranca da sua conta.", + "contextTitle": "Historico de eventos", + "contextDescription": "Eventos de seguranca: mudancas de PIN, bloqueios, atualizacoes de perguntas de seguranca e atividades relacionadas.", + "total": "Total de eventos: {COUNT}", + "filters": { + "channel": "Canal", + "eventType": "Tipo de evento", + "search": "Buscar", + "clear": "Limpar filtros" + }, + "channels": { + "all": "Todos os canais", + "frontend": "Frontend", + "bot": "Bot" + }, + "table": { + "date": "Data", + "type": "Tipo", + "channel": "Canal", + "message": "Mensagem", + "details": "Detalhes" + }, + "empty": { + "title": "Sem eventos", + "description": "Nao ha eventos de seguranca com os filtros atuais." + }, + "errors": { + "generic": "Nao foi possivel carregar os eventos de seguranca. Tente novamente." + }, + "types": { + "QUESTIONS_UPDATED": "Perguntas atualizadas", + "PIN_RESET": "PIN redefinido", + "PIN_BLOCKED": "PIN bloqueado", + "PIN_VERIFY_FAILED": "Falha na verificacao do PIN", + "PIN_RESET_FAILED": "Falha ao redefinir PIN", + "QUESTIONS_SET": "Perguntas configuradas", + "PIN_SET": "PIN configurado" + } + }, "recovery": { "title": "Perguntas de recuperacao", "description": "Configure ou atualize suas perguntas e respostas.", From f893d1bd2f28b2e9c3061f0ee455da7c67d2d8de Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:27 -0300 Subject: [PATCH 14/16] [feat] :globe_with_meridians: add security events translations (es) (#288) --- src/locales/langs/es.json | 47 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/locales/langs/es.json b/src/locales/langs/es.json index 679d277..b1a863b 100644 --- a/src/locales/langs/es.json +++ b/src/locales/langs/es.json @@ -63,7 +63,7 @@ }, "security": { "title": "Seguridad", - "description": "PIN, preguntas de recuperacion y estado." + "description": "Estado, PIN, preguntas de recuperacion y eventos." }, "referrals": { "title": "Referidos", @@ -101,7 +101,9 @@ "recoveryQuestions": "Preguntas de recuperacion", "recoveryQuestionsDescription": "Actualizar tus preguntas y respuestas de seguridad.", "pinManagement": "PIN de seguridad", - "pinManagementDescription": "Configurar o restablecer tu PIN de seguridad." + "pinManagementDescription": "Configurar o restablecer tu PIN de seguridad.", + "events": "Eventos de seguridad", + "eventsDescription": "Revisa la actividad y los cambios de seguridad." } } }, @@ -464,6 +466,47 @@ "reset_required": "Requiere restablecimiento" } }, + "events": { + "title": "Eventos de seguridad", + "description": "Segui la actividad de seguridad de tu cuenta.", + "contextTitle": "Historial de eventos", + "contextDescription": "Eventos de seguridad: cambio de PIN, bloqueos, actualizaciones de preguntas de seguridad y actividades relacionadas.", + "total": "Eventos totales: {COUNT}", + "filters": { + "channel": "Canal", + "eventType": "Tipo de evento", + "search": "Buscar", + "clear": "Limpiar filtros" + }, + "channels": { + "all": "Todos los canales", + "frontend": "Frontend", + "bot": "Bot" + }, + "table": { + "date": "Fecha", + "type": "Tipo", + "channel": "Canal", + "message": "Mensaje", + "details": "Detalles" + }, + "empty": { + "title": "Sin eventos", + "description": "No hay eventos de seguridad con los filtros actuales." + }, + "errors": { + "generic": "No se pudieron cargar los eventos de seguridad. Intenta nuevamente." + }, + "types": { + "QUESTIONS_UPDATED": "Preguntas actualizadas", + "PIN_RESET": "PIN restablecido", + "PIN_BLOCKED": "PIN bloqueado", + "PIN_VERIFY_FAILED": "Verificacion de PIN fallida", + "PIN_RESET_FAILED": "Fallo al restablecer PIN", + "QUESTIONS_SET": "Preguntas configuradas", + "PIN_SET": "PIN configurado" + } + }, "recovery": { "title": "Preguntas de recuperación", "description": "Configura o actualiza tus preguntas y respuestas.", From fbdcc9864820498f1317ad258ed21b96bc695db7 Mon Sep 17 00:00:00 2001 From: dappsar Date: Mon, 19 Jan 2026 23:28:27 -0300 Subject: [PATCH 15/16] [docs] :memo: add sample security events response (#288) --- events.txt | 668 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 668 insertions(+) create mode 100644 events.txt diff --git a/events.txt b/events.txt new file mode 100644 index 0000000..564cc31 --- /dev/null +++ b/events.txt @@ -0,0 +1,668 @@ +{ + "status": "success", + "data": { + "message": "Security events fetched successfully", + "events": [ + { + "_id": "696ea672bc520bd3ff57aa6e", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-19T21:47:30.107Z", + "__v": 0 + }, + { + "_id": "696ea2d4bc520bd3ff57aa07", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-19T21:32:04.191Z", + "__v": 0 + }, + { + "_id": "696ea2c4bc520bd3ff57a9bd", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-19T21:31:48.743Z", + "__v": 0 + }, + { + "_id": "696ea2aebc520bd3ff57a983", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-19T21:31:26.935Z", + "__v": 0 + }, + { + "_id": "696e98b934d1b467f717192e", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:49:57.151Z" + }, + "created_at": "2026-01-19T20:48:57.441Z", + "__v": 0 + }, + { + "_id": "696e98b934d1b467f717192b", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 7 + }, + "created_at": "2026-01-19T20:48:57.299Z", + "__v": 0 + }, + { + "_id": "696e987934d1b467f717190d", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:48:53.976Z" + }, + "created_at": "2026-01-19T20:47:53.994Z", + "__v": 0 + }, + { + "_id": "696e987934d1b467f717190a", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 6 + }, + "created_at": "2026-01-19T20:47:53.986Z", + "__v": 0 + }, + { + "_id": "696e982e34d1b467f71718e6", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:47:38.548Z" + }, + "created_at": "2026-01-19T20:46:38.616Z", + "__v": 0 + }, + { + "_id": "696e982e34d1b467f71718e3", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 5 + }, + "created_at": "2026-01-19T20:46:38.579Z", + "__v": 0 + }, + { + "_id": "696e979b34d1b467f71718bb", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:45:11.166Z" + }, + "created_at": "2026-01-19T20:44:11.246Z", + "__v": 0 + }, + { + "_id": "696e979b34d1b467f71718b8", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 4 + }, + "created_at": "2026-01-19T20:44:11.208Z", + "__v": 0 + }, + { + "_id": "696e95f434d1b467f717187d", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:38:08.592Z" + }, + "created_at": "2026-01-19T20:37:08.613Z", + "__v": 0 + }, + { + "_id": "696e95f434d1b467f717187a", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 3 + }, + "created_at": "2026-01-19T20:37:08.600Z", + "__v": 0 + }, + { + "_id": "696e95f334d1b467f7171875", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 2 + }, + "created_at": "2026-01-19T20:37:07.769Z", + "__v": 0 + }, + { + "_id": "696e95f234d1b467f7171870", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 1 + }, + "created_at": "2026-01-19T20:37:06.927Z", + "__v": 0 + }, + { + "_id": "696e95e334d1b467f7171850", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-19T20:36:51.132Z", + "__v": 0 + }, + { + "_id": "696e958834d1b467f71717cb", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-19T20:35:20.241Z", + "__v": 0 + }, + { + "_id": "696e953d34d1b467f71717a2", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:35:05.287Z" + }, + "created_at": "2026-01-19T20:34:05.306Z", + "__v": 0 + }, + { + "_id": "696e953d34d1b467f717179f", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 5 + }, + "created_at": "2026-01-19T20:34:05.293Z", + "__v": 0 + }, + { + "_id": "696e94b634d1b467f7171790", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:32:50.598Z" + }, + "created_at": "2026-01-19T20:31:50.639Z", + "__v": 0 + }, + { + "_id": "696e94b634d1b467f717178d", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 4 + }, + "created_at": "2026-01-19T20:31:50.615Z", + "__v": 0 + }, + { + "_id": "696e942034d1b467f7171767", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-19T20:30:20.083Z" + }, + "created_at": "2026-01-19T20:29:20.109Z", + "__v": 0 + }, + { + "_id": "696e942034d1b467f7171764", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 3 + }, + "created_at": "2026-01-19T20:29:20.090Z", + "__v": 0 + }, + { + "_id": "696e941f34d1b467f717175f", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 2 + }, + "created_at": "2026-01-19T20:29:19.019Z", + "__v": 0 + }, + { + "_id": "696e941434d1b467f7171741", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 1 + }, + "created_at": "2026-01-19T20:29:08.235Z", + "__v": 0 + }, + { + "_id": "696e6296366c24741bea64f1", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-19T16:57:58.154Z", + "__v": 0 + }, + { + "_id": "696e5ed9366c24741bea641f", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-19T16:42:01.426Z", + "__v": 0 + }, + { + "_id": "696e5ea9366c24741bea6419", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-19T16:41:13.140Z", + "__v": 0 + }, + { + "_id": "696e5e78366c24741bea63fa", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-19T16:40:24.052Z", + "__v": 0 + }, + { + "_id": "696e5dc7366c24741bea638e", + "user_id": "5491153475204", + "event_type": "QUESTIONS_SET", + "channel": "frontend", + "created_at": "2026-01-19T16:37:27.993Z", + "__v": 0 + }, + { + "_id": "696e5d36366c24741bea6326", + "user_id": "5491153475204", + "event_type": "PIN_SET", + "channel": "frontend", + "created_at": "2026-01-19T16:35:02.774Z", + "__v": 0 + }, + { + "_id": "696e5b51366c24741bea6245", + "user_id": "5491153475204", + "event_type": "QUESTIONS_SET", + "channel": "frontend", + "created_at": "2026-01-19T16:26:57.587Z", + "__v": 0 + }, + { + "_id": "696e57dd366c24741bea6109", + "user_id": "5491153475204", + "event_type": "PIN_SET", + "channel": "frontend", + "created_at": "2026-01-19T16:12:13.248Z", + "__v": 0 + }, + { + "_id": "696dc22b994b20baed51de66", + "user_id": "5491153475204", + "event_type": "PIN_SET", + "channel": "frontend", + "created_at": "2026-01-19T05:33:31.808Z", + "__v": 0 + }, + { + "_id": "696dc1c2994b20baed51de11", + "user_id": "5491153475204", + "event_type": "PIN_SET", + "channel": "frontend", + "created_at": "2026-01-19T05:31:46.581Z", + "__v": 0 + }, + { + "_id": "696dc0ae994b20baed51dd99", + "user_id": "5491153475204", + "event_type": "PIN_SET", + "channel": "frontend", + "created_at": "2026-01-19T05:27:10.542Z", + "__v": 0 + }, + { + "_id": "696dbc8e994b20baed51dc2d", + "user_id": "5491153475204", + "event_type": "PIN_SET", + "channel": "frontend", + "created_at": "2026-01-19T05:09:34.211Z", + "__v": 0 + }, + { + "_id": "696dbc28994b20baed51dbd7", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-19T05:07:52.389Z", + "__v": 0 + }, + { + "_id": "696dbbf4994b20baed51db74", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-19T05:07:00.965Z", + "__v": 0 + }, + { + "_id": "696dbb9c994b20baed51db3c", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-19T05:05:32.362Z", + "__v": 0 + }, + { + "_id": "696dbb26994b20baed51dad4", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-19T05:03:34.313Z", + "__v": 0 + }, + { + "_id": "696d0d8785a0dc2f1872a4bf", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-18T16:42:47.700Z", + "__v": 0 + }, + { + "_id": "696d08b185a0dc2f1872a334", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 1 + }, + "created_at": "2026-01-18T16:22:09.783Z", + "__v": 0 + }, + { + "_id": "696d08ad85a0dc2f1872a32f", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-18T16:22:05.750Z", + "__v": 0 + }, + { + "_id": "696d089a85a0dc2f1872a329", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-18T16:22:46.791Z" + }, + "created_at": "2026-01-18T16:21:46.844Z", + "__v": 0 + }, + { + "_id": "696d089a85a0dc2f1872a326", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 7 + }, + "created_at": "2026-01-18T16:21:46.816Z", + "__v": 0 + }, + { + "_id": "696d07f285a0dc2f1872a2e5", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-18T16:19:58.599Z" + }, + "created_at": "2026-01-18T16:18:58.638Z", + "__v": 0 + }, + { + "_id": "696d07f285a0dc2f1872a2e2", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 6 + }, + "created_at": "2026-01-18T16:18:58.618Z", + "__v": 0 + }, + { + "_id": "696d070e85a0dc2f1872a2cb", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-18T16:16:10.160Z" + }, + "created_at": "2026-01-18T16:15:10.188Z", + "__v": 0 + }, + { + "_id": "696d070e85a0dc2f1872a2c8", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 5 + }, + "created_at": "2026-01-18T16:15:10.170Z", + "__v": 0 + }, + { + "_id": "696d065085a0dc2f1872a257", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-18T16:13:00.617Z" + }, + "created_at": "2026-01-18T16:12:00.650Z", + "__v": 0 + }, + { + "_id": "696d065085a0dc2f1872a254", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 4 + }, + "created_at": "2026-01-18T16:12:00.633Z", + "__v": 0 + }, + { + "_id": "696d045c85a0dc2f1872a21d", + "user_id": "5491153475204", + "event_type": "PIN_BLOCKED", + "channel": "frontend", + "metadata": { + "blocked_until": "2026-01-18T16:04:40.280Z" + }, + "created_at": "2026-01-18T16:03:40.309Z", + "__v": 0 + }, + { + "_id": "696d045c85a0dc2f1872a21a", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 3 + }, + "created_at": "2026-01-18T16:03:40.294Z", + "__v": 0 + }, + { + "_id": "696d045b85a0dc2f1872a215", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 2 + }, + "created_at": "2026-01-18T16:03:39.300Z", + "__v": 0 + }, + { + "_id": "696d044e85a0dc2f1872a1de", + "user_id": "5491153475204", + "event_type": "PIN_VERIFY_FAILED", + "channel": "frontend", + "metadata": { + "failed_attempts": 1 + }, + "created_at": "2026-01-18T16:03:26.083Z", + "__v": 0 + }, + { + "_id": "696d03fa85a0dc2f1872a18c", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-18T16:02:02.501Z", + "__v": 0 + }, + { + "_id": "696d03cf85a0dc2f1872a13b", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-18T16:01:19.559Z", + "__v": 0 + }, + { + "_id": "696d035d85a0dc2f1872a0aa", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-18T15:59:25.364Z", + "__v": 0 + }, + { + "_id": "696c96c085a0dc2f18729dad", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-18T08:16:00.148Z", + "__v": 0 + }, + { + "_id": "696c963c85a0dc2f18729d75", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-18T08:13:48.728Z", + "__v": 0 + }, + { + "_id": "696c8e3185a0dc2f18729cdd", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-18T07:39:29.331Z", + "__v": 0 + }, + { + "_id": "696c800685a0dc2f18729c64", + "user_id": "5491153475204", + "event_type": "PIN_RESET", + "channel": "frontend", + "created_at": "2026-01-18T06:39:02.725Z", + "__v": 0 + }, + { + "_id": "696c7ffa85a0dc2f18729c5e", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-18T06:38:50.780Z", + "__v": 0 + }, + { + "_id": "696c7fe985a0dc2f18729c44", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-18T06:38:33.646Z", + "__v": 0 + }, + { + "_id": "696c7fe485a0dc2f18729c40", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-18T06:38:28.538Z", + "__v": 0 + }, + { + "_id": "696c7fe085a0dc2f18729c3c", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-18T06:38:24.892Z", + "__v": 0 + }, + { + "_id": "696c7fd785a0dc2f18729c38", + "user_id": "5491153475204", + "event_type": "PIN_RESET_FAILED", + "channel": "frontend", + "created_at": "2026-01-18T06:38:15.406Z", + "__v": 0 + }, + { + "_id": "696c7f9b85a0dc2f18729c33", + "user_id": "5491153475204", + "event_type": "QUESTIONS_UPDATED", + "channel": "frontend", + "created_at": "2026-01-18T06:37:15.951Z", + "__v": 0 + } + ] + }, + "timestamp": "2026-01-19T22:09:58.156Z" +} \ No newline at end of file From 8a73cb2bde9f24092682a2cee3b73e4f3d0886fe Mon Sep 17 00:00:00 2001 From: dappsar Date: Thu, 5 Feb 2026 15:47:28 -0300 Subject: [PATCH 16/16] [fix] :bug: fix language selection (#298) --- src/auth/context/jwt/auth-provider.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/auth/context/jwt/auth-provider.tsx b/src/auth/context/jwt/auth-provider.tsx index 7b6ab94..efcd699 100644 --- a/src/auth/context/jwt/auth-provider.tsx +++ b/src/auth/context/jwt/auth-provider.tsx @@ -3,7 +3,7 @@ import { useMemo, useEffect, useReducer, useCallback } from 'react' import { post, fetcher, endpoints } from 'src/app/api/hooks/api-resolver' -import { useLocales } from 'src/locales' +import { useTranslate } from 'src/locales' import { setSession } from 'src/auth/context/jwt/utils' import { AuthContext } from './auth-context' @@ -70,7 +70,7 @@ type Props = { children: React.ReactNode } export function AuthProvider({ children }: Props) { const [state, dispatch] = useReducer(reducer, initialState) - const { currentLang } = useLocales() + const { i18n } = useTranslate() const initialize = useCallback(async () => { try { @@ -112,11 +112,11 @@ export function AuthProvider({ children }: Props) { phone, codeMsg, recaptchaToken, - preferred_language: normalizePreferredLanguage(currentLang?.value) + preferred_language: normalizePreferredLanguage(i18n?.language) }) dispatch({ type: Types.GENERATE_CODE_LOGIN, payload: { user: null } }) }, - [currentLang?.value] + [i18n?.language] ) const generate2faCodeEmail = useCallback(