diff --git a/frontend/components/PersonPage.tsx b/frontend/components/PersonPage.tsx new file mode 100644 index 0000000..4cd2cfa --- /dev/null +++ b/frontend/components/PersonPage.tsx @@ -0,0 +1,329 @@ +import Head from "next/head"; +import { Field, Name, Person, Pronoun, User, WordStatus } from "../lib/types"; +import ReactMarkdown from "react-markdown"; +import { userState } from "../lib/state"; +import { useRecoilValue } from "recoil"; +import FallbackImage from "./FallbackImage"; +import { + EmojiLaughing, + HandThumbsDown, + HandThumbsUp, + HeartFill, + People, +} from "react-bootstrap-icons"; +import BlueLink from "./BlueLink"; +import React from "react"; +import Card from "./Card"; + +export default function PersonPage({ person }: { person: Person }) { + return ( + <> + + {`${personFullHandle(person)} - pronouns.cc`} + + + +
+
+ + +
+ + + + {"user" in person && } +
+ + ); +} + +/** Full handle of a person. */ +function personFullHandle(person: Person) { + return "user" in person + ? `&${person.name}@${person.user.name}` + : `@${person.name}`; +} + +/** Short handle of a person. */ +function personShortHandle(person: Person) { + return ("user" in person ? "&" : "@") + person.name; +} + +/** The user (account) associated with a person. */ +function personUser(person: Person) { + return "user" in person ? person.user : person; +} + +/** The (relative) URL pointing to a person. */ +function personURL(person: Person) { + const domain = + typeof window !== "undefined" ? window.location.origin : process.env.DOMAIN; + return `${domain}/u/${"user" in person ? `${person.user.name}/` : ""}${ + person.name + }`; +} + +function PersonHead({ person }: { person: Person }) { + const { + id, + name, + display_name, + avatar_urls, + bio, + links, + names, + pronouns, + fields, + } = person; + let description = ""; + if ( + names?.filter((name) => name.status === WordStatus.Favourite)?.length && + pronouns?.filter((pronoun) => pronoun.status === WordStatus.Favourite) + ?.length + ) { + description = `${personShortHandle(person)} goes by ${names + .filter((name) => name.status === WordStatus.Favourite) + .map((name) => name.name) + .join(", ")} and uses ${pronouns + .filter((pronoun) => pronoun.status === WordStatus.Favourite) + .map( + (pronoun) => + pronoun.display_text ?? + pronoun.pronouns.split("/").slice(0, 2).join("/") + ) + .join(", ")} pronouns.`; + } else if ( + names?.filter((name) => name.status === WordStatus.Favourite)?.length + ) { + description = `${personShortHandle(person)} goes by ${names + .filter((name) => name.status === WordStatus.Favourite) + .map((name) => name.name) + .join(", ")}.`; + } else if ( + pronouns?.filter((pronoun) => pronoun.status === WordStatus.Favourite) + ?.length + ) { + description = `${personShortHandle(person)} uses ${pronouns + .filter((pronoun) => pronoun.status === WordStatus.Favourite) + .map( + (pronoun) => + pronoun.display_text ?? + pronoun.pronouns.split("/").slice(0, 2).join("/") + ) + .join(", ")} pronouns.`; + } else if (bio && bio !== "") { + description = bio.slice(0, 500); + } + + return ( + + + + {avatar_urls && avatar_urls.length > 0 ? ( + + ) : ( + <> + )} + + + + ); +} + +function IsOwnUserPageNotice({ person }: { person: Person }) { + return useRecoilValue(userState)?.id === person.id ? ( +
+ You are currently viewing your public user profile. +
+ Edit your profile +
+ ) : ( + <> + ); +} + +function MemberList({ user, className }: { user: User; className?: string }) { + const partialMembers = user.members; + return ( +
+

Members

+
    + {partialMembers?.map((partialMember) => ( +
  • + + {partialMember.display_name ?? partialMember.name} + +
  • + ))} +
+
+ ); +} + +function PersonAvatar({ person }: { person: Person }) { + const { display_name, name, avatar_urls } = person; + return avatar_urls && avatar_urls.length !== 0 ? ( + + ) : ( + <> + ); +} + +function PersonInfo({ person }: { person: Person }) { + const { display_name, name, bio, links } = person; + return ( +
+ {/* display name */} + {display_name &&

{display_name}

} + {/* name */} +

+ {personFullHandle(person)} +

+ {/* bio */} + {bio && ( + + {bio} + + )} + {/* links */} + {links?.length && ( +
+ {links.map((link, index) => ( + + {link} + + ))} +
+ )} +
+ ); +} + +function LabelList({ content }: { content: Name[] | Pronoun[] }) { + return content?.length > 0 ? ( +
+ {content.map((label, index) => ( + + ))} +
+ ) : ( + <> + ); +} + +function LabelStatusIcon({ status }: { status: WordStatus }) { + return React.createElement( + { + [WordStatus.Favourite]: HeartFill, + [WordStatus.Okay]: HandThumbsUp, + [WordStatus.Jokingly]: EmojiLaughing, + [WordStatus.FriendsOnly]: People, + [WordStatus.Avoid]: HandThumbsDown, + }[status], + { className: "inline" } + ); +} + +function LabelsLine({ labels }: { labels: Name[] | Pronoun[] }) { + if (!labels?.length) return <>; + const status = labels[0].status; + const text = labels + .map((label) => + "name" in label + ? label.name + : label.display_text ?? label.pronouns.split("/").slice(0, 2).join("/") + ) + .join(", "); + return ( +

+ {text} +

+ ); +} + +function LabelLine({ label }: { label: Name | Pronoun }) { + return ; +} + +function FieldCardGrid({ fields }: { fields: Field[] }) { + return ( +
+ {fields?.map((field, index) => ( + + ))} +
+ ); +} + +const fieldEntryStatus: { [key in string]: WordStatus } = { + favourite: WordStatus.Favourite, + okay: WordStatus.Okay, + jokingly: WordStatus.Jokingly, + friends_only: WordStatus.FriendsOnly, + avoid: WordStatus.Avoid, +}; + +function FieldCard({ + field, + draggable, +}: { + field: Field; + draggable?: boolean; +}) { + return ( + + {Object.entries(fieldEntryStatus).map(([statusName, status], i) => ( + ({ + name, + status, + }))} + /> + ))} + + ); +} diff --git a/frontend/pages/u/[user]/index.tsx b/frontend/pages/u/[user]/index.tsx index bba091b..15f4901 100644 --- a/frontend/pages/u/[user]/index.tsx +++ b/frontend/pages/u/[user]/index.tsx @@ -1,31 +1,7 @@ import { GetServerSideProps } from "next"; -import Head from "next/head"; +import PersonPage from "../../../components/PersonPage"; import fetchAPI from "../../../lib/fetch"; -import { - Field, - Member, - Name, - PartialMember, - PartialUser, - Person, - Pronoun, - User, - WordStatus, -} from "../../../lib/types"; -import ReactMarkdown from "react-markdown"; -import { userState } from "../../../lib/state"; -import { useRecoilValue } from "recoil"; -import FallbackImage from "../../../components/FallbackImage"; -import { - EmojiLaughing, - HandThumbsDown, - HandThumbsUp, - HeartFill, - People, -} from "react-bootstrap-icons"; -import BlueLink from "../../../components/BlueLink"; -import React from "react"; -import Card from "../../../components/Card"; +import { PartialMember, User } from "../../../lib/types"; interface Props { user: User; @@ -33,9 +9,7 @@ interface Props { } export default function Index({ user, partialMembers }: Props) { - return ( - - ); + return ; } export const getServerSideProps: GetServerSideProps = async (context) => { @@ -58,320 +32,3 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return { notFound: true }; } }; - -function PersonPage({ person }: { person: Person }) { - return ( - <> - - {`${personFullHandle(person)} - pronouns.cc`} - - - -
-
- - -
- - - - { 'user' in person && ( - - )} -
- - ); -} - -/** Full handle of a person. */ -function personFullHandle(person: Person) { - return 'user' in person - ? `&${person.name}@${person.user.name}` - : `@${person.name}`; -} - -/** Short handle of a person. */ -function personShortHandle(person: Person) { - return ('user' in person ? '&' : '@') + person.name; -} - -/** The user (account) associated with a person. */ -function personUser(person: Person) { - return 'user' in person ? person.user : person; -} - -/** The (relative) URL pointing to a person. */ -function personURL(person: Person) { - const domain = typeof window !== "undefined" ? window.location.origin : process.env.DOMAIN; - return `${domain}/u/${'user' in person ? `${person.user.name}/` : ''}${person.name}`; -} - -function PersonHead({ person }: { person: Person }) { - const { id, name, display_name, avatar_urls, bio, links, names, pronouns, fields } = person; - let description = ""; - if ( - names?.filter((name) => name.status === WordStatus.Favourite) - ?.length && - pronouns?.filter((pronoun) => pronoun.status === WordStatus.Favourite) - ?.length - ) { - description = `${personShortHandle(person)} goes by ${names - .filter((name) => name.status === WordStatus.Favourite) - .map((name) => name.name) - .join(", ")} and uses ${pronouns - .filter((pronoun) => pronoun.status === WordStatus.Favourite) - .map( - (pronoun) => - pronoun.display_text ?? - pronoun.pronouns.split("/").slice(0, 2).join("/") - ) - .join(", ")} pronouns.`; - } else if ( - names?.filter((name) => name.status === WordStatus.Favourite)?.length - ) { - description = `${personShortHandle(person)} goes by ${names - .filter((name) => name.status === WordStatus.Favourite) - .map((name) => name.name) - .join(", ")}.`; - } else if ( - pronouns?.filter((pronoun) => pronoun.status === WordStatus.Favourite) - ?.length - ) { - description = `${personShortHandle(person)} uses ${pronouns - .filter((pronoun) => pronoun.status === WordStatus.Favourite) - .map( - (pronoun) => - pronoun.display_text ?? - pronoun.pronouns.split("/").slice(0, 2).join("/") - ) - .join(", ")} pronouns.`; - } else if (bio && bio !== "") { - description = bio.slice(0, 500); - } - - return ( - - - - {avatar_urls && avatar_urls.length > 0 ? ( - - ) : ( - <> - )} - - - - ); -} - -function IsOwnUserPageNotice({ person }: { person: Person }) { - return useRecoilValue(userState)?.id === person.id ? ( -
- You are currently viewing your public user profile. -
- Edit your profile -
- ) : ( - <> - ); -} - -function MemberList({ - user, - className, -}: { - user: User; - className?: string; -}) { - const partialMembers = user.members; - return ( -
-

Members

-
    - {partialMembers?.map((partialMember) => ( -
  • - - {partialMember.display_name ?? partialMember.name} - -
  • - ))} -
-
- ); -} - -function PersonAvatar({ person }: { person: Person }) { - const { display_name, name, avatar_urls } = person; - return avatar_urls && avatar_urls.length !== 0 ? ( - - ) : ( - <> - ); -} - -function PersonInfo({ person }: { person: Person }) { - const { display_name, name, bio, links } = person; - return ( -
- {/* display name */} - {display_name &&

{display_name}

} - {/* name */} -

- {personFullHandle(person)} -

- {/* bio */} - {bio && ( - - {bio} - - )} - {/* links */} - {links?.length && ( -
- {links.map((link, index) => ( - - {link} - - ))} -
- )} -
- ); -} - -function LabelList({ content }: { content: Name[] | Pronoun[] }) { - return content?.length > 0 ? ( -
- {content.map((label, index) => ( - - ))} -
- ) : ( - <> - ); -} - -function LabelStatusIcon({ status }: { status: WordStatus }) { - return React.createElement( - { - [WordStatus.Favourite]: HeartFill, - [WordStatus.Okay]: HandThumbsUp, - [WordStatus.Jokingly]: EmojiLaughing, - [WordStatus.FriendsOnly]: People, - [WordStatus.Avoid]: HandThumbsDown, - }[status], - { className: "inline" } - ); -} - -function LabelsLine({ labels }: { labels: Name[] | Pronoun[] }) { - if (!labels?.length) return <>; - const status = labels[0].status; - const text = labels - .map((label) => - "name" in label - ? label.name - : label.display_text ?? label.pronouns.split("/").slice(0, 2).join("/") - ) - .join(", "); - return ( -

- {text} -

- ); -} - -function LabelLine({ label }: { label: Name | Pronoun }) { - return ; -} - -function FieldCardGrid({ fields }: { fields: Field[] }) { - return ( -
- {fields?.map((field, index) => ( - - ))} -
- ); -} - -const fieldEntryStatus: { [key in string]: WordStatus } = { - favourite: WordStatus.Favourite, - okay: WordStatus.Okay, - jokingly: WordStatus.Jokingly, - friends_only: WordStatus.FriendsOnly, - avoid: WordStatus.Avoid, -}; - -function FieldCard({ - field, - draggable, -}: { - field: Field; - draggable?: boolean; -}) { - return ( - - {Object.entries(fieldEntryStatus).map(([statusName, status], i) => ( - ({ - name, - status, - }))} - /> - ))} - - ); -}