Skip to content

Commit

Permalink
refactor: user bio (#28)
Browse files Browse the repository at this point in the history
* feat: Truncate text util

* feat: Make buttons gray when disabled.

* feat: Ability to disable buttons

* refactor: Use state to not have to reload page when updating bio

* fix: Forgot to ellipsis

* fix: Catch undefined.

* chore: use provided length

* chore: use toaster

Co-authored-by: Nico <[email protected]>
  • Loading branch information
Uhuh and zaida04 authored May 31, 2022
1 parent ffec629 commit 4d8de51
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 47 deletions.
4 changes: 3 additions & 1 deletion components/button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export default function Button(props: any) {
const colorName: string = props.color ?? "gilded";
let className = "px-4 py-1 rounded text-lg ";

if (props.bold === "true") className += "font-bold ";

switch (colorName) {
case "gilded":
className += "bg-guilded-gilded text-guilded-black";
Expand All @@ -15,5 +17,5 @@ export default function Button(props: any) {
default:
break;
}
return <button className={className} {...props} />;
return <button disabled={props.disabled ?? false} className={className} {...props} />;
}
3 changes: 2 additions & 1 deletion components/profile/card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Image from "next/image";
import Link from "next/link";
import { GuildedUser } from "../../types/user";
import { TruncateText } from "../../utility/utils";
import { UserFlairs } from "./flairs";

export const Card = (props: { user: GuildedUser; bio: string | null }) => {
Expand All @@ -21,7 +22,7 @@ export const Card = (props: { user: GuildedUser; bio: string | null }) => {
<div className="flex shadow-inner">
{props.bio ? (
<p className="w-full max-h-20 text-guilded-white text-left whitespace-pre-wrap overflow-wrap-anywhere linear-gradient">
{props.bio.length > 150 ? props.bio.slice(0, 150) + "..." : props.bio}
{TruncateText(props.bio)}
</p>
) : (
<p className="italic text-guilded-subtitle">No content yet, but we&apos;re sure they&apos;re an amazing person!</p>
Expand Down
91 changes: 48 additions & 43 deletions pages/users/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import prisma from "../../lib/prisma";
import { GuildedUser, BadgeName, badgeMap } from "../../types/user";
import { MouseEventHandler, useState } from "react";
import Button from "../../components/button";
import { DeNullishFilter } from "../../utility/utils";
import { DeNullishFilter, TruncateText } from "../../utility/utils";
import { UserFlairs } from "../../components/profile/flairs";
import { useRouter } from "next/router";
import Link from "next/link";
import { toast } from "react-toastify";

Expand Down Expand Up @@ -44,23 +43,32 @@ function ToolbarButton(props: { icon: string; onClick: MouseEventHandler }) {
const UserPage: NextPage<Props> = ({ user, bio }) => {
const { data: session } = useSession();
const [isInEditingMode, setIsInEditingMode] = useState(false);
const [bioContent, setBioContent] = useState(bio?.content);
const [userBio, setUserBio] = useState(bio);

/**
* @var bioContent - Users currently saved bio
* @var newBioContent - Potentially new bio, which is just an edited current bio. Set equal to bioContent for initial save button disable.
*/
const [bioContent, setBioContent] = useState(userBio?.content);
const [newBioContent, setNewBioContent] = useState(bioContent);
const router = useRouter();

const handleSubmit = async (event: any) => {
// Stop the form from submitting and refreshing the page.
event.preventDefault();

if (!event.target) return;

// If the bio hasn't changed ignore.
if (newBioContent === bioContent) return;

// Send the form data to our forms API on Vercel and get a response.
const response = await fetch(bio ? `/api/users/${user.id}/bios/${bio.id}` : `/api/users/${user.id}/bios`, {
method: bio ? "PUT" : "POST",
const response = await fetch(userBio ? `/api/users/${user.id}/bios/${userBio.id}` : `/api/users/${user.id}/bios`, {
method: userBio ? "PUT" : "POST",
headers: {
"Content-Type": "application/json",
},
// author: user.id is a placeholder for now until i get auth on the API settled.
body: JSON.stringify(bio ? { content: newBioContent } : { content: newBioContent, default: true, author: user.id }),
body: JSON.stringify(userBio ? { content: newBioContent } : { content: newBioContent, default: true, author: user.id }),
});

if (!response.ok) {
Expand All @@ -71,15 +79,37 @@ const UserPage: NextPage<Props> = ({ user, bio }) => {
}))) as { error: { message: string } };
return toast.error(data.error.message);
}

const data = (await response.json()) as { bio: Bio };
if (!bio) router.reload();
else {
setIsInEditingMode(false);
setBioContent(data.bio.content);
}

setIsInEditingMode(false);

// User bio
setUserBio(data.bio);
setBioContent(data.bio.content);
setNewBioContent(data.bio.content);

return true;
};

const handleDelete = async () => {
const confirmed = confirm("Are you sure you want to delete this bio? This cannot be undone!");
if (!confirmed || !userBio) return;

const response = await fetch(`/api/users/${user.id}/bios/${userBio.id}`, {
method: "DELETE",
});

if (!response.ok) {
const data = await response.json();
return toast.error(data.error.message);
}

setUserBio(null);
setBioContent("");
setNewBioContent("");
};

if (!user) {
return (
<>
Expand Down Expand Up @@ -109,13 +139,7 @@ const UserPage: NextPage<Props> = ({ user, bio }) => {
<meta property="og:image" content={user.profilePictureLg} />
<meta
property="og:description"
content={`${
bio?.content
? bio.content.length > 125
? bio.content.slice(0, 125) + "..."
: bio.content
: "No bio yet, but we're sure they're an amazing person!"
}`}
content={`${bioContent?.length ? TruncateText(bioContent, 125) : "No bio yet, but we're sure they're an amazing person!"}`}
/>
<meta name="theme-color" content="#F5C400" />
</Head>
Expand Down Expand Up @@ -150,7 +174,7 @@ const UserPage: NextPage<Props> = ({ user, bio }) => {
<div className="text-white flex flex-wrap">
<textarea
id="newBioContent"
defaultValue={bio?.content ? bioContent : ""}
defaultValue={bioContent?.length ? bioContent : ""}
maxLength={250}
onChange={(data) => setNewBioContent(data.target.value)}
className="w-full px-3 pt-3 pb-40 rounded-lg bg-guilded-gray resize-none"
Expand All @@ -163,8 +187,8 @@ const UserPage: NextPage<Props> = ({ user, bio }) => {
{newBioContent == null ? 0 : newBioContent.length}/250
</p>
</div>
<div className="pt-4">
<Button>Save</Button>
<div className="pt-2">
<Button disabled={newBioContent === bioContent}>Save</Button>
<button
form=""
className="ml-3 font-bold text-guilded-subtitle hover:text-guilded-white transition-colors"
Expand All @@ -180,7 +204,7 @@ const UserPage: NextPage<Props> = ({ user, bio }) => {
) : (
<div className="flex">
<div className="flex w-full max-h-48 overflow-y-auto overflow-x-hidden">
{bio?.content ? (
{bioContent?.length ? (
<p className="text-clip whitespace-pre-wrap overflow-wrap-anywhere">{bioContent}</p>
) : (
<p className="italic text-guilded-subtitle break-all">
Expand All @@ -196,26 +220,7 @@ const UserPage: NextPage<Props> = ({ user, bio }) => {
setIsInEditingMode(true);
}}
/>
{bio && (
<ToolbarButton
icon="trash_full"
onClick={async () => {
const confirmed = confirm("Are you sure you want to delete this bio? This cannot be undone!");
if (!confirmed) return;

const response = await fetch(`/api/users/${user.id}/bios/${bio.id}`, {
method: "DELETE",
});

if (!response.ok) {
const data = await response.json();
return alert(`Error: ${data.error.message}`);
}
// Not ideal
router.reload();
}}
/>
)}
{userBio && !!bioContent?.length && <ToolbarButton icon="trash_full" onClick={handleDelete} />}
</div>
)}
</div>
Expand Down
7 changes: 6 additions & 1 deletion styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ body::-webkit-scrollbar {
}

.overflow-wrap-anywhere {
overflow-wrap: anywhere;
overflow-wrap: anywhere;
}

button:disabled {
background-color: gray;
cursor: not-allowed;
}

/* width */
Expand Down
3 changes: 2 additions & 1 deletion utility/utils.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const DeNullishFilter = <T>(b: T): b is Exclude<T, null|undefined|0|''> => !!b;
export const DeNullishFilter = <T>(b: T): b is Exclude<T, null|undefined|0|''> => !!b;
export const TruncateText = (content: string, length = 150) => content.length > length ? content.slice(0, length) + '...' : content;

1 comment on commit 4d8de51

@vercel
Copy link

@vercel vercel bot commented on 4d8de51 May 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.