diff --git a/src/components/activity/ActivityListItem.tsx b/src/components/activity/ActivityListItem.tsx index e08ee3b99..c18271963 100644 --- a/src/components/activity/ActivityListItem.tsx +++ b/src/components/activity/ActivityListItem.tsx @@ -41,7 +41,7 @@ export const ActivityListItem: React.FC = ({ const context = useLanguageHeaderContext(); const { t } = useTranslation(); const { profileUrl } = useAuthContext(); - const avatarThumbnail = R.prop('avatarThumbnail', causingUser); + const avatarThumbnail = R.propOr('', 'avatarThumbnail', causingUser); const onCompleted = ({ markActivityAsRead }: MarkActivityAsReadMutation): void => { if (markActivityAsRead?.errors?.length) { diff --git a/src/components/dialogs/CustomInviteDialog.tsx b/src/components/dialogs/CustomInviteDialog.tsx index b18aa7100..9e59e41f8 100644 --- a/src/components/dialogs/CustomInviteDialog.tsx +++ b/src/components/dialogs/CustomInviteDialog.tsx @@ -9,10 +9,8 @@ import ArrowForwardOutlined from '@material-ui/icons/ArrowForwardOutlined'; import FileCopyOutlined from '@material-ui/icons/FileCopyOutlined'; import { useAuthContext, useNotificationsContext, useShareContext } from 'context'; import { useTranslation } from 'lib'; -import { useRouter } from 'next/router'; import React, { useCallback, useMemo } from 'react'; import { useMediaQueries } from 'styles'; -import { ShareDialogParams } from 'types'; import { SLOGAN } from 'utils'; import { DialogHeader } from './DialogHeader'; @@ -34,8 +32,8 @@ interface Props { header?: JSX.Element | string; dynamicContent: JSX.Element[]; handleClose: () => void; - shareDialogParams?: ShareDialogParams; hideInviteCode?: boolean; + handleClickInviteButton?: () => void; } export const CustomInviteDialog: React.FC = ({ @@ -43,27 +41,28 @@ export const CustomInviteDialog: React.FC = ({ header, dynamicContent, handleClose, - shareDialogParams: _shareDialogParams, hideInviteCode, + handleClickInviteButton: _handleClickInviteButton, }) => { const classes = useStyles(); const { t } = useTranslation(); const { inviteCode } = useAuthContext(); - const { asPath } = useRouter(); const { mdUp } = useMediaQueries(); const { handleOpenShareDialog } = useShareContext(); const { toggleNotification } = useNotificationsContext(); const { username } = useAuthContext(); + const title = t('common:inviteTitle', { username }); + const text = SLOGAN; + const customLink = `${process.env.FRONTEND_URL}?code=${inviteCode}`; const shareDialogParams = useMemo( - () => - _shareDialogParams || { - header: t('common:inviteShareDialogHeader'), - title: t('common:inviteTitle', { username }), - text: SLOGAN, - linkSuffix: `?code=${inviteCode}`, - }, - [t, inviteCode, _shareDialogParams, username], + () => ({ + header: t('common:inviteShareDialogHeader'), + title, + text, + customLink, + }), + [t, text, title, customLink], ); const handleClickInviteButton = useCallback(async (): Promise => { @@ -72,21 +71,20 @@ export const CustomInviteDialog: React.FC = ({ handleOpenShareDialog(shareDialogParams); } else { const { navigator } = window; - const { title, text, linkSuffix } = shareDialogParams; if (navigator?.share) { try { await navigator.share({ title, text, - url: `${process.env.FRONTEND_URL}${asPath}${linkSuffix}`, + url: customLink, }); } catch { // User cancelled. } } } - }, [asPath, handleOpenShareDialog, mdUp, handleClose, shareDialogParams]); + }, [handleOpenShareDialog, mdUp, handleClose, shareDialogParams, customLink, text, title]); const handleClickCopyCodeButton = useCallback((): void => { toggleNotification(t('common:inviteCodeCopied')); @@ -128,14 +126,14 @@ export const CustomInviteDialog: React.FC = ({ () => ( ), - [handleClickInviteButton, t], + [handleClickInviteButton, t, _handleClickInviteButton], ); return ( diff --git a/src/components/dialogs/SkoleDialog.tsx b/src/components/dialogs/SkoleDialog.tsx index 49b840099..a79ed1a9b 100644 --- a/src/components/dialogs/SkoleDialog.tsx +++ b/src/components/dialogs/SkoleDialog.tsx @@ -35,7 +35,7 @@ export const SkoleDialog: React.FC = ({ ...props }) => { const classes = useStyles(); - const { smDown, smUp } = useMediaQueries(); + const { smDown, mdUp } = useMediaQueries(); const PaperProps = { className: clsx( @@ -50,7 +50,7 @@ export const SkoleDialog: React.FC = ({ e.stopPropagation()} fullScreen={fullScreen === false ? fullScreen : smDown} - fullWidth={smUp} + fullWidth={mdUp} TransitionComponent={Transition} PaperProps={PaperProps} {...props} diff --git a/src/components/layout/TopNavbar.tsx b/src/components/layout/TopNavbar.tsx index 2d54d3997..bf53e5bb3 100644 --- a/src/components/layout/TopNavbar.tsx +++ b/src/components/layout/TopNavbar.tsx @@ -23,6 +23,7 @@ import HowToRegOutlined from '@material-ui/icons/HowToRegOutlined'; import LaunchOutlined from '@material-ui/icons/LaunchOutlined'; import NotificationsOutlined from '@material-ui/icons/NotificationsOutlined'; import StarBorderOutlined from '@material-ui/icons/StarBorderOutlined'; +import clsx from 'clsx'; import { useAuthContext, useDarkModeContext, useInviteContext } from 'context'; import { useTranslation } from 'lib'; import React, { MouseEvent, useCallback, useMemo, useState } from 'react'; @@ -63,6 +64,9 @@ const useStyles = makeStyles(({ breakpoints, spacing }) => ({ paddingRight: spacing(4), }, }, + buttonSpacing: { + marginLeft: spacing(1), + }, activityPopper: { zIndex: 3, // Overlap top navbar. }, @@ -84,6 +88,7 @@ export const TopNavbar: React.FC = ({ hideBackButton, hideSearch, hideDynamicButtons, + hideDynamicAuthButtons, hideLoginButton, hideRegisterButton, hideGetStartedButton, @@ -102,6 +107,7 @@ export const TopNavbar: React.FC = ({ const dense = !!renderHeaderLeft || !!renderHeaderRightSecondary; const [activityPopperOpen, setActivityPopperOpen] = useState(false); const { handleOpenGeneralInviteDialog } = useInviteContext(); + const { verified } = useAuthContext(); const { userMe, @@ -144,20 +150,21 @@ export const TopNavbar: React.FC = ({ const renderLogo = useMemo(() => !hideLogo && , [hideLogo]); - const renderLanguageButton = useMemo(() => !hideLanguageButton && , [ - hideLanguageButton, - ]); + const renderLanguageButton = useMemo( + () => !hideLanguageButton && , + [hideLanguageButton, classes.buttonSpacing], + ); const renderDarkModeButton = useMemo( () => !hideDarkModeButton && ( - + {darkMode ? : } ), - [darkMode, hideDarkModeButton, t, toggleDarkMode], + [darkMode, hideDarkModeButton, t, toggleDarkMode, classes.buttonSpacing], ); const renderMobileContent = useMemo( @@ -192,14 +199,18 @@ export const TopNavbar: React.FC = ({ const renderActivityButton = useMemo( () => ( - + ), - [handleActivityButtonClick, t, unreadActivityCount], + [handleActivityButtonClick, t, unreadActivityCount, classes.buttonSpacing], ); const renderActivityPopper = useMemo( @@ -267,57 +278,101 @@ export const TopNavbar: React.FC = ({ [rank, renderRankEmoji, renderScore], ); + const renderInviteButton = useMemo( + () => + !!verified && ( + + + + + + + + ), + [handleOpenGeneralInviteDialog, inviteCodeUsages, t, verified, classes.buttonSpacing], + ); + + const renderActivity = useMemo( + () => + !!verified && ( + + + {renderActivityButton} + {renderActivityPopper} + + + ), + [renderActivityButton, renderActivityPopper, verified], + ); + + const renderStarButton = useMemo( + () => + !!verified && ( + + + + ), + [verified, t, classes.buttonSpacing], + ); + + const renderRank = useMemo( + () => ( + + + + + + ), + [t, rank, renderRankLabel, score, classes.buttonSpacing], + ); + + const renderAvatar = useMemo( + () => ( + + + + + + + + + + ), + [avatarThumbnail, profileUrl, t, classes.buttonSpacing], + ); + const renderAuthenticatedButtons = useMemo( () => !!userMe && - !hideDynamicButtons && ( + !hideDynamicButtons && + !hideDynamicAuthButtons && ( <> - - - - - - - - - - {renderActivityButton} - {renderActivityPopper} - - - - - - - - - - - - - - - - - - - + {renderInviteButton} + {renderActivity} + {renderStarButton} + {renderRank} + {renderAvatar} ), [ - avatarThumbnail, + hideDynamicAuthButtons, hideDynamicButtons, - profileUrl, - rank, - renderActivityButton, - renderActivityPopper, - renderRankLabel, - score, - t, + renderActivity, + renderAvatar, + renderInviteButton, + renderRank, + renderStarButton, userMe, - inviteCodeUsages, - handleOpenGeneralInviteDialog, ], ); diff --git a/src/components/shared/LanguageButton.tsx b/src/components/shared/LanguageButton.tsx index 2332a4d30..9b619b6fe 100644 --- a/src/components/shared/LanguageButton.tsx +++ b/src/components/shared/LanguageButton.tsx @@ -1,4 +1,4 @@ -import IconButton from '@material-ui/core/IconButton'; +import IconButton, { IconButtonProps } from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import { useLanguageContext } from 'context'; import { useTranslation } from 'lib'; @@ -7,7 +7,7 @@ import { useMediaQueries } from 'styles'; import { LanguageFlag } from './LanguageFlag'; -export const LanguageButton: React.FC = () => { +export const LanguageButton: React.FC = (props) => { const { t, lang } = useTranslation(); const { handleOpenLanguageMenu } = useLanguageContext(); const { smDown } = useMediaQueries(); @@ -15,7 +15,7 @@ export const LanguageButton: React.FC = () => { return ( - + diff --git a/src/components/shared/TopNavbarSearchWidget.tsx b/src/components/shared/TopNavbarSearchWidget.tsx index 96ca18119..6cc4db829 100644 --- a/src/components/shared/TopNavbarSearchWidget.tsx +++ b/src/components/shared/TopNavbarSearchWidget.tsx @@ -15,7 +15,6 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ borderRadius: BORDER_RADIUS, color: palette.common.white, padding: `${spacing(1)} ${spacing(3)}`, - marginRight: spacing(2), '&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.25)', }, diff --git a/src/components/templates/FormTemplate.tsx b/src/components/templates/FormTemplate.tsx index faeb74f5e..72994a403 100644 --- a/src/components/templates/FormTemplate.tsx +++ b/src/components/templates/FormTemplate.tsx @@ -32,7 +32,7 @@ export const FormTemplate: React.FC = ({ ...props }) => { const classes = useStyles(); - const { smUp } = useMediaQueries(); + const { mdUp } = useMediaQueries(); const { userMe } = useAuthContext(); const header = topNavbarProps?.header; const emoji = topNavbarProps?.emoji; @@ -51,7 +51,7 @@ export const FormTemplate: React.FC = ({ const renderHeader = useMemo( () => - smUp && ( + mdUp && ( = ({ title={renderHeaderTitle} /> ), - [classes.cardHeaderRoot, classes.cardHeaderTitle, renderHeaderTitle, smUp], + [classes.cardHeaderRoot, classes.cardHeaderTitle, renderHeaderTitle, mdUp], ); const renderForm = useMemo( diff --git a/src/components/templates/MarkdownTemplate.tsx b/src/components/templates/MarkdownTemplate.tsx index 87776a40a..2c11c0b75 100644 --- a/src/components/templates/MarkdownTemplate.tsx +++ b/src/components/templates/MarkdownTemplate.tsx @@ -55,7 +55,7 @@ export const MarkdownTemplate: React.FC = ({ ...props }) => { const classes = useStyles(); - const { smUp } = useMediaQueries(); + const { mdUp } = useMediaQueries(); const { t } = useTranslation(); const { userMe } = useAuthContext(); const header = topNavbarProps?.header; @@ -75,7 +75,7 @@ export const MarkdownTemplate: React.FC = ({ const renderCardHeader = useMemo( () => - smUp && ( + mdUp && ( = ({ title={renderHeaderTitle} /> ), - [classes.cardHeaderRoot, classes.cardHeaderTitle, renderHeaderTitle, smUp], + [classes.cardHeaderRoot, classes.cardHeaderTitle, renderHeaderTitle, mdUp], ); const renderCustomTopContent = useMemo(() => customTopContent?.map((t) => t), [customTopContent]); diff --git a/src/components/thread/CommentCard.tsx b/src/components/thread/CommentCard.tsx index ca96998e5..87eb14a52 100644 --- a/src/components/thread/CommentCard.tsx +++ b/src/components/thread/CommentCard.tsx @@ -135,7 +135,7 @@ export const CommentCard: React.FC = ({ comment, onCommentDeleted, topCom const { confirm } = useConfirmContext(); const { handleOpenActionsDialog } = useActionsContext(); const { verified } = useAuthContext(); - const avatarThumbnail = R.prop('avatarThumbnail', comment.user); + const avatarThumbnail = R.propOr('', 'avatarThumbnail', comment.user); const initialVote = R.prop('vote', comment); const initialScore = R.prop('score', comment); const commentId = R.prop('id', comment); diff --git a/src/components/thread/CommentTextField.tsx b/src/components/thread/CommentTextField.tsx index 2474ed2d0..5d88957ad 100644 --- a/src/components/thread/CommentTextField.tsx +++ b/src/components/thread/CommentTextField.tsx @@ -9,12 +9,12 @@ import { TextFormField } from '../form-fields'; export const CommentTextField = ( props: FormikProps & TextFieldProps, ): JSX.Element => { - const { smUp } = useMediaQueries(); + const { mdUp } = useMediaQueries(); const { formRef } = useThreadContext(); // On desktop, submit form from enter key and add new line from Shift + Enter. const handleKeydown = (e: KeyboardEvent): void => { - if (smUp && e.code === 'Enter' && !e.shiftKey) { + if (mdUp && e.code === 'Enter' && !e.shiftKey) { e.preventDefault(); formRef.current?.submitForm(); } diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 4957f3c84..0c3dd94ba 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -47,7 +47,7 @@ export const useAuthContext = (): UseAuthContext => { const unreadActivityCount = R.propOr(0, 'unreadActivityCount', userMe); const id = R.prop('id', userMe); const slug = R.prop('slug', userMe); - const fcmTokens = R.prop('fcmTokens', userMe); + const fcmTokens = R.propOr([], 'fcmTokens', userMe); const username = R.prop('username', userMe); const email = R.prop('email', userMe); const backupEmail = R.prop('backupEmail', userMe); diff --git a/src/hocs/withUserMe.tsx b/src/hocs/withUserMe.tsx index 3718f1f18..41dd47e08 100644 --- a/src/hocs/withUserMe.tsx +++ b/src/hocs/withUserMe.tsx @@ -35,7 +35,7 @@ export const withUserMe = >( case 'REGISTER_FCM_TOKEN': { const { token } = json; - if (!!fcmTokens && !fcmTokens.includes(token)) { + if (!fcmTokens.includes(token)) { await registerFcmToken({ variables: { token } }); } diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 34ba8deca..98099d78f 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,4 +1,5 @@ import { ErrorTemplate } from 'components'; +import { withUserMe } from 'hocs'; import { loadNamespaces } from 'lib'; import { GetStaticProps, NextPage } from 'next'; import React from 'react'; @@ -11,4 +12,4 @@ export const getStaticProps: GetStaticProps = async ({ locale }) => ({ }, }); -export default NotFoundPage; +export default withUserMe(NotFoundPage); diff --git a/src/pages/_error.tsx b/src/pages/_error.tsx index 735c8c343..c4035cd7c 100644 --- a/src/pages/_error.tsx +++ b/src/pages/_error.tsx @@ -1,4 +1,5 @@ import { ErrorTemplate } from 'components'; +import { withUserMe } from 'hocs'; import { loadNamespaces } from 'lib'; import { GetStaticProps, NextPage } from 'next'; import React from 'react'; @@ -11,4 +12,4 @@ export const getStaticProps: GetStaticProps = async ({ locale }) => ({ }, }); -export default ErrorPage; +export default withUserMe(ErrorPage); diff --git a/src/pages/home.tsx b/src/pages/home.tsx index dc5f98fb4..1a1963ac4 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -115,40 +115,50 @@ const HomePage: NextPage = () => { }, [handleCloseCustomInviteDialog]); const renderInviteButton = useMemo( - () => ( - - - - - - ), - [handleOpenGeneralInviteDialog, inviteCodeUsages], + () => + !!verified && ( + + + + + + ), + [handleOpenGeneralInviteDialog, inviteCodeUsages, verified], ); const renderStarredButton = useMemo( - () => ( - - ), - [], + () => + !!verified && ( + + ), + [verified], ); const renderCreateThread = useMemo( () => mdUp && ( - handleOpenThreadForm()}> + { + e.target.blur(); + handleOpenThreadForm(); + }} className={classes.createThreadInputBase} fullWidth /> - diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 3c3d0dcbb..92a1f4296 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -391,6 +391,7 @@ const RegisterPage: NextPage = () => { hideSearch: true, hideRegisterButton: true, hideGetStartedButton: true, + hideDynamicAuthButtons: true, }, }; diff --git a/src/pages/reset-password.tsx b/src/pages/reset-password.tsx index e148fed01..e33d36636 100644 --- a/src/pages/reset-password.tsx +++ b/src/pages/reset-password.tsx @@ -1,6 +1,12 @@ import FormControl from '@material-ui/core/FormControl'; import Typography from '@material-ui/core/Typography'; -import { ActionRequiredTemplate, FormSubmitSection, FormTemplate, TextFormField } from 'components'; +import { + ActionRequiredTemplate, + FormSubmitSection, + FormTemplate, + PasswordField, + TextFormField, +} from 'components'; import { useAuthContext, useNotificationsContext } from 'context'; import { Field, Form, Formik, FormikProps } from 'formik'; import { @@ -138,26 +144,16 @@ const ResetPasswordPage: NextPage = () => { [resetPassword, token], ); - const renderNewPasswordField = useMemo( - () => ( - + const renderNewPasswordField = useCallback( + (props: FormikProps) => ( + ), [t], ); - const renderConfirmNewPasswordField = useMemo( - () => ( - + const renderConfirmNewPasswordField = useCallback( + (props: FormikProps) => ( + ), [t], ); @@ -172,9 +168,9 @@ const ResetPasswordPage: NextPage = () => { const renderPasswordFormFields = useCallback( (props: FormikProps): JSX.Element => (
- {renderNewPasswordField} - {renderConfirmNewPasswordField} - {renderFormSubmitSection(props)} + {renderNewPasswordField(props)} + {renderConfirmNewPasswordField(props)} + {renderFormSubmitSection(props)} ), [renderConfirmNewPasswordField, renderFormSubmitSection, renderNewPasswordField], diff --git a/src/pages/score.tsx b/src/pages/score.tsx index d06c6b88c..fc608b134 100644 --- a/src/pages/score.tsx +++ b/src/pages/score.tsx @@ -9,10 +9,10 @@ import { MarkdownPageProps } from 'types'; const ScorePage: NextPage = ({ data: { title }, content: markdownContent }) => { const { t } = useTranslation(); - const { smUp } = useMediaQueries(); + const { mdUp } = useMediaQueries(); // The emoji won't stand out from the top navbar on mobile. - const emoji = smUp && '💯'; + const emoji = mdUp && '💯'; const layoutProps = { seoProps: { diff --git a/src/pages/search.tsx b/src/pages/search.tsx index b3cc8914a..eac3ec98a 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -105,7 +105,7 @@ const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ const SearchPage: NextPage = () => { const classes = useStyles(); - const { smUp } = useMediaQueries(); + const { mdUp } = useMediaQueries(); const { t } = useTranslation(); const { pathname, query } = useRouter(); const variables = R.pick(['searchTerm', 'page', 'pageSize'], query); @@ -211,7 +211,7 @@ const SearchPage: NextPage = () => { const renderHeader = useMemo( () => - smUp && ( + mdUp && (
{ classes.searchForm, classes.searchInputBase, handleSubmitSearch, - smUp, + mdUp, searchInputValue, t, renderSearchInputEndAdornment, diff --git a/src/pages/threads/[slug].tsx b/src/pages/threads/[slug].tsx index 74647e8be..8454a74f3 100644 --- a/src/pages/threads/[slug].tsx +++ b/src/pages/threads/[slug].tsx @@ -404,7 +404,7 @@ const ThreadPage: NextPage = () => { [t, title, creatorUsername, commentCount], ); - const handleShareButtonClick = useCallback((): void => handleOpenShareDialog(shareDialogParams), [ + const handleClickShareButton = useCallback((): void => handleOpenShareDialog(shareDialogParams), [ shareDialogParams, handleOpenShareDialog, ]); @@ -414,7 +414,7 @@ const ThreadPage: NextPage = () => { @@ -422,7 +422,7 @@ const ThreadPage: NextPage = () => { ), - [t, handleShareButtonClick, classes.headerActionItem, smDown], + [t, handleClickShareButton, classes.headerActionItem, smDown], ); const actionsDialogParams = useMemo( @@ -483,11 +483,11 @@ const ThreadPage: NextPage = () => { const renderMobileShareButton = useMemo( () => ( - + ), - [handleShareButtonClick], + [handleClickShareButton], ); // Only render for verified user who are not owners. @@ -861,7 +861,7 @@ const ThreadPage: NextPage = () => { handleClose={handleCloseCustomInviteDialog} header={renderInviteDialogHeader} dynamicContent={[renderInviteDialogText]} - shareDialogParams={shareDialogParams} + handleClickInviteButton={handleClickShareButton} hideInviteCode /> ), @@ -870,7 +870,7 @@ const ThreadPage: NextPage = () => { renderInviteDialogText, customInviteDialogOpen, handleCloseCustomInviteDialog, - shareDialogParams, + handleClickShareButton, ], ); diff --git a/src/styles.ts b/src/styles.ts index 8928be8df..2419f77f9 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -19,25 +19,15 @@ export const COLORS = { interface UseMediaQueries { smDown: boolean; - smUp: boolean; - mdDown: boolean; mdUp: boolean; - lgUp: boolean; } export const useMediaQueries = (): UseMediaQueries => { const { breakpoints } = useTheme(); const smDown = useMediaQuery(breakpoints.down('sm')); - const smUp = useMediaQuery(breakpoints.up('sm')); - const mdDown = useMediaQuery(breakpoints.down('md')); - const mdUp = useMediaQuery(breakpoints.up('md')); - const lgUp = useMediaQuery(breakpoints.up('lg')); return { smDown, - smUp, - mdDown, - mdUp, - lgUp, + mdUp: !smDown, }; }; diff --git a/src/types/layout.ts b/src/types/layout.ts index a86a6d20b..0b1981b19 100644 --- a/src/types/layout.ts +++ b/src/types/layout.ts @@ -12,7 +12,8 @@ export interface TopNavbarProps { hideBackButton?: boolean; // Hide the back button on mobile. hideLogo?: boolean; // Hide the logo. hideSearch?: boolean; // Hide search field on desktop. - hideDynamicButtons?: boolean; // Hide all dynamic buttons (Login, Register, Get Started, Activity, Starred, Profile) on desktop. + hideDynamicButtons?: boolean; // Hide all dynamic buttons (Login, Register, Get Started, Invite, Activity, Starred, Rank, Profile) on desktop. + hideDynamicAuthButtons?: boolean; // Hide all dynamic buttons for authenticated users (Invite, Activity, Starred, Rank, Profile) on desktop. hideLoginButton?: boolean; // Hide the `Login` button on desktop. hideRegisterButton?: boolean; // Hide the `Register` button on desktop. hideGetStartedButton?: boolean; // Hide the `Get Started` button on desktop.