mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-19 00:57:55 -05:00
build modal initial
This commit is contained in:
parent
5b12157e4c
commit
7eabc4a7be
|
|
@ -14,20 +14,17 @@ const MySelect: React.FC<Props> = ({
|
|||
placeholder,
|
||||
children,
|
||||
name,
|
||||
}) => {
|
||||
console.log({ value });
|
||||
return (
|
||||
<Select
|
||||
value={value ?? ""}
|
||||
onChange={(e) =>
|
||||
setValue(e.target.value !== "" ? undefined : e.target.value)
|
||||
}
|
||||
name={name}
|
||||
>
|
||||
{placeholder && <option value="">{placeholder}</option>}
|
||||
{children}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<Select
|
||||
value={value ?? ""}
|
||||
onChange={(e) =>
|
||||
setValue(e.target.value === "" ? undefined : e.target.value)
|
||||
}
|
||||
name={name}
|
||||
>
|
||||
{placeholder && <option value="">{placeholder}</option>}
|
||||
{children}
|
||||
</Select>
|
||||
);
|
||||
|
||||
export default MySelect;
|
||||
|
|
|
|||
274
components/u/BuildModal.tsx
Normal file
274
components/u/BuildModal.tsx
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { useLingui } from "@lingui/react";
|
||||
import MySelect from "components/common/MySelect";
|
||||
import WeaponSelector from "components/common/WeaponSelector";
|
||||
import { gear } from "lib/lists/gear";
|
||||
import { Unpacked } from "lib/types";
|
||||
import {
|
||||
buildSchema,
|
||||
DESCRIPTION_CHARACTER_LIMIT,
|
||||
TITLE_CHARACTER_LIMIT,
|
||||
} from "lib/validators/build";
|
||||
import { GetBuildsByUserData } from "prisma/queries/getBuildsByUser";
|
||||
import { Fragment } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
build?: Unpacked<NonNullable<GetBuildsByUserData>>;
|
||||
}
|
||||
|
||||
type FormData = z.infer<typeof buildSchema>;
|
||||
|
||||
const BuildModal: React.FC<Props> = ({ onClose, build }) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
const { handleSubmit, errors, register, watch, control } = useForm<FormData>({
|
||||
resolver: zodResolver(buildSchema),
|
||||
defaultValues: build,
|
||||
});
|
||||
|
||||
const watchTitle = watch("title", build?.title ?? "");
|
||||
const watchDescription = watch("description", build?.description ?? "");
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
// const onSubmit = async (formData: FormData) => {
|
||||
|
||||
// for (const [key, value] of Object.entries(mutationData)) {
|
||||
// if (value === "" || value === undefined) {
|
||||
// const typedKey = key as keyof Omit<typeof mutationData, "weaponPool">;
|
||||
// mutationData[typedKey] = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// const success = await sendData("PUT", "/api/me/profile", mutationData);
|
||||
// if (!success) return;
|
||||
|
||||
// mutate(`/api/users/${user.id}`);
|
||||
|
||||
// toast(getToastOptions(build ? t`Profile updated` : t`Profile updated`, "success"));
|
||||
// onClose();
|
||||
// };
|
||||
const onSubmit = async (formData: FormData) => console.log({ formData });
|
||||
|
||||
return (
|
||||
<Modal isOpen onClose={onClose} size="xl" closeOnOverlayClick={false}>
|
||||
<ModalOverlay>
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
{build ? (
|
||||
<Trans>Editing build</Trans>
|
||||
) : (
|
||||
<Trans>Adding a new build</Trans>
|
||||
)}
|
||||
</ModalHeader>
|
||||
<ModalCloseButton borderRadius="50%" />
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<ModalBody pb={6}>
|
||||
<FormLabel htmlFor="weapon">
|
||||
<Trans>Weapon</Trans>
|
||||
</FormLabel>
|
||||
<Controller
|
||||
name="weapon"
|
||||
control={control}
|
||||
defaultValue={null}
|
||||
render={({ onChange, value }) => (
|
||||
<WeaponSelector
|
||||
name="weapon"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormControl isInvalid={!!errors.title}>
|
||||
<FormLabel htmlFor="title" mt={4}>
|
||||
<Trans>Title</Trans>
|
||||
</FormLabel>
|
||||
<Input name="title" ref={register} />
|
||||
<FormHelperText>
|
||||
{watchTitle!.length}/{TITLE_CHARACTER_LIMIT}
|
||||
</FormHelperText>
|
||||
<FormErrorMessage>{errors.title?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isInvalid={!!errors.description}>
|
||||
<FormLabel htmlFor="description" mt={4}>
|
||||
<Trans>Description</Trans>
|
||||
</FormLabel>
|
||||
<Textarea name="description" ref={register} />
|
||||
<FormHelperText>
|
||||
{watchDescription!.length}/{DESCRIPTION_CHARACTER_LIMIT}
|
||||
</FormHelperText>
|
||||
<FormErrorMessage>
|
||||
{errors.description?.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
<FormLabel htmlFor="headGear" mt={4}>
|
||||
<Trans>Head</Trans>
|
||||
</FormLabel>
|
||||
<Controller
|
||||
name="headGear"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ onChange, value, name }) => (
|
||||
<MySelect
|
||||
placeholder={t`Select gear (head)`}
|
||||
name={name}
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
>
|
||||
{gear.map(({ brand, head }) => (
|
||||
<Fragment key={brand}>
|
||||
<optgroup>{brand}</optgroup>
|
||||
{head.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</MySelect>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormLabel htmlFor="clothingGear" mt={4}>
|
||||
<Trans>Clothing</Trans>
|
||||
</FormLabel>
|
||||
<Controller
|
||||
name="clothingGear"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ onChange, value, name }) => (
|
||||
<MySelect
|
||||
placeholder={t`Select gear (clothing)`}
|
||||
name={name}
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
>
|
||||
{gear.map(({ brand, clothing }) => (
|
||||
<Fragment key={brand}>
|
||||
<optgroup>{brand}</optgroup>
|
||||
{clothing.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</MySelect>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormLabel htmlFor="shoesGear" mt={4}>
|
||||
<Trans>Shoes</Trans>
|
||||
</FormLabel>
|
||||
<Controller
|
||||
name="headGear"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ onChange, value, name }) => (
|
||||
<MySelect
|
||||
placeholder={t`Select gear (shoes)`}
|
||||
name={name}
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
>
|
||||
{gear
|
||||
.filter((brand) => brand.shoes.length > 0)
|
||||
.map(({ brand, shoes }) => (
|
||||
<Fragment key={brand}>
|
||||
<optgroup>{brand}</optgroup>
|
||||
{shoes.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</MySelect>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="modes" mt={4}>
|
||||
<Trans>Modes</Trans>
|
||||
</FormLabel>
|
||||
<Controller
|
||||
name="headGear"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ onChange, value }) => (
|
||||
<CheckboxGroup value={value} onChange={onChange}>
|
||||
<Stack spacing={4} direction="row">
|
||||
<Checkbox value="TW">
|
||||
<Trans>TW</Trans>
|
||||
</Checkbox>
|
||||
<Checkbox value="SZ">
|
||||
<Trans>SZ</Trans>
|
||||
</Checkbox>
|
||||
<Checkbox value="TC">
|
||||
<Trans>TC</Trans>
|
||||
</Checkbox>
|
||||
<Checkbox value="RM">
|
||||
<Trans>RM</Trans>
|
||||
</Checkbox>
|
||||
<Checkbox value="CB">
|
||||
<Trans>CB</Trans>
|
||||
</Checkbox>
|
||||
</Stack>
|
||||
</CheckboxGroup>
|
||||
)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
<Trans>
|
||||
Choose at least one mode where you use this build
|
||||
</Trans>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
mr={3}
|
||||
type="submit"
|
||||
// FIXME:
|
||||
//isLoading={loading}
|
||||
>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="outline">
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</ModalOverlay>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default BuildModal;
|
||||
1184
lib/lists/gear.ts
1184
lib/lists/gear.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -4,10 +4,17 @@ import { weaponsWithHero } from "lib/lists/weaponsWithHero";
|
|||
import * as z from "zod";
|
||||
import { hasNoDuplicates } from "./common";
|
||||
|
||||
export const TITLE_CHARACTER_LIMIT = 100;
|
||||
export const DESCRIPTION_CHARACTER_LIMIT = 1000;
|
||||
|
||||
export const buildSchema = z.object({
|
||||
weapon: z.string().refine((val) => weaponsWithHero.includes(val as any)),
|
||||
title: z.string().max(100).optional().nullable(),
|
||||
description: z.string().max(1000).optional().nullable(),
|
||||
title: z.string().max(TITLE_CHARACTER_LIMIT).optional().nullable(),
|
||||
description: z
|
||||
.string()
|
||||
.max(DESCRIPTION_CHARACTER_LIMIT)
|
||||
.optional()
|
||||
.nullable(),
|
||||
modes: z
|
||||
.array(z.string())
|
||||
.min(1)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -7,6 +7,7 @@ import Breadcrumbs from "components/common/Breadcrumbs";
|
|||
import Markdown from "components/common/Markdown";
|
||||
import MyInfiniteScroller from "components/common/MyInfiniteScroller";
|
||||
import AvatarWithInfo from "components/u/AvatarWithInfo";
|
||||
import BuildModal from "components/u/BuildModal";
|
||||
import ProfileModal from "components/u/ProfileModal";
|
||||
import { useBuildsByUser } from "hooks/u";
|
||||
import { getFullUsername } from "lib/strings";
|
||||
|
|
@ -30,7 +31,8 @@ interface Props {
|
|||
}
|
||||
|
||||
const ProfilePage = (props: Props) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showProfileModal, setShowProfileModal] = useState(false);
|
||||
const [showBuildModal, setShowBuildModal] = useState(false);
|
||||
|
||||
const [loggedInUser] = useUser();
|
||||
const { data: user } = useSWR<GetUserByIdentifierData>(
|
||||
|
|
@ -51,6 +53,13 @@ const ProfilePage = (props: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{showProfileModal && (
|
||||
<ProfileModal onClose={() => setShowProfileModal(false)} user={user} />
|
||||
)}
|
||||
{showBuildModal && (
|
||||
<BuildModal onClose={() => setShowBuildModal(false)} />
|
||||
)}
|
||||
|
||||
<Breadcrumbs
|
||||
pages={[
|
||||
{ name: t`Users`, link: "/u" },
|
||||
|
|
@ -59,13 +68,10 @@ const ProfilePage = (props: Props) => {
|
|||
/>
|
||||
<AvatarWithInfo user={user} peakXPowers={props.peakXPowers} />
|
||||
{loggedInUser?.id === user.id && (
|
||||
<Button onClick={() => setShowModal(true)}>
|
||||
<Button onClick={() => setShowProfileModal(true)}>
|
||||
<Trans>Edit profile</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{showModal && (
|
||||
<ProfileModal onClose={() => setShowModal(false)} user={user} />
|
||||
)}
|
||||
{user.profile?.bio && (
|
||||
<>
|
||||
<Divider my={6} />
|
||||
|
|
@ -94,6 +100,11 @@ const ProfilePage = (props: Props) => {
|
|||
))}
|
||||
</Select>
|
||||
)}
|
||||
{loggedInUser?.id === user.id && (
|
||||
<Button onClick={() => setShowBuildModal(true)}>
|
||||
<Trans>Add build</Trans>
|
||||
</Button>
|
||||
)}
|
||||
<MyInfiniteScroller>
|
||||
{builds.map((build) => (
|
||||
<BuildCard key={build.id} build={build} m={2} showWeapon />
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user