improved admin and events tables

This commit is contained in:
z1glr
2025-01-21 12:51:59 +00:00
parent 8fef9b5318
commit 7265a4e36a
17 changed files with 357 additions and 237 deletions

View File

@@ -11,6 +11,11 @@ type User struct {
Admin bool `db:"admin" json:"admin"` Admin bool `db:"admin" json:"admin"`
} }
type UserChangePassword struct {
UserName string `json:"userName" validate:"required" db:"userName"`
Password string `json:"password" validate:"required,min=12"`
}
// hashes a password // hashes a password
func hashPassword(password string) ([]byte, error) { func hashPassword(password string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
@@ -64,11 +69,6 @@ func Add(user UserAdd) error {
} }
} }
type UserChangePassword struct {
UserName string `json:"userName" validate:"required" db:"userName"`
Password string `json:"password" validate:"required,min=12"`
}
func ChangePassword(user UserChangePassword) (string, error) { func ChangePassword(user UserChangePassword) (string, error) {
// try to hash teh password // try to hash teh password
if hash, err := hashPassword(user.Password); err != nil { if hash, err := hashPassword(user.Password); err != nil {
@@ -103,3 +103,9 @@ func SetAdmin(userName string, admin bool) error {
return err return err
} }
func Delete(userName string) error {
_, err := db.DB.Exec("DELETE FROM USERS WHERE name = $1", userName)
return err
}

View File

@@ -102,6 +102,7 @@ func init() {
"event": deleteEvent, "event": deleteEvent,
"tasks": deleteTask, "tasks": deleteTask,
"availabilities": deleteAvailability, "availabilities": deleteAvailability,
"users": deleteUser,
}, },
} }

View File

@@ -189,3 +189,48 @@ func patchUser(args HandlerArgs) responseMessage {
return response return response
} }
func deleteUser(args HandlerArgs) responseMessage {
// check admin
if !args.User.Admin {
logger.Warn().Msg("user-deletion failed: user is no admin")
return responseMessage{
Status: fiber.StatusUnauthorized,
}
// get the username from the query
} else if userName := args.C.Query("userName"); userName == "" {
logger.Log().Msg("user-deletion failed: query is missing \"userName\"")
return responseMessage{
Status: fiber.StatusBadRequest,
}
// check wether the user tries to delete himself
} else if userName == args.User.UserName {
logger.Log().Msg("user-deletion failed: self-deletion is illegal")
return responseMessage{
Status: fiber.StatusBadRequest,
}
// check wether the user tries to delete the admin
} else if userName == "admin" {
logger.Log().Msg("user-deletion failed: admin-deletion is illegal")
return responseMessage{
Status: fiber.StatusBadRequest,
}
// delete the user
} else if err := users.Delete(userName); err != nil {
logger.Error().Msgf("user-deletion failed: user doesn't exist")
return responseMessage{
Status: fiber.StatusNotFound,
}
} else {
return responseMessage{}
}
}

View File

@@ -23,6 +23,10 @@ export interface User {
admin: boolean; admin: boolean;
} }
export type UserAddModify = User & {
password: string;
};
interface Zustand { interface Zustand {
user: User | null; user: User | null;
tasks?: Task[]; tasks?: Task[];

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { apiCall, vaidatePassword as validatePassword } from "@/lib"; import { apiCall, validatePassword as validatePassword } from "@/lib";
import { import {
Button, Button,
Card, Card,

View File

@@ -91,7 +91,7 @@ export default function Availabilities() {
} }
const topContent = ( const topContent = (
<> <div>
<Button <Button
color="primary" color="primary"
startContent={<AddLarge />} startContent={<AddLarge />}
@@ -99,7 +99,7 @@ export default function Availabilities() {
> >
Add Availability Add Availability
</Button> </Button>
</> </div>
); );
return ( return (
@@ -112,6 +112,13 @@ export default function Availabilities() {
topContent={topContent} topContent={topContent}
sortDescriptor={availabilities.sortDescriptor} sortDescriptor={availabilities.sortDescriptor}
onSortChange={availabilities.sort} onSortChange={availabilities.sort}
topContentPlacement="outside"
classNames={{
wrapper: "bg-accent-4",
tr: "even:bg-accent-5 ",
th: "font-subheadline text-xl text-accent-1 bg-transparent ",
thead: "[&>tr]:first:!shadow-border",
}}
> >
<TableHeader> <TableHeader>
<TableColumn allowsSorting key="userName"> <TableColumn allowsSorting key="userName">
@@ -182,7 +189,7 @@ export default function Availabilities() {
onOpenChange={(isOpen) => onOpenChange={(isOpen) =>
!isOpen ? setDeleteAvailability(undefined) : null !isOpen ? setDeleteAvailability(undefined) : null
} }
header="Delete Availability" itemName="Availability"
onDelete={() => sendDeleteAvailability(deleteAvailability?.id)} onDelete={() => sendDeleteAvailability(deleteAvailability?.id)}
> >
{!!deleteAvailability ? ( {!!deleteAvailability ? (

View File

@@ -58,6 +58,9 @@ export default function AvailabilityEditor(props: {
isOpen={props.isOpen} isOpen={props.isOpen}
onOpenChange={props.onOpenChange} onOpenChange={props.onOpenChange}
shadow={"none" as "sm"} shadow={"none" as "sm"}
classNames={{
base: "bg-accent-5",
}}
> >
<Form <Form
validationBehavior="native" validationBehavior="native"
@@ -65,7 +68,6 @@ export default function AvailabilityEditor(props: {
e.preventDefault(); e.preventDefault();
submit(e); submit(e);
}} }}
className="w-fit border-2"
> >
<ModalContent> <ModalContent>
<ModalHeader> <ModalHeader>
@@ -81,6 +83,7 @@ export default function AvailabilityEditor(props: {
variant="bordered" variant="bordered"
/> />
<ColorSelector <ColorSelector
isRequired
value={color} value={color}
onValueChange={setColor} onValueChange={setColor}
name="color" name="color"

View File

@@ -47,7 +47,11 @@ export default function TaskEditor(props: {
<Modal <Modal
isOpen={props.isOpen} isOpen={props.isOpen}
onOpenChange={props.onOpenChange} onOpenChange={props.onOpenChange}
shadow={"none" as "sm"} shadow={"none"}
backdrop="blur"
classNames={{
base: "bg-accent-5",
}}
> >
<Form <Form
validationBehavior="native" validationBehavior="native"
@@ -55,7 +59,6 @@ export default function TaskEditor(props: {
e.preventDefault(); e.preventDefault();
submit(e); submit(e);
}} }}
className="w-fit border-2"
> >
<ModalContent> <ModalContent>
<ModalHeader> <ModalHeader>

View File

@@ -88,7 +88,7 @@ export default function Tasks() {
} }
const topContent = ( const topContent = (
<> <div>
<Button <Button
color="primary" color="primary"
startContent={<AddLarge />} startContent={<AddLarge />}
@@ -96,7 +96,7 @@ export default function Tasks() {
> >
Add Task Add Task
</Button> </Button>
</> </div>
); );
return ( return (
@@ -109,6 +109,13 @@ export default function Tasks() {
topContent={topContent} topContent={topContent}
sortDescriptor={tasks.sortDescriptor} sortDescriptor={tasks.sortDescriptor}
onSortChange={tasks.sort} onSortChange={tasks.sort}
topContentPlacement="outside"
classNames={{
wrapper: "bg-accent-4",
tr: "even:bg-accent-5 ",
th: "font-subheadline text-xl text-accent-1 bg-transparent ",
thead: "[&>tr]:first:!shadow-border",
}}
> >
<TableHeader> <TableHeader>
<TableColumn allowsSorting key="userName"> <TableColumn allowsSorting key="userName">
@@ -173,7 +180,7 @@ export default function Tasks() {
<DeleteConfirmation <DeleteConfirmation
isOpen={!!deleteTask} isOpen={!!deleteTask}
onOpenChange={(isOpen) => (!isOpen ? setDeleteTask(undefined) : null)} onOpenChange={(isOpen) => (!isOpen ? setDeleteTask(undefined) : null)}
header="Delete Task" itemName="Task"
onDelete={() => sendDeleteTask(deleteTask?.id)} onDelete={() => sendDeleteTask(deleteTask?.id)}
> >
{!!deleteTask ? ( {!!deleteTask ? (

View File

@@ -1,79 +1,35 @@
import { AddLarge, Copy } from "@carbon/icons-react"; import { apiCall } from "@/lib";
import { import { AddLarge } from "@carbon/icons-react";
Button, import { Button } from "@heroui/react";
Checkbox, import UserEditor from "./UserEditor";
Form, import { UserAddModify } from "@/Zustand";
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@heroui/react";
import { FormEvent, useState } from "react";
export default function AddUser(props: { export default function AddUser(props: {
isOpen: boolean; isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
onSubmit?: (e: FormEvent<HTMLFormElement>) => void; onSuccess?: () => void;
}) { }) {
const [password, setPassword] = useState(""); // send an addUser request to the backend then reload the table
async function sendAddUser(user: UserAddModify) {
const result = await apiCall("POST", "users", undefined, user);
if (result.ok) {
props.onSuccess?.();
}
}
return ( return (
<Modal <UserEditor
isPasswordRequired
isOpen={props.isOpen} isOpen={props.isOpen}
onOpenChange={props.onOpenChange} onOpenChange={props.onOpenChange}
shadow={"none" as "sm"} header="Add User"
backdrop="blur" footer={
>
<ModalContent>
<ModalHeader>
<h1 className="text-2xl">Add User</h1>
</ModalHeader>
<Form
validationBehavior="native"
onSubmit={(e) => {
e.preventDefault();
props.onSubmit?.(e);
}}
>
<ModalBody className="w-full">
<Input
isRequired
type="user"
label="Name"
name="userName"
variant="bordered"
/>
<Input
isRequired
label="Password"
name="password"
variant="bordered"
endContent={
<Button
isIconOnly
variant="light"
onPress={() => navigator.clipboard.writeText(password)}
>
<Copy />
</Button>
}
value={password}
onValueChange={setPassword}
/>
<Checkbox value="admin" name="admin">
Admin
</Checkbox>
</ModalBody>
<ModalFooter>
<Button type="submit" color="primary" startContent={<AddLarge />}> <Button type="submit" color="primary" startContent={<AddLarge />}>
Add User Add User
</Button> </Button>
</ModalFooter> }
</Form> onSubmit={sendAddUser}
</ModalContent> />
</Modal>
); );
} }

View File

@@ -1,137 +1,54 @@
import { import { apiCall } from "@/lib";
apiCall, import zustand, { User, UserAddModify } from "@/Zustand";
classNames, import { Button } from "@heroui/react";
vaidatePassword as validatePassword, import UserEditor from "./UserEditor";
} from "@/lib"; import { Renew } from "@carbon/icons-react";
import zustand, { User } from "@/Zustand";
import {
Button,
Checkbox,
Form,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@heroui/react";
import { FormEvent, useEffect, useState } from "react";
export default function EditUser(props: { export default function EditUser(props: {
isOpen: boolean; value?: User;
user?: User; isOpen?: boolean;
onOpenChange: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
onSuccess: () => void; onSuccess?: () => void;
}) { }) {
const [name, setName] = useState(props.user?.userName);
const [admin, setAdmin] = useState(props.user?.admin);
const [password, setPassword] = useState("");
const pwErrors = validatePassword(password);
// set the states on value changes
useEffect(() => {
if (props.user !== undefined) {
setName(props.user.userName);
setAdmin(props.user.admin);
// reset the password
setPassword("");
}
}, [props.user]);
// update the user in the backend // update the user in the backend
async function updateUser(e: FormEvent<HTMLFormElement>) { async function updateUser(user: UserAddModify) {
const formData = Object.fromEntries(new FormData(e.currentTarget)); const result = await apiCall("PATCH", "users", undefined, {
...user,
const data = { userName: props.value?.userName,
...formData, newName: user.userName,
userName: props.user?.userName, });
admin: formData.admin !== undefined,
};
// if we modify ourself, set admin to true since it isn't included in the form data because the checkbox is disabled
data.admin ||= props.user?.userName === zustand.getState().user?.userName;
const result = await apiCall("PATCH", "users", undefined, data);
if (result.ok) { if (result.ok) {
// if we updated ourself // if we updated ourself
if (props.user?.userName === zustand.getState().user?.userName) { if (props.value?.userName === zustand.getState().user?.userName) {
zustand.setState({ user: null }); zustand.setState({ user: null });
} }
props.onSuccess(); props.onSuccess?.();
props.onOpenChange?.(false);
} }
} }
return ( return (
<Modal isOpen={props.isOpen} onOpenChange={props.onOpenChange}> <UserEditor
<Form key={props.value?.userName}
validationBehavior="native" header={
onSubmit={(e) => { <>
e.preventDefault();
updateUser(e);
}}
>
{props.user !== undefined ? (
<ModalContent>
<ModalHeader>
<h1 className="text-2xl">
Edit User{" "} Edit User{" "}
<span className="font-numbers font-normal italic"> <span className="font-numbers font-normal italic">
{props.user.userName} &quot;{props.value?.userName}&quot;
</span> </span>
</h1> </>
</ModalHeader>
<ModalBody className="w-full">
<Input
label="Name"
color={name !== props.user.userName ? "warning" : "default"}
name="newName"
value={name}
onValueChange={setName}
/>
<Input
label="Password"
color={password.length > 0 ? "warning" : "default"}
name="password"
value={password}
onValueChange={setPassword}
isInvalid={password.length > 0 && pwErrors.length > 0}
errorMessage={
<ul>
{pwErrors.map((e, ii) => (
<li key={ii}>{e}</li>
))}
</ul>
} }
/> footer={
<Checkbox <Button type="submit" color="primary" startContent={<Renew />}>
name="admin"
color={admin !== props.user.admin ? "warning" : "primary"}
isDisabled={
props.user.userName === zustand.getState().user?.userName
}
isSelected={admin}
onValueChange={setAdmin}
classNames={{
label: classNames({
"text-warning": admin !== props.user.admin,
}),
}}
>
Admin
</Checkbox>
</ModalBody>
<ModalFooter>
<Button type="submit" color="primary">
Update Update
</Button> </Button>
</ModalFooter> }
</ModalContent> value={props.value}
) : null} isOpen={props.isOpen}
</Form> onOpenChange={props.onOpenChange}
</Modal> onSubmit={updateUser}
/>
); );
} }

View File

@@ -0,0 +1,127 @@
import { classNames, validatePassword as validatePassword } from "@/lib";
import zustand, { User, UserAddModify } from "@/Zustand";
import {
Checkbox,
Form,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@heroui/react";
import React, { FormEvent, useState } from "react";
export default function UserEditor(props: {
header: React.ReactNode;
footer: React.ReactNode;
value?: User;
isPasswordRequired?: boolean;
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
onSubmit: (user: UserAddModify) => void;
}) {
const [name, setName] = useState(props.value?.userName ?? "");
const [admin, setAdmin] = useState(props.value?.admin ?? false);
const [password, setPassword] = useState("");
const pwErrors = validatePassword(password);
// update the user in the backend
async function submit(e: FormEvent<HTMLFormElement>) {
const formData = Object.fromEntries(new FormData(e.currentTarget)) as {
userName: string;
password: string;
admin: string;
};
const data = {
...formData,
admin: formData.admin !== undefined,
};
// if we modify ourself, set admin to true since it isn't included in the form data because the checkbox is disabled
data.admin ||= props.value?.userName === zustand.getState().user?.userName;
props.onSubmit(data);
}
return (
<Modal
isOpen={props.isOpen}
onOpenChange={props.onOpenChange}
shadow={"none"}
backdrop="blur"
classNames={{
base: "bg-accent-5",
}}
>
<Form
validationBehavior="native"
onSubmit={(e) => {
e.preventDefault();
submit(e);
}}
>
<ModalContent>
<ModalHeader>
<h1 className="text-2xl">{props.header}</h1>
</ModalHeader>
<ModalBody className="w-full">
<Input
isRequired
label="Name"
color={
!!props.value && name !== props.value?.userName
? "warning"
: "default"
}
name="userName"
variant="bordered"
value={name}
onValueChange={setName}
/>
<Input
isRequired={props.isPasswordRequired}
label="Password"
color={password.length > 0 ? "warning" : "default"}
name="password"
variant="bordered"
value={password}
onValueChange={setPassword}
isInvalid={password.length > 0 && pwErrors.length > 0}
errorMessage={
<ul>
{pwErrors.map((e, ii) => (
<li key={ii}>{e}</li>
))}
</ul>
}
/>
<Checkbox
name="admin"
color={
!!props.value && admin !== props.value?.admin
? "warning"
: "primary"
}
isDisabled={
props.value?.userName === zustand.getState().user?.userName
}
isSelected={admin}
onValueChange={setAdmin}
classNames={{
label: classNames({
"text-warning": !!props.value && admin !== props.value?.admin,
}),
}}
>
Admin
</Checkbox>
</ModalBody>
<ModalFooter>{props.footer}</ModalFooter>
</ModalContent>
</Form>
</Modal>
);
}

View File

@@ -1,7 +1,8 @@
import { apiCall } from "@/lib"; import { apiCall } from "@/lib";
import { User } from "@/Zustand"; import zustand, { User } from "@/Zustand";
import { import {
Button, Button,
ButtonGroup,
Checkbox, Checkbox,
Table, Table,
TableBody, TableBody,
@@ -12,14 +13,17 @@ import {
Tooltip, Tooltip,
} from "@heroui/react"; } from "@heroui/react";
import { useAsyncList } from "@react-stately/data"; import { useAsyncList } from "@react-stately/data";
import { FormEvent, useState } from "react"; import { useState } from "react";
import AddUser from "./AddUser"; import AddUser from "./AddUser";
import { AddLarge, Edit } from "@carbon/icons-react"; import { AddLarge, Edit, TrashCan } from "@carbon/icons-react";
import EditUser from "./EditUser"; import EditUser from "./EditUser";
import DeleteConfirmation from "@/components/DeleteConfirmation";
export default function Users() { export default function Users() {
const [showAddUser, setShowAddUser] = useState(false); const [showAddUser, setShowAddUser] = useState(false);
const [editUser, setEditUser] = useState<User | undefined>(); const [editUser, setEditUser] = useState<User | undefined>();
const [deleteUser, setDeleteUser] = useState<User | undefined>();
const loggedInUser = zustand((state) => state.user);
const users = useAsyncList<User>({ const users = useAsyncList<User>({
async load() { async load() {
@@ -64,23 +68,22 @@ export default function Users() {
}, },
}); });
// send an addUser request to the backend then reload the table async function sendDeleteUser(userName: User["userName"] | undefined) {
async function addUser(e: FormEvent<HTMLFormElement>) { if (!!userName) {
const data = Object.fromEntries(new FormData(e.currentTarget)); const result = await apiCall("DELETE", "users", {
userName,
const result = await apiCall("POST", "users", undefined, {
...data,
admin: data.admin === "admin",
}); });
if (result.ok) { if (result.ok) {
users.reload(); users.reload();
setDeleteUser(undefined);
}
} }
} }
// content above the user-tabel // content above the user-tabel
const topContent = ( const topContent = (
<> <div>
<Button <Button
color="primary" color="primary"
startContent={<AddLarge />} startContent={<AddLarge />}
@@ -88,7 +91,7 @@ export default function Users() {
> >
Add User Add User
</Button> </Button>
</> </div>
); );
return ( return (
@@ -101,6 +104,13 @@ export default function Users() {
topContent={topContent} topContent={topContent}
sortDescriptor={users.sortDescriptor} sortDescriptor={users.sortDescriptor}
onSortChange={users.sort} onSortChange={users.sort}
topContentPlacement="outside"
classNames={{
wrapper: "bg-accent-4",
tr: "even:bg-accent-5 ",
th: "font-subheadline text-xl text-accent-1 bg-transparent ",
thead: "[&>tr]:first:!shadow-border",
}}
> >
<TableHeader> <TableHeader>
<TableColumn allowsSorting key="userName"> <TableColumn allowsSorting key="userName">
@@ -119,16 +129,34 @@ export default function Users() {
<Checkbox isSelected={user.admin} /> <Checkbox isSelected={user.admin} />
</TableCell> </TableCell>
<TableCell> <TableCell>
<ButtonGroup>
<Button <Button
isIconOnly isIconOnly
variant="light" variant="light"
size="sm" size="sm"
onPress={() => setEditUser(user)} onPress={() => setEditUser(user)}
isDisabled={
user.userName === "admin" &&
loggedInUser?.userName !== "admin"
}
> >
<Tooltip content="Edit user"> <Tooltip content="Edit user">
<Edit /> <Edit />
</Tooltip> </Tooltip>
</Button> </Button>
<Button
isIconOnly
variant="light"
size="sm"
color="danger"
isDisabled={["admin", loggedInUser?.userName].includes(
user.userName,
)}
onPress={() => setDeleteUser(user)}
>
<TrashCan />
</Button>
</ButtonGroup>
</TableCell> </TableCell>
</TableRow> </TableRow>
)} )}
@@ -138,11 +166,14 @@ export default function Users() {
<AddUser <AddUser
isOpen={showAddUser} isOpen={showAddUser}
onOpenChange={setShowAddUser} onOpenChange={setShowAddUser}
onSubmit={(e) => void addUser(e)} onSuccess={() => {
setShowAddUser(false);
users.reload();
}}
/> />
<EditUser <EditUser
isOpen={editUser !== undefined} isOpen={editUser !== undefined}
user={editUser} value={editUser}
onOpenChange={(isOpen) => onOpenChange={(isOpen) =>
!isOpen ? setEditUser(undefined) : undefined !isOpen ? setEditUser(undefined) : undefined
} }
@@ -151,6 +182,16 @@ export default function Users() {
setEditUser(undefined); setEditUser(undefined);
}} }}
/> />
<DeleteConfirmation
isOpen={!!deleteUser}
onOpenChange={(isOpen) => (!isOpen ? setDeleteUser(undefined) : null)}
onDelete={() => sendDeleteUser(deleteUser?.userName)}
itemName="User"
>
{" "}
The user <span>{deleteUser?.userName}</span> will be deleted.
</DeleteConfirmation>
</div> </div>
); );
} }

View File

@@ -238,14 +238,15 @@ export default function AdminPanel() {
topContent={topContent} topContent={topContent}
topContentPlacement="outside" topContentPlacement="outside"
isHeaderSticky isHeaderSticky
isStriped
sortDescriptor={events.sortDescriptor} sortDescriptor={events.sortDescriptor}
onSortChange={events.sort}
classNames={{ classNames={{
wrapper: "bg-accent-4", wrapper: "bg-accent-4",
tr: "even:bg-accent-5 ", tr: "even:bg-accent-5 ",
th: "font-subheadline text-xl text-accent-1 bg-transparent ", th: "font-subheadline text-xl text-accent-1 bg-transparent ",
thead: "[&>tr]:first:!shadow-border", thead: "[&>tr]:first:!shadow-border",
}} }}
onSortChange={events.sort}
className="w-fit max-w-full" className="w-fit max-w-full"
> >
<TableHeader columns={headers.items}> <TableHeader columns={headers.items}>

View File

@@ -31,12 +31,14 @@ export function color2Tailwind(v: string): string | undefined {
} }
export default function ColorSelector(props: { export default function ColorSelector(props: {
isRequired?: boolean;
name?: string; name?: string;
value?: string; value?: string;
onValueChange?: (value: string) => void; onValueChange?: (value: string) => void;
}) { }) {
return ( return (
<RadioGroup <RadioGroup
isRequired={props.isRequired}
value={props.value} value={props.value}
onValueChange={props.onValueChange} onValueChange={props.onValueChange}
classNames={{ wrapper: "grid grid-cols-4" }} classNames={{ wrapper: "grid grid-cols-4" }}

View File

@@ -13,7 +13,7 @@ export default function DeleteConfirmation(props: {
isOpen: boolean; isOpen: boolean;
onOpenChange: (isOpen: boolean) => void; onOpenChange: (isOpen: boolean) => void;
children: React.ReactNode; children: React.ReactNode;
header: React.ReactNode; itemName: string;
onDelete?: () => void; onDelete?: () => void;
}) { }) {
return ( return (
@@ -28,7 +28,7 @@ export default function DeleteConfirmation(props: {
> >
<ModalContent> <ModalContent>
<ModalHeader> <ModalHeader>
<h1 className="text-2xl">{props.header}</h1> <h1 className="text-2xl">Delete {props.itemName}</h1>
</ModalHeader> </ModalHeader>
<ModalBody>{props.children}</ModalBody> <ModalBody>{props.children}</ModalBody>
<ModalFooter> <ModalFooter>
@@ -40,7 +40,7 @@ export default function DeleteConfirmation(props: {
color="danger" color="danger"
onPress={() => props.onDelete?.()} onPress={() => props.onDelete?.()}
> >
Delete event Delete {props.itemName}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@@ -83,7 +83,7 @@ export class DateFormatter {
} }
} }
export function vaidatePassword(password: string): string[] { export function validatePassword(password: string): string[] {
const errors = []; const errors = [];
if (password.length < 12) { if (password.length < 12) {