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,
- }))}
- />
- ))}
-
- );
-}