diff --git a/packages/components/src/elements/Gravatar.tsx b/packages/components/src/elements/Gravatar.tsx index a3e303ea23..009ddb0c5a 100644 --- a/packages/components/src/elements/Gravatar.tsx +++ b/packages/components/src/elements/Gravatar.tsx @@ -7,6 +7,7 @@ interface Props { username?: string; avatarIndex?: number; backgroundIndex?: number; + onClick?: () => void; } function Gravatar(props: Props) { @@ -35,6 +36,7 @@ function Gravatar(props: Props) { height={props.size} width={props.size} style={{ backgroundColor: getBackgroundColor(props.backgroundIndex) }} + onClick={props.onClick} /> ); } diff --git a/packages/components/src/session/SessionWrapper.ts b/packages/components/src/session/SessionWrapper.ts index 33b8c5d2e6..4df52de6d4 100644 --- a/packages/components/src/session/SessionWrapper.ts +++ b/packages/components/src/session/SessionWrapper.ts @@ -1,17 +1,21 @@ -import { GetSessionInfo, GetSessionInfoIsOk, SessionInfoSuccessResponse } from '../api/session/GetSessionInfo' -import { StudioRolloutHostnameAllowed } from '../utils/StudioRolloutHostnameAllowed'; +import { + GetSessionInfo, + GetSessionInfoIsOk, + SessionInfoSuccessResponse, +} from "../api/session/GetSessionInfo"; +import { StudioRolloutHostnameAllowed } from "../utils/StudioRolloutHostnameAllowed"; -// A lot of the APIs return null or leave values absent. +// A lot of the APIs return null or leave values absent. // I need to pick a single strategy and stick with it. type MaybeString = string | null | undefined; /** * SessionWrapper can wrap each of the following states: - * + * * 1) logged-in session * 2) non-existing session (logged out) * 3) lookup errors, server errors, 401, etc. (logged out) - * + * * It then makes frontend permission checking simple. */ export class SessionWrapper { @@ -24,7 +28,7 @@ export class SessionWrapper { } // Perform lookup and return a wrapper of a valid, inval - public static async lookupSession() : Promise { + public static async lookupSession(): Promise { let response = await GetSessionInfo(); if (GetSessionInfoIsOk(response)) { return SessionWrapper.wrapResponse(response); @@ -33,165 +37,198 @@ export class SessionWrapper { } } - public static emptySession() : SessionWrapper { + public static emptySession(): SessionWrapper { return new SessionWrapper(); } - public static wrapResponse(sessionStateResponse: SessionInfoSuccessResponse) : SessionWrapper { + public static wrapResponse( + sessionStateResponse: SessionInfoSuccessResponse + ): SessionWrapper { return new SessionWrapper(sessionStateResponse); } - public isLoggedIn() : boolean { + public isLoggedIn(): boolean { return this.sessionStateResponse?.logged_in || false; } // Feature flag rollout for Storyteller Studio // This depends on the domain name and the backend. - public canAccessStudio() : boolean { + public canAccessStudio(): boolean { const hostnameAllowed = StudioRolloutHostnameAllowed(); - const userAllowed = this.sessionStateResponse?.user?.can_access_studio || false; + const userAllowed = + this.sessionStateResponse?.user?.can_access_studio || false; return hostnameAllowed && userAllowed; } // Username is all lowercase - public getUsername() : string | undefined { + public getUsername(): string | undefined { return this.sessionStateResponse?.user?.username; } // Display name has capitalization - public getDisplayName() : string | undefined { + public getDisplayName(): string | undefined { return this.sessionStateResponse?.user?.display_name; } - public getEmailGravatarHash() : string | undefined { + public getEmailGravatarHash(): string | undefined { return this.sessionStateResponse?.user?.email_gravatar_hash; } - public canEditUserProfile(username: string) : boolean { + public canEditUserProfile(username: string): boolean { if (this.getUsername() === username) { return true; } return this.canEditOtherUsersProfiles(); } - public canEditMediaFileByUserToken(userToken: MaybeString) : boolean { - return this.canEditOtherUsersTtsModels() || this.userTokenMatches(userToken); + public canEditMediaFileByUserToken(userToken: MaybeString): boolean { + return ( + this.canEditOtherUsersTtsModels() || this.userTokenMatches(userToken) + ); } - public canEditWeightByUserToken(userToken: MaybeString) : boolean { - return this.canEditOtherUsersTtsModels() || this.userTokenMatches(userToken); + public canEditWeightByUserToken(userToken: MaybeString): boolean { + return ( + this.canEditOtherUsersTtsModels() || this.userTokenMatches(userToken) + ); } - public canEditTtsModelByUserToken(userToken: MaybeString) : boolean { - return this.canEditOtherUsersTtsModels() || this.userTokenMatches(userToken); + public canEditTtsModelByUserToken(userToken: MaybeString): boolean { + return ( + this.canEditOtherUsersTtsModels() || this.userTokenMatches(userToken) + ); } - public canDeleteTtsModelByUserToken(userToken: MaybeString) : boolean { - return this.canDeleteOtherUsersTtsModels() || this.userTokenMatches(userToken); + public canDeleteTtsModelByUserToken(userToken: MaybeString): boolean { + return ( + this.canDeleteOtherUsersTtsModels() || this.userTokenMatches(userToken) + ); } - public canDeleteTtsResultByUserToken(userToken: MaybeString) : boolean { - return this.canDeleteOtherUsersTtsResults() || this.userTokenMatches(userToken); + public canDeleteTtsResultByUserToken(userToken: MaybeString): boolean { + return ( + this.canDeleteOtherUsersTtsResults() || this.userTokenMatches(userToken) + ); } - public canDeleteW2lTemplateByUserToken(userToken: MaybeString) : boolean { - return this.canDeleteOtherUsersW2lTemplates() || this.userTokenMatches(userToken); + public canDeleteW2lTemplateByUserToken(userToken: MaybeString): boolean { + return ( + this.canDeleteOtherUsersW2lTemplates() || this.userTokenMatches(userToken) + ); } - public canDeleteW2lResultByUserToken(userToken: MaybeString) : boolean { - return this.canDeleteOtherUsersW2lResults() || this.userTokenMatches(userToken); + public canDeleteW2lResultByUserToken(userToken: MaybeString): boolean { + return ( + this.canDeleteOtherUsersW2lResults() || this.userTokenMatches(userToken) + ); } - public canUseTts() : boolean { + public canUseTts(): boolean { return this.sessionStateResponse?.user?.can_use_tts || true; // NB: Default true. } - public canUseW2l() : boolean { + public canUseW2l(): boolean { return this.sessionStateResponse?.user?.can_use_w2l || true; // NB: Default true } - public canUploadTtsModels() : boolean { + public canUploadTtsModels(): boolean { return this.sessionStateResponse?.user?.can_upload_tts_models || false; } - public canUploadW2lTemplates() : boolean { + public canUploadW2lTemplates(): boolean { return this.sessionStateResponse?.user?.can_upload_w2l_templates || false; } - public canBanUsers() : boolean { + public canBanUsers(): boolean { return this.sessionStateResponse?.user?.can_ban_users || false; } - public canEditCategories() : boolean { + public canEditCategories(): boolean { // TODO / NB: There aren't fine-grained controls over categories. return this.canBanUsers(); } - public canApproveW2lTemplates() : boolean { + public canApproveW2lTemplates(): boolean { return this.sessionStateResponse?.user?.can_approve_w2l_templates || false; } - public canEditOtherUsersProfiles() : boolean { - return this.sessionStateResponse?.user?.can_edit_other_users_profiles || false; + public canEditOtherUsersProfiles(): boolean { + return ( + this.sessionStateResponse?.user?.can_edit_other_users_profiles || false + ); } - public canEditOtherUsersTtsModels() : boolean { - return this.sessionStateResponse?.user?.can_edit_other_users_tts_models || false; + public canEditOtherUsersTtsModels(): boolean { + return ( + this.sessionStateResponse?.user?.can_edit_other_users_tts_models || false + ); } - public canDeleteOtherUsersTtsModels() : boolean { - return this.sessionStateResponse?.user?.can_delete_other_users_tts_models || false; + public canDeleteOtherUsersTtsModels(): boolean { + return ( + this.sessionStateResponse?.user?.can_delete_other_users_tts_models || + false + ); } - public canDeleteOtherUsersTtsResults() : boolean { - return this.sessionStateResponse?.user?.can_delete_other_users_tts_results || false; + public canDeleteOtherUsersTtsResults(): boolean { + return ( + this.sessionStateResponse?.user?.can_delete_other_users_tts_results || + false + ); } - public canDeleteOtherUsersW2lTemplates() : boolean { - return this.sessionStateResponse?.user?.can_delete_other_users_w2l_templates || false; + public canDeleteOtherUsersW2lTemplates(): boolean { + return ( + this.sessionStateResponse?.user?.can_delete_other_users_w2l_templates || + false + ); } - public canDeleteOtherUsersW2lResults() : boolean { - return this.sessionStateResponse?.user?.can_delete_other_users_w2l_results || false; + public canDeleteOtherUsersW2lResults(): boolean { + return ( + this.sessionStateResponse?.user?.can_delete_other_users_w2l_results || + false + ); } - - public canEditTtsResultAsUserOrMod(creatorUserToken: MaybeString) : boolean { + + public canEditTtsResultAsUserOrMod(creatorUserToken: MaybeString): boolean { if (this.userTokenMatches(creatorUserToken)) { return true; } return this.canDeleteOtherUsersTtsResults(); // TODO: There is no granular permission for this yet. } - public deleteTtsResultAsMod(creatorUserToken: MaybeString) : boolean { + public deleteTtsResultAsMod(creatorUserToken: MaybeString): boolean { if (this.userTokenMatches(creatorUserToken)) { return true; } return this.canDeleteOtherUsersTtsResults(); } - public deleteTtsModelAsMod(creatorUserToken: MaybeString) : boolean { + public deleteTtsModelAsMod(creatorUserToken: MaybeString): boolean { if (this.userTokenMatches(creatorUserToken)) { return true; } return this.canDeleteOtherUsersTtsModels(); } - public canEditW2lResultAsUserOrMod(creatorUserToken: MaybeString) : boolean { + public canEditW2lResultAsUserOrMod(creatorUserToken: MaybeString): boolean { if (this.userTokenMatches(creatorUserToken)) { return true; } return this.canDeleteOtherUsersW2lResults(); // TODO: There is no granular permission for this yet. } - public canDeleteW2lResultAsUserOrMod(creatorUserToken: MaybeString) : boolean { + public canDeleteW2lResultAsUserOrMod(creatorUserToken: MaybeString): boolean { if (this.userTokenMatches(creatorUserToken)) { return true; } return this.canDeleteOtherUsersW2lResults(); } - public userTokenMatches(otherUserToken: MaybeString) : boolean { - // Default to false if user token on either side is falsey. + public userTokenMatches(otherUserToken: MaybeString): boolean { + // Default to false if user token on either side is falsey. if (!otherUserToken) { return false; } diff --git a/packages/fakeyou.com/public/fakeyou/FakeYou-Logo-2.png b/packages/fakeyou.com/public/fakeyou/FakeYou-Logo-2.png new file mode 100644 index 0000000000..616af230d2 Binary files /dev/null and b/packages/fakeyou.com/public/fakeyou/FakeYou-Logo-2.png differ diff --git a/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-2.png b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-2.png new file mode 100644 index 0000000000..6b71b82707 Binary files /dev/null and b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-2.png differ diff --git a/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-3.png b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-3.png new file mode 100644 index 0000000000..7aad0dce0a Binary files /dev/null and b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-3.png differ diff --git a/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-4.png b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-4.png new file mode 100644 index 0000000000..ccd27266bc Binary files /dev/null and b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-4.png differ diff --git a/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-5.png b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-5.png new file mode 100644 index 0000000000..e10ccb06fe Binary files /dev/null and b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo-5.png differ diff --git a/packages/fakeyou.com/public/fakeyou/Storyteller-Logo.png b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo.png new file mode 100644 index 0000000000..02e5be9716 Binary files /dev/null and b/packages/fakeyou.com/public/fakeyou/Storyteller-Logo.png differ diff --git a/packages/fakeyou.com/src/components/common/Container/Container.tsx b/packages/fakeyou.com/src/components/common/Container/Container.tsx index ba3a1b05b2..40bd75a515 100644 --- a/packages/fakeyou.com/src/components/common/Container/Container.tsx +++ b/packages/fakeyou.com/src/components/common/Container/Container.tsx @@ -29,8 +29,7 @@ export default function PageContainer({ } // Merge the classNames - const mergedClassNames = - `${containerClass} ${className} min-height-100vh`.trim(); + const mergedClassNames = `${containerClass} ${className}`.trim(); return
{children}
; } diff --git a/packages/fakeyou.com/src/components/common/DropdownMenu/DropdownMenu.scss b/packages/fakeyou.com/src/components/common/DropdownMenu/DropdownMenu.scss new file mode 100644 index 0000000000..787078adbf --- /dev/null +++ b/packages/fakeyou.com/src/components/common/DropdownMenu/DropdownMenu.scss @@ -0,0 +1,30 @@ +@import "scss/colours.scss"; + +.fy-dropdown-menu { + display: none; + position: absolute; + left: 0; + top: 100%; + background-color: $panel-inner; + border: 1px solid lighten($panel-inner, 8%); + border-radius: 0.5rem; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; + overflow: hidden; +} + +.fy-dropdown-item { + padding: 10px 15px; + text-decoration: none; + display: block; + color: $white !important; + transition: 0.15s all; + cursor: pointer; + font-size: 15px !important; + font-weight: 500 !important; +} + +.fy-dropdown-item:hover { + background-color: lighten($panel-inner, 8%); +} diff --git a/packages/fakeyou.com/src/components/common/DropdownMenu/DropdownMenu.tsx b/packages/fakeyou.com/src/components/common/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 0000000000..d3ae668014 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import { a, useSpring } from "@react-spring/web"; +import { Link } from "react-router-dom"; +import { IconDefinition } from "@fortawesome/fontawesome-common-types"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import "./DropdownMenu.scss"; + +interface DropdownItem { + id: number; + name: string; + link?: string; + onClick?: () => void; + icon?: IconDefinition; +} + +interface DropdownMenuProps { + items: DropdownItem[]; + className?: string; + onClose: () => void; +} + +export default function DropdownMenu({ + items, + className, + onClose, +}: DropdownMenuProps) { + const fadeIn = useSpring({ + from: { opacity: 0 }, + to: { opacity: 1 }, + config: { duration: 100 }, + }); + + return ( + + {items.map(item => ( + <> + {item.onClick ? ( +
{ + if (item.onClick) { + e.preventDefault(); + item.onClick(); + } + onClose(); + }} + className="fy-dropdown-item" + > + + {item.icon && ( + + )} + {item.name} + +
+ ) : ( + <> + {item.link && ( + onClose()} + > + + {item.icon && ( + + )} + {item.name} + + + )} + + )} + + ))} +
+ ); +} diff --git a/packages/fakeyou.com/src/components/common/DropdownMenu/index.ts b/packages/fakeyou.com/src/components/common/DropdownMenu/index.ts new file mode 100644 index 0000000000..7e4906822e --- /dev/null +++ b/packages/fakeyou.com/src/components/common/DropdownMenu/index.ts @@ -0,0 +1 @@ +export { default } from "./DropdownMenu"; diff --git a/packages/fakeyou.com/src/components/common/NavItem/NavItem.scss b/packages/fakeyou.com/src/components/common/NavItem/NavItem.scss new file mode 100644 index 0000000000..7bb28aa022 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/NavItem/NavItem.scss @@ -0,0 +1,22 @@ +@import "scss/colours.scss"; + +.fy-nav-item { + position: relative; + padding: 10px; + cursor: pointer; + font-size: 15px; + font-weight: 500; + color: rgba($white, 0.8); + transition: 0.15s all; + + a { + color: rgba($white, 0.8); + font-size: 15px; + font-weight: 500; + transition: 0.15s all; + } +} + +.fy-nav-item:hover .fy-dropdown-menu { + display: block; +} diff --git a/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx b/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx new file mode 100644 index 0000000000..c0a38350da --- /dev/null +++ b/packages/fakeyou.com/src/components/common/NavItem/NavItem.tsx @@ -0,0 +1,72 @@ +import React, { useState } from "react"; +import DropdownMenu from "../DropdownMenu/DropdownMenu"; +import "./NavItem.scss"; +import { IconDefinition } from "@fortawesome/fontawesome-common-types"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCaretDown } from "@fortawesome/pro-solid-svg-icons"; +import { Link } from "react-router-dom"; + +interface NavItemProps { + icon?: IconDefinition; + label: string; + isHoverable?: boolean; + link?: string; + className?: string; + dropdownItems?: { + id: number; + name: string; + link: string; + icon?: IconDefinition; + }[]; +} + +export default function NavItem({ + label, + dropdownItems, + icon, + isHoverable, + link, + className, +}: NavItemProps) { + const [isHovered, setIsHovered] = useState(false); + + const textHover = `${isHovered && "text-white"}`.trim(); + + const renderContent = () => { + if (isHoverable && dropdownItems) { + return ( + <> + {icon && } + + {label} + + + {isHovered && ( + {}} /> + )} + + ); + } else { + return ( + <> + {link && ( + + {icon && } + {label} + + )} + + ); + } + }; + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {renderContent()} +
+ ); +} diff --git a/packages/fakeyou.com/src/components/common/NavItem/index.ts b/packages/fakeyou.com/src/components/common/NavItem/index.ts new file mode 100644 index 0000000000..52fe1d829e --- /dev/null +++ b/packages/fakeyou.com/src/components/common/NavItem/index.ts @@ -0,0 +1 @@ +export { default } from "./NavItem"; diff --git a/packages/fakeyou.com/src/components/common/Panel/Panel.tsx b/packages/fakeyou.com/src/components/common/Panel/Panel.tsx index ae176eb29e..422a4239c6 100644 --- a/packages/fakeyou.com/src/components/common/Panel/Panel.tsx +++ b/packages/fakeyou.com/src/components/common/Panel/Panel.tsx @@ -20,8 +20,10 @@ export default function Panel({ return (
{children}
diff --git a/packages/fakeyou.com/src/components/common/ProfileDropdown/ProfileDropdown.scss b/packages/fakeyou.com/src/components/common/ProfileDropdown/ProfileDropdown.scss new file mode 100644 index 0000000000..60857fa1af --- /dev/null +++ b/packages/fakeyou.com/src/components/common/ProfileDropdown/ProfileDropdown.scss @@ -0,0 +1,16 @@ +.fy-profile-dropdown { + position: relative; + display: inline-block; + transition: 0.15s all; +} + +.fy-profile-pic { + cursor: pointer; +} + +.fy-profile-dropdown-menu { + display: block; + right: 0; + left: auto; + margin-top: 6px; +} diff --git a/packages/fakeyou.com/src/components/common/ProfileDropdown/ProfileDropdown.tsx b/packages/fakeyou.com/src/components/common/ProfileDropdown/ProfileDropdown.tsx new file mode 100644 index 0000000000..d27050e138 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/ProfileDropdown/ProfileDropdown.tsx @@ -0,0 +1,86 @@ +import React, { useEffect, useRef, useState } from "react"; +import DropdownMenu from "../DropdownMenu"; +import { Gravatar } from "@storyteller/components/src/elements/Gravatar"; +import "./ProfileDropdown.scss"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faCaretDown, + faSignOutAlt, + faUser, +} from "@fortawesome/pro-solid-svg-icons"; + +interface ProfileDropdownProps { + username: string; + displayName: string; + avatarIndex: number; + backgroundColorIndex: number; + emailHash: string; + logoutHandler: () => void; +} + +export default function ProfileDropdown({ + username, + displayName, + avatarIndex, + backgroundColorIndex, + emailHash, + logoutHandler, +}: ProfileDropdownProps) { + const dropdownRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const profileOptions = [ + { + id: 1, + name: "My Profile", + link: `/profile/${displayName}`, + icon: faUser, + }, + { id: 2, name: "Logout", onClick: logoutHandler, icon: faSignOutAlt }, + ]; + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + } + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+
{ + setIsOpen(!isOpen); + }} + className="fy-profile-pic d-flex gap-2 align-items-center" + > + + +
+ + {isOpen && ( + { + setIsOpen(false); + }} + /> + )} +
+ ); +} diff --git a/packages/fakeyou.com/src/components/common/ProfileDropdown/index.ts b/packages/fakeyou.com/src/components/common/ProfileDropdown/index.ts new file mode 100644 index 0000000000..eec6053e9d --- /dev/null +++ b/packages/fakeyou.com/src/components/common/ProfileDropdown/index.ts @@ -0,0 +1 @@ +export { default } from "./ProfileDropdown"; diff --git a/packages/fakeyou.com/src/components/common/SearchBar/SearchBar.scss b/packages/fakeyou.com/src/components/common/SearchBar/SearchBar.scss index 56e8398508..75fd08fe6a 100644 --- a/packages/fakeyou.com/src/components/common/SearchBar/SearchBar.scss +++ b/packages/fakeyou.com/src/components/common/SearchBar/SearchBar.scss @@ -85,6 +85,12 @@ } } +@media (max-width: 1200px) { + .search-bar-container { + width: 400px; + } +} + @media (max-width: 992px) { .search-bar-container { width: 100%; diff --git a/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx b/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx index b560920460..04787c54c8 100644 --- a/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx +++ b/packages/fakeyou.com/src/components/layout/SideNav/SideNav.tsx @@ -56,6 +56,13 @@ export default function SideNav({ const isDevelopmentEnv = fakeYouFrontendEnv.isDevelopment(); const wrapper = document.getElementById("wrapper"); const isMenuOpen = wrapper?.classList.contains("toggled"); + const isLoggedIn = sessionWrapper.isLoggedIn(); + const isOnLandingPage = window.location.pathname === "/"; + const isOnLoginOrSignUpPage = + window.location.pathname === "/login" || + window.location.pathname === "/login/" || + window.location.pathname === "/signup" || + window.location.pathname === "/signup/"; let history = useHistory(); const handleNavLinkClick = () => { @@ -105,17 +112,25 @@ export default function SideNav({ }; }, []); - const shouldShowSidebar = windowWidth >= 992; + const shouldNotShowSidebar = + !isLoggedIn && (isOnLandingPage || isOnLoginOrSignUpPage); + const shouldShowSidebar = windowWidth >= 992 && !shouldNotShowSidebar; + const sidebarClassName = `sidebar ${ + shouldShowSidebar ? "visible" : "" + }`.trim(); useEffect(() => { const contentWrapper = document.getElementById("page-content-wrapper"); - if (windowWidth >= 992) { + if ( + (shouldShowSidebar && isLoggedIn) || + (shouldShowSidebar && !isOnLandingPage) + ) { contentWrapper?.classList.remove("no-padding"); } else { contentWrapper?.classList.add("no-padding"); } - }, [windowWidth]); + }, [isLoggedIn, isOnLandingPage, shouldShowSidebar]); const [queueStats, setQueueStats] = useState({ success: true, @@ -349,10 +364,7 @@ export default function SideNav({ return ( <> - + {/*
test
*/} + {/* Mobile Searchbar */} {isMobileSearchBarVisible && (
diff --git a/packages/fakeyou.com/src/context/DomainConfigContext.tsx b/packages/fakeyou.com/src/context/DomainConfigContext.tsx new file mode 100644 index 0000000000..179a3648cc --- /dev/null +++ b/packages/fakeyou.com/src/context/DomainConfigContext.tsx @@ -0,0 +1,22 @@ +import React, { createContext, useContext, ReactNode } from "react"; +import { getCurrentDomainConfig } from "../utils/domainConfig"; + +const DomainConfigContext = createContext(getCurrentDomainConfig()); + +export const useDomainConfig = () => useContext(DomainConfigContext); + +interface DomainConfigProviderProps { + children: ReactNode; +} + +export default function DomainConfigProvider({ + children, +}: DomainConfigProviderProps) { + const domainConfig = getCurrentDomainConfig(); + + return ( + + {children} + + ); +} diff --git a/packages/fakeyou.com/src/context/index.ts b/packages/fakeyou.com/src/context/index.ts index 7682959f70..241d3b9459 100644 --- a/packages/fakeyou.com/src/context/index.ts +++ b/packages/fakeyou.com/src/context/index.ts @@ -2,5 +2,12 @@ import AccountModalContext from "./AccountModalContext"; import InferenceJobsContext from "./InferenceJobsContext"; import ModalContext from "./ModalContext"; import SessionContext from "./SessionContext"; +import DomainConfigContext from "./DomainConfigContext"; -export { AccountModalContext, InferenceJobsContext, ModalContext, SessionContext }; +export { + AccountModalContext, + InferenceJobsContext, + ModalContext, + SessionContext, + DomainConfigContext, +}; diff --git a/packages/fakeyou.com/src/scss/colours.scss b/packages/fakeyou.com/src/scss/colours.scss index 7770768e10..9ad8b75233 100644 --- a/packages/fakeyou.com/src/scss/colours.scss +++ b/packages/fakeyou.com/src/scss/colours.scss @@ -12,7 +12,7 @@ $warning: darken(#f7941c, 5%); $accent: #80cfd3; $workflow: $primary; $panel: #242433; -$panel-inner: lighten($panel, 4%); +$panel-inner: #2b2b3b; $panel-inner-border: lighten($panel, 8%); $primary-lm: #000; diff --git a/packages/fakeyou.com/src/scss/nav.scss b/packages/fakeyou.com/src/scss/nav.scss index b63b9d5360..58da67e677 100644 --- a/packages/fakeyou.com/src/scss/nav.scss +++ b/packages/fakeyou.com/src/scss/nav.scss @@ -60,6 +60,7 @@ $topbar-height: 66px; position: relative; padding-left: 260px; padding-top: $topbar-height; + transition: all 0.2s ease; } /* Sidebar Styles */ diff --git a/packages/fakeyou.com/src/scss/site-refresh.scss b/packages/fakeyou.com/src/scss/site-refresh.scss index 58af52afc2..3dd12afb8c 100644 --- a/packages/fakeyou.com/src/scss/site-refresh.scss +++ b/packages/fakeyou.com/src/scss/site-refresh.scss @@ -11,21 +11,21 @@ styles with important tags: // // Once the rollout is complete, this outer wrapper class can be removed. - @mixin panel { - background-color: $panel; - border-color: #ffffff12 !important; - border: 1px solid; - border-left: none; - border-right: none; - border-radius: 0px; - box-shadow: - 0 0 0 1px rgba(0 0 0 / 16%), - 0 70px 65px rgb(0 0 0 / 8%), - 0 30px 30px rgb(0 0 0 / 7%), - 0 15px 15px rgb(0 0 0 / 5%), - 0 10px 8px rgb(0 0 0 / 3%), - 0 4px 4px rgb(0 0 0 / 2%), - 0 2px 2px rgb(0 0 0 / 1%) !important; +@mixin panel { + background-color: $panel; + border-color: #ffffff12 !important; + border: 1px solid; + border-left: none; + border-right: none; + border-radius: 0px; + box-shadow: + 0 0 0 1px rgba(0 0 0 / 16%), + 0 70px 65px rgb(0 0 0 / 8%), + 0 30px 30px rgb(0 0 0 / 7%), + 0 15px 15px rgb(0 0 0 / 5%), + 0 10px 8px rgb(0 0 0 / 3%), + 0 4px 4px rgb(0 0 0 / 2%), + 0 2px 2px rgb(0 0 0 / 1%) !important; } .fakeyou-refresh { @@ -45,11 +45,11 @@ styles with important tags: margin: 0; } /* Firefox */ - .numberInputNoArrows input[type=number], - input.numberInputNoArrows[type=number] { + .numberInputNoArrows input[type="number"], + input.numberInputNoArrows[type="number"] { -moz-appearance: textfield; } - + //-end--// ::-webkit-scrollbar-thumb { @@ -360,7 +360,7 @@ styles with important tags: } .panel-inner { - background-color: lighten($panel, 4%); + background-color: $panel-inner; } .panel-tts-results { @@ -1373,15 +1373,15 @@ styles with important tags: .hanashi-before { position: absolute; - top: -35%; - left: -140%; + top: -80%; + left: -150%; pointer-events: none; } .hanashi-after { position: absolute; - top: -35%; - right: -140%; + top: -80%; + right: -150%; pointer-events: none; } } @@ -1696,8 +1696,8 @@ textarea { } .section { - padding-top: 7rem; - padding-bottom: 7rem; + padding-top: 6rem; + padding-bottom: 6rem; } } } diff --git a/packages/fakeyou.com/src/utils/domainConfig.ts b/packages/fakeyou.com/src/utils/domainConfig.ts new file mode 100644 index 0000000000..39162a6452 --- /dev/null +++ b/packages/fakeyou.com/src/utils/domainConfig.ts @@ -0,0 +1,35 @@ +type DomainConfig = Record; + +const domainConfigs: Record = { + "storyteller.ai": { + logo: "/fakeyou/Storyteller-Logo-5.png", + title: "Storyteller AI", + link: "https://storyteller.ai", + }, + "fakeyou.com": { + logo: "/fakeyou/FakeYou-Logo-2.png", + title: "FakeYou", + link: "https://fakeyou.com", + }, + // Default to FakeYou.com + default: { + logo: "/fakeyou/FakeYou-Logo-2.png", + title: "FakeYou", + link: "https://fakeyou.com", + }, +}; + +export const getCurrentDomainConfig = (): DomainConfig => { + const currentHostname = window.location.hostname; + + const matchingConfigKey = Object.keys(domainConfigs).find( + key => currentHostname === key || currentHostname.endsWith(`.${key}`) + ); + + const domainConfig = + matchingConfigKey !== undefined + ? domainConfigs[matchingConfigKey] + : domainConfigs["default"]; + + return domainConfig; +}; diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index 14547d6e4d..f0901f76e9 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -112,8 +112,7 @@ import UploadLoraWeightPage from "./pages/upload/UploadLoraWeightPage"; import VideoWorkflowPage from "./pages/video_workflow"; import ScrollToTop from "./_common/ScrollToTop"; import TextToImagePage from "./pages/text_to_image/TextToImagePage"; - - +import DomainConfigProvider from "context/DomainConfigContext"; import DevUpload from "./pages/dev_upload/DevUpload"; import DevMediaInput from "./pages/dev_upload/DevMediaInput"; @@ -224,156 +223,596 @@ class PageContainer extends React.Component< public render() { return ( - - -
-
+ + + +
+
- - + + -
- -
- - - + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* New Profile Page */} + + + + + + {}} + sessionWrapper={this.props.sessionWrapper} + /> + + + + + + + + {}} + sessionWrapper={this.props.sessionWrapper} + /> + + + {}} + sessionWrapper={this.props.sessionWrapper} + /> + + + {}} + sessionWrapper={this.props.sessionWrapper} + /> + + + + + + + + + + + + + + + ( + + )} /> - - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - {/* New Profile Page */} - - - + + + - - {}} - sessionWrapper={this.props.sessionWrapper} - /> - + + + - - - + + + - - {}} - sessionWrapper={this.props.sessionWrapper} - /> - - - {}} - sessionWrapper={this.props.sessionWrapper} - /> - - - {}} - sessionWrapper={this.props.sessionWrapper} - /> - + + + - - - + + + - - - + + + - - - + + + - ( - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Route for initial voice creation */} + + + + + {/* Route for editing the dataset details */} + + + + + {/* Route for handling dataset token for uploading samples */} + + + + + + + + + + - )} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* Route for initial voice creation */} - - - - - {/* Route for editing the dataset details */} - - - - - {/* Route for handling dataset token for uploading samples */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* Route for initial voice creation */} - - - - - {/* Route for editing the dataset details */} - - - - - {/* Route for handling dataset token for uploading samples */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Route for initial voice creation */} + + + + + {/* Route for editing the dataset details */} + + + + + {/* Route for handling dataset token for uploading samples */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
-
-
-
+ +
); } } diff --git a/packages/fakeyou.com/src/v2/view/nav/FooterNav.tsx b/packages/fakeyou.com/src/v2/view/nav/FooterNav.tsx index b59d7614e3..d36f06f6a1 100644 --- a/packages/fakeyou.com/src/v2/view/nav/FooterNav.tsx +++ b/packages/fakeyou.com/src/v2/view/nav/FooterNav.tsx @@ -19,12 +19,14 @@ import { } from "@storyteller/components/src/api/server/GetServerInfo"; import { useLocalize } from "hooks"; import { Container } from "components/common"; +import { useDomainConfig } from "context/DomainConfigContext"; interface Props { sessionWrapper: SessionWrapper; } function FooterNav(props: Props) { + const domain = useDomainConfig(); const { t } = useLocalize("Footer"); const [serverInfo, setServerInfo] = useState< GetServerInfoSuccessResponse | undefined @@ -84,8 +86,8 @@ function FooterNav(props: Props) {
FakeYou: Cartoon and Celebrity Text to Speech @@ -204,12 +206,8 @@ function FooterNav(props: Props) {
- - © 2023 FakeYou by{" "} - - Storyteller.ai - - + © {domain.title} 2024 +
{moderationLink}
diff --git a/packages/fakeyou.com/src/v2/view/pages/landing/Dashboard.tsx b/packages/fakeyou.com/src/v2/view/pages/landing/Dashboard.tsx new file mode 100644 index 0000000000..198524cafb --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/landing/Dashboard.tsx @@ -0,0 +1,193 @@ +import { + faArrowRight, + faFileArrowUp, + faFlask, + faSparkles, +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { Panel } from "components/common"; +import { useLocalize } from "hooks"; +import React from "react"; +import { Link } from "react-router-dom"; + +interface DashboardProps { + sessionWrapper: SessionWrapper; +} + +export default function Dashboard(props: DashboardProps) { + const { t } = useLocalize("LandingPage"); + const isLoggedIn = props.sessionWrapper.isLoggedIn(); + + let uploadModelSection = <>; + + if (isLoggedIn) { + uploadModelSection = ( + <> +

Upload Models

+
+
+
+ + + Upload TTS Model + +
+
+ + + Upload V2V Model + +
+
+ + + Upload W2L Model + +
+
+
+ + ); + } + + return ( + +

{t("productsTitle")}

+
+
+ +
+
+

+ {t("productTtsTitle")} +

+
+ {t("productTtsText")} +
+
+ + + +
+ + Text to speech + +
+
+ +
+
+

+ {t("productVcTitle")} +

+
+ {t("productVcText")} +
+
+ + + +
+ + Voice Conversion + +
+
+ +
+
+
+ + + BETA + + +

+ Voice Designer +

+
+ +
+ Create your own AI voice +
+
+ + + +
+ + Voice Designer + +
+
+ +
+
+
+ + + {t("productNewTag")} + + +

+ {t("productFaceAnimatorTitle")} +

+
+ +
+ {t("productFaceAnimatorText")} +
+
+ + + +
+ + Face Animator + +
+ background dots + background dots +
+ + {uploadModelSection} +
+ ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/landing/LandingPage.tsx b/packages/fakeyou.com/src/v2/view/pages/landing/LandingPage.tsx index d940edd121..21b3af732d 100644 --- a/packages/fakeyou.com/src/v2/view/pages/landing/LandingPage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/landing/LandingPage.tsx @@ -1,25 +1,15 @@ -import React, { useMemo } from "react"; +import React from "react"; import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle"; -import { Link } from "react-router-dom"; import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; -import { useLocalize } from "hooks"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faStar, - faArrowRight, - faUser, -} from "@fortawesome/free-solid-svg-icons"; -import { faDiscord, faTwitter } from "@fortawesome/free-brands-svg-icons"; -import { WebUrl } from "../../../../common/WebUrl"; -import { - faFileArrowUp, - faFlask, - faSparkles, -} from "@fortawesome/pro-solid-svg-icons"; +import {} from "@fortawesome/pro-solid-svg-icons"; import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient"; -import Alert from "components/common/Alert/Alert"; -// import { Analytics } from "../../../../../common/Analytics"; +import { Container } from "components/common"; +import FakeYouLandingHeader from "./fakeyou/FakeYouLandingHeader"; +import Dashboard from "./Dashboard"; +import FakeYouLandingBody from "./fakeyou/FakeYouLandingBody"; +import { useDomainConfig } from "context/DomainConfigContext"; +import StorytellerLanding from "./storyteller/StorytellerLanding"; interface Props { sessionWrapper: SessionWrapper; @@ -28,537 +18,40 @@ interface Props { function LandingPage(props: Props) { usePrefixedDocumentTitle("FakeYou Celebrity Voice Generator"); - PosthogClient.recordPageview(); + const domain = useDomainConfig(); - const { t } = useLocalize("LandingPage"); - - let signUpButton = <>; - let viewPricingButton = <>; - let upgradeButton = <>; - let myProfileButton = <>; - let uploadModelSection = <>; - - if (!props.sessionWrapper.isLoggedIn()) { - signUpButton = ( - <> - { - // Analytics.ttsClickHeroSignup(); - // }} - > - - - - ); - viewPricingButton = ( - <> - { - // Analytics.ttsClickHeroViewPricing(); - // }} - > - - - - ); - } - if (props.sessionWrapper.isLoggedIn()) { - let displayName = props.sessionWrapper.getDisplayName() as string; // NB: If logged in, should be string - let url = WebUrl.userProfilePage(displayName); - myProfileButton = ( - <> - { - // Analytics.ttsClickHeroViewProfile(); - // }} - > - - - - ); - - uploadModelSection = ( - <> -

Upload Models

-
-
-
- - - Upload TTS Model - -
-
- - - Upload V2V Model - -
-
- - - Upload W2L Model - -
-
-
- - ); - - if (!props.sessionSubscriptionsWrapper.hasPaidFeatures()) { - upgradeButton = ( - <> - { - // Analytics.ttsClickHeroUpgradePlan(); - // }} - > - - - - ); - } - } - - const randomHeroImage = useMemo(() => { - const images = [ - // Main Images: - "mascot/kitsune_pose2.webp", - // "mascot/may4th.webp", - // "mascot/halloween_1.webp", - // "mascot/halloween_2.webp", - // "mascot/halloween_3.webp", - // "mascot/xmas_1.webp", - // "mascot/xmas_2.webp", - // "mascot/xmas_3.webp", - // "mascot/xmas_4.webp", - ]; - - return images[Math.floor(Math.random() * images.length)]; - }, []); + const isLoggedIn = props.sessionWrapper.isLoggedIn(); return ( -
-
- {/* Face Animation Alert Banner */} - - -
-
- FakeYou Mascot -
-
-

- {t("heroTitle")} -

-

- {t("heroText")} -

-
- {upgradeButton} - {signUpButton} - {viewPricingButton} - {myProfileButton} -
-
-
-
- - {/*
-
-
-
-
-
- -
- - - - - -
-
-

- - Pick one to get started, or choose from - {" "} - thousands more! -

-
-
- -
- - - -
-
-
-
-
-
*/} - -
-

{t("productsTitle")}

-
-
- -
-
-

- {t("productTtsTitle")} -

-
- {t("productTtsText")} -
-
- - - -
- - Text to speech + + {domain.title === "FakeYou" ? ( + <> + {/* FAKEYOU.COM */} + {!isLoggedIn && ( + - -
-
- -
-
-

- {t("productVcTitle")} -

-
- {t("productVcText")} -
-
- - - -
+ )} - Voice Conversion - -
-
- -
-
-
- - - BETA - + -

- Voice Designer -

-
- -
- Create your own AI voice -
-
- - - -
- - Voice Designer - -
-
- -
-
-
- - - {t("productNewTag")} - - -

- {t("productFaceAnimatorTitle")} -

-
- -
- {t("productFaceAnimatorText")} -
-
- - - -
- - Face Animator - -
- background dots - background dots -
- - {uploadModelSection} -
- -
-
-
-
-
- -
- background dots -
-
-
-

{t("contextTitle")}

-

{t("contextText")}

-
- - {t("contextButtonTts")} - - - {t("contextButtonVc")} - -
-
-
-
- - {/*
-
-
-
-
- -
- background dots -
-
-
-

Speak as your favorite characters

-

- Transform your speaking voice into someone else's voice with our - cutting-edge voice conversion service. Just upload or record your - audio and let our AI-powered technology do the rest. Perfect for - entertainment, voiceovers, and language learning materials. -

-
- - Convert your voice - -
-
-
-
*/} - -
-
-
- hanashi before - red chevrons - hanashi after -
-

{t("ctaTitle")}

-

{t("ctaText")}

-
- - {t("ctaButtonTts")} - - - {t("ctaButtonVc")} - -
- hanashi before - grey chevrons - red chevrons - hanashi after -
-
- -
-
-
-

{t("communityTitle")}

-

{t("communityText")}

- -
- background dots -
-
-
+ {!isLoggedIn && } + + ) : ( + <> + {/* STORYTELLER,AI */} + {!isLoggedIn ? ( + + ) : ( + + )} + + )} + + ); } diff --git a/packages/fakeyou.com/src/v2/view/pages/landing/fakeyou/FakeYouLandingBody.tsx b/packages/fakeyou.com/src/v2/view/pages/landing/fakeyou/FakeYouLandingBody.tsx new file mode 100644 index 0000000000..88ee7c3dea --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/landing/fakeyou/FakeYouLandingBody.tsx @@ -0,0 +1,88 @@ +import { faDiscord, faTwitter } from "@fortawesome/free-brands-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Panel } from "components/common"; +import { useLocalize } from "hooks"; +import React from "react"; +import { Link } from "react-router-dom"; + +interface FakeYouLandingBodyProps {} + +export default function FakeYouLandingBody(props: FakeYouLandingBodyProps) { + const { t } = useLocalize("LandingPage"); + + return ( + <> + +
+
+
+
+
+ +
+ background dots +
+
+
+

{t("contextTitle")}

+

{t("contextText")}

+
+ + {t("contextButtonTts")} + + + {t("contextButtonVc")} + +
+
+
+
+
+ +
+
+
+

{t("communityTitle")}

+

{t("communityText")}

+ +
+ background dots +
+
+ + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/landing/fakeyou/FakeYouLandingHeader.tsx b/packages/fakeyou.com/src/v2/view/pages/landing/fakeyou/FakeYouLandingHeader.tsx new file mode 100644 index 0000000000..e3a292d3c3 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/landing/fakeyou/FakeYouLandingHeader.tsx @@ -0,0 +1,152 @@ +import { faArrowRight, faStar, faUser } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { WebUrl } from "common/WebUrl"; +import { Panel } from "components/common"; +import { useLocalize } from "hooks"; +import React, { useMemo } from "react"; +import { Link } from "react-router-dom"; + +interface FakeYouLandingHeaderProps { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +export default function FakeYouLandingHeader(props: FakeYouLandingHeaderProps) { + const { t } = useLocalize("LandingPage"); + const isLoggedIn = props.sessionWrapper.isLoggedIn(); + const isSubscribed = props.sessionSubscriptionsWrapper.hasPaidFeatures(); + + let signUpButton = <>; + let viewPricingButton = <>; + let upgradeButton = <>; + let myProfileButton = <>; + + if (!isLoggedIn) { + signUpButton = ( + <> + { + // Analytics.ttsClickHeroSignup(); + // }} + > + + + + ); + viewPricingButton = ( + <> + { + // Analytics.ttsClickHeroViewPricing(); + // }} + > + + + + ); + } + + if (isLoggedIn) { + let displayName = props.sessionWrapper.getDisplayName() as string; // NB: If logged in, should be string + let url = WebUrl.userProfilePage(displayName); + myProfileButton = ( + <> + { + // Analytics.ttsClickHeroViewProfile(); + // }} + > + + + + ); + + if (!isSubscribed) { + upgradeButton = ( + <> + { + // Analytics.ttsClickHeroUpgradePlan(); + // }} + > + + + + ); + } + } + + const randomHeroImage = useMemo(() => { + const images = [ + // Main Images: + "mascot/kitsune_pose2.webp", + // "mascot/may4th.webp", + // "mascot/halloween_1.webp", + // "mascot/halloween_2.webp", + // "mascot/halloween_3.webp", + // "mascot/xmas_1.webp", + // "mascot/xmas_2.webp", + // "mascot/xmas_3.webp", + // "mascot/xmas_4.webp", + ]; + + return images[Math.floor(Math.random() * images.length)]; + }, []); + + return ( + + {/* */} + +
+
+ FakeYou Mascot +
+
+

+ {t("heroTitle")} +

+

+ {t("heroText")} +

+
+ {upgradeButton} + {signUpButton} + {viewPricingButton} + {myProfileButton} +
+
+
+
+ ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/landing/storyteller/StorytellerLanding.tsx b/packages/fakeyou.com/src/v2/view/pages/landing/storyteller/StorytellerLanding.tsx new file mode 100644 index 0000000000..0814566a11 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/landing/storyteller/StorytellerLanding.tsx @@ -0,0 +1,224 @@ +import { faDiscord, faTwitter } from "@fortawesome/free-brands-svg-icons"; +import { + faArrowRight, + faCompass, + faMessageDots, + faWaveformLines, +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Button, Container, Input, Panel, Select } from "components/common"; +import { useLocalize } from "hooks"; +import React from "react"; +import { Link } from "react-router-dom"; + +interface StorytellerLandingProps {} + +export default function StorytellerLanding(props: StorytellerLandingProps) { + const { t } = useLocalize("LandingPage"); + + return ( + <> + + +
+
+
+
+ +
+ background dots +
+
+
+

+ Enabling Anyone to Create Quality Movies with AI +

+

+ We are combining generative AI and User Generated Content to + radically democratize both audio and video production. +

+
+
+
+
+
+
+ +
+ + +
+
+

AI Audio Generation

+

{t("ctaText")}

+
+ hanashi before + red chevrons + hanashi after +
+ hanashi before + grey chevrons + red chevrons + hanashi after +
+
+
+
+ +
+

+ + Text to Speech +

+ + Try more voices{" "} + + +
+ +
+ +
+
+
+
+
+
+ +
+

+ + Voice to Voice +

+ + Try more voices{" "} + + +
+
+