build modal initial

This commit is contained in:
Kalle (Sendou) 2020-11-22 16:59:20 +02:00
parent 5b12157e4c
commit 7eabc4a7be
18 changed files with 32962 additions and 860 deletions

View File

@ -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
View 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;

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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 />