mirror of
https://codeberg.org/MarkusThielker/next-ory.git
synced 2025-04-10 11:58:41 +00:00
NORY-46: add initial create-client form to page
This commit is contained in:
parent
253ad4e2b0
commit
749974b7ec
3 changed files with 292 additions and 0 deletions
|
@ -1,3 +1,6 @@
|
|||
import { CreateClientForm } from '@/components/forms/client-form';
|
||||
import { createClient } from '@/lib/action/client';
|
||||
|
||||
export default async function CreateClientPage() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
@ -7,6 +10,7 @@ export default async function CreateClientPage() {
|
|||
Configure your new OAuth2 Client.
|
||||
</p>
|
||||
</div>
|
||||
<CreateClientForm action={createClient}/>
|
||||
</div>
|
||||
);
|
||||
}
|
276
dashboard/src/components/forms/client-form.tsx
Normal file
276
dashboard/src/components/forms/client-form.tsx
Normal file
|
@ -0,0 +1,276 @@
|
|||
'use client';
|
||||
|
||||
import { z } from 'zod';
|
||||
import { clientFormSchema } from '@/lib/forms/client-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { kratos } from '@/ory';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Identity, OAuth2Client } from '@ory/client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
||||
interface CreateClientFormProps {
|
||||
action: (data: z.infer<typeof clientFormSchema>) => Promise<AxiosResponse<OAuth2Client, any>>;
|
||||
}
|
||||
|
||||
export function CreateClientForm({ action }: CreateClientFormProps) {
|
||||
|
||||
const router = useRouter();
|
||||
const [identity, setIdentity] = useState<Identity | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
kratos.toSession()
|
||||
.then(response => response.data)
|
||||
.then(session => setIdentity(session.identity));
|
||||
}, []);
|
||||
|
||||
const form = useForm<z.infer<typeof clientFormSchema>>({
|
||||
resolver: zodResolver(clientFormSchema),
|
||||
defaultValues: {
|
||||
client_name: '',
|
||||
owner: '',
|
||||
scope: '',
|
||||
skip: false,
|
||||
logo_uri: '',
|
||||
policy_uri: '',
|
||||
tos_uri: '',
|
||||
},
|
||||
});
|
||||
|
||||
const [successDialogOpen, setSuccessDialogOpen] = useState(false);
|
||||
const [createdClient, setCreatedClient] = useState<OAuth2Client>();
|
||||
const handleSubmit = async (data: z.infer<typeof clientFormSchema>) => {
|
||||
await action(data)
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
return response.data;
|
||||
})
|
||||
.then((client) => {
|
||||
setCreatedClient(client);
|
||||
setSuccessDialogOpen(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue('owner', identity?.id ?? '');
|
||||
}, [identity]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
createdClient && (
|
||||
<AlertDialog open={successDialogOpen} onOpenChange={() => setSuccessDialogOpen(false)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Client created</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
Your client was created successfully. Make sure to safe the client secret!
|
||||
<Input value={createdClient.client_secret} readOnly/>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
Essentials
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="client_name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Client Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="ACME INC SSO" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The human-readable name of the client to be presented to the end-user
|
||||
during
|
||||
authorization.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scope"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Scopes</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="post:read post:write user:read" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Scope is a string containing a space-separated list of scope values (as
|
||||
described in
|
||||
Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when
|
||||
requesting
|
||||
access
|
||||
tokens.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scope"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Scopes</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="post:read post:write user:read" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Scope is a string containing a space-separated list of scope values (as
|
||||
described in
|
||||
Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when
|
||||
requesting
|
||||
access
|
||||
tokens.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
Consent Screen
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="skip"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">
|
||||
Skip consent
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
Whether or not the consent screen is skipped for this client
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}/>
|
||||
{
|
||||
!form.getValues('skip') && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo_uri"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Logo URI</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://myapp.example/logo.png" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A URL string referencing the client's logo.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="policy_uri"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Policy URI</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://myapp.example/privacy_policy" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A URL string pointing to a human-readable
|
||||
privacy policy document for the client that describes how the
|
||||
deployment organization collects, uses, retains, and discloses
|
||||
personal data.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tos_uri"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Terms URI</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://myapp.example/terms_of_service" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A URL string pointing to a human-readable terms of service
|
||||
document for the client that describes a contractual
|
||||
relationship between the end-user and the client that the
|
||||
end-user accepts when authorizing the client.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="owner"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Owner</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Identity ID" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Owner is a string identifying the owner of the OAuth 2.0 Client.
|
||||
</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="space-x-2">
|
||||
<Button type="button" variant="outline" onClick={() => {
|
||||
router.back();
|
||||
}}>Cancel</Button>
|
||||
<Button type="submit">Create client</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
12
dashboard/src/lib/forms/client-form.ts
Normal file
12
dashboard/src/lib/forms/client-form.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const clientFormSchema = z.object({
|
||||
access_token_strategy: z.string().default('opaque').readonly(),
|
||||
client_name: z.string().min(1, 'Client name is required'),
|
||||
owner: z.string().min(1, 'Owner is required'),
|
||||
scope: z.string(),
|
||||
skip: z.boolean(),
|
||||
logo_uri: z.string().url(),
|
||||
tos_uri: z.string().url(),
|
||||
policy_uri: z.string().url(),
|
||||
});
|
Loading…
Add table
Reference in a new issue