From 254c9db9a66dac7b32bb147beb0930ea86ff18da Mon Sep 17 00:00:00 2001 From: BFlat Date: Tue, 21 May 2024 01:57:29 +0700 Subject: [PATCH] new profile page and sidebar removal (#566) * top and side nav changes * profile page v3 and sidebar removal * updates * padding fixes * side panel size increase --- .../src/components/common/Button/Button.scss | 17 + .../src/components/common/Button/Button.tsx | 11 +- .../src/components/common/Card/Card.scss | 1 + .../common/Card/VideoCard/VideoCard.tsx | 9 +- .../components/common/Container/Container.tsx | 8 +- .../src/components/common/NavItem/NavItem.tsx | 9 + .../src/components/common/Tabs/Tabs.scss | 8 +- .../ProfileSidePanel/ProfileSidePanel.scss | 307 ++++++++++++++ .../ProfileSidePanel/ProfileSidePanel.tsx | 88 ++++ .../ProfileSidePanel/UserProfileInfo.tsx | 375 ++++++++++++++++++ .../src/components/layout/SideNav/SideNav.tsx | 13 +- .../src/components/layout/TopNav/TopNav.tsx | 305 +++++++++++--- packages/fakeyou.com/src/scss/colours.scss | 1 + packages/fakeyou.com/src/scss/nav.scss | 2 +- .../fakeyou.com/src/scss/site-refresh.scss | 2 +- .../fakeyou.com/src/v2/view/PageContainer.tsx | 27 +- .../src/v2/view/pages/media/MediaPage.tsx | 53 +-- .../profile/profile_view/ProfilePageV3.tsx | 171 ++++++++ .../profile/profile_view/tabs/AudiosTab.tsx | 117 ++++++ .../profile/profile_view/tabs/VideosTab.tsx | 122 ++++++ 20 files changed, 1545 insertions(+), 101 deletions(-) create mode 100644 packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.scss create mode 100644 packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.tsx create mode 100644 packages/fakeyou.com/src/components/layout/ProfileSidePanel/UserProfileInfo.tsx create mode 100644 packages/fakeyou.com/src/v2/view/pages/profile/profile_view/ProfilePageV3.tsx create mode 100644 packages/fakeyou.com/src/v2/view/pages/profile/profile_view/tabs/AudiosTab.tsx create mode 100644 packages/fakeyou.com/src/v2/view/pages/profile/profile_view/tabs/VideosTab.tsx diff --git a/packages/fakeyou.com/src/components/common/Button/Button.scss b/packages/fakeyou.com/src/components/common/Button/Button.scss index fe4a74b99d..79279a90b2 100644 --- a/packages/fakeyou.com/src/components/common/Button/Button.scss +++ b/packages/fakeyou.com/src/components/common/Button/Button.scss @@ -65,6 +65,23 @@ border-color: lighten($secondary, 5%); } +.button-action, +.button-action:focus, +.button-action:active { + background-color: $action; + color: $white !important; + border: 2.5px solid; + border-color: $action; + transition: 0.2s; +} + +.button-action:hover { + background-color: lighten($action, 5%); + color: $white !important; + border: 2.5px solid; + border-color: lighten($action, 5%); +} + .button-danger, .button-danger:focus, .button-danger:active { diff --git a/packages/fakeyou.com/src/components/common/Button/Button.tsx b/packages/fakeyou.com/src/components/common/Button/Button.tsx index 04c0adc82a..9829853bf0 100644 --- a/packages/fakeyou.com/src/components/common/Button/Button.tsx +++ b/packages/fakeyou.com/src/components/common/Button/Button.tsx @@ -28,8 +28,15 @@ export default function Button({ }: ButtonProps) { let variant = variantProps; if (isActive !== undefined) { - if (isActive) variant = "primary"; - else variant = "secondary"; + if (isActive) { + variant = "primary"; + } else variant = "secondary"; + } + if (variant === "action") { + variant = "action"; + } + if (variant === "danger") { + variant = "danger"; } let iconMarginClass = !square && label ? (iconFlip ? "ms-2" : "me-2") : ""; let IconComponent = icon ? ( diff --git a/packages/fakeyou.com/src/components/common/Card/Card.scss b/packages/fakeyou.com/src/components/common/Card/Card.scss index 01794519c4..987df2ffa2 100644 --- a/packages/fakeyou.com/src/components/common/Card/Card.scss +++ b/packages/fakeyou.com/src/components/common/Card/Card.scss @@ -48,6 +48,7 @@ transition: 0.4s ease; object-fit: cover; object-position: center; + aspect-ratio: 16/9; } .card-img-overlay { diff --git a/packages/fakeyou.com/src/components/common/Card/VideoCard/VideoCard.tsx b/packages/fakeyou.com/src/components/common/Card/VideoCard/VideoCard.tsx index 2029efab18..30022720c6 100644 --- a/packages/fakeyou.com/src/components/common/Card/VideoCard/VideoCard.tsx +++ b/packages/fakeyou.com/src/components/common/Card/VideoCard/VideoCard.tsx @@ -1,11 +1,9 @@ import React, { useEffect, useState } from "react"; import Card from "../Card"; import useTimeAgo from "hooks/useTimeAgo"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowRight, faPlayCircle } from "@fortawesome/pro-solid-svg-icons"; +import { faArrowRight } from "@fortawesome/pro-solid-svg-icons"; import Badge from "components/common/Badge"; import Button from "components/common/Button"; -import { CardFooter } from "components/entities"; import { BucketConfig } from "@storyteller/components/src/api/BucketConfig"; import { Link } from "react-router-dom"; import getCardUrl from "../getCardUrl"; @@ -164,14 +162,13 @@ export default function VideoCard({ /> )} -
{data.weight_name}

{timeAgo}

- + /> */}
diff --git a/packages/fakeyou.com/src/components/common/Container/Container.tsx b/packages/fakeyou.com/src/components/common/Container/Container.tsx index b7da490352..9cb8656e0d 100644 --- a/packages/fakeyou.com/src/components/common/Container/Container.tsx +++ b/packages/fakeyou.com/src/components/common/Container/Container.tsx @@ -4,12 +4,14 @@ interface ContainerProps { children: React.ReactNode; type?: "full" | "padded" | "panel" | "panel-full"; className?: string; + style?: React.CSSProperties; } export default function PageContainer({ children, type = "full", className = "", + style, }: ContainerProps) { let containerClass = ""; @@ -34,5 +36,9 @@ export default function PageContainer({ // Merge the classNames const mergedClassNames = `${containerClass} ${className}`.trim(); - return
{children}
; + return ( +
+ {children} +
+ ); } diff --git a/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx b/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx index 3195eedf42..bba821fc95 100644 --- a/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx +++ b/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx @@ -11,6 +11,7 @@ interface NavItemProps { label: string; isHoverable?: boolean; link?: string; + externalLink?: string; className?: string; dropdownItems?: { id: number; @@ -27,6 +28,7 @@ export default function NavItem({ isHoverable, link, className, + externalLink, }: NavItemProps) { const [isHovered, setIsHovered] = useState(false); @@ -55,6 +57,13 @@ export default function NavItem({ {label} )} + {externalLink && ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + + {icon && } + {label} + + )} ); } diff --git a/packages/fakeyou.com/src/components/common/Tabs/Tabs.scss b/packages/fakeyou.com/src/components/common/Tabs/Tabs.scss index cf649e05fb..4fe0c3deca 100644 --- a/packages/fakeyou.com/src/components/common/Tabs/Tabs.scss +++ b/packages/fakeyou.com/src/components/common/Tabs/Tabs.scss @@ -3,10 +3,14 @@ .nav-tabs { border-bottom: 2px solid #ffffff15; flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; + overflow-x: auto !important; + overflow-y: hidden !important; -webkit-overflow-scrolling: touch; + @media screen and (max-width: 768px) { + width: 100vw; + } + .nav-link { border: none; padding: 1rem 1.4rem; diff --git a/packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.scss b/packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.scss new file mode 100644 index 0000000000..5c5804ef0a --- /dev/null +++ b/packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.scss @@ -0,0 +1,307 @@ +@import "scss/colours.scss"; + +/* Toggle Styles */ + +#wrapper { + padding-left: 0; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +$topbar-height: 66px; + +#profile-sidebar-wrapper { + z-index: 999; + position: fixed; + left: 340px; + width: 0; + height: calc(100% - $topbar-height); + margin-left: -340px; + overflow-x: hidden; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; + margin-top: $topbar-height; + display: grid; + grid-template-rows: 1fr auto; + + &::-webkit-scrollbar { + width: 0; + } + + &::-webkit-scrollbar-thumb { + background-color: rgba($secondary, 1); + border-radius: 0.5rem; + } + + &::-webkit-scrollbar-track { + background-color: rgba($panel, 1) !important; + } + + &:hover::-webkit-scrollbar { + width: 10px; + } +} + +.profile-sidebar-panel { + background-color: #39394d; + height: 100%; + border-radius: 0.8rem; +} + +#wrapper.toggled #profile-sidebar-wrapper { + width: 340px; + margin-left: 0; +} + +#page-content-wrapper { + height: 100%; + margin: 0; + display: flex; + flex-direction: column; + width: 100%; + position: relative; + padding-left: 340px; + padding-top: $topbar-height; + transition: all 0.2s ease; + + &.no-page-padding-top { + padding-top: 0; + } +} + +/* Sidebar Styles */ + +.profile-sidebar-nav { + width: 340px; + margin: 0; + padding: 0; + list-style: none; + height: 100%; + background: $panel; + border-color: rgba(255, 255, 255, 0.04) !important; + border-right: 1px solid; + padding-top: 1rem; +} + +.profile-sidebar-nav li a { + display: block; + text-decoration: none; + color: rgba($white, 0.7); + font-weight: 500; + font-size: 14px; + margin: 0rem 1rem; + padding: 0.4rem 1rem; + border-radius: 0.3rem; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.profile-sidebar-nav li a:hover { + text-decoration: none; + color: $white; +} + +.profile-sidebar-nav li a:active, +.profile-sidebar-nav li a:focus { + text-decoration: none; +} + +.profile-sidebar-brand { + font-size: 18px; + padding-top: 1.5rem; + line-height: 0; + margin-left: 1.5rem; +} + +.profile-sidebar-buttons { + margin-left: 1.5rem; +} + +.profile-sidebar-heading { + margin-left: 1.5rem; + margin-bottom: 0.25rem; + margin-top: 0.75rem; + opacity: 0.5; + font-size: 13px; +} + +.profile-sidebar-heading-icon { + margin-right: 0.75rem; +} + +.active-link { + color: $white !important; + background-color: $secondary; +} + +#overlay { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 900; +} +.toggled #page-content-wrapper { + position: relative; + z-index: 2; +} +.toggled #overlay { + display: block; +} + +.no-padding { + padding-left: 0 !important; +} + +.sidebar { + transform: translateX(-100%); + transition: transform 0.3s ease-out; +} + +/* Visible state for the sidebar */ +.sidebar.visible { + transform: translateX(0); +} + +#topbar-wrapper { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: $topbar-height; + background: $panel; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); + z-index: 1000; + transition: 0.15s; + + &.topbar-wrapper-transparent { + background: none; + } +} + +.topbar-nav { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + height: 100%; + padding: 0 1rem; + justify-content: space-between; + align-items: center; + + .topbar-nav-right { + display: flex; + justify-content: flex-end; + } + + .topbar-nav-right { + display: flex; + justify-content: flex-end; + } + + .topbar-nav-center { + grid-column: 2; + } + + .button { + white-space: nowrap; + } +} + +.topbar-mobile-search-bar-container { + position: absolute; + width: 100%; + z-index: 1000; + top: 0; + height: $topbar-height; + background-color: $panel; + + .topbar-mobile-search-bar { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; + padding: 0 1rem; + + .close-search-button { + position: absolute; + top: 0; + right: 0; + margin: 0 1.8rem; + color: $white; + height: 100%; + font-size: 1.5rem; + cursor: pointer; + z-index: 4; + background: none; + border: none; + padding: 0; + } + } +} + +.mobile-fixed-bottom { + position: sticky; + bottom: 0; + padding: 0.75rem 0rem; + width: 100%; + background-color: $panel-inner; + border-top: 1px solid lighten($panel-inner, 5%) !important; + font-size: 11px; + + .profile-sidebar-heading { + margin-top: 0; + font-size: 13px; + } +} + +@media (min-width: 992px) { + #wrapper { + padding-left: 340px; + } + + #wrapper.toggled { + padding-left: 0; + } + + #profile-sidebar-wrapper { + width: 340px; + } + + #wrapper.toggled #profile-sidebar-wrapper { + width: 0; + } + + #page-content-wrapper { + position: relative; + } + + #wrapper.toggled #page-content-wrapper { + position: relative; + margin-right: 0; + } +} + +@media (max-width: 992px) { + #wrapper.toggled #profile-sidebar-wrapper { + width: 100%; + } + + #profile-sidebar-wrapper { + left: 100%; + margin-left: 0; + } + + .profile-sidebar-nav { + width: 100%; + } +} diff --git a/packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.tsx b/packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.tsx new file mode 100644 index 0000000000..a18f86cdad --- /dev/null +++ b/packages/fakeyou.com/src/components/layout/ProfileSidePanel/ProfileSidePanel.tsx @@ -0,0 +1,88 @@ +import React, { useEffect, useState } from "react"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { FakeYouFrontendEnvironment } from "@storyteller/components/src/env/FakeYouFrontendEnvironment"; +import "./ProfileSidePanel.scss"; +import UserProfileInfo from "./UserProfileInfo"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; + +interface ProfileSidePanelProps { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +export default function ProfileSidePanel({ + sessionWrapper, + sessionSubscriptionsWrapper, +}: ProfileSidePanelProps) { + const [windowWidth, setWindowWidth] = useState(window.innerWidth); + const fakeYouFrontendEnv = FakeYouFrontendEnvironment.getInstance(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const isDevelopmentEnv = fakeYouFrontendEnv.isDevelopment(); + const isOnProfilePage = window.location.pathname.includes("/profile/"); + + useEffect(() => { + const handleOutsideClick = (event: MouseEvent) => { + const wrapper = document.getElementById("wrapper"); + const overlay = document.getElementById("overlay"); + + if ( + (!wrapper?.contains(event.target as Node) || + overlay?.contains(event.target as Node)) && + window.innerWidth < 992 + ) { + wrapper?.classList.remove("toggled"); + } + }; + + document.addEventListener("click", handleOutsideClick); + + return () => { + document.removeEventListener("click", handleOutsideClick); + }; + }, []); + + useEffect(() => { + // Update window width on resize + const handleResize = () => { + setWindowWidth(window.innerWidth); + }; + + window.addEventListener("resize", handleResize); + + return () => { + // Cleanup listener on unmount + window.removeEventListener("resize", handleResize); + }; + }, []); + + const shouldNotShowSidebar = !isOnProfilePage; + const shouldShowSidebar = windowWidth >= 992 && !shouldNotShowSidebar; + const sidebarClassName = `sidebar d-none d-lg-block ${ + shouldShowSidebar ? "visible" : "" + }`.trim(); + + useEffect(() => { + const contentWrapper = document.getElementById("page-content-wrapper"); + + if (shouldShowSidebar && isOnProfilePage) { + contentWrapper?.classList.remove("no-padding"); + } else { + contentWrapper?.classList.add("no-padding"); + } + }, [shouldShowSidebar, isOnProfilePage]); + + return ( + <> +
+
+
+ +
+
+
+ + ); +} diff --git a/packages/fakeyou.com/src/components/layout/ProfileSidePanel/UserProfileInfo.tsx b/packages/fakeyou.com/src/components/layout/ProfileSidePanel/UserProfileInfo.tsx new file mode 100644 index 0000000000..6f0314a56c --- /dev/null +++ b/packages/fakeyou.com/src/components/layout/ProfileSidePanel/UserProfileInfo.tsx @@ -0,0 +1,375 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { + GetUserByUsername, + GetUserByUsernameIsErr, + GetUserByUsernameIsOk, + User, + UserLookupError, +} from "@storyteller/components/src/api/user/GetUserByUsername"; +import { useLocation } from "react-router-dom"; +import { Gravatar } from "@storyteller/components/src/elements/Gravatar"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faBan, + faCalendarAlt, + faDollarSign, + faGear, + faGlobe, + faStar, +} from "@fortawesome/pro-solid-svg-icons"; +import { format } from "date-fns"; +import { Button } from "components/common"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; +import { WebUrl } from "common/WebUrl"; +import { + faDiscord, + faGithub, + faTwitch, + faTwitter, +} from "@fortawesome/free-brands-svg-icons"; +import Tippy from "@tippyjs/react"; + +interface UserProfileInfoProps { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +export default function UserProfileInfo(props: UserProfileInfoProps) { + const location = useLocation(); + const pathSegments = location.pathname.split("/"); + const username = pathSegments[2]; + const [userData, setUserData] = useState(undefined); + const [notFoundState, setNotFoundState] = useState(false); + + const getUser = useCallback(async username => { + const response = await GetUserByUsername(username); + console.log("Response received:", response); + if (GetUserByUsernameIsOk(response)) { + setUserData(response); + } else if (GetUserByUsernameIsErr(response)) { + switch (response) { + case UserLookupError.NotFound: + setNotFoundState(true); + break; + } + } + }, []); + + useEffect(() => { + getUser(username); + }, [username, getUser]); + + if (notFoundState) { + return

User not found

; + } + + if (!userData) { + return null; + } + + let profileJoinDate: Array = []; + + const createdAt = new Date(userData.created_at); + const joinDate = format(createdAt, "LLLL y"); + profileJoinDate.push( +
+ +

Joined {joinDate}

+
+ ); + + let profileDesc = ( +
No profile description.
+ ); + + if (!!userData.profile_rendered_html) { + profileDesc = ( +
+ ); + } + + let editProfileButton = undefined; + + let banUserButton = undefined; + + let upgradeButton = undefined; + + if (props.sessionWrapper.isLoggedIn()) { + if (props.sessionWrapper.userTokenMatches(userData.user_token)) { + if (!props.sessionSubscriptionsWrapper.hasPaidFeatures()) { + const upgradeLinkUrl = WebUrl.pricingPage(); + + upgradeButton = ( + <> +
+ } + interactive + hideOnClick + placement="bottom" + theme="fakeyou" + arrow={false} + > + + + ); + } + + if (userData.github_username) { + let githubUrl = `https://github.com/${userData.github_username}`; + let githubLink = ( + + + + + + ); + profileRows.push(
{githubLink}
); + } + + if (userData.cashapp_username) { + // NB: URL includes a dollar sign + let cashAppUrl = `https://cash.me/$${userData.cashapp_username}`; + let cashAppLink = ( + + + + + + ); + profileRows.push(
{cashAppLink}
); + } + + return ( + <> + {/* DESKTOP */} +
+ +

+ {userData.display_name || "Profile Name"} +

+
{profileJoinDate}
+
{profileDesc}
+ + <> +
+
+ {profileRows} +
+ {props.sessionWrapper.isLoggedIn() && ( +
+ {editProfileButton} + {banUserButton} + {upgradeButton} +
+ )} + +
+ {/* MOBILE */} +
+
+ +
+

+ {userData.display_name || "Profile Name"} +

+
{profileJoinDate}
+
+
+ + {/*
{profileDesc}
*/} + +
+ {profileRows} +
+ + {props.sessionWrapper.isLoggedIn() && ( +
+ {editProfileButton} + {banUserButton} + {upgradeButton} +
+ )} +
+ + ); +} diff --git a/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx b/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx index e4fbb727b9..93d54ebd3a 100644 --- a/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx +++ b/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx @@ -62,6 +62,7 @@ export default function SideNav({ const isOnLoginPage = window.location.pathname.includes("/login"); const isOnSignUpPage = window.location.pathname.includes("/signup"); const isOnStudioPage = window.location.pathname.includes("/studio"); + const isOnProfilePage = window.location.pathname.includes("/profile/"); const isOnBetaKeyRedeemPage = window.location.pathname.includes("/beta-key/redeem"); const domain: WebsiteConfig = useDomainConfig(); @@ -115,9 +116,13 @@ export default function SideNav({ }, []); const shouldNotShowSidebar = - (!isLoggedIn && (isOnLandingPage || isOnLoginPage || isOnSignUpPage)) || + (!isLoggedIn && + (isOnLandingPage || + isOnLoginPage || + isOnSignUpPage || + isOnProfilePage)) || isOnStudioPage || - (domain.website === Website.StorytellerAi && isOnLandingPage) || + isOnProfilePage || isOnBetaKeyRedeemPage; const shouldShowSidebar = windowWidth >= 992 && !shouldNotShowSidebar; const sidebarClassName = `sidebar ${ @@ -337,6 +342,10 @@ export default function SideNav({ ); + if (domain.website === Website.StorytellerAi) { + return null; + } + return ( <>