mirror of
https://github.com/Sevichecc/m-oauth.git
synced 2025-04-30 06:59:29 +08:00
feat: copy button
This commit is contained in:
parent
ae0fc58569
commit
c2900df6e9
9 changed files with 102 additions and 45 deletions
10
app/page.tsx
10
app/page.tsx
|
@ -8,7 +8,7 @@ import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import DataDisplay from "@/components/DataDisplay";
|
import FormContainer from "@/components/FormContainer";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
|
@ -19,21 +19,15 @@ export default function Home() {
|
||||||
<CardTitle>M-OAuth</CardTitle>
|
<CardTitle>M-OAuth</CardTitle>
|
||||||
<CardDescription>Card Description</CardDescription>
|
<CardDescription>Card Description</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<InputForm />
|
<FormContainer/>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
{/* <CardFooter>
|
{/* <CardFooter>
|
||||||
<p>Card Footer</p>
|
<p>Card Footer</p>
|
||||||
</CardFooter> */}
|
</CardFooter> */}
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
|
||||||
<CardContent>
|
|
||||||
<DataDisplay />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,40 +1,44 @@
|
||||||
'use client'
|
"use client";
|
||||||
|
|
||||||
import useCreateApp from "@/hooks/useCreateApp";
|
import {
|
||||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
Table,
|
||||||
import { AppEntry } from "../lib/types";
|
TableBody,
|
||||||
|
TableCaption,
|
||||||
interface DataDisplayProps {
|
TableCell,
|
||||||
appEntry: AppEntry;
|
TableHead,
|
||||||
}
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { AppEntry } from "@/lib/types";
|
||||||
|
import { CopyButton } from "./ui/copybutton";
|
||||||
|
|
||||||
const renderTableRow = (label: string, value: string | undefined) => (
|
const renderTableRow = (label: string, value: string | undefined) => (
|
||||||
<TableRow>
|
<TableRow className="group relative">
|
||||||
<TableCell className="font-medium">{label}</TableCell>
|
<TableCell className="font-medium">{label}</TableCell>
|
||||||
<TableCell>{value}</TableCell>
|
<TableCell className="font-mono break-all flex justify-between items-center">
|
||||||
|
{value}
|
||||||
|
{value && <CopyButton value={value} className="end-2 shrink-0"/>}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DataDisplay = () => {
|
const DataDisplay = ({ appEntry }: { appEntry: AppEntry }) => {
|
||||||
const { appEntry } = useCreateApp();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Data Name</TableHead>
|
<TableHead className="min-w-[120px]">Type</TableHead>
|
||||||
<TableHead>Value</TableHead>
|
<TableHead>Value</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{renderTableRow("ID", appEntry?.id)}
|
{renderTableRow("ID", appEntry?.id)}
|
||||||
{renderTableRow("Name", appEntry?.name)}
|
{renderTableRow("Name", appEntry?.name)}
|
||||||
{renderTableRow("Website", appEntry?.website || '')}
|
{renderTableRow("Website", appEntry?.website || "")}
|
||||||
{renderTableRow("Redirect URI", appEntry?.redirectUri)}
|
{renderTableRow("Redirect URI", appEntry?.redirect_uri)}
|
||||||
{renderTableRow("Client ID", appEntry?.clientId)}
|
{renderTableRow("Client ID", appEntry?.client_id)}
|
||||||
{renderTableRow("Client Secret", appEntry?.clientSecret)}
|
{renderTableRow("Client Secret", appEntry?.client_secret)}
|
||||||
{renderTableRow("Vapid Key", appEntry?.vapidKey)}
|
{renderTableRow("Vapid Key", appEntry?.vapid_key)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
|
|
16
components/FormContainer.tsx
Normal file
16
components/FormContainer.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"use client";
|
||||||
|
import InputForm from "@/components/InputForm";
|
||||||
|
import DataDisplay from "@/components/DataDisplay";
|
||||||
|
import useCreateApp from "@/hooks/useCreateApp";
|
||||||
|
|
||||||
|
const FormContainer = () => {
|
||||||
|
const { appEntry, createApp } = useCreateApp();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InputForm createApp={createApp} />
|
||||||
|
{appEntry && <DataDisplay appEntry={appEntry} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FormContainer;
|
|
@ -15,9 +15,8 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
|
|
||||||
import ScopeSection from "@/components/ScopeSection";
|
import ScopeSection from "@/components/scopes/ScopeSection";
|
||||||
import { scopesInfo } from "@/lib/utils";
|
import { scopesInfo } from "@/lib/utils";
|
||||||
import useCreateApp from "@/hooks/useCreateApp";
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
instance: z.string().trim(),
|
instance: z.string().trim(),
|
||||||
|
@ -28,9 +27,11 @@ const formSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormSchema = z.infer<typeof formSchema>;
|
export type FormSchema = z.infer<typeof formSchema>;
|
||||||
|
interface InputFormProps {
|
||||||
|
createApp: ({}: FormSchema) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
const InputForm = () => {
|
const InputForm: React.FC<InputFormProps> = ({ createApp }) => {
|
||||||
const { createApp } = useCreateApp();
|
|
||||||
const form = useForm<FormSchema>({
|
const form = useForm<FormSchema>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
@ -43,7 +44,10 @@ const InputForm = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(createApp)} className="space-y-6 flex flex-col">
|
<form
|
||||||
|
onSubmit={form.handleSubmit(createApp)}
|
||||||
|
className="flex flex-col space-y-6 mb-6"
|
||||||
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="instance"
|
name="instance"
|
||||||
|
@ -132,7 +136,9 @@ const InputForm = () => {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" className="w-ful">Submit</Button>
|
<Button type="submit" className="w-ful">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import ScopeItem from "@/components/ScopeItem";
|
import ScopeItem from "@/components/scopes/ScopeItem";
|
||||||
|
|
||||||
import { ScopeInfo } from "@/lib/types";
|
import { ScopeInfo } from "@/lib/types";
|
||||||
|
|
40
components/ui/copybutton.tsx
Normal file
40
components/ui/copybutton.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Check, Copy } from "lucide-react";
|
||||||
|
|
||||||
|
interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyToClipboardWithMeta = (value: string, event?: Event) => {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CopyButton({ value, className, ...props }: CopyButtonProps) {
|
||||||
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setHasCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}, [hasCopied]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
"z-20 inline-flex h-8 w-8 items-center justify-center rounded-md border bg-background text-sm font-medium transition-all hover:bg-muted focus:outline-none ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboardWithMeta(value);
|
||||||
|
setHasCopied(true);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Copy</span>
|
||||||
|
{hasCopied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
|
@ -24,7 +24,6 @@ const useCreateApp = () => {
|
||||||
scopes: scopes?.join(" "),
|
scopes: scopes?.join(" "),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("app,", app);
|
|
||||||
try {
|
try {
|
||||||
let request = await fetch(`${instance}/api/v1/apps`, {
|
let request = await fetch(`${instance}/api/v1/apps`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -41,10 +40,9 @@ const useCreateApp = () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error((error as MError).error);
|
throw new Error((error as MError).error);
|
||||||
}
|
}
|
||||||
},
|
},[]
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
console.log(appEntry);
|
|
||||||
return {
|
return {
|
||||||
appEntry,
|
appEntry,
|
||||||
createApp,
|
createApp,
|
||||||
|
|
|
@ -18,10 +18,9 @@ export interface ScopeInfo {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
website: string | null;
|
website: string | null;
|
||||||
redirectUri: string;
|
redirect_uri: string;
|
||||||
clientId: string;
|
client_id: string;
|
||||||
clientSecret: string;
|
client_secret: string;
|
||||||
vapidKey: string;
|
vapid_key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue