feat: useCreateApp hook

This commit is contained in:
SevicheCC 2023-06-06 00:31:36 +08:00
parent 1990cdb898
commit ae0fc58569
Signed by: SevicheCC
GPG key ID: C577000000000000
10 changed files with 402 additions and 79 deletions

View file

@ -1,15 +1,40 @@
import InputForm from "@/components/InputForm"; import InputForm from "@/components/InputForm";
import ClientOnly from "@/components/ClientOnly"; import ClientOnly from "@/components/ClientOnly";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import DataDisplay from "@/components/DataDisplay";
export default function Home() { export default function Home() {
return ( return (
<main> <main>
<h1 className="mb-5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl"> <div className="flex flex-col gap-5">
M-OAuth <Card className="mt-5">
</h1> <CardHeader>
<CardTitle>M-OAuth</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<ClientOnly> <ClientOnly>
<InputForm /> <InputForm />
</ClientOnly> </ClientOnly>
</CardContent>
{/* <CardFooter>
<p>Card Footer</p>
</CardFooter> */}
</Card>
<Card>
<CardContent>
<DataDisplay />
</CardContent>
</Card>
</div>
</main> </main>
); );
} }

View file

@ -0,0 +1,43 @@
'use client'
import useCreateApp from "@/hooks/useCreateApp";
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { AppEntry } from "../lib/types";
interface DataDisplayProps {
appEntry: AppEntry;
}
const renderTableRow = (label: string, value: string | undefined) => (
<TableRow>
<TableCell className="font-medium">{label}</TableCell>
<TableCell>{value}</TableCell>
</TableRow>
);
const DataDisplay = () => {
const { appEntry } = useCreateApp();
return (
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Data Name</TableHead>
<TableHead>Value</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{renderTableRow("ID", appEntry?.id)}
{renderTableRow("Name", appEntry?.name)}
{renderTableRow("Website", appEntry?.website || '')}
{renderTableRow("Redirect URI", appEntry?.redirectUri)}
{renderTableRow("Client ID", appEntry?.clientId)}
{renderTableRow("Client Secret", appEntry?.clientSecret)}
{renderTableRow("Vapid Key", appEntry?.vapidKey)}
</TableBody>
</Table>
);
};
export default DataDisplay;

View file

@ -1,10 +1,10 @@
"use client"; "use client";
import * as z from "zod"; import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { import {
Form, Form,
FormControl, FormControl,
@ -14,66 +14,10 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
READ_SCOPES,
WRITE_SCOPES,
ADMIN_READ_SCOPES,
ADMIN_WRITE_SCOPES,
} from "@/lib/utils";
import ScopeSection from "./ScopeSection";
export type MethodType = import ScopeSection from "@/components/ScopeSection";
| "read" import { scopesInfo } from "@/lib/utils";
| "write" import useCreateApp from "@/hooks/useCreateApp";
| "follow"
| "crypto"
| "follow"
| "admin"
| "push";
export interface ScopeInfo {
method: MethodType;
label: string;
scopes?: string[] | string[][];
description: string;
}
const scopesInfo: ScopeInfo[] = [
{
method: "read",
label: "Read",
scopes: READ_SCOPES,
description: "read account's data",
},
{
method: "write",
label: "Write",
scopes: WRITE_SCOPES,
description: "modify account's data",
},
{
method: "admin",
label: "Admin",
scopes: [ADMIN_READ_SCOPES, ADMIN_WRITE_SCOPES],
description: "read all data on the server",
},
{
method: "follow",
label: "Follow",
description: "modify account relationships,deprecated in 3.5.0 and newer.",
},
{
method: "push",
label: "Push",
description: "receive push notifications",
},
{
method: "crypto",
label: "Crypto",
description: "use end-to-end encryption",
},
];
const formSchema = z.object({ const formSchema = z.object({
instance: z.string().trim(), instance: z.string().trim(),
@ -83,25 +27,23 @@ const formSchema = z.object({
website: z.string().url().trim().optional(), website: z.string().url().trim().optional(),
}); });
export type FormSchema = z.infer<typeof formSchema>;
const InputForm = () => { const InputForm = () => {
const form = useForm<z.infer<typeof formSchema>>({ const { createApp } = useCreateApp();
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
instance: "https://", instance: "https://",
clientName: "", clientName: "",
redirectUris: "", redirectUris: "",
scopes: [], scopes: ["read"],
}, },
}); });
function onSubmit(values: z.infer<typeof formSchema>) {
const scopes = values.scopes?.join(" ");
console.log(scopes);
}
return ( return (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(createApp)} className="space-y-6 flex flex-col">
<FormField <FormField
control={form.control} control={form.control}
name="instance" name="instance"
@ -190,7 +132,7 @@ const InputForm = () => {
</FormItem> </FormItem>
)} )}
/> />
<Button type="submit">Submit</Button> <Button type="submit" className="w-ful">Submit</Button>
</form> </form>
</Form> </Form>
); );

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { MethodType } from "./InputForm";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { MethodType } from "@/lib/types";
interface ScopeCheckboxProps { interface ScopeCheckboxProps {
scope: string; scope: string;

View file

@ -1,5 +1,6 @@
"use client"; "use client";
import { ChevronsUpDown } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { import {
Collapsible, Collapsible,
@ -7,10 +8,10 @@ import {
CollapsibleTrigger, CollapsibleTrigger,
} from "@/components/ui/collapsible"; } from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ChevronsUpDown } from "lucide-react"; import ScopeItem from "@/components/ScopeItem";
import { ScopeInfo } from "@/lib/types";
import { ScopeInfo } from "./InputForm";
import ScopeItem from "./ScopeItem";
interface ScopeSectionProps { interface ScopeSectionProps {
info: ScopeInfo; info: ScopeInfo;
field: any; field: any;

79
components/ui/card.tsx Normal file
View file

@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(" flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

114
components/ui/table.tsx Normal file
View file

@ -0,0 +1,114 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("bg-primary font-medium text-primary-foreground", className)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

54
hooks/useCreateApp.ts Normal file
View file

@ -0,0 +1,54 @@
import { FormSchema } from "@/components/InputForm";
import { useCallback, useState } from "react";
import { AppEntry } from "@/lib/types";
type MError = {
error: string;
};
const useCreateApp = () => {
const [appEntry, setAppEntry] = useState<AppEntry>();
const createApp = useCallback(
async ({
instance,
website,
clientName,
redirectUris,
scopes,
}: FormSchema) => {
const app = {
website,
client_name: clientName,
redirect_uris: redirectUris,
scopes: scopes?.join(" "),
};
console.log("app,", app);
try {
let request = await fetch(`${instance}/api/v1/apps`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(app),
});
if (!request.ok || request.status === 424) {
throw new Error((await request.json()).error);
}
setAppEntry(await request.json());
} catch (error) {
throw new Error((error as MError).error);
}
},
[]
);
console.log(appEntry);
return {
appEntry,
createApp,
};
};
export default useCreateApp;

27
lib/types.ts Normal file
View file

@ -0,0 +1,27 @@
export type MethodType =
| "read"
| "write"
| "follow"
| "crypto"
| "follow"
| "admin"
| "push";
export interface ScopeInfo {
method: MethodType;
label: string;
scopes?: string[] | string[][];
description: string;
}
export interface AppEntry {
id: string;
name: string;
website: string | null;
redirectUri: string;
clientId: string;
clientSecret: string;
vapidKey: string;
}

View file

@ -1,5 +1,6 @@
import { clsx, type ClassValue } from "clsx"; import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { ScopeInfo } from "./types";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
@ -53,3 +54,40 @@ export const ADMIN_WRITE_SCOPES = [
"admin:write:email_domain_blocks", "admin:write:email_domain_blocks",
"admin:write:canonical_email_blocks", "admin:write:canonical_email_blocks",
]; ];
export const scopesInfo: ScopeInfo[] = [
{
method: "read",
label: "Read",
scopes: READ_SCOPES,
description: "read account's data",
},
{
method: "write",
label: "Write",
scopes: WRITE_SCOPES,
description: "modify account's data",
},
{
method: "admin",
label: "Admin",
scopes: [ADMIN_READ_SCOPES, ADMIN_WRITE_SCOPES],
description: "read all data on the server",
},
{
method: "follow",
label: "Follow",
description: "modify account relationships,deprecated in 3.5.0 and newer.",
},
{
method: "push",
label: "Push",
description: "receive push notifications",
},
{
method: "crypto",
label: "Crypto",
description: "use end-to-end encryption",
},
];