feat: add captcha functionality to PublicDashboardPage and OrganizationSettings components
This commit is contained in:
@@ -11,7 +11,7 @@ import TopReferrers from '@/components/dashboard/TopReferrers'
|
|||||||
import Locations from '@/components/dashboard/Locations'
|
import Locations from '@/components/dashboard/Locations'
|
||||||
import TechSpecs from '@/components/dashboard/TechSpecs'
|
import TechSpecs from '@/components/dashboard/TechSpecs'
|
||||||
import PerformanceStats from '@/components/dashboard/PerformanceStats'
|
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'
|
import { ZapIcon } from '@ciphera-net/ui'
|
||||||
|
|
||||||
// Helper to get date ranges
|
// Helper to get date ranges
|
||||||
@@ -37,6 +37,11 @@ export default function PublicDashboardPage() {
|
|||||||
const [password, setPassword] = useState(passwordParam || '')
|
const [password, setPassword] = useState(passwordParam || '')
|
||||||
const [isPasswordProtected, setIsPasswordProtected] = useState(false)
|
const [isPasswordProtected, setIsPasswordProtected] = useState(false)
|
||||||
|
|
||||||
|
// Captcha State
|
||||||
|
const [captchaId, setCaptchaId] = useState('')
|
||||||
|
const [captchaSolution, setCaptchaSolution] = useState('')
|
||||||
|
const [captchaToken, setCaptchaToken] = useState('')
|
||||||
|
|
||||||
// Date range state
|
// Date range state
|
||||||
const [dateRange, setDateRange] = useState(getDateRange(30))
|
const [dateRange, setDateRange] = useState(getDateRange(30))
|
||||||
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false)
|
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false)
|
||||||
@@ -69,16 +74,29 @@ export default function PublicDashboardPage() {
|
|||||||
dateRange.end,
|
dateRange.end,
|
||||||
10,
|
10,
|
||||||
dateRange.start === dateRange.end ? todayInterval : multiDayInterval,
|
dateRange.start === dateRange.end ? todayInterval : multiDayInterval,
|
||||||
password
|
password,
|
||||||
|
{
|
||||||
|
captcha_id: captchaId,
|
||||||
|
captcha_solution: captchaSolution,
|
||||||
|
captcha_token: captchaToken
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setData(dashboardData)
|
setData(dashboardData)
|
||||||
setIsPasswordProtected(false)
|
setIsPasswordProtected(false)
|
||||||
|
// Reset captcha
|
||||||
|
setCaptchaId('')
|
||||||
|
setCaptchaSolution('')
|
||||||
|
setCaptchaToken('')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if ((error.status === 401 || error.response?.status === 401) && (error.data?.is_protected || error.response?.data?.is_protected)) {
|
if ((error.status === 401 || error.response?.status === 401) && (error.data?.is_protected || error.response?.data?.is_protected)) {
|
||||||
setIsPasswordProtected(true)
|
setIsPasswordProtected(true)
|
||||||
if (password) {
|
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) {
|
} else if (error.status === 404 || error.response?.status === 404) {
|
||||||
toast.error('Site not found')
|
toast.error('Site not found')
|
||||||
@@ -126,6 +144,16 @@ export default function PublicDashboardPage() {
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<Captcha
|
||||||
|
onVerify={(id, solution, token) => {
|
||||||
|
setCaptchaId(id)
|
||||||
|
setCaptchaSolution(solution)
|
||||||
|
setCaptchaToken(token || '')
|
||||||
|
}}
|
||||||
|
apiUrl={process.env.NEXT_PUBLIC_CAPTCHA_API_URL}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full btn-primary"
|
className="w-full btn-primary"
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
UserIcon,
|
UserIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
|
Captcha
|
||||||
} from '@ciphera-net/ui'
|
} from '@ciphera-net/ui'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Button, Input } from '@ciphera-net/ui'
|
import { Button, Input } from '@ciphera-net/ui'
|
||||||
@@ -48,6 +49,11 @@ export default function OrganizationSettings() {
|
|||||||
const [inviteRole, setInviteRole] = useState('member')
|
const [inviteRole, setInviteRole] = useState('member')
|
||||||
const [isInviting, setIsInviting] = useState(false)
|
const [isInviting, setIsInviting] = useState(false)
|
||||||
|
|
||||||
|
// Captcha State
|
||||||
|
const [captchaId, setCaptchaId] = useState('')
|
||||||
|
const [captchaSolution, setCaptchaSolution] = useState('')
|
||||||
|
const [captchaToken, setCaptchaToken] = useState('')
|
||||||
|
|
||||||
// Org Update State
|
// Org Update State
|
||||||
const [orgDetails, setOrgDetails] = useState<Organization | null>(null)
|
const [orgDetails, setOrgDetails] = useState<Organization | null>(null)
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
@@ -358,6 +364,16 @@ export default function OrganizationSettings() {
|
|||||||
Invite
|
Invite
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Captcha
|
||||||
|
onVerify={(id, solution, token) => {
|
||||||
|
setCaptchaId(id)
|
||||||
|
setCaptchaSolution(solution)
|
||||||
|
setCaptchaToken(token || '')
|
||||||
|
}}
|
||||||
|
apiUrl={process.env.NEXT_PUBLIC_CAPTCHA_API_URL}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,19 @@ export async function getOrganizationMembers(organizationId: string): Promise<Or
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send an invitation
|
// Send an invitation
|
||||||
export async function sendInvitation(organizationId: string, email: string, role: string = 'member'): Promise<OrganizationInvitation> {
|
export async function sendInvitation(
|
||||||
|
organizationId: string,
|
||||||
|
email: string,
|
||||||
|
role: string = 'member',
|
||||||
|
captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
|
||||||
|
): Promise<OrganizationInvitation> {
|
||||||
return await authFetch<OrganizationInvitation>(`/auth/organizations/${organizationId}/invites`, {
|
return await authFetch<OrganizationInvitation>(`/auth/organizations/${organizationId}/invites`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ email, role }),
|
body: JSON.stringify({
|
||||||
|
email,
|
||||||
|
role,
|
||||||
|
...captcha
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,12 +236,24 @@ export async function getDashboard(siteId: string, startDate?: string, endDate?:
|
|||||||
return apiRequest<DashboardData>(`/sites/${siteId}/dashboard?${params.toString()}`)
|
return apiRequest<DashboardData>(`/sites/${siteId}/dashboard?${params.toString()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPublicDashboard(siteId: string, startDate?: string, endDate?: string, limit = 10, interval?: string, password?: string): Promise<DashboardData> {
|
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<DashboardData> {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
if (startDate) params.append('start_date', startDate)
|
if (startDate) params.append('start_date', startDate)
|
||||||
if (endDate) params.append('end_date', endDate)
|
if (endDate) params.append('end_date', endDate)
|
||||||
if (interval) params.append('interval', interval)
|
if (interval) params.append('interval', interval)
|
||||||
if (password) params.append('password', password)
|
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())
|
params.append('limit', limit.toString())
|
||||||
return apiRequest<DashboardData>(`/public/sites/${siteId}/dashboard?${params.toString()}`)
|
return apiRequest<DashboardData>(`/public/sites/${siteId}/dashboard?${params.toString()}`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user