diff --git a/packages/harmony/src/foundations/color/primitive.css b/packages/harmony/src/foundations/color/primitive.css index b6fd18c998e..0ab60cc26e7 100644 --- a/packages/harmony/src/foundations/color/primitive.css +++ b/packages/harmony/src/foundations/color/primitive.css @@ -256,7 +256,7 @@ html[data-theme='default-light'] { --harmony-n-700: #4a5263; --harmony-n-800: #343b49; --harmony-n-900: #232834; - --harmony-n-950: #141821; + --harmony-n-950: #0c0f14; /* Special Colors */ --harmony-white: #ffffff; --harmony-background: #f6f5f7; @@ -296,7 +296,7 @@ html[data-theme='default-dark'] { --harmony-s-400: #cf90ff; --harmony-s-500: #d7a3ff; /* Neutral Colors */ - --harmony-neutral: #949494; + --harmony-neutral: #d1d1d1; --harmony-n-25: #0f0f0f; --harmony-n-50: #141414; --harmony-n-100: #1f1f1f; @@ -304,12 +304,12 @@ html[data-theme='default-dark'] { --harmony-n-200: #333333; --harmony-n-300: #474747; --harmony-n-400: #636363; - --harmony-n-500: #6b6b6b; - --harmony-n-600: #757575; - --harmony-n-700: #858585; - --harmony-n-800: #949494; - --harmony-n-900: #a3a3a3; - --harmony-n-950: #b2b2b2; + --harmony-n-500: #757575; + --harmony-n-600: #8f8f8f; + --harmony-n-700: #9e9e9e; + --harmony-n-800: #d1d1d1; + --harmony-n-900: #e0e0e0; + --harmony-n-950: #ededed; /* Special Colors */ --harmony-white: #0f0f0f; --harmony-background: #000000; diff --git a/packages/harmony/src/foundations/color/primitive.ts b/packages/harmony/src/foundations/color/primitive.ts index 9c360fe6744..c771bae7e2b 100644 --- a/packages/harmony/src/foundations/color/primitive.ts +++ b/packages/harmony/src/foundations/color/primitive.ts @@ -41,7 +41,7 @@ const defaultLightPrimitives = { n700: '#4A5263', n800: '#343B49', n900: '#232834', - n950: '#141821', + n950: '#0C0F14', default: '#343B49', neutral: '#343B49' }, @@ -107,14 +107,14 @@ const defaultDarkPrimitives = { n200: '#333333', n300: '#474747', n400: '#636363', - n500: '#6B6B6B', - n600: '#757575', - n700: '#858585', - n800: '#949494', - n900: '#A3A3A3', - n950: '#B2B2B2', - default: '#949494', - neutral: '#949494' + n500: '#757575', + n600: '#8F8F8F', + n700: '#9E9E9E', + n800: '#D1D1D1', + n900: '#E0E0E0', + n950: '#EDEDED', + default: '#D1D1D1', + neutral: '#D1D1D1' }, special: { background: '#000000', diff --git a/packages/mobile/package.json b/packages/mobile/package.json index a6730aaa7aa..500adeb60e1 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@audius/mobile", - "version": "1.5.166", + "version": "1.5.167", "private": true, "scripts": { "android:dev": "ENVFILE=.env.dev turbo run android -- --mode=prodDebug", diff --git a/packages/mobile/src/assets/images/DJportrait.jpg b/packages/mobile/src/assets/images/DJportrait.jpg deleted file mode 100644 index 185be27e0b6..00000000000 Binary files a/packages/mobile/src/assets/images/DJportrait.jpg and /dev/null differ diff --git a/packages/mobile/src/screens/sign-on-screen/screens/SignOnScreen.tsx b/packages/mobile/src/screens/sign-on-screen/screens/SignOnScreen.tsx index 4a86965d982..ed0229ae8b2 100644 --- a/packages/mobile/src/screens/sign-on-screen/screens/SignOnScreen.tsx +++ b/packages/mobile/src/screens/sign-on-screen/screens/SignOnScreen.tsx @@ -1,4 +1,3 @@ -import type { ReactNode } from 'react' import { useEffect, useState } from 'react' import { css } from '@emotion/native' @@ -9,32 +8,20 @@ import { updateRouteOnCompletion, setValueField } from 'common/store/pages/signon/actions' -import { ImageBackground, SafeAreaView } from 'react-native' -import Animated, { - CurvedTransition, - FadeIn, - FadeOut, - SlideInUp -} from 'react-native-reanimated' +import { useDarkMode } from 'react-native-dynamic' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { usePrevious } from 'react-use' import { + Divider, Flex, - IconAudiusLogoHorizontalColorNew, IconAudiusLogoHorizontalNew, - Paper, - RadialGradient, Text, TextLink, - useTheme + ThemeProvider as HarmonyThemeProvider } from '@audius/harmony-native' -import DJBackground from 'app/assets/images/DJportrait.jpg' import type { NonLinkProps } from 'app/harmony-native/components/TextLink/types' import { dispatch } from 'app/store' -import { AudiusValues } from '../components/AudiusValues' -import { PANEL_EXPAND_DURATION } from '../constants' import type { SignOnScreenParamList } from '../types' import { CreateEmailScreen } from './CreateEmailScreen' @@ -46,102 +33,25 @@ const messages = { createAccount: 'Create an Account' } -const AnimatedPaper = Animated.createAnimatedComponent(Paper) -const AnimatedFlex = Animated.createAnimatedComponent(Flex) - const CreateAccountLink = (props: NonLinkProps) => { const { onPress } = props - const { spacing } = useTheme() - - return ( - - - - {messages.newToAudius}{' '} - - {messages.createAccount} - - - - - ) -} -const Background = () => { return ( - - + + {messages.newToAudius}{' '} + + {messages.createAccount} + + ) } -type ExpandablePanelProps = { - children: ReactNode -} - -const ExpandablePanel = (props: ExpandablePanelProps) => { - const { children } = props - const insets = useSafeAreaInsets() - const { cornerRadius } = useTheme() - return ( - - - {children} - - - ) -} - export type SignOnScreenParams = { screen: SignOnScreenType guestEmail?: string @@ -158,7 +68,6 @@ type SignOnScreenProps = { */ export const SignOnScreen = (props: SignOnScreenProps) => { const { isSplashScreenDismissed } = props - const theme = useTheme() const { params } = useRoute>() const { screen: screenParam = 'sign-up', @@ -166,7 +75,7 @@ export const SignOnScreen = (props: SignOnScreenProps) => { routeOnCompletion } = params ?? {} const [screen, setScreen] = useState(screenParam) - const previousScreen = usePrevious(screen) + const insets = useSafeAreaInsets() useEffect(() => { if (guestEmail) { @@ -181,38 +90,41 @@ export const SignOnScreen = (props: SignOnScreenProps) => { setScreen(screenParam) }, [guestEmail, routeOnCompletion, screenParam]) + const isDarkMode = useDarkMode() + const signOnThemeName = isDarkMode ? 'default-dark' : 'default-light' + + if (!isSplashScreenDismissed) return null + return ( - <> - - {isSplashScreenDismissed ? ( - - - {theme.type === 'dark' ? ( - - ) : ( - - )} - {screen === 'sign-up' ? ( - - ) : ( - - )} - + + + + + {screen === 'sign-up' ? ( - + ) : ( - setScreen('sign-up')} /> + )} + {screen === 'sign-in' ? ( + setScreen('sign-up')} /> + ) : null} - ) : null} - + + ) } diff --git a/packages/web/package.json b/packages/web/package.json index 56b09c1b7d3..82775c916df 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -3,7 +3,7 @@ "productName": "Audius", "description": "The Audius web client reference implementation", "author": "Audius", - "version": "1.5.166", + "version": "1.5.167", "private": true, "scripts": { "DEV & BUILD========================================": "", diff --git a/packages/web/src/app/ThemeProvider.tsx b/packages/web/src/app/ThemeProvider.tsx index a82fc9e2ef4..dcd6c877c96 100644 --- a/packages/web/src/app/ThemeProvider.tsx +++ b/packages/web/src/app/ThemeProvider.tsx @@ -56,8 +56,9 @@ const selectHarmonyTheme = (state: AppState): Theme => { case LegacyTheme.MATRIX: return 'matrix' case LegacyTheme.AUTO: - default: return sysAppearance === 'dark' ? 'classic-dark' : 'classic-light' + default: + return sysAppearance === 'dark' ? 'default-dark' : 'default-light' } } diff --git a/packages/web/src/assets/img/DJportrait.jpg b/packages/web/src/assets/img/DJportrait.jpg deleted file mode 100644 index 185be27e0b6..00000000000 Binary files a/packages/web/src/assets/img/DJportrait.jpg and /dev/null differ diff --git a/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx b/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx index 79375a3bd30..cd5d3076067 100644 --- a/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx +++ b/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx @@ -33,12 +33,18 @@ import { SelectArtistsPreviewContext } from './selectArtistsPreviewContext' type FollowArtistTileProps = { user: UserMetadata mobileWidth?: string + /** When true, uses a smaller card size for dense grids (e.g. 4-column sign-up) */ + compact?: boolean } & HTMLProps +const COMPACT_CARD_WIDTH = 168 +const COMPACT_CARD_HEIGHT = 260 + export const FollowArtistCard = (props: FollowArtistTileProps) => { const { user: { name, user_id, is_verified, track_count, follower_count }, - mobileWidth = 'calc(50% - 4px)' + mobileWidth = 'calc(50% - 4px)', + compact = false } = props const dispatch = useDispatch() const { isMobile } = useMedia() @@ -58,8 +64,9 @@ export const FollowArtistCard = (props: FollowArtistTileProps) => { const isPlaying = isPreviewPlaying && nowPlayingArtistId === user_id const hasTracks = track_count && track_count > 0 + const avatarTop = compact ? 28 : 34 const [avatar] = useHover((isHovered) => ( - + { )) + const cardWidth = isMobile ? mobileWidth : compact ? COMPACT_CARD_WIDTH : 235 + const cardHeight = isMobile ? undefined : compact ? COMPACT_CARD_HEIGHT : 220 + return ( - + {isPlaying ? ( { {avatar} { - - + + {name} @@ -218,13 +240,19 @@ export const FollowArtistCard = (props: FollowArtistTileProps) => { ) } -export const FollowArtistTileSkeleton = () => { +export const FollowArtistTileSkeleton = ({ + compact = false +}: { + compact?: boolean +} = {}) => { const { isMobile } = useMedia() + const desktopWidth = compact ? COMPACT_CARD_WIDTH : 235 + const desktopHeight = compact ? COMPACT_CARD_HEIGHT : 220 return ( { const toggleOtpUI = (on: boolean) => { if (on) { setShowOtpInput(true) - setSignInError(messages.otpError) + setSignInError(null) } else { setSignInError(null) setShowOtpInput(false) @@ -342,9 +342,14 @@ export const OAuthLoginPage = () => { ) : (
- - - + {/* Logos + Title */} + + + Audius Logo @@ -352,7 +357,7 @@ export const OAuthLoginPage = () => { h='88px' w='88px' borderRadius='l' - css={{ overflow: 'hidden' }} + css={{ overflow: 'hidden', flexShrink: 0 }} > {appImage ? ( {`${appName} @@ -372,11 +377,17 @@ export const OAuthLoginPage = () => { )} - {`${messages.allow}:`} - - {appName} - + + + {`${messages.allow}:`} + + + {appName} + + + + {/* Permissions */} {userAlreadyWriteAuthorized ? null : ( { txParams={txParams} /> )} -
- {isLoggedIn ? ( -
- + + {/* Account / Sign-in section */} + {isLoggedIn ? ( + + + {messages.signedInAs}
@@ -407,71 +416,78 @@ export const OAuthLoginPage = () => { user={account} />
- - - {messages.signOut} - - {accounts.length > 0 ? ( - - {messages.switchAccount} - - ) : null} - - - {userAlreadyWriteAuthorized - ? messages.continueButton - : messages.authorizeButton} - -
- ) : ( -
-
- {/* @ts-ignore */} - - {/* @ts-ignore */} - - {signInError == null ? null : ( -
- - {signInError} -
- )} - {showOtpInput ? ( - // @ts-ignore + + + + {messages.signOut} + + {accounts.length > 0 ? ( + + {messages.switchAccount} + + ) : null} + + + {userAlreadyWriteAuthorized + ? messages.continueButton + : messages.authorizeButton} + + + ) : ( + + + {/* @ts-ignore */} + + {/* @ts-ignore */} + + {signInError == null ? null : ( +
+ + {signInError} +
+ )} + {showOtpInput ? ( + <> + + + {messages.otpPrompt} + + + {/* @ts-ignore */} { onChange={setOtpInput} className={cn(styles.otpInput)} /> - ) : null} - - {messages.signInButton} - - -
- - {messages.signUp} - -
-
- )} - {generalSubmitError == null ? null : ( -
- {generalSubmitError} + + ) : null} + + {messages.signInButton} + + +
+ + {messages.signUp} +
- )} -
+ + )} + + {generalSubmitError == null ? null : ( +
+ {generalSubmitError} +
+ )}
)} diff --git a/packages/web/src/pages/oauth-login-page/components/ContentWrapper.tsx b/packages/web/src/pages/oauth-login-page/components/ContentWrapper.tsx index ff26cd73cde..08ec97e667a 100644 --- a/packages/web/src/pages/oauth-login-page/components/ContentWrapper.tsx +++ b/packages/web/src/pages/oauth-login-page/components/ContentWrapper.tsx @@ -23,6 +23,7 @@ export const ContentWrapper = ({ direction='column' mv='3xl' alignSelf='flex-start' + border='strong' borderRadius='xl' > {children} diff --git a/packages/web/src/pages/oauth-login-page/components/PermissionsSection.tsx b/packages/web/src/pages/oauth-login-page/components/PermissionsSection.tsx index 5eb2ccd42c7..3816e03073e 100644 --- a/packages/web/src/pages/oauth-login-page/components/PermissionsSection.tsx +++ b/packages/web/src/pages/oauth-login-page/components/PermissionsSection.tsx @@ -1,13 +1,13 @@ import { PropsWithChildren } from 'react' import { + Divider, Flex, IconInfo, IconPencil, IconVisibilityPublic, Text } from '@audius/harmony' -import cn from 'classnames' import LoadingSpinner from 'components/loading-spinner/LoadingSpinner' @@ -15,25 +15,14 @@ import styles from '../OAuthLoginPage.module.css' import { messages } from '../messages' import { WriteOnceParams, WriteOnceTx } from '../utils' -type PermissionTextProps = PropsWithChildren<{}> -const PermissionText = ({ children }: PermissionTextProps) => { - return ( - - {children} - - ) -} - -type PermissionDetailProps = PropsWithChildren<{ - className?: string -}> +type PermissionDetailProps = PropsWithChildren<{}> const PermissionDetail = ({ children }: PermissionDetailProps) => { return ( -
+ {children} -
+
) } @@ -62,76 +51,69 @@ export const PermissionsSection = ({ txParams?: WriteOnceParams }) => { return ( - <> -
- - {messages.permissionsRequestedHeader} - -
+ + + {messages.permissionsRequestedHeader} +
-
- - {scope === 'write' || scope === 'write_once' ? ( - - ) : ( - - )} - - - - + {/* First permission */} + + + + {scope === 'write' || scope === 'write_once' ? ( + + ) : ( + + )} + + {scope === 'write' ? messages.writeAccountAccess : scope === 'write_once' ? getWriteOncePermissionTitle(tx) : messages.readOnlyAccountAccess} - {scope === 'write' ? ( - - - {messages.writeAccessGrants} - - - ) : null} - {scope === 'write_once' ? ( - - {txParams?.wallet.slice(0, 6)}...{txParams?.wallet.slice(-4)} - - ) : null} - {scope === 'read' ? ( - - - {messages.readOnlyGrants} - - - ) : null} - -
-
- - - - {messages.yourAccountData} - {isLoggedIn ? ( - - {isLoading ? ( - - ) : userEmail ? ( - `${messages.yourAccountDataAccess}: ${userEmail}` - ) : ( - messages.yourAccountDataAccessNoEmail - )} - - ) : null} + {scope === 'write' ? ( + {messages.writeAccessGrants} + ) : null} + {scope === 'write_once' ? ( + + {txParams?.wallet.slice(0, 6)}...{txParams?.wallet.slice(-4)} + + ) : null} + {scope === 'read' ? ( + {messages.readOnlyGrants} + ) : null} + + + + + + + {/* Second permission */} + + + + + + + {messages.yourAccountData} + -
+ {isLoggedIn ? ( + + {isLoading ? ( + + ) : userEmail ? ( + `${messages.yourAccountDataAccess}: ${userEmail}` + ) : ( + messages.yourAccountDataAccessNoEmail + )} + + ) : null} +
- +
) } diff --git a/packages/web/src/pages/oauth-login-page/messages.ts b/packages/web/src/pages/oauth-login-page/messages.ts index e0d95b1d715..e1f177666a8 100644 --- a/packages/web/src/pages/oauth-login-page/messages.ts +++ b/packages/web/src/pages/oauth-login-page/messages.ts @@ -18,9 +18,9 @@ export const messages = { signOut: 'Sign Out', signUp: `Don't have an account? Sign up`, switchAccount: 'Switch Account', - authorizeButton: 'Authorize App', + authorizeButton: 'Sign In & Authorize', continueButton: 'Continue', - signInButton: 'Sign In & Authorize App', + signInButton: 'Sign In & Authorize', invalidCredentialsError: 'Invalid Credentials', miscError: 'An error has occurred. Please try again.', accountIncompleteError: @@ -30,7 +30,7 @@ export const messages = { redirectURIInvalidError: 'Whoops, this is an invalid link (redirect URI missing or invalid).', missingAppNameError: 'Whoops, this is an invalid link (app name missing).', - otpError: 'Enter the verification code sent to your email', + otpPrompt: 'Enter the verification code sent to your email', scopeError: `Whoops, this is an invalid link (scope missing or invalid).`, connectWalletNoPostMessageError: 'Whoops, this is an invalid link (redirectUri must be `postMessage` if tx is `connectDashboardWallet`).', @@ -48,7 +48,7 @@ export const messages = { 'Whoops, something went wrong. Please close this window and try again.', responseModeError: 'Whoops, this is an invalid link (response mode invalid - if set, must be "fragment" or "query").', - signedInAs: `You’re signed in as`, + signedInAs: `You’re Signed in as`, missingApiKeyError: 'Whoops, this is an invalid link (app API Key missing)', invalidApiKeyError: 'Whoops, this is an invalid link (app API Key invalid)', approveTxToConnectProfile: diff --git a/packages/web/src/pages/sign-in-page/ConfirmEmailPage.tsx b/packages/web/src/pages/sign-in-page/ConfirmEmailPage.tsx index 9b4ed6fad2b..d380209273b 100644 --- a/packages/web/src/pages/sign-in-page/ConfirmEmailPage.tsx +++ b/packages/web/src/pages/sign-in-page/ConfirmEmailPage.tsx @@ -24,7 +24,6 @@ import { } from 'common/store/pages/signon/selectors' import { HarmonyTextField } from 'components/form-fields/HarmonyTextField' import { ToastContext } from 'components/toast/ToastContext' -import { useMedia } from 'hooks/useMedia' import { Heading, Page, PageFooter } from 'pages/sign-up-page/components/layout' import { useSelector } from 'utils/reducer' @@ -40,7 +39,6 @@ const ConfirmEmailSchema = toFormikValidationSchema(confirmEmailSchema) export const ConfirmEmailPage = () => { const dispatch = useDispatch() - const { isMobile } = useMedia() const { value: email } = useSelector(getEmailField) const { value: password } = useSelector(getPasswordField) const { value: otp } = useSelector(getOtpField) @@ -65,7 +63,7 @@ export const ConfirmEmailPage = () => { onSubmit={handleSubmit} validationSchema={ConfirmEmailSchema} > - + { const dispatch = useDispatch() const { isMobile } = useMedia() - const { height: windowHeight } = useWindowSize() - const isSmallDesktop = windowHeight < smallDesktopWindowHeight const navigate = useNavigateToPage() const [showForgotPassword, setShowForgotPassword] = useState(false) const { value: existingEmail } = useSelector(getEmailField) @@ -72,8 +71,6 @@ export const SignInPage = () => { useEffect(() => { if (requiresOtp) { navigate(SIGN_IN_CONFIRM_EMAIL_PAGE) - // This unsets the otp error so we can come back to this page - // if necessary dispatch(setValueField('password', existingPassword)) } }, [navigate, requiresOtp, existingPassword, dispatch]) @@ -96,6 +93,9 @@ export const SignInPage = () => { [dispatch] ) + const logoHeight = isMobile ? 48 : 56 + const logoWidth = isMobile ? 236 : 275 + return ( <> { validationSchema={SignInSchema} validateOnChange={false} > - - - - {isMobile || isSmallDesktop ? ( - - ) : ( - - )} - - + - + + - + - {!isMobile ? : null} { - setShowForgotPassword(true) - }} + onClick={() => setShowForgotPassword(true)} + > + {messages.forgotPassword} + + + + + {messages.newToAudius}{' '} + + {messages.createAnAccount} + + + + ) : ( + + + + + + + setShowForgotPassword(true)} > - {signInPageMessages.forgotPassword} + {messages.forgotPassword} + + + + + {messages.newToAudius}{' '} + + {messages.createAnAccount} + + + - {!isMobile ? ( - - ) : null} - + )} { const { children } = props - const { spacing, motion, color } = useTheme() + const { spacing } = useTheme() const routeOnExit = useSelector(getRouteOnExit) const location = useLocation() - const hideCloseButton = [ - SIGN_UP_GENRES_PAGE, - SIGN_UP_ARTISTS_PAGE, - SIGN_UP_APP_CTA_PAGE, - SIGN_UP_LOADING_PAGE - ].some((path) => { + const hideCloseButton = [SIGN_UP_LOADING_PAGE].some((path) => { const match = matchPath(path, location.pathname) return match && match.pathname === location.pathname }) - const collapsedDesktopPageMatch = [ + const isCompactCard = [ SIGN_IN_PAGE, SIGN_IN_CONFIRM_EMAIL_PAGE, SIGN_UP_PAGE, SIGN_UP_EMAIL_PAGE, SIGN_UP_PASSWORD_PAGE, - SIGN_UP_REVIEW_HANDLE_PAGE, - SIGN_UP_APP_CTA_PAGE + SIGN_UP_REVIEW_HANDLE_PAGE ].some((path) => { const match = matchPath(path, location.pathname) return match && match.pathname === location.pathname }) - const isExpanded = !collapsedDesktopPageMatch - return ( + {/* Background image + dark overlay */} + + + + {/* Nav overlay: X close (left) + Audius logo (right) - always white in light/dark mode */} {!hideCloseButton ? ( - - - + + + + + ) : null} - - {children} - - - - - } - /> - } - /> - } - /> - } - /> - } - /> - - - + +
) } -type MobileSignOnRootProps = RootProps & { - isLoaded?: boolean -} - -type PanelState = 'collapsed' | 'expanding' | 'expanded' | 'collapsing' +type MobileSignOnRootProps = RootProps const MobileSignOnRoot = (props: MobileSignOnRootProps) => { - const { children, isLoaded } = props - const [panelState, setPanelState] = useState('collapsed') - const { motion } = useTheme() - const [ref, { height: panelHeight }] = useMeasure() - const collapsedPanelHeight = useRef(panelHeight) - - const location = useLocation() - const collapsedMobilePageMatch = [ - SIGN_IN_PAGE, - SIGN_UP_PAGE, - SIGN_UP_EMAIL_PAGE - ].some((path) => { - const match = matchPath(path, location.pathname) - return match && match.pathname === location.pathname - }) - - const shouldPageExpand = !collapsedMobilePageMatch - - useLayoutEffect(() => { - if (panelState === 'collapsed') { - collapsedPanelHeight.current = panelHeight - } - }, [panelState, panelHeight]) - - // TODO: use `onTransitionEnd` - useEffect(() => { - if (shouldPageExpand) { - setPanelState('expanding') - const timeout = setTimeout(() => { - setPanelState('expanded') - }, 100) - return () => clearTimeout(timeout) - } else if (!shouldPageExpand && panelState === 'expanded') { - setPanelState('collapsing') - const timeout = setTimeout(() => { - setPanelState('collapsed') - }, 500) - return () => clearTimeout(timeout) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [shouldPageExpand]) - - const panelHeightMap = { - collapsed: 'auto', - expanding: panelHeight, - expanded: '100%', - collapsing: collapsedPanelHeight.current - } + const { children } = props return ( - - - {children} - + - - - } - /> - - } - /> - - {messages.newToAudius}{' '} - - {messages.createAccount} - - - } - /> - - {messages.newToAudius}{' '} - - {messages.createAccount} - - - } - /> - + + {children} + ) } +const { getSystemAppearance } = themeSelectors + export const SignOnPage = () => { const { isMobile } = useMedia() const signOnStatus = useSelector(getStatus) const routeOnExit = useSelector(getRouteOnExit) + const systemAppearance = useSelector(getSystemAppearance) + const signOnTheme: Theme = + systemAppearance === SystemAppearance.DARK + ? 'default-dark' + : 'default-light' const dispatch = useDispatch() const navigate = useNavigate() const [searchParams] = useSearchParams() @@ -422,23 +287,20 @@ export const SignOnPage = () => { } }) - const [isLoaded, setIsLoaded] = useState(false) const SignOnRoot = isMobile ? MobileSignOnRoot : DesktopSignOnRoot const location = useLocation() const isSignUp = location.pathname.startsWith(SIGN_UP_PAGE) - useEffectOnce(() => { - setIsLoaded(true) - }) - if (signOnStatus === EditingStatus.SUCCESS) { return } return ( - - - {isSignUp ? : } - + + + + {isSignUp ? : } + + ) } diff --git a/packages/web/src/pages/sign-up-page/components/NavHeader.tsx b/packages/web/src/pages/sign-up-page/components/NavHeader.tsx index 418859e458b..95cb1b38f34 100644 --- a/packages/web/src/pages/sign-up-page/components/NavHeader.tsx +++ b/packages/web/src/pages/sign-up-page/components/NavHeader.tsx @@ -1,36 +1,10 @@ -import { ReactNode, useCallback } from 'react' +import { useCallback } from 'react' -import { route } from '@audius/common/utils' -import { - Box, - Flex, - FlexProps, - IconAudiusLogoHorizontal, - IconCaretLeft, - IconCloseAlt, - PlainButton, - useTheme -} from '@audius/harmony' -import { Route, Routes, useMatch, useNavigate } from 'react-router' - -import { getRouteOnExit } from 'common/store/pages/signon/selectors' -import { useMedia } from 'hooks/useMedia' -import { useSelector } from 'utils/reducer' +import { Flex, IconCaretLeft, PlainButton } from '@audius/harmony' +import { useMatch, useNavigate } from 'react-router' import { useDetermineAllowedRoute } from '../utils/useDetermineAllowedRoutes' -import { ProgressHeader } from './ProgressHeader' - -const { - SIGN_IN_PAGE, - SIGN_UP_ARTISTS_PAGE, - SIGN_UP_EMAIL_PAGE, - SIGN_UP_FINISH_PROFILE_PAGE, - SIGN_UP_GENRES_PAGE, - SIGN_UP_HANDLE_PAGE, - SIGN_UP_PAGE -} = route - const useIsBackAllowed = () => { const match = useMatch('/signup/:currentPath') const matchSignIn = useMatch('/signin/:currentPath') @@ -50,171 +24,33 @@ const useIsBackAllowed = () => { return false } -type HeaderRootProps = FlexProps & { - children: ReactNode -} - -const HeaderRoot = (props: HeaderRootProps) => { - const { children, ...other } = props +export const NavHeader = () => { const isBackAllowed = useIsBackAllowed() - const { spacing } = useTheme() + const navigate = useNavigate() + + const handleBack = useCallback(() => { + navigate(-1) + }, [navigate]) + + if (!isBackAllowed) return null return ( - {children} - - ) -} - -export const NavHeader = () => { - const isBackAllowed = useIsBackAllowed() - const navigate = useNavigate() - const { isMobile } = useMedia() - const { iconSizes } = useTheme() - const routeOnExit = useSelector(getRouteOnExit) - - const handleClose = useCallback(() => { - navigate(routeOnExit) - }, [navigate, routeOnExit]) - - const audiusLogo = - - return ( - - - - - ) : null - } - /> - - - - ) : null - } + - - - - ) : null - } - /> - {!isMobile ? ( - <> - - - {audiusLogo} - - - - } - /> - - - {audiusLogo} - - - - } - /> - - - {audiusLogo} - - - - } - /> - - - {audiusLogo} - - - - } - /> - - ) : null} - - {isBackAllowed ? ( - <> - navigate(-1)} - iconLeft={IconCaretLeft} - variant='subdued' - /> - {audiusLogo} - - - ) : ( - - {audiusLogo} - - )} - - } - /> - + ) } diff --git a/packages/web/src/pages/sign-up-page/components/layout.tsx b/packages/web/src/pages/sign-up-page/components/layout.tsx index 2022d714dbc..0fcd9a25257 100644 --- a/packages/web/src/pages/sign-up-page/components/layout.tsx +++ b/packages/web/src/pages/sign-up-page/components/layout.tsx @@ -16,8 +16,6 @@ import { Flex, FlexProps, IconArrowRight, - Paper, - PaperProps, Text } from '@audius/harmony' import styled, { CSSObject } from '@emotion/styled' @@ -187,10 +185,10 @@ type PageFooterProps = { buttonProps?: ButtonProps centered?: boolean sticky?: boolean -} & Omit +} & Omit export const PageFooter = (props: PageFooterProps) => { - const { prefix, postfix, buttonProps, centered, sticky, ...other } = props + const { prefix, postfix, buttonProps, sticky, ...other } = props const { isMobile } = useMedia() // On the MobileCTAPage we use this footer outside a formik context, hence the default values const { isSubmitting, touched, isValid } = useFormikContext() ?? { @@ -203,22 +201,17 @@ export const PageFooter = (props: PageFooterProps) => { const showButtonsSideBySide = !isMobile && postfix && !prefix return ( - @@ -229,7 +222,7 @@ export const PageFooter = (props: PageFooterProps) => { justifyContent='space-between' alignItems='center' gap='l' - css={centered ? { maxWidth: 343 } : undefined} + css={{ maxWidth: '100%' }} > {postfix} - - - {createEmailPageMessages.haveAccount} {signInLink} - - - {!isMobile && window.ethereum ? ( - + {({ isSubmitting }) => + isMobile ? ( + + + + + + + - - - {createEmailPageMessages.metaMaskNotRecommended}{' '} - - {createEmailPageMessages.learnMore} + + {messages.alreadyHaveAccount}{' '} + + {messages.mobileLogIn} - ) : null} - - )} + + ) : ( + + + + + + + + + {messages.alreadyHaveAccount}{' '} + + {messages.logIn} + + + + + + ) + } ) } diff --git a/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx b/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx index 68567d91f56..c7a935f78bf 100644 --- a/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx @@ -8,7 +8,7 @@ import { } from '@audius/common/schemas' import { MAX_DISPLAY_NAME_LENGTH } from '@audius/common/services' import { route } from '@audius/common/utils' -import { Button, Flex, Paper, Text, useTheme } from '@audius/harmony' +import { Flex, Paper, Text, useTheme } from '@audius/harmony' import { Formik, Form, useFormikContext } from 'formik' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router' @@ -25,7 +25,6 @@ import { getCoverPhotoField, getEmailField, getHandleField, - getLinkedSocialOnFirstPage, getNameField, getProfileImageField } from 'common/store/pages/signon/selectors' @@ -34,11 +33,10 @@ import { useMedia } from 'hooks/useMedia' import { AccountHeader } from '../components/AccountHeader' import { ImageFieldValue } from '../components/ImageField' -import { OutOfText } from '../components/OutOfText' import { Heading, Page, PageFooter } from '../components/layout' import { useFastReferral } from '../hooks/useFastReferral' -const { SIGN_UP_GENRES_PAGE, SIGN_UP_LOADING_PAGE } = route +const { SIGN_UP_LOADING_PAGE } = route type FinishProfileValues = { profileImage?: ImageFieldValue @@ -55,11 +53,9 @@ const ImageUploadErrorText = () => { if (errors.coverPhoto === finishProfilePageMessages.coverPhotoUploadError) { errorText = errors.coverPhoto } - // Profile image error takes priority if ( errors.profileImage === finishProfilePageMessages.profileImageUploadError ) { - // If both images have errors, we show a combined error message if (errorText !== undefined) { errorText = finishProfilePageMessages.bothImageUploadError } else { @@ -88,12 +84,10 @@ export const FinishProfilePage = () => { const { value: savedDisplayName } = useSelector(getNameField) const handle = useSelector(getHandleField) const email = useSelector(getEmailField) - const linkedSocialOnFirstPage = useSelector(getLinkedSocialOnFirstPage) const savedCoverPhoto = useSelector(getCoverPhotoField) const savedProfileImage = useSelector(getProfileImageField) const isFastReferral = useFastReferral() - // If the user comes back from a later page we start with whats in the store const initialValues = { profileImage: savedProfileImage || undefined, coverPhoto: savedCoverPhoto || undefined, @@ -136,12 +130,10 @@ export const FinishProfilePage = () => { } dispatch(setFinishedPhase1(true)) if (isFastReferral) { - // Fast referral: create account immediately and skip genre/artist selection dispatch(signUp()) - navigate(SIGN_UP_LOADING_PAGE) + navigate(SIGN_UP_LOADING_PAGE, { replace: true }) } else { - // Normal flow: don't create account yet, let user select genres/artists first - navigate(SIGN_UP_GENRES_PAGE) + navigate('/signup/select-genres', { replace: true }) } }, [dispatch, isFastReferral, navigate] @@ -164,14 +156,9 @@ export const FinishProfilePage = () => { autoFocusInputRef={displayNameInputRef} > - ) - } heading={finishProfilePageMessages.header} description={finishProfilePageMessages.description} - centered={!isMobile} + centered /> { }} /> - navigate(-1)} - > - {finishProfilePageMessages.goBack} - - ) - } - /> + )} diff --git a/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx b/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx index cf892e7d634..63a47f61f9f 100644 --- a/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx @@ -17,7 +17,6 @@ import { useNavigateToPage } from 'hooks/useNavigateToPage' import { restrictedHandles } from 'utils/restrictedHandles' import { HandleField } from '../components/HandleField' -import { OutOfText } from '../components/OutOfText' import { Heading, Page, PageFooter } from '../components/layout' import { useFastReferral } from '../hooks/useFastReferral' @@ -77,7 +76,6 @@ export const PickHandlePage = () => { autoFocusInputRef={handleInputRef} > } heading={pickHandlePageMessages.title} description={pickHandlePageMessages.description} centered={!isMobile} diff --git a/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx b/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx index 9d30fd87d74..6f23b7f3ff2 100644 --- a/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx @@ -29,11 +29,11 @@ import { useNavigateToPage } from 'hooks/useNavigateToPage' import { env } from 'services/env' import { useSelector } from 'utils/reducer' -import { AccountHeader } from '../components/AccountHeader' import { PreviewArtistHint } from '../components/PreviewArtistHint' import { Heading, HiddenLegend, + Page, PageFooter, ScrollView } from '../components/layout' @@ -50,12 +50,10 @@ const initialValues: SelectArtistsValues = { selectedArtists: [] } -// This is a workaround for local dev environments that don't have any artists -// This will ensure any errors set on mount get cleared out const DevModeClearErrors = () => { const { setErrors } = useFormikContext() useEffect(() => { - setErrors({}) // empty all errors + setErrors({}) }, [setErrors]) return null } @@ -67,9 +65,9 @@ export const SelectArtistsPage = () => { const [currentGenre, setCurrentGenre] = useState('Featured') const dispatch = useDispatch() const navigate = useNavigateToPage() - const { color } = useTheme() const headerContainerRef = useRef(null) const { isMobile } = useMedia() + const { color } = useTheme() const handleChangeGenre = useCallback((e: ChangeEvent) => { setCurrentGenre(e.target.value) @@ -79,13 +77,11 @@ export const SelectArtistsPage = () => { (values: SelectArtistsValues) => { const { selectedArtists } = values - // Only add artists if some were selected if (selectedArtists.length > 0) { const artistsIDArray = [...selectedArtists].map((a) => Number(a)) dispatch(addFollowArtists(artistsIDArray)) } - // Always create the account (whether artists selected or not) dispatch(signUp()) if (isMobile) { @@ -116,11 +112,9 @@ export const SelectArtistsPage = () => { ? isFeaturedArtistsPending : isTopArtistsPending - // Note: this doesn't catch when running `web:prod` const isDevEnvironment = env.ENVIRONMENT === 'development' || window.localStorage.getItem('FORCE_DEV') === 'true' - // This a workaround flag for local envs that don't have any artists and get stuck at this screen const noArtistsSkipValidation = (!artists || artists.length === 0) && isDevEnvironment @@ -140,7 +134,11 @@ export const SelectArtistsPage = () => { const artistContent = artists?.length ? ( artists.map((user) => ( - + )) ) : ( @@ -155,52 +153,29 @@ export const SelectArtistsPage = () => { {({ values, isValid, isSubmitting, isValidating }) => { const { selectedArtists } = values return ( - - + {noArtistsSkipValidation && !isValid ? ( ) : undefined} - + { genre as GenreLabel ) return ( - // TODO: max of 6, kebab overflow { pv='xl' ph={isMobile ? 'l' : 'xl'} css={{ - minHeight: 500, + minHeight: 400, minWidth: !isMobile ? 530 : undefined }} direction='column' @@ -253,33 +227,59 @@ export const SelectArtistsPage = () => { gap={isMobile ? 's' : 'm'} wrap='wrap' justifyContent='center' + css={ + !isMobile + ? { + display: 'grid', + gridTemplateColumns: 'repeat(4, 1fr)', + maxWidth: 720, + margin: '0 auto', + width: '100%', + boxSizing: 'border-box' + } + : undefined + } > {isLoading ? range(9).map((index) => ( - + )) : artistContent} - - - {selectArtistsPageMessages.selected}{' '} - {selectedArtists.length || 0}/3 - - - } - /> - + > + + + {selectArtistsPageMessages.selected}{' '} + {selectedArtists.length || 0}/3 + + + } + /> + + ) }} diff --git a/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx b/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx index d2cb795d2f5..2be81dac721 100644 --- a/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx @@ -16,9 +16,8 @@ import { SelectablePillField } from 'components/form-fields/SelectablePillField' import { useMedia } from 'hooks/useMedia' import { useNavigateToPage } from 'hooks/useNavigateToPage' -import { AccountHeader } from '../components/AccountHeader' import { SkipButton } from '../components/SkipButton' -import { Heading, Page, PageFooter, ScrollView } from '../components/layout' +import { Heading, Page, PageFooter } from '../components/layout' const { SIGN_UP_ARTISTS_PAGE } = route @@ -48,7 +47,6 @@ export const SelectGenresPage = () => { (label: string): MouseEventHandler => (e) => { if ((e.target as HTMLInputElement).checked) { - // add to genres list const newGenres = [...currentGenres, label as Genre] dispatch( make(Name.CREATE_ACCOUNT_SELECT_GENRE, { @@ -58,7 +56,6 @@ export const SelectGenresPage = () => { ) setCurrentGenres(newGenres) } else { - // remove from genres list const newGenres = [...currentGenres] const genreIndex = currentGenres.indexOf(label as Genre) newGenres.splice(genreIndex, 1) @@ -67,57 +64,49 @@ export const SelectGenresPage = () => { } return ( - - - - {() => ( - + {() => ( + + + - - - {selectableGenres.map((genre) => { - const { label, value } = genre - return ( - - ) - })} - + {selectableGenres.map((genre) => { + const { label, value } = genre + return ( + + ) + })} - } /> - - )} - - + + } /> + + )} + ) } diff --git a/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts b/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts index 4747ae21bfe..f3036210430 100644 --- a/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts +++ b/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts @@ -54,7 +54,11 @@ export const useDetermineAllowedRoute = () => { correctedRoute: FEED_PAGE } } - const attemptedPath = requestedRoute.toString().replace('/signup/', '') + // Normalize path: strip /signup/ or signup/ prefix and trailing slash so "select-genres" always matches + const attemptedPath = requestedRoute + .toString() + .replace(/^\/?signup\/?/, '') + .replace(/^\/|\/$/g, '') // Have to type as string[] to avoid too narrow of a type for comparing against let allowedRoutes: string[] = [SignUpPath.createEmail] @@ -124,18 +128,36 @@ export const useDetermineAllowedRoute = () => { } } - const isAllowedRoute = allowedRoutes.includes(attemptedPath) - // If requested route is allowed return that, otherwise return the last step in the route stack - const correctedPath = + let isAllowedRoute = allowedRoutes.includes(attemptedPath) + // When past account phase, ensure select-genres is always allowed so we never redirect to a later step + if ( + pastAccountPhase && + attemptedPath === SignUpPath.selectGenres && + !isAllowedRoute + ) { + isAllowedRoute = true + } + // If requested route is allowed return that, otherwise return the appropriate step + let correctedPath = attemptedPath === '/signup' && hasAlreadySignedUp ? allowedRoutes[allowedRoutes.length - 1] : isAllowedRoute ? attemptedPath - : // IF we attempted to go to /signup directly, that means it was a link from somewhere else in the app, so we should start back at the beginning - attemptedPath === '/signup' + : attemptedPath === 'signup' || attemptedPath === '' ? allowedRoutes[0] : allowedRoutes[allowedRoutes.length - 1] + // After finish-profile we must show select-genres before select-artists: if we're past account phase + // and would redirect to select-artists but user hasn't done genres yet, send them to select-genres + if ( + pastAccountPhase && + !isAllowedRoute && + correctedPath === SignUpPath.selectArtists && + !(signUpState.genres && signUpState.genres.length > 0) + ) { + correctedPath = SignUpPath.selectGenres + } + if (correctedPath === SignUpPath.completedRedirect) { setIsWelcomeModalOpen(true) }