Continued creating the entity system
This commit is contained in:
32
src/components/High/CardList.tsx
Normal file
32
src/components/High/CardList.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import {useListSearch} from "@/hooks/useListSearch";
|
||||
import usePagination from "@/hooks/usePagination";
|
||||
import {ReactNode} from "react";
|
||||
import Checkbox from "../Low/Checkbox";
|
||||
import Separator from "../Low/Separator";
|
||||
|
||||
interface Props<T> {
|
||||
list: T[];
|
||||
searchFields: string[][];
|
||||
pageSize?: number;
|
||||
firstCard?: () => ReactNode;
|
||||
renderCard: (item: T) => ReactNode;
|
||||
}
|
||||
|
||||
export default function CardList<T>({list, searchFields, renderCard, firstCard, pageSize = 20}: Props<T>) {
|
||||
const {rows, renderSearch} = useListSearch(searchFields, list);
|
||||
|
||||
const {items, page, renderMinimal} = usePagination(rows, pageSize);
|
||||
|
||||
return (
|
||||
<section className="flex flex-col gap-4 w-full">
|
||||
<div className="w-full flex items-center gap-4">
|
||||
{renderSearch()}
|
||||
{renderMinimal()}
|
||||
</div>
|
||||
<div className="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{page === 0 && !!firstCard && firstCard()}
|
||||
{items.map(renderCard)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
67
src/components/Medium/InviteWithUserCard.tsx
Normal file
67
src/components/Medium/InviteWithUserCard.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import {Invite, InviteWithUsers} from "@/interfaces/invite";
|
||||
import {User} from "@/interfaces/user";
|
||||
import {getUserName} from "@/utils/users";
|
||||
import axios from "axios";
|
||||
import {useMemo, useState} from "react";
|
||||
import {BsArrowRepeat} from "react-icons/bs";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
interface Props {
|
||||
invite: InviteWithUsers;
|
||||
reload: () => void;
|
||||
}
|
||||
|
||||
export default function InviteWithUserCard({invite, reload}: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const name = useMemo(() => (!invite.from ? null : getUserName(invite.from)), [invite.from]);
|
||||
|
||||
const decide = (decision: "accept" | "decline") => {
|
||||
if (!confirm(`Are you sure you want to ${decision} this invite?`)) return;
|
||||
|
||||
setIsLoading(true);
|
||||
axios
|
||||
.get(`/api/invites/${decision}/${invite.id}`)
|
||||
.then(() => {
|
||||
toast.success(`Successfully ${decision === "accept" ? "accepted" : "declined"} the invite!`, {toastId: "success"});
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.success(`Something went wrong, please try again later!`, {
|
||||
toastId: "error",
|
||||
});
|
||||
reload();
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-mti-gray-anti-flash flex min-w-[200px] flex-col gap-6 rounded-xl border p-4 text-black">
|
||||
<span>Invited by {name}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => decide("accept")}
|
||||
disabled={isLoading}
|
||||
className="bg-mti-green-ultralight hover:bg-mti-green-light w-24 rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
|
||||
{!isLoading && "Accept"}
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center">
|
||||
<BsArrowRepeat className="animate-spin text-white" size={25} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => decide("decline")}
|
||||
disabled={isLoading}
|
||||
className="bg-mti-red-ultralight hover:bg-mti-red-light w-24 rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
|
||||
{!isLoading && "Decline"}
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center">
|
||||
<BsArrowRepeat className="animate-spin text-white" size={25} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user