From 5bc9c12fba54400595881afa11b40954f5dfc9ec Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 24 Jan 2026 23:29:59 +0100 Subject: [PATCH] feat: add captcha functionality to PublicDashboardPage and OrganizationSettings components --- app/share/[id]/page.tsx | 34 ++++++++++++++++++-- components/settings/OrganizationSettings.tsx | 20 ++++++++++-- lib/api/organization.ts | 13 ++++++-- lib/api/stats.ts | 14 +++++++- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/app/share/[id]/page.tsx b/app/share/[id]/page.tsx index bc2f445..9360a52 100644 --- a/app/share/[id]/page.tsx +++ b/app/share/[id]/page.tsx @@ -11,7 +11,7 @@ import TopReferrers from '@/components/dashboard/TopReferrers' import Locations from '@/components/dashboard/Locations' import TechSpecs from '@/components/dashboard/TechSpecs' import PerformanceStats from '@/components/dashboard/PerformanceStats' -import { Select, DatePicker as DatePickerModal } from '@ciphera-net/ui' +import { Select, DatePicker as DatePickerModal, Captcha } from '@ciphera-net/ui' import { ZapIcon } from '@ciphera-net/ui' // Helper to get date ranges @@ -37,6 +37,11 @@ export default function PublicDashboardPage() { const [password, setPassword] = useState(passwordParam || '') const [isPasswordProtected, setIsPasswordProtected] = useState(false) + // Captcha State + const [captchaId, setCaptchaId] = useState('') + const [captchaSolution, setCaptchaSolution] = useState('') + const [captchaToken, setCaptchaToken] = useState('') + // Date range state const [dateRange, setDateRange] = useState(getDateRange(30)) const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) @@ -69,16 +74,29 @@ export default function PublicDashboardPage() { dateRange.end, 10, dateRange.start === dateRange.end ? todayInterval : multiDayInterval, - password + password, + { + captcha_id: captchaId, + captcha_solution: captchaSolution, + captcha_token: captchaToken + } ) setData(dashboardData) setIsPasswordProtected(false) + // Reset captcha + setCaptchaId('') + setCaptchaSolution('') + setCaptchaToken('') } catch (error: any) { if ((error.status === 401 || error.response?.status === 401) && (error.data?.is_protected || error.response?.data?.is_protected)) { setIsPasswordProtected(true) if (password) { - toast.error('Invalid password') + toast.error('Invalid password or captcha') + // Reset captcha on failure + setCaptchaId('') + setCaptchaSolution('') + setCaptchaToken('') } } else if (error.status === 404 || error.response?.status === 404) { toast.error('Site not found') @@ -126,6 +144,16 @@ export default function PublicDashboardPage() { autoFocus /> +
+ { + setCaptchaId(id) + setCaptchaSolution(solution) + setCaptchaToken(token || '') + }} + apiUrl={process.env.NEXT_PUBLIC_CAPTCHA_API_URL} + /> +
+
+ { + setCaptchaId(id) + setCaptchaSolution(solution) + setCaptchaToken(token || '') + }} + apiUrl={process.env.NEXT_PUBLIC_CAPTCHA_API_URL} + /> +
diff --git a/lib/api/organization.ts b/lib/api/organization.ts index e2f447f..7d047de 100644 --- a/lib/api/organization.ts +++ b/lib/api/organization.ts @@ -81,10 +81,19 @@ export async function getOrganizationMembers(organizationId: string): Promise { +export async function sendInvitation( + organizationId: string, + email: string, + role: string = 'member', + captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string } +): Promise { return await authFetch(`/auth/organizations/${organizationId}/invites`, { method: 'POST', - body: JSON.stringify({ email, role }), + body: JSON.stringify({ + email, + role, + ...captcha + }), }) } diff --git a/lib/api/stats.ts b/lib/api/stats.ts index 6e480a9..68c80da 100644 --- a/lib/api/stats.ts +++ b/lib/api/stats.ts @@ -236,12 +236,24 @@ export async function getDashboard(siteId: string, startDate?: string, endDate?: return apiRequest(`/sites/${siteId}/dashboard?${params.toString()}`) } -export async function getPublicDashboard(siteId: string, startDate?: string, endDate?: string, limit = 10, interval?: string, password?: string): Promise { +export async function getPublicDashboard( + siteId: string, + startDate?: string, + endDate?: string, + limit = 10, + interval?: string, + password?: string, + captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string } +): Promise { const params = new URLSearchParams() if (startDate) params.append('start_date', startDate) if (endDate) params.append('end_date', endDate) if (interval) params.append('interval', interval) if (password) params.append('password', password) + if (captcha?.captcha_id) params.append('captcha_id', captcha.captcha_id) + if (captcha?.captcha_solution) params.append('captcha_solution', captcha.captcha_solution) + if (captcha?.captcha_token) params.append('captcha_token', captcha.captcha_token) + params.append('limit', limit.toString()) return apiRequest(`/public/sites/${siteId}/dashboard?${params.toString()}`) }