import { zodResolver } from '@hookform/resolvers/zod' import dayjs from 'dayjs' import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' import { useParams } from 'common' import { AuthorizeRequesterDetails } from 'components/interfaces/Organization/OAuthApps/AuthorizeRequesterDetails' import APIAuthorizationLayout from 'components/layouts/APIAuthorizationLayout' import { useApiAuthorizationApproveMutation } from 'data/api-authorization/api-authorization-approve-mutation' import { useApiAuthorizationDeclineMutation } from 'data/api-authorization/api-authorization-decline-mutation' import { useApiAuthorizationQuery } from 'data/api-authorization/api-authorization-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { withAuth } from 'hooks/misc/withAuth' import type { NextPageWithLayout } from 'types' import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, Card, CardContent, CardFooter, CardHeader, CheckIcon, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, FormMessage_Shadcn_, Select_Shadcn_, SelectContent_Shadcn_, SelectItem_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, WarningIcon, } from 'ui' import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' // Need to handle if no organizations in account // Need to handle if not logged in yet state const APIAuthorizationPage: NextPageWithLayout = () => { const router = useRouter() const { auth_id, organization_slug } = useParams() const [isApproving, setIsApproving] = useState(false) const [isDeclining, setIsDeclining] = useState(false) const formSchema = z.object({ selectedOrgSlug: z.string().min(1, 'Please select an organization'), }) const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { selectedOrgSlug: '' }, mode: 'onSubmit', reValidateMode: 'onBlur', }) const selectedOrgSlug = form.watch('selectedOrgSlug') const { data: organizations, isSuccess: isSuccessOrganizations, isPending: isLoadingOrganizations, } = useOrganizationsQuery() const { data: requester, isPending: isLoading, isError, isSuccess, error, } = useApiAuthorizationQuery({ id: auth_id }) const isApproved = (requester?.approved_at ?? null) !== null const isExpired = dayjs().isAfter(dayjs(requester?.expires_at)) const isMcpClient = requester?.registration_type === 'dynamic' const searchParams = typeof window !== 'undefined' ? new URLSearchParams(location.search) : new URLSearchParams() const basePath = process.env.NEXT_PUBLIC_BASE_PATH const pathname = typeof window !== 'undefined' ? basePath ? location.pathname.replace(basePath, '') : location.pathname : '' searchParams.set('returnTo', pathname) const { mutate: approveRequest } = useApiAuthorizationApproveMutation({ onSuccess: (res) => { window.location.href = res.url }, }) const { mutate: declineRequest } = useApiAuthorizationDeclineMutation({ onSuccess: () => { toast.success('Declined API authorization request') router.push('/organizations') }, }) const onApproveRequest = form.handleSubmit((values) => { if (!auth_id) { return toast.error('Unable to approve request: auth_id is missing ') } setIsApproving(true) approveRequest( { id: auth_id, slug: values.selectedOrgSlug }, { onError: () => setIsApproving(false) } ) }) const onDeclineRequest = form.handleSubmit((values) => { if (!auth_id) { return toast.error('Unable to decline request: auth_id is missing ') } setIsDeclining(true) declineRequest( { id: auth_id, slug: values.selectedOrgSlug }, { onError: () => setIsDeclining(false) } ) }) useEffect(() => { if (isSuccessOrganizations && organizations.length > 0) { if (organization_slug) { const preselected = organizations.find(({ slug }) => slug === organization_slug)?.slug if (preselected) form.setValue('selectedOrgSlug', preselected) } else if (!form.getValues('selectedOrgSlug') && organizations.length === 1) { // If user only has one organization and none pre-selected via URL, default to it form.setValue('selectedOrgSlug', organizations[0].slug) } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccessOrganizations]) if (isLoading) { return ( Authorize API access
) } if (isError) { return ( Authorize API access Failed to fetch details for API authorization request

Please retry your authorization request from the requesting app

{error !== undefined &&

Error: {error?.message}

}
) } if (isApproved) { const approvedOrganization = organizations?.find( (org) => org?.slug === requester.approved_organization_slug ) return ( Authorize API access for {requester?.name} This authorization request has been approved

{requester.name} has been approved access to the organization " {approvedOrganization?.name ?? 'Unknown'}" and all of its projects for the following scopes:

Approved on: {dayjs(requester.approved_at).format('DD MMM YYYY HH:mm:ss (ZZ)')}

) } return ( Authorize API access for {requester?.name} {isMcpClient && ( MCP Client Connection This is an MCP (Model Context Protocol) client designed to connect with AI applications. Please ensure you trust this application before granting access to your organization's data. )} {isExpired && ( This authorization request is expired Please retry your authorization request from the requesting app )} {isLoadingOrganizations ? (
) : organizations?.length === 0 ? ( Organization is needed for installing an integration Your account isn't associated with any organizations. To use this integration, it must be installed within an organization. You'll be redirected to create an organization first. ) : organization_slug && !selectedOrgSlug ? ( Organization is needed for installing an integration Your account is not a member of the pre-selected organization. To use this integration, it must be installed within an organization your account is associated with. ) : ( ( {(organizations ?? []).map((organization) => ( {organization.name} ))} )} /> )}
{isLoadingOrganizations ? ( ) : isSuccessOrganizations && organizations.length === 0 ? ( ) : ( )}
) } APIAuthorizationPage.getLayout = (page) => {page} export default withAuth(APIAuthorizationPage)