-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP client management. Needs typescript work. #57
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,135 @@ | ||||||||||||||||||||||
import { revalidatePath } from "next/cache" | ||||||||||||||||||||||
import { Session } from "@auth0/nextjs-auth0" | ||||||||||||||||||||||
import { ClientCreateAppTypeEnum } from "auth0" | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
import { managementClient } from "@/lib/auth0" | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
import { withServerActionAuth } from "@/lib/with-server-action-auth" | ||||||||||||||||||||||
|
||||||||||||||||||||||
type CreateApiClientResult = | ||||||||||||||||||||||
| { error: string } | ||||||||||||||||||||||
| { clientId: string; clientSecret: string } | ||||||||||||||||||||||
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
export const createApiClient = withServerActionAuth( | ||||||||||||||||||||||
async function createApiClient(formData: FormData, session: Session): Promise<CreateApiClientResult> { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused variable. Change to
Suggested change
|
||||||||||||||||||||||
const name = formData.get("name") | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
const appType = formData.get("app_type") | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
if (!name || typeof name !== "string") { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Client name is required.", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (!appType || typeof appType !== "string" || !isValidAppType(appType)) { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would recommend letting the server handle the actual enum check. This makes the client more flexible in case the server ever changes or adds types. Then the client simply has to have proper error handling.
Suggested change
|
||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Application type is required and must be valid.", | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
try { | ||||||||||||||||||||||
const newClient = await managementClient.clients.create({ | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. managementClient responses have a
Suggested change
|
||||||||||||||||||||||
name, | ||||||||||||||||||||||
app_type: appType as ClientCreateAppTypeEnum, | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
is_first_party: true, | ||||||||||||||||||||||
}) | ||||||||||||||||||||||
|
||||||||||||||||||||||
revalidatePath("/dashboard/organization/api-clients") | ||||||||||||||||||||||
return { | ||||||||||||||||||||||
clientId: newClient.client_id!, | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unnecessary once you get the response object corrected.
Suggested change
|
||||||||||||||||||||||
clientSecret: newClient.client_secret! | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||
console.error("Failed to create API client", error) | ||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Failed to create API client.", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
role: "admin", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
function isValidAppType(appType: string): appType is ClientCreateAppTypeEnum { | ||||||||||||||||||||||
return ['native', 'spa', 'regular_web', 'non_interactive'].includes(appType) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
Comment on lines
+53
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary function if you let the server handle the enum check.
Suggested change
|
||||||||||||||||||||||
export const deleteApiClient = withServerActionAuth( | ||||||||||||||||||||||
async function deleteApiClient(clientId: string, session: Session) { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
try { | ||||||||||||||||||||||
await managementClient.clients.delete({ client_id: clientId }) | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
revalidatePath("/dashboard/organization/api-clients") | ||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||
console.error("Failed to delete API client", error) | ||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Failed to delete API client.", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return {} | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
role: "admin", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
export const updateApiClient = withServerActionAuth( | ||||||||||||||||||||||
async function updateApiClient(clientId: string, formData: FormData, session: Session) { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
const name = formData.get("name") | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
const appType = formData.get("app_type") | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
if (!name || typeof name !== "string") { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Client name is required.", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (!appType || typeof appType !== "string" || !["native", "spa", "regular_web", "non_interactive"].includes(appType)) { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Application type is required and must be valid.", | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
try { | ||||||||||||||||||||||
await managementClient.clients.update( | ||||||||||||||||||||||
{ client_id: clientId }, | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
name, | ||||||||||||||||||||||
app_type: appType, | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
revalidatePath("/dashboard/organization/api-clients") | ||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||
console.error("Failed to update API client", error) | ||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Failed to update API client.", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return {} | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
role: "admin", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
export const rotateApiClientSecret = withServerActionAuth( | ||||||||||||||||||||||
async function rotateApiClientSecret(clientId: string, session: Session) { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
try { | ||||||||||||||||||||||
const result = await managementClient.clients.rotateSecret({ client_id: clientId }) | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
revalidatePath("/dashboard/organization/api-clients") | ||||||||||||||||||||||
return { clientSecret: result.client_secret } | ||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||
console.error("Failed to rotate API client secret", error) | ||||||||||||||||||||||
return { | ||||||||||||||||||||||
error: "Failed to rotate API client secret.", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
role: "admin", | ||||||||||||||||||||||
} | ||||||||||||||||||||||
) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,123 @@ | ||||||
"use client" | ||||||
|
||||||
import { DotsVerticalIcon, TrashIcon, ReloadIcon } from "@radix-ui/react-icons" | ||||||
import { toast } from "sonner" | ||||||
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
import { Button } from "@/components/ui/button" | ||||||
import { | ||||||
Card, | ||||||
CardContent, | ||||||
CardDescription, | ||||||
CardHeader, | ||||||
CardTitle, | ||||||
} from "@/components/ui/card" | ||||||
import { | ||||||
DropdownMenu, | ||||||
DropdownMenuContent, | ||||||
DropdownMenuItem, | ||||||
DropdownMenuTrigger, | ||||||
} from "@/components/ui/dropdown-menu" | ||||||
import { | ||||||
Table, | ||||||
TableBody, | ||||||
TableCell, | ||||||
TableHead, | ||||||
TableHeader, | ||||||
TableRow, | ||||||
} from "@/components/ui/table" | ||||||
|
||||||
import { deleteApiClient, rotateApiClientSecret } from "./actions" | ||||||
|
||||||
interface Props { | ||||||
clients: { | ||||||
id: string | ||||||
name: string | ||||||
type: string | ||||||
}[] | ||||||
} | ||||||
|
||||||
export function ApiClientsList({ clients }: Props) { | ||||||
return ( | ||||||
<Card> | ||||||
<CardHeader> | ||||||
<CardTitle>API Clients</CardTitle> | ||||||
<CardDescription>The current API clients for your organization.</CardDescription> | ||||||
</CardHeader> | ||||||
<CardContent> | ||||||
<Table> | ||||||
<TableHeader> | ||||||
<TableRow> | ||||||
<TableHead>Client</TableHead> | ||||||
<TableHead>Type</TableHead> | ||||||
<TableHead></TableHead> | ||||||
</TableRow> | ||||||
</TableHeader> | ||||||
<TableBody> | ||||||
{clients.map((client) => ( | ||||||
<TableRow key={client.id}> | ||||||
<TableCell> | ||||||
<div className="flex items-center space-x-4"> | ||||||
<Avatar> | ||||||
<AvatarFallback> | ||||||
{client.name.charAt(0).toUpperCase()} | ||||||
</AvatarFallback> | ||||||
</Avatar> | ||||||
<div> | ||||||
<p className="text-sm font-medium leading-none"> | ||||||
{client.name} | ||||||
</p> | ||||||
<p className="text-sm text-muted-foreground"> | ||||||
{client.id} | ||||||
</p> | ||||||
</div> | ||||||
</div> | ||||||
</TableCell> | ||||||
<TableCell> | ||||||
<span className="capitalize">{client.type}</span> | ||||||
</TableCell> | ||||||
<TableCell className="flex justify-end"> | ||||||
<DropdownMenu> | ||||||
<DropdownMenuTrigger asChild> | ||||||
<Button size="icon" variant="outline"> | ||||||
<DotsVerticalIcon className="size-4" /> | ||||||
</Button> | ||||||
</DropdownMenuTrigger> | ||||||
<DropdownMenuContent align="end"> | ||||||
<DropdownMenuItem | ||||||
onSelect={async () => { | ||||||
const result = await rotateApiClientSecret(client.id) | ||||||
if (result.error) { | ||||||
return toast.error(result.error) | ||||||
} | ||||||
toast.success(`Rotated secret for client: ${client.name}`) | ||||||
// You might want to display the new secret here, or copy it to clipboard | ||||||
}} | ||||||
> | ||||||
<ReloadIcon className="mr-1 size-4" /> | ||||||
Rotate Secret | ||||||
</DropdownMenuItem> | ||||||
<DropdownMenuItem | ||||||
className="text-destructive" | ||||||
onSelect={async () => { | ||||||
const result = await deleteApiClient(client.id) | ||||||
if (result?.error) { | ||||||
return toast.error(result.error) | ||||||
} | ||||||
toast.success(`Deleted client: ${client.name}`) | ||||||
}} | ||||||
> | ||||||
<TrashIcon className="mr-1 size-4" /> | ||||||
Delete | ||||||
</DropdownMenuItem> | ||||||
</DropdownMenuContent> | ||||||
</DropdownMenu> | ||||||
</TableCell> | ||||||
</TableRow> | ||||||
))} | ||||||
</TableBody> | ||||||
</Table> | ||||||
</CardContent> | ||||||
</Card> | ||||||
) | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,87 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"use client" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useRef } from "react" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { toast } from "sonner" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Card, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CardContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CardDescription, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CardFooter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CardHeader, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CardTitle, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} from "@/components/ui/card" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Input } from "@/components/ui/input" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Label } from "@/components/ui/label" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Select, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SelectContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SelectItem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SelectTrigger, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SelectValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} from "@/components/ui/select" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { SubmitButton } from "@/components/submit-button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { createApiClient } from "./actions" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function CreateApiClientForm() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ref = useRef<HTMLFormElement>(null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Card> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<form | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ref={ref} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
action={async (formData: FormData) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const result = await createApiClient(formData) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ('error' in result) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
toast.error(result.error) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
toast.success(`API Client created: ${formData.get("name")}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
toast.info(`Client ID: ${result.clientId}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
toast.info(`Client Secret: ${result.clientSecret}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ref.current?.reset() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+35
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CardHeader> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CardTitle>Create API Client</CardTitle> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CardDescription> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Create a new API client for your application to access this organization's resources. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</CardDescription> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</CardHeader> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CardContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex space-x-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="grid w-full items-center gap-1.5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label htmlFor="name">Client Name</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type="text" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
id="name" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name="name" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
placeholder="My Application" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="grid w-full items-center gap-1.5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label htmlFor="app_type">Application Type</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Select defaultValue="regular_web" name="app_type"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectTrigger id="app_type"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectValue placeholder="Select an application type" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</SelectTrigger> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectItem value="native">Native</SelectItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectItem value="spa">Single Page App</SelectItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectItem value="regular_web">Regular Web App</SelectItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SelectItem value="non_interactive">Non Interactive</SelectItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</SelectContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Select> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</CardContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CardFooter className="flex justify-end"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<SubmitButton>Create Client</SubmitButton> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</CardFooter> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Card> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.