feat: add loading indicator to submit buttons
This commit is contained in:
parent
99c3aa5add
commit
e1d248853f
7 changed files with 53 additions and 33 deletions
|
|
@ -7,6 +7,7 @@ import { createPortal } from "react-dom";
|
|||
import { Icon } from "@iconify/react";
|
||||
|
||||
import LikeButton from "./like-button";
|
||||
import SubmitButton from "./submit-button";
|
||||
|
||||
interface Props {
|
||||
miiId: number;
|
||||
|
|
@ -20,7 +21,7 @@ export default function DeleteMiiButton({ miiId, miiName, likes }: Props) {
|
|||
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
const submit = async () => {
|
||||
const handleSubmit = async () => {
|
||||
const response = await fetch(`/api/mii/${miiId}/delete`, { method: "DELETE" });
|
||||
if (!response.ok) {
|
||||
const { error } = await response.json();
|
||||
|
|
@ -92,9 +93,7 @@ export default function DeleteMiiButton({ miiId, miiName, likes }: Props) {
|
|||
<button onClick={close} className="pill button">
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={submit} className="pill button !bg-red-400 !border-red-500 hover:!bg-red-500">
|
||||
Delete
|
||||
</button>
|
||||
<SubmitButton onClick={handleSubmit} text="Delete" className="!bg-red-400 !border-red-500 hover:!bg-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
|
|||
import { createPortal } from "react-dom";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import SubmitButton from "../submit-button";
|
||||
|
||||
export default function DeleteAccount() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
|
@ -11,7 +12,7 @@ export default function DeleteAccount() {
|
|||
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
const submit = async () => {
|
||||
const handleSubmit = async () => {
|
||||
const response = await fetch("/api/auth/delete", { method: "DELETE" });
|
||||
if (!response.ok) {
|
||||
const { error } = await response.json();
|
||||
|
|
@ -78,9 +79,7 @@ export default function DeleteAccount() {
|
|||
<button onClick={close} className="pill button">
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={submit} className="pill button !bg-red-400 !border-red-500 hover:!bg-red-500">
|
||||
Delete
|
||||
</button>
|
||||
<SubmitButton onClick={handleSubmit} text="Delete" className="!bg-red-400 !border-red-500 hover:!bg-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Icon } from "@iconify/react";
|
||||
import SubmitButton from "../submit-button";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
|
@ -71,9 +72,7 @@ export default function SubmitDialogButton({ title, description, onSubmit, error
|
|||
<button onClick={close} className="pill button">
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={submit} className="pill button">
|
||||
Submit
|
||||
</button>
|
||||
<SubmitButton onClick={submit} />
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
|
|
|||
32
src/components/submit-button.tsx
Normal file
32
src/components/submit-button.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
interface Props {
|
||||
onClick: () => void | Promise<void>;
|
||||
text?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SubmitButton({ onClick, text = "Submit", className }: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClick = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await onClick();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="submit" onClick={handleClick} className={`pill button w-min ${className}`}>
|
||||
{text}
|
||||
{isLoading && <Icon icon="svg-spinners:180-ring-with-bg" className="ml-2" />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import TagSelector from "../tag-selector";
|
|||
import ImageList from "./image-list";
|
||||
import LikeButton from "../like-button";
|
||||
import Carousel from "../carousel";
|
||||
import SubmitButton from "../submit-button";
|
||||
|
||||
interface Props {
|
||||
mii: Mii;
|
||||
|
|
@ -46,9 +47,7 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
const [tags, setTags] = useState(mii.tags);
|
||||
const hasFilesChanged = useRef(false);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Validate before sending request
|
||||
const nameValidation = nameSchema.safeParse(name);
|
||||
if (!nameValidation.success) {
|
||||
|
|
@ -110,7 +109,7 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
}, [mii.id, mii.imageCount]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
||||
<form className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
||||
<div className="flex justify-center">
|
||||
<div className="w-[18.75rem] h-min flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3">
|
||||
<Carousel images={[`/mii/${mii.id}/mii.webp`, `/mii/${mii.id}/qr-code.webp`, ...files.map((file) => URL.createObjectURL(file))]} />
|
||||
|
|
@ -201,9 +200,7 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
<div className="flex justify-between items-center">
|
||||
{error && <span className="text-red-400 font-bold">Error: {error}</span>}
|
||||
|
||||
<button type="submit" className="pill button w-min ml-auto">
|
||||
Edit
|
||||
</button>
|
||||
<SubmitButton onClick={handleSubmit} text="Edit" className="ml-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import QrUpload from "./qr-upload";
|
|||
import QrScanner from "./qr-scanner";
|
||||
import LikeButton from "../like-button";
|
||||
import Carousel from "../carousel";
|
||||
import SubmitButton from "../submit-button";
|
||||
|
||||
export default function SubmitForm() {
|
||||
const [files, setFiles] = useState<FileWithPath[]>([]);
|
||||
|
|
@ -49,9 +50,7 @@ export default function SubmitForm() {
|
|||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [qrBytesRaw, setQrBytesRaw] = useState<number[]>([]);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Validate before sending request
|
||||
const nameValidation = nameSchema.safeParse(name);
|
||||
if (!nameValidation.success) {
|
||||
|
|
@ -129,7 +128,7 @@ export default function SubmitForm() {
|
|||
}, [qrBytesRaw]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
||||
<form className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
||||
<div className="flex justify-center">
|
||||
<div className="w-[18.75rem] h-min flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3">
|
||||
<Carousel
|
||||
|
|
@ -242,9 +241,7 @@ export default function SubmitForm() {
|
|||
<div className="flex justify-between items-center">
|
||||
{error && <span className="text-red-400 font-bold">Error: {error}</span>}
|
||||
|
||||
<button type="submit" className="pill button w-min ml-auto">
|
||||
Submit
|
||||
</button>
|
||||
<SubmitButton onClick={handleSubmit} className="ml-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { usernameSchema } from "@/lib/schemas";
|
||||
import SubmitButton from "./submit-button";
|
||||
|
||||
export default function UsernameForm() {
|
||||
const [username, setUsername] = useState("");
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
const handleSubmit = async (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const parsed = usernameSchema.safeParse(username);
|
||||
if (!parsed.success) setError(parsed.error.errors[0].message);
|
||||
|
||||
|
|
@ -30,7 +29,7 @@ export default function UsernameForm() {
|
|||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col items-center">
|
||||
<form className="flex flex-col items-center">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type your username..."
|
||||
|
|
@ -40,9 +39,7 @@ export default function UsernameForm() {
|
|||
className="pill input w-96 mb-2"
|
||||
/>
|
||||
|
||||
<button type="submit" className="pill button w-min">
|
||||
Submit
|
||||
</button>
|
||||
<SubmitButton onClick={handleSubmit} />
|
||||
{error && <p className="text-red-400 font-semibold mt-4">Error: {error}</p>}
|
||||
</form>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue