refactor: user page code adjustments
This commit is contained in:
		
							parent
							
								
									b48a493883
								
							
						
					
					
						commit
						f51a1329da
					
				
					 2 changed files with 167 additions and 196 deletions
				
			
		|  | @ -1,64 +0,0 @@ | |||
| import { | ||||
|   HeartFill, | ||||
|   HandThumbsUp, | ||||
|   HandThumbsDown, | ||||
|   People, | ||||
|   EmojiLaughing, | ||||
| } from "react-bootstrap-icons"; | ||||
| import BlueLink from "./BlueLink"; | ||||
| 
 | ||||
| import Card from "./Card"; | ||||
| import type { Field } from "../lib/types"; | ||||
| 
 | ||||
| function linkPronoun(input: string) { | ||||
|   if (input.includes(" ") || input.split("/").length !== 5) | ||||
|     return <span>{input}</span>; | ||||
| 
 | ||||
|   const [sub, obj, possDet, possPro, reflexive] = input.split("/"); | ||||
| 
 | ||||
|   return ( | ||||
|     <BlueLink to={`/pronouns/${sub}/${obj}/${possDet}/${possPro}/${reflexive}`}> | ||||
|       <span> | ||||
|         {sub}/{obj}/{possDet} | ||||
|       </span> | ||||
|     </BlueLink> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default function FieldCard({ | ||||
|   field, | ||||
|   draggable, | ||||
| }: { | ||||
|   field: Field; | ||||
|   draggable?: boolean; | ||||
| }) { | ||||
|   return ( | ||||
|     <Card title={field.name} draggable={draggable}> | ||||
|       {field.favourite?.map((entry) => ( | ||||
|         <p className="text-lg font-bold" key={entry}> | ||||
|           <HeartFill className="inline" /> {linkPronoun(entry)} | ||||
|         </p> | ||||
|       ))} | ||||
|       {field.okay && field.okay.length !== 0 && ( | ||||
|         <p> | ||||
|           <HandThumbsUp className="inline" /> {field.okay!.join(", ")} | ||||
|         </p> | ||||
|       )} | ||||
|       {field.jokingly && field.jokingly.length !== 0 && ( | ||||
|         <p> | ||||
|           <EmojiLaughing className="inline" /> {field.jokingly!.join(", ")} | ||||
|         </p> | ||||
|       )} | ||||
|       {field.friends_only && field.friends_only.length !== 0 && ( | ||||
|         <p> | ||||
|           <People className="inline" /> {field.friends_only!.join(", ")} | ||||
|         </p> | ||||
|       )} | ||||
|       {field.avoid && field.avoid.length !== 0 && ( | ||||
|         <p className="text-slate-600 dark:text-slate-400"> | ||||
|           <HandThumbsDown className="inline" /> {field.avoid!.join(", ")} | ||||
|         </p> | ||||
|       )} | ||||
|     </Card> | ||||
|   ); | ||||
| } | ||||
|  | @ -1,16 +1,11 @@ | |||
| import { GetServerSideProps } from "next"; | ||||
| import Head from "next/head"; | ||||
| import fetchAPI from "../../../lib/fetch"; | ||||
| import { Name, Pronoun, User, WordStatus } from "../../../lib/types"; | ||||
| import FieldCard from "../../../components/FieldCard"; | ||||
| import Card from "../../../components/Card"; | ||||
| import { Field, Name, Pronoun, User, WordStatus } from "../../../lib/types"; | ||||
| import ReactMarkdown from "react-markdown"; | ||||
| import Image from "next/image"; | ||||
| import { userState } from "../../../lib/state"; | ||||
| import { useRecoilValue } from "recoil"; | ||||
| import Link from "next/link"; | ||||
| import FallbackImage from "../../../components/FallbackImage"; | ||||
| import { ReactNode } from "react"; | ||||
| import { | ||||
|   EmojiLaughing, | ||||
|   HandThumbsDown, | ||||
|  | @ -19,150 +14,41 @@ import { | |||
|   People, | ||||
| } from "react-bootstrap-icons"; | ||||
| import BlueLink from "../../../components/BlueLink"; | ||||
| import React from "react"; | ||||
| import Card from "../../../components/Card"; | ||||
| 
 | ||||
| interface Props { | ||||
|   user: User; | ||||
| } | ||||
| 
 | ||||
| export default function Index({ user }: Props) { | ||||
|   const isMeUser = useRecoilValue(userState)?.id === user.id; | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <Head> | ||||
|         <title key='title'>{`@${user.username} - pronouns.cc`}</title> | ||||
|       </Head> | ||||
|       {isMeUser && ( | ||||
|         <div className="lg:w-1/3 mx-auto bg-slate-100 dark:bg-slate-700 shadow rounded-md p-2"> | ||||
|           <span> | ||||
|             You are currently viewing your{" "} | ||||
|             <span className="font-bold">public</span> profile. | ||||
|           </span> | ||||
|           <br /> | ||||
|           <BlueLink to="/edit/profile">Edit your profile</BlueLink> | ||||
|         </div> | ||||
|       )} | ||||
|       <IsOwnPageNotice user={user} /> | ||||
|       <div className="container mx-auto"> | ||||
|         <div className="flex flex-col m-2 p-2 lg:flex-row justify-center lg:justify-start items-center space-y-4 lg:space-y-0 lg:space-x-16 lg:items-start border-b border-slate-200 dark:border-slate-700"> | ||||
|           {user.avatar_urls && user.avatar_urls.length !== 0 && ( | ||||
|             <FallbackImage | ||||
|               className="max-w-xs rounded-full" | ||||
|               urls={user.avatar_urls} | ||||
|               alt={`@${user.username}'s avatar`} | ||||
|             /> | ||||
|           )} | ||||
|           <div className="flex flex-col"> | ||||
|             {user.display_name && ( | ||||
|               <h1 className="text-2xl font-bold">{user.display_name}</h1> | ||||
|             )} | ||||
|             <h3 | ||||
|               className={`${ | ||||
|                 user.display_name | ||||
|                   ? "text-xl italic text-slate-600 dark:text-slate-400" | ||||
|                   : "text-2xl font-bold" | ||||
|               }`}
 | ||||
|             > | ||||
|               @{user.username} | ||||
|             </h3> | ||||
|             {user.bio && ( | ||||
|               <ReactMarkdown className="prose dark:prose-invert prose-slate"> | ||||
|                 {user.bio} | ||||
|               </ReactMarkdown> | ||||
|             )} | ||||
|             {user.links?.length && ( | ||||
|               <div className="flex flex-col mx-auto lg:ml-auto"> | ||||
|                 {user.links.map((link, index) => ( | ||||
|                   <a | ||||
|                     key={index} | ||||
|                     href={link} | ||||
|                     rel="nofollow noopener noreferrer me" | ||||
|                     className="hover:underline text-sky-500 dark:text-sky-400" | ||||
|                   > | ||||
|                     {link} | ||||
|                   </a> | ||||
|                 ))} | ||||
|               </div> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|         {user.names?.length > 0 && ( | ||||
|           <div className="border-b border-slate-200 dark:border-slate-700"> | ||||
|             {user.names.map((name, index) => ( | ||||
|               <NameEntry name={name} key={index} /> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
|         {user.pronouns?.length > 0 && ( | ||||
|           <div className="border-b border-slate-200 dark:border-slate-700"> | ||||
|             {user.pronouns.map((pronoun, index) => ( | ||||
|               <PronounEntry pronoun={pronoun} key={index} /> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
|         <div className="grid grid-cols-1 md:grid-cols-3 gap-4 py-2"> | ||||
|           {user.fields?.map((field, index) => ( | ||||
|             <FieldCard key={index} field={field}></FieldCard> | ||||
|           ))} | ||||
|         <div className=" | ||||
|           m-2 p-2 | ||||
|           flex flex-col lg:flex-row | ||||
|           justify-center lg:justify-start | ||||
|           items-center lg:items-start | ||||
|           lg:space-x-16 | ||||
|           space-y-4 lg:space-y-0 | ||||
|           border-b border-slate-200 dark:border-slate-700 | ||||
|         "> | ||||
|           <UserAvatar user={user} /> | ||||
|           <UserInfo user={user} /> | ||||
|         </div> | ||||
|         <LabelList source={user.names} /> | ||||
|         <LabelList source={user.pronouns} /> | ||||
|         <FieldCardGrid fields={user.fields} /> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| const entryIcon = (status: WordStatus) => { | ||||
|   let icon: ReactNode; | ||||
| 
 | ||||
|   switch (status) { | ||||
|     case WordStatus.Favourite: | ||||
|       icon = <HeartFill className="inline" />; | ||||
|       break; | ||||
|     case WordStatus.Okay: | ||||
|       icon = <HandThumbsUp className="inline" />; | ||||
|       break; | ||||
|     case WordStatus.Jokingly: | ||||
|       icon = <EmojiLaughing className="inline" />; | ||||
|       break; | ||||
|     case WordStatus.FriendsOnly: | ||||
|       icon = <People className="inline" />; | ||||
|       break; | ||||
|     case WordStatus.Avoid: | ||||
|       icon = <HandThumbsDown className="inline" />; | ||||
|       break; | ||||
|   } | ||||
| 
 | ||||
|   return icon; | ||||
| }; | ||||
| 
 | ||||
| function NameEntry(props: { name: Name }) { | ||||
|   const { name } = props; | ||||
| 
 | ||||
|   return ( | ||||
|     <p | ||||
|       className={`text-lg ${ | ||||
|         name.status === WordStatus.Favourite && "font-bold" | ||||
|       }`}
 | ||||
|     > | ||||
|       {entryIcon(name.status)} {name.name} | ||||
|     </p> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function PronounEntry(props: { pronoun: Pronoun }) { | ||||
|   const { pronoun } = props; | ||||
| 
 | ||||
|   return ( | ||||
|     <p | ||||
|       className={`text-lg ${ | ||||
|         pronoun.status === WordStatus.Favourite && "font-bold" | ||||
|       }`}
 | ||||
|     > | ||||
|       {entryIcon(pronoun.status)}{" "} | ||||
|       {pronoun.display_text ?? | ||||
|         pronoun.pronouns.split("/").slice(0, 2).join("/")} | ||||
|     </p> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export const getServerSideProps: GetServerSideProps = async (context) => { | ||||
|   try { | ||||
|     const user = await fetchAPI<User>(`/users/${context.params!.user}`); | ||||
|  | @ -174,3 +60,152 @@ export const getServerSideProps: GetServerSideProps = async (context) => { | |||
|     return { notFound: true }; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function IsOwnPageNotice({ user }: { user: User }) { | ||||
|   const isThisMyPage = useRecoilValue(userState)?.id === user.id; | ||||
|   return ( | ||||
|     isThisMyPage || true ? ( | ||||
|       <div className="lg:w-1/3 mx-auto bg-slate-100 dark:bg-slate-700 shadow rounded-md p-2"> | ||||
|         You are currently viewing your <b>public</b> profile. | ||||
|         <br /> | ||||
|         <BlueLink to="/edit/profile">Edit your profile</BlueLink> | ||||
|       </div> | ||||
|     ) : <></> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function UserAvatar({ user }: { user: User }) { | ||||
|   return ( | ||||
|     user.avatar_urls && user.avatar_urls.length !== 0 ? ( | ||||
|       <FallbackImage | ||||
|         className="max-w-xs rounded-full" | ||||
|         urls={user.avatar_urls} | ||||
|         alt={`@${user.username}'s avatar`} | ||||
|       /> | ||||
|     ) : <></> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function UserInfo({ user }: { user: User }) { | ||||
|   const { display_name, username, bio, links } = user; | ||||
|   return ( | ||||
|     <div className="flex flex-col"> | ||||
|       {/* display name */} | ||||
|       {display_name && ( | ||||
|         <h1 className="text-2xl font-bold">{display_name}</h1> | ||||
|       )} | ||||
|       {/* username */} | ||||
|       <h3 | ||||
|         className={`${ | ||||
|           display_name | ||||
|             ? "text-xl italic text-slate-600 dark:text-slate-400" | ||||
|             : "text-2xl font-bold" | ||||
|         }`}
 | ||||
|       > | ||||
|         @{username} | ||||
|       </h3> | ||||
|       {/* bio */} | ||||
|       {bio && ( | ||||
|         <ReactMarkdown className="prose dark:prose-invert prose-slate"> | ||||
|           {bio} | ||||
|         </ReactMarkdown> | ||||
|       )} | ||||
|       {/* links */} | ||||
|       {links?.length && ( | ||||
|         <div className="flex flex-col mx-auto lg:ml-auto"> | ||||
|           {links.map((link, index) => ( | ||||
|             <a | ||||
|               key={index} | ||||
|               href={link} | ||||
|               rel="nofollow noopener noreferrer me" | ||||
|               className="hover:underline text-sky-500 dark:text-sky-400" | ||||
|             > | ||||
|               {link} | ||||
|             </a> | ||||
|           ))} | ||||
|         </div> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function LabelList({ source }: { source: Name[] | Pronoun[] }) { | ||||
|   return ( | ||||
|     source?.length > 0 ? ( | ||||
|       <div className="border-b border-slate-200 dark:border-slate-700"> | ||||
|         {source.map((label, index) => ( | ||||
|           <LabelLine key={index} label={label} /> | ||||
|         ))} | ||||
|       </div> | ||||
|     ) : <></> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| 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 === 0) 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 ( | ||||
|     <p className={` | ||||
|       ${status === WordStatus.Favourite ? 'text-lg font-bold' : ''} | ||||
|       ${status === WordStatus.Avoid ? 'text-slate-600 dark:text-slate-400' : ''}`}>
 | ||||
|       <LabelStatusIcon status={status} /> {text} | ||||
|     </p> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function LabelLine({ label }: { label: Name | Pronoun }) { | ||||
|   return <LabelsLine labels={[label] as Name[] | Pronoun[]} />; | ||||
| } | ||||
| 
 | ||||
| function FieldCardGrid({ fields }: { fields: Field[] }) { | ||||
|   return ( | ||||
|     <div className="grid grid-cols-1 md:grid-cols-3 gap-4 py-2"> | ||||
|       {fields?.map((field, index) => ( | ||||
|         <FieldCard field={field} key={index} /> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| 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 ( | ||||
|     <Card title={field.name} draggable={draggable}> | ||||
|       {Object.entries(fieldEntryStatus).map(([statusName, status], i) => | ||||
|         <LabelsLine key={i} labels={((field as any)[statusName])?.map((name: string) => ({ name, status }))} /> | ||||
|       )} | ||||
|     </Card> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue