diff --git a/packages/fakeyou.com/src/components/common/Tabs/Tabs.tsx b/packages/fakeyou.com/src/components/common/Tabs/Tabs.tsx new file mode 100644 index 0000000000..c205a96d76 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/Tabs/Tabs.tsx @@ -0,0 +1,62 @@ +import React, { useState } from "react"; +import { NavLink } from "react-router-dom"; + +interface TabProps { + to: string; + label: string; + content?: React.ReactNode; +} + +interface TabsProps { + tabs: TabProps[]; +} + +interface TabContentProps { + children: React.ReactNode; +} + +function Tab({ to, label }: TabProps) { + return ( +
  • + + {label} + +
  • + ); +} + +function TabContent({ children }: TabContentProps) { + return
    {children}
    ; +} + +function Tabs({ tabs }: TabsProps) { + const [activeTab, setActiveTab] = useState(tabs[0].to); + + return ( + + ); +} + +export default Tabs; diff --git a/packages/fakeyou.com/src/components/common/Tabs/index.ts b/packages/fakeyou.com/src/components/common/Tabs/index.ts new file mode 100644 index 0000000000..9a31da7f16 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/Tabs/index.ts @@ -0,0 +1 @@ +export { default } from "./Tabs"; diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index edd1671dba..0ec563b011 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -8,7 +8,6 @@ import { ModerationIpBanListFc } from "./pages/moderation/moderation_ip_ban_list import { ModerationViewIpBanFc } from "./pages/moderation/moderation_view_ip_ban/ModerationViewIpBanFc"; import { LipsyncEditor } from "./pages/lipsync"; import { ProfileEditFc } from "./pages/profile/profile_edit/ProfileEditFc"; -import { ProfilePage } from "./pages/profile/profile_view/ProfilePage"; import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; import { SignupPage } from "./pages/signup/SignupPage"; @@ -96,6 +95,7 @@ import { VoiceDesignerVoiceEditPage } from "./pages/voice_designer/VoiceDesigner import VoiceDesignerUseVoicePage from "./pages/voice_designer/VoiceDesignerUseVoicePage"; import { PasswordResetEmailPage } from "./pages/password_reset/PasswordResetEmailPage"; import { PasswordResetVerificationPage } from "./pages/password_reset/PasswordResetVerificationPage"; +import { NewProfilePage } from "./pages/profile/profile_view/NewProfilePage"; interface Props { sessionWrapper: SessionWrapper; @@ -297,7 +297,7 @@ class PageContainer extends React.Component< - {/* Route for initial voice creation */} - - + + {/* Route for editing the dataset details */} - - + + {/* Route for handling dataset token for uploading samples */} - - + + @@ -695,14 +692,16 @@ class PageContainer extends React.Component< - + diff --git a/packages/fakeyou.com/src/v2/view/pages/profile/profile_view/NewProfilePage.tsx b/packages/fakeyou.com/src/v2/view/pages/profile/profile_view/NewProfilePage.tsx new file mode 100644 index 0000000000..99f87cd8f3 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/profile/profile_view/NewProfilePage.tsx @@ -0,0 +1,417 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import React, { useCallback, useEffect, useState } from "react"; +import { Gravatar } from "@storyteller/components/src/elements/Gravatar"; +import { Link, NavLink, Redirect, useLocation } from "react-router-dom"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; +import { useParams } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faDiscord, + faGithub, + faTwitch, + faTwitter, +} from "@fortawesome/free-brands-svg-icons"; +import { + faBan, + faGear, + faGlobe, + faDollarSign, + faStar, +} from "@fortawesome/free-solid-svg-icons"; + +import { + GetUserByUsername, + GetUserByUsernameIsErr, + GetUserByUsernameIsOk, + User, + UserLookupError, +} from "@storyteller/components/src/api/user/GetUserByUsername"; +import { format } from "date-fns"; +import { WebUrl } from "../../../../../common/WebUrl"; + +import Tippy from "@tippyjs/react"; +import "tippy.js/dist/tippy.css"; + +import { usePrefixedDocumentTitle } from "../../../../../common/UsePrefixedDocumentTitle"; +import { faCalendarAlt } from "@fortawesome/pro-solid-svg-icons"; +// import { CommentComponent } from "../../../_common/comments/CommentComponent"; +import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient"; +import { Panel } from "components/common"; +import Container from "components/common/Container"; +import Tabs from "components/common/Tabs"; + +interface Props { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +function NewProfilePage(this: any, props: Props) { + const { username }: { username: string } = useParams(); + const { pathname } = useLocation(); + PosthogClient.recordPageview(); + + const [userData, setUserData] = useState(undefined); + const [notFoundState, setNotFoundState] = useState(false); + + const getUser = useCallback(async (username) => { + const response = await GetUserByUsername(username); + if (GetUserByUsernameIsOk(response)) { + setUserData(response); + } else if (GetUserByUsernameIsErr(response)) { + switch (response) { + case UserLookupError.NotFound: + setNotFoundState(true); + break; + } + } + }, []); + + useEffect(() => { + getUser(username); + }, [username, getUser]); + + const documentTitle = + userData?.display_name === undefined + ? undefined + : `${userData.display_name}`; + usePrefixedDocumentTitle(documentTitle); + + if ( + pathname === `/profile/${username}` || + pathname === `/profile/${username}/` + ) { + return ; + } + + if (notFoundState) { + return ( +
    +
    +

    User not found

    +
    + + Back to main + +
    +
    +
    + ); + } + + if (!userData) { + return
    ; + } + + let userEmailHash = userData.email_gravatar_hash; + + 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 = ( + <> + + + Upgrade + + + ); + } + } + } + + if (props.sessionWrapper.canBanUsers()) { + const currentlyBanned = userData.maybe_moderator_fields?.is_banned; + const banLinkUrl = WebUrl.userProfileBanPage(userData.username); + const buttonLabel = currentlyBanned ? "Unban" : "Ban"; + const banButtonCss = currentlyBanned + ? "btn btn-secondary" + : "btn btn-destructive"; + + banUserButton = ( + <> + + + {buttonLabel} + + + ); + } + + let profileButtonsMobile = ; + + if (props.sessionWrapper.canEditUserProfile(userData.username)) { + const editLinkUrl = WebUrl.userProfileEditPage(userData.username); + + // Mods shouldn't edit preferences. + const buttonLabel = props.sessionWrapper.userTokenMatches( + userData.user_token + ) + ? "Edit Profile & Preferences" + : "Edit Profile"; + + editProfileButton = ( + <> + + + {buttonLabel} + + + ); + + profileButtonsMobile = ( +
    +
    + {upgradeButton} + {editProfileButton} + {banUserButton} +
    +
    + ); + } + + let profileDesc = ( +
    + No profile description. +
    + ); + + if (!!userData.profile_rendered_html) { + profileDesc = ( +
    + ); + } + + let profileRows: Array = []; + let profileJoinDate: Array = []; + + const createdAt = new Date(userData.created_at); + const joinDate = format(createdAt, "LLLL y"); + profileJoinDate.push( +
    + +

    Joined {joinDate}

    +
    + ); + + if (userData.website_url !== undefined && userData.website_url !== null) { + let websiteUrl = {userData.website_url}; + if ( + userData.website_url?.startsWith("http://") || + userData.website_url?.startsWith("https://") + ) { + websiteUrl = ( + + + + + + ); + } + + profileRows.push(
    {websiteUrl}
    ); + } + + if (userData.twitch_username) { + let twitchUrl = `https://twitch.com/${userData.twitch_username}`; + let twitchLink = ( + + + + + + ); + + profileRows.push(
    {twitchLink}
    ); + } + + if (userData.twitter_username) { + let twitterUrl = `https://twitter.com/${userData.twitter_username}`; + let twitterLink = ( + + + + + + ); + profileRows.push(
    {twitterLink}
    ); + } + + if (userData.discord_username) { + profileRows.push( + + Discord +
    +

    {userData.discord_username}

    +
    + } + 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}
    ); + } + + const tabs = [ + { + to: `/profile/${username}/media`, + label: "Media", + content:
    Media
    , + }, + { + to: `/profile/${username}/weights`, + label: "Weights", + content:
    Weights
    , + }, + { + to: `/profile/${username}/favorites`, + label: "Favorites", + content:
    Favorites
    , + }, + ]; + + return ( + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +

    + {userData.display_name} +

    +
    + {banUserButton} + {upgradeButton} + {editProfileButton} +
    +
    +
    {profileJoinDate}
    +
    {profileDesc}
    +
    + {profileRows} +
    +
    +
    + + {profileButtonsMobile} + + + + +
    + ); +} + +export { NewProfilePage };