diff --git a/frontend/components/Editable.tsx b/frontend/components/Editable.tsx new file mode 100644 index 0000000..a3bdef6 --- /dev/null +++ b/frontend/components/Editable.tsx @@ -0,0 +1,128 @@ +import { + EmojiLaughing, + HandThumbsDown, + HandThumbsUp, + Heart, + People, + Trash3, +} from "react-bootstrap-icons"; + +import Card from "./Card"; +import TextInput from "./TextInput"; + +export interface EditField { + id: number; + name: string; + pronouns: Record; +} + +export enum PronounChoice { + favourite, + okay, + jokingly, + friendsOnly, + avoid, +} + +type EditableCardProps = { + field: EditField; + onChangeName: React.ChangeEventHandler; + onChangeFavourite( + e: React.MouseEvent, + entry: string + ): void; + onChangeOkay(e: React.MouseEvent, entry: string): void; + onChangeJokingly(e: React.MouseEvent, entry: string): void; + onChangeFriends(e: React.MouseEvent, entry: string): void; + onChangeAvoid(e: React.MouseEvent, entry: string): void; + onClickDelete: React.MouseEventHandler; +}; + +export function EditableCard(props: EditableCardProps) { + const footer = ( +
+ + +
+ ); + + return ( + +
    + {Object.keys(props.field.pronouns).map((pronoun, index) => { + const choice = props.field.pronouns[pronoun]; + return ( +
  • +
    {pronoun}
    +
    + + + + + + +
    +
  • + ); + })} +
+
+ ); +} diff --git a/frontend/components/TextInput.tsx b/frontend/components/TextInput.tsx new file mode 100644 index 0000000..2d7e5ff --- /dev/null +++ b/frontend/components/TextInput.tsx @@ -0,0 +1,19 @@ +import { ChangeEventHandler } from "react"; + +export type Props = { + defaultValue?: string; + value?: string; + onChange?: ChangeEventHandler; +}; + +export default function TextInput(props: Props) { + return ( + + ); +} diff --git a/frontend/pages/edit/member/[member]/index.tsx b/frontend/pages/edit/member/[member]/index.tsx new file mode 100644 index 0000000..b8ba6de --- /dev/null +++ b/frontend/pages/edit/member/[member]/index.tsx @@ -0,0 +1,3 @@ +export default function EditMember() { + return <>Editing a member!; +} \ No newline at end of file diff --git a/frontend/pages/edit/member/index.tsx b/frontend/pages/edit/member/index.tsx new file mode 100644 index 0000000..913d038 --- /dev/null +++ b/frontend/pages/edit/member/index.tsx @@ -0,0 +1,12 @@ +import { useRouter } from "next/router"; +import { useEffect } from "react"; +import Loading from "../../../components/Loading"; + +export default function Redirect() { + const router = useRouter(); + useEffect(() => { + router.push("/") + }, []) + + return ; +} \ No newline at end of file diff --git a/frontend/pages/edit/profile.tsx b/frontend/pages/edit/profile.tsx new file mode 100644 index 0000000..e6ae83b --- /dev/null +++ b/frontend/pages/edit/profile.tsx @@ -0,0 +1,164 @@ +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import Loading from "../../components/Loading"; +import fetchAPI from "../../lib/fetch"; +import { userState } from "../../lib/state"; +import { MeUser, Field } from "../../lib/types"; +import cloneDeep from "lodash/cloneDeep"; +import { ReactSortable } from "react-sortablejs"; +import Card from "../../components/Card"; + +import { EditableCard, EditField, PronounChoice } from "../../components/Editable"; + +export default function Index() { + const [user, setUser] = useRecoilState(userState); + const router = useRouter(); + + useEffect(() => { + if (!user) { + router.push("/"); + } + }, [user]) + + if (!user) { + return ; + } + + const [state, setState] = useState(cloneDeep(user)); + + const originalOrder = state.fields ? state.fields.map((f, i) => { + const field: EditField = { + id: i, + name: f.name, + pronouns: {}, + }; + + f.favourite?.forEach((val) => { + field.pronouns[val] = PronounChoice.favourite; + }); + f.okay?.forEach((val) => { + field.pronouns[val] = PronounChoice.okay; + }); + f.jokingly?.forEach((val) => { + field.pronouns[val] = PronounChoice.jokingly; + }); + f.friends_only?.forEach((val) => { + field.pronouns[val] = PronounChoice.friendsOnly; + }); + f.avoid?.forEach((val) => { + field.pronouns[val] = PronounChoice.avoid; + }); + + return field; + }) : []; + + const [fields, setFields] = useState(cloneDeep(originalOrder)); + const fieldsUpdated = !fieldsEqual(fields, originalOrder); + + return ( +
+
{`fieldsUpdated: ${fieldsUpdated}`}
+ {/* @ts-ignore: This component isn't updated to have a "children" prop yet, but it accepts it just fine. */} + + {fields.map((field, i) => ( + { + field.name = e.target.value; + setFields([...fields]); + }} + onChangeFavourite={(e, entry: string) => { + field.pronouns[entry] = PronounChoice.favourite; + setFields([...fields]); + }} + onChangeOkay={(e, entry: string) => { + field.pronouns[entry] = PronounChoice.okay; + setFields([...fields]); + }} + onChangeJokingly={(e, entry: string) => { + field.pronouns[entry] = PronounChoice.jokingly; + setFields([...fields]); + }} + onChangeFriends={(e, entry: string) => { + field.pronouns[entry] = PronounChoice.friendsOnly; + setFields([...fields]); + }} + onChangeAvoid={(e, entry: string) => { + field.pronouns[entry] = PronounChoice.avoid; + setFields([...fields]); + }} + onClickDelete={(_) => { + const newFields = [...fields]; + newFields.splice(i, 1); + setFields(newFields); + }} + /> + ))} + +
+ ); +} + +function fieldsEqual(arr1: EditField[], arr2: EditField[]) { + if (arr1?.length !== arr2?.length) return false; + + if (!arr1.every((_, i) => arr1[i].id === arr2[i].id)) return false; + + return arr1.every((_, i) => + Object.keys(arr1[i].pronouns).every( + (val) => arr1[i].pronouns[val] === arr2[i].pronouns[val] + ) + ); +} + +async function updateUser(args: { + displayName: string; + bio: string; + fields: EditField[]; +}) { + const newFields = args.fields.map((editField) => { + const field: Field = { + name: editField.name, + favourite: [], + okay: [], + jokingly: [], + friends_only: [], + avoid: [], + }; + + Object.keys(editField).forEach((pronoun) => { + switch (editField.pronouns[pronoun]) { + case PronounChoice.favourite: + field.favourite?.push(pronoun); + break; + case PronounChoice.okay: + field.okay?.push(pronoun); + break; + case PronounChoice.jokingly: + field.jokingly?.push(pronoun); + break; + case PronounChoice.friendsOnly: + field.friends_only?.push(pronoun); + break; + case PronounChoice.avoid: + field.avoid?.push(pronoun); + break; + } + }); + + return field; + }); + + return await fetchAPI("/users/@me", "PATCH", { + display_name: args.displayName, + bio: args.bio, + fields: newFields, + }); +}