diff --git a/backend/src/index.ts b/backend/src/index.ts index d7e78f8d8..561e8c020 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -333,7 +333,7 @@ db().then(() => { if (user) { res.status(200).send(user.toJson()); - } else { + } else if (!config.DISABLE_ANONYMOUS_LOGIN) { const anonUser = await registerAnonymousUser( generateUsername() + '^' + v4(), v4() @@ -353,6 +353,8 @@ db().then(() => { } else { res.status(401).send('Not logged in'); } + } else { + res.status(403).send('Cannot login anonymously'); } }); diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx index e3cbff4ce..cb14233ad 100644 --- a/frontend/src/Layout.tsx +++ b/frontend/src/Layout.tsx @@ -1,33 +1,15 @@ -import { useEffect, useCallback, lazy, Suspense } from 'react'; -import { - useNavigate, - Routes, - Route, - useLocation, - useMatch, -} from 'react-router-dom'; +import { useEffect, lazy, Suspense } from 'react'; +import { Routes, Route, useLocation } from 'react-router-dom'; import { trackPageView } from './track'; -import styled from '@emotion/styled'; -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import IconButton from '@mui/material/IconButton'; -import Typography from '@mui/material/Typography'; -import MenuIcon from '@mui/icons-material/Menu'; -import AccountMenu from './auth/AccountMenu'; import useIsCompatibleBrowser from './hooks/useIsCompatibleBrowser'; import OutdatedBrowser from './components/OutdatedBrowser'; -import useIsInitialised from './auth/useIsInitialised'; import useUser from './auth/useUser'; -import { HomeOutlined } from '@mui/icons-material'; -import ProPill from './components/ProPill'; import { CodeSplitLoader } from './CodeSplitLoader'; -import useSidePanel from './views/panel/useSidePanel'; -import { Alert, AlertTitle, Button, Hidden } from '@mui/material'; +import { Alert, AlertTitle } from '@mui/material'; import useBackendCapabilities from './global/useBackendCapabilities'; -import useIsPro from 'auth/useIsPro'; -import ProButton from 'components/ProButton'; import { useTranslation } from 'react-i18next'; import { Welcome } from 'views/Welcome'; +import { Header } from 'views/layout/Header'; const Home = lazy(() => import('./views/Home')); const Game = lazy(() => import('./views/Game')); @@ -40,28 +22,18 @@ const SubscribePageOuter = lazy( ); const ResetPasswordPage = lazy(() => import('./views/Reset')); const ValidatePage = lazy(() => import('./views/Validate')); -const Invite = lazy(() => import('./views/layout/Invite')); const Panel = lazy(() => import('./views/Panel')); const EncryptionDoc = lazy(() => import('./views/home/Encryption')); const AdminPage = lazy(() => import('./views/admin/AdminPage')); const Demo = lazy(() => import('./views/Demo')); -const Title = styled(Typography)` - color: white; -`; - function App() { - const navigate = useNavigate(); const backend = useBackendCapabilities(); const isCompatible = useIsCompatibleBrowser(); - const { toggle: togglePanel } = useSidePanel(); - const isInitialised = useIsInitialised(); const user = useUser(); - const isPro = useIsPro(); - const displayGoPro = !isPro && user && user.accountType !== 'anonymous'; - const goToHome = useCallback(() => navigate('/'), [navigate]); + const location = useLocation(); - const isOnGamePage = !!useMatch('game/:gameId/*'); + const { t } = useTranslation(); // Tracks page views on every location change @@ -87,58 +59,7 @@ function App() { administration panel. ) : null} - - - - - - - - - - - - Retrospected  - - - - - {displayGoPro ? ( - - - - - - - - ) : null} - - {isOnGamePage ? : null} - {isInitialised ? ( - - ) : ( - {t('Main.loading')} - )} - - +
}> : } /> @@ -162,28 +83,4 @@ function App() { ); } -const MainTitle = styled(Title)` - cursor: pointer; - margin-right: 10px; - @media screen and (max-width: 600px) { - display: none; - } -`; - -const HomeButton = styled.div` - margin-right: 10px; -`; - -const ProPillContainer = styled.div``; - -const GoProContainer = styled.div` - margin-left: 20px; -`; - -const Initialising = styled.div``; - -const Spacer = styled.div` - flex: 1; -`; - export default App; diff --git a/frontend/src/auth/AccountMenu.tsx b/frontend/src/auth/AccountMenu.tsx index 95443413a..be691e5dc 100644 --- a/frontend/src/auth/AccountMenu.tsx +++ b/frontend/src/auth/AccountMenu.tsx @@ -11,7 +11,13 @@ import UserContext from './Context'; import Avatar from '../components/Avatar'; import { useMatch, useNavigate } from 'react-router-dom'; import { Key, Logout, Star } from '@mui/icons-material'; -import { colors, Divider, ListItemIcon, ListItemText } from '@mui/material'; +import { + Chip, + colors, + Divider, + ListItemIcon, + ListItemText, +} from '@mui/material'; import AccountCircle from '@mui/icons-material/AccountCircle'; import useIsAdmin from './useIsAdmin'; import { useTranslation } from 'react-i18next'; @@ -74,6 +80,11 @@ const AccountMenu = () => { > {user.name} + + {user.accountType === 'anonymous' ? ( + + ) : null} + {menuAnchor.current ? ( @@ -160,4 +171,10 @@ const DisplayName = styled.div` overflow: hidden; `; +const ChipContainer = styled.div` + @media screen and (max-width: 700px) { + display: none; + } +`; + export default AccountMenu; diff --git a/frontend/src/auth/modal/LoginContent.tsx b/frontend/src/auth/modal/LoginContent.tsx index 9500fb9c9..50f88a214 100644 --- a/frontend/src/auth/modal/LoginContent.tsx +++ b/frontend/src/auth/modal/LoginContent.tsx @@ -10,25 +10,26 @@ import { Alert } from '@mui/material'; import styled from '@emotion/styled'; interface LoginContentProps { - allowAnonymous?: boolean; onClose: () => void; } -export default function LoginContent({ - onClose, - allowAnonymous = true, -}: LoginContentProps) { +export default function LoginContent({ onClose }: LoginContentProps) { const { any } = useOAuthAvailabilities(); const { disableAnonymous, disablePasswords } = useBackendCapabilities(); const hasNoSocialMediaAuth = !any; const hasNoWayOfLoggingIn = hasNoSocialMediaAuth && disableAnonymous && disablePasswords; + const hasNoWayOtherThanAnonymous = hasNoSocialMediaAuth && disablePasswords; const { t } = useTranslation(); const { setUser } = useContext(UserContext); + if (hasNoWayOfLoggingIn) { + {t('AuthCommon.noAuthWarning')}; + } + return ( <> - {hasNoWayOfLoggingIn ? ( + {hasNoWayOtherThanAnonymous ? ( {t('AuthCommon.noAuthWarning')} ) : ( <> @@ -37,9 +38,11 @@ export default function LoginContent({ {!hasNoSocialMediaAuth ? ( ) : null} - - {t('AuthCommon.or')} - + {!hasNoSocialMediaAuth && !disablePasswords ? ( + + {t('AuthCommon.or')} + + ) : null} {!disablePasswords ? ( ) : null} diff --git a/frontend/src/components/LanguagePicker.tsx b/frontend/src/components/LanguagePicker.tsx index 96c5f4d11..2dd6cb307 100644 --- a/frontend/src/components/LanguagePicker.tsx +++ b/frontend/src/components/LanguagePicker.tsx @@ -9,12 +9,14 @@ import { Flag } from './Flag'; interface LanguagePickerProps { value: string; variant?: 'outlined' | 'standard' | 'filled'; + small?: boolean; onChange: (value: string) => void; } const LanguagePicker = ({ value, variant = 'standard', + small, onChange, }: LanguagePickerProps) => { const handleSelect = useCallback( @@ -29,6 +31,9 @@ const LanguagePicker = ({ value={value} onChange={handleSelect} variant={variant} + small={small} + size={small ? 'small' : 'medium'} + disableUnderline={small} data-cy="language-picker" > {languages.map((language) => ( @@ -39,10 +44,12 @@ const LanguagePicker = ({ > - - {language.name} - {language.englishName} - + {!small ? ( + + {language.name} + {language.englishName} + + ) : null} ))} @@ -50,8 +57,8 @@ const LanguagePicker = ({ ); }; -const StyledSelect = styled(Select)` - width: 250px; +const StyledSelect = styled(Select)<{ small?: boolean }>` + width: ${(props) => (props.small ? '100px' : '250px;')}; `; const LanguageItem = styled.div` diff --git a/frontend/src/translations/locales/ar-SA.json b/frontend/src/translations/locales/ar-SA.json index 3b9056345..b7d013c9f 100644 --- a/frontend/src/translations/locales/ar-SA.json +++ b/frontend/src/translations/locales/ar-SA.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "مرحبا، {{name}}", - "howDoesThatWork": "كيف يعمل ذلك؟" + "anonWarning": "أنت تستخدم حساب مجهول. قد ترغب في تسجيل الدخول للتأكد من أن البيانات الخاصة بك يمكن استرجاعها عند استخدام جهاز آخر.", + "howDoesThatWork": "كيف يعمل ذلك؟", + "login": "تسجيل الدخول" }, "PreviousGame": { "createdBy": "تم إنشاؤها بواسطة", diff --git a/frontend/src/translations/locales/de-DE.json b/frontend/src/translations/locales/de-DE.json index 6a118b83e..618c73f55 100644 --- a/frontend/src/translations/locales/de-DE.json +++ b/frontend/src/translations/locales/de-DE.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Willkommen, {{name}}", - "howDoesThatWork": "Wie funktioniert das?" + "anonWarning": "Du verwendest ein anonymes Konto. Du solltest dich einloggen, um sicherzustellen, dass deine Daten abgerufen werden können, wenn du ein anderes Gerät verwendest.", + "howDoesThatWork": "Wie funktioniert das?", + "login": "Anmelden" }, "PreviousGame": { "createdBy": "Erstellt von", diff --git a/frontend/src/translations/locales/en-GB.json b/frontend/src/translations/locales/en-GB.json index 04c6bdc35..d383068af 100644 --- a/frontend/src/translations/locales/en-GB.json +++ b/frontend/src/translations/locales/en-GB.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Welcome, {{name}}", - "howDoesThatWork": "How does that work?" + "anonWarning": "You are using an Anonymous account. You might want to login to make sure your data can be retrieved when using another device.", + "howDoesThatWork": "How does that work?", + "login": "Login" }, "PreviousGame": { "createdBy": "Created by", diff --git a/frontend/src/translations/locales/es-ES.json b/frontend/src/translations/locales/es-ES.json index 36744bc9b..c6506c4c4 100644 --- a/frontend/src/translations/locales/es-ES.json +++ b/frontend/src/translations/locales/es-ES.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Bienvenido, {{name}}", - "howDoesThatWork": "¿Cómo funciona esto?" + "anonWarning": "Estás usando una cuenta anónima. Tal vez quieras iniciar sesión para asegurarte de que tus datos pueden ser recuperados cuando usas otro dispositivo.", + "howDoesThatWork": "¿Cómo funciona esto?", + "login": "Ingresar" }, "PreviousGame": { "createdBy": "Creado por", diff --git a/frontend/src/translations/locales/fr-FR.json b/frontend/src/translations/locales/fr-FR.json index 6af17ed58..2dff2efe5 100644 --- a/frontend/src/translations/locales/fr-FR.json +++ b/frontend/src/translations/locales/fr-FR.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Bienvenue, {{name}}", - "howDoesThatWork": "Comment cela fonctionne-t-il ?" + "anonWarning": "Vous utilisez un compte anonyme. Connectez-vous afin que vos données puissent être récupérées lorsque vous utilisez un autre appareil.", + "howDoesThatWork": "Comment cela fonctionne-t-il ?", + "login": "Connexion" }, "PreviousGame": { "createdBy": "Créé par", @@ -293,7 +295,7 @@ "cancelButton": "Non merci" }, "AccountPage": { - "convertTitle": "Convertir en vrai compte", + "convertTitle": "Convertir votre compte anonyme", "noEmptyNameError": "Vous ne pouvez pas choisir un nom d'affichage vide. Veuillez réessayer.", "anonymousError": "Les comptes anonymes ne peuvent avoir accès à leur profil (puisqu'ils n'en ont pas).", "convertWarning": "Vous utilisez un compte anonyme. Ne perdez pas vos données et convertissez votre compte anonyme vers un vrai compte. Connectez-vous avec votre compte Google, GitHub, Twitter ou créez un compte avec mot de passe, et nous fusionnerons automatiquement vos données.", diff --git a/frontend/src/translations/locales/hu-HU.json b/frontend/src/translations/locales/hu-HU.json index 8695e29f0..471aa7823 100644 --- a/frontend/src/translations/locales/hu-HU.json +++ b/frontend/src/translations/locales/hu-HU.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Isten hozott, {{name}}", - "howDoesThatWork": "Hogyan működik?" + "anonWarning": "", + "howDoesThatWork": "Hogyan működik?", + "login": "" }, "PreviousGame": { "createdBy": "Készítette", diff --git a/frontend/src/translations/locales/it-IT.json b/frontend/src/translations/locales/it-IT.json index 872ed6258..b09732371 100644 --- a/frontend/src/translations/locales/it-IT.json +++ b/frontend/src/translations/locales/it-IT.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Benvenuto, {{name}}", - "howDoesThatWork": "Come funziona quello?" + "anonWarning": "Stai utilizzando un account anonimo. Potresti voler effettuare il login per assicurarsi che i tuoi dati possano essere recuperati quando usi un altro dispositivo.", + "howDoesThatWork": "Come funziona quello?", + "login": "Accedi" }, "PreviousGame": { "createdBy": "Creato da", diff --git a/frontend/src/translations/locales/ja-JP.json b/frontend/src/translations/locales/ja-JP.json index 69d91106e..9a10de366 100644 --- a/frontend/src/translations/locales/ja-JP.json +++ b/frontend/src/translations/locales/ja-JP.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "ようこそ、 {{name}}", - "howDoesThatWork": "それはどのように機能しますか?" + "anonWarning": "匿名アカウントを使用しています。別のデバイスを使用しているときにデータを取得できることを確認するためにログインする場合があります。", + "howDoesThatWork": "それはどのように機能しますか?", + "login": "ログイン" }, "PreviousGame": { "createdBy": "作成者:", diff --git a/frontend/src/translations/locales/nl-NL.json b/frontend/src/translations/locales/nl-NL.json index 66ea87550..208d4185a 100644 --- a/frontend/src/translations/locales/nl-NL.json +++ b/frontend/src/translations/locales/nl-NL.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Welkom, {{name}}", - "howDoesThatWork": "Hoe werkt dat?" + "anonWarning": "Je gebruikt een anonieme account. Je kunt misschien inloggen om ervoor te zorgen dat je gegevens kunnen worden opgehaald wanneer je een ander apparaat gebruikt.", + "howDoesThatWork": "Hoe werkt dat?", + "login": "Aanmelden" }, "PreviousGame": { "createdBy": "Aangemaakt door", diff --git a/frontend/src/translations/locales/pl-PL.json b/frontend/src/translations/locales/pl-PL.json index ffeada784..a18d72b8f 100644 --- a/frontend/src/translations/locales/pl-PL.json +++ b/frontend/src/translations/locales/pl-PL.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Witaj, {{name}}", - "howDoesThatWork": "Jak to działa?" + "anonWarning": "Używasz konta anonimowego. Możesz się zalogować, aby mieć pewność, że dane mogą być pobrane podczas korzystania z innego urządzenia.", + "howDoesThatWork": "Jak to działa?", + "login": "Logowanie" }, "PreviousGame": { "createdBy": "Utworzony przez", diff --git a/frontend/src/translations/locales/pt-BR.json b/frontend/src/translations/locales/pt-BR.json index 9b146430b..f288e59a7 100644 --- a/frontend/src/translations/locales/pt-BR.json +++ b/frontend/src/translations/locales/pt-BR.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Bem-vindo, {{name}}", - "howDoesThatWork": "Como funciona isso?" + "anonWarning": "Você está usando uma conta anônima. Talvez você queira fazer login para garantir que seus dados possam ser recuperados usando outro dispositivo.", + "howDoesThatWork": "Como funciona isso?", + "login": "Conectar-se" }, "PreviousGame": { "createdBy": "Criado Por", diff --git a/frontend/src/translations/locales/pt-PT.json b/frontend/src/translations/locales/pt-PT.json index d5c65c01b..86abd65bc 100644 --- a/frontend/src/translations/locales/pt-PT.json +++ b/frontend/src/translations/locales/pt-PT.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Bem-vindo, {{name}}", - "howDoesThatWork": "Como funciona isso?" + "anonWarning": "Você está usando uma conta anônima. Talvez você queira fazer login para garantir que seus dados possam ser recuperados usando outro dispositivo.", + "howDoesThatWork": "Como funciona isso?", + "login": "Conectar-se" }, "PreviousGame": { "createdBy": "Criado Por", diff --git a/frontend/src/translations/locales/uk-UA.json b/frontend/src/translations/locales/uk-UA.json index 58b5a21f5..712405324 100644 --- a/frontend/src/translations/locales/uk-UA.json +++ b/frontend/src/translations/locales/uk-UA.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "Ласкаво просимо, {{name}}", - "howDoesThatWork": "Як це працює?" + "anonWarning": "Ви використовуєте анонімний обліковий запис. Ви можете увійти в систему, щоб переконатися, що ваші дані можуть бути отримані при використанні іншого пристрою.", + "howDoesThatWork": "Як це працює?", + "login": "Логін" }, "PreviousGame": { "createdBy": "Створено", diff --git a/frontend/src/translations/locales/zh-CN.json b/frontend/src/translations/locales/zh-CN.json index fd1050cc7..40c8e9e43 100644 --- a/frontend/src/translations/locales/zh-CN.json +++ b/frontend/src/translations/locales/zh-CN.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "欢迎, {{name}}", - "howDoesThatWork": "这是如何运作的?" + "anonWarning": "您正在使用匿名帐户。您可能想要登录以确保您的数据可以在使用其他设备时检索。", + "howDoesThatWork": "这是如何运作的?", + "login": "登录" }, "PreviousGame": { "createdBy": "创建者", diff --git a/frontend/src/translations/locales/zh-TW.json b/frontend/src/translations/locales/zh-TW.json index e192ba60a..59c536515 100644 --- a/frontend/src/translations/locales/zh-TW.json +++ b/frontend/src/translations/locales/zh-TW.json @@ -23,7 +23,9 @@ }, "Home": { "welcome": "歡迎, {{name}}", - "howDoesThatWork": "這是如何運作的?" + "anonWarning": "", + "howDoesThatWork": "這是如何運作的?", + "login": "" }, "PreviousGame": { "createdBy": "由...製作", diff --git a/frontend/src/views/Home.tsx b/frontend/src/views/Home.tsx index 7fcbd2d08..1f1353477 100644 --- a/frontend/src/views/Home.tsx +++ b/frontend/src/views/Home.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import styled from '@emotion/styled'; -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import Fab from '@mui/material/Fab'; import { makeStyles } from '@mui/styles'; import { colors } from '@mui/material'; @@ -19,6 +19,7 @@ import { useSnackbar } from 'notistack'; import TrialPrompt from './home/TrialPrompt'; import HowDoesItWorkButton from '../components/HowDoesItWorkButton'; import { useTranslation } from 'react-i18next'; +import ClosableAlert from 'components/ClosableAlert'; const useStyles = makeStyles({ media: { @@ -84,6 +85,12 @@ function Home() { return ( <> + + {t('Home.anonWarning')}    + + {t('Home.login')} + + {t('Home.welcome', { name: user?.name || '' })} diff --git a/frontend/src/views/account/AccountPage.tsx b/frontend/src/views/account/AccountPage.tsx index c47808b71..9669a5562 100644 --- a/frontend/src/views/account/AccountPage.tsx +++ b/frontend/src/views/account/AccountPage.tsx @@ -99,7 +99,7 @@ function AccountPage() { {user.accountType === 'anonymous' ? (
{t('AccountPage.convertWarning')} - +
) : (
@@ -117,20 +117,17 @@ function AccountPage() { {t('AccountPage.details.accountType')} {user.accountType} - - - {t('AccountPage.details.language')} - - - -
)} +
+ +
+ {capabilities.slackClientId ? (
diff --git a/frontend/src/views/layout/Header.tsx b/frontend/src/views/layout/Header.tsx new file mode 100644 index 000000000..eab0da741 --- /dev/null +++ b/frontend/src/views/layout/Header.tsx @@ -0,0 +1,113 @@ +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import MenuIcon from '@mui/icons-material/Menu'; +import { HomeOutlined } from '@mui/icons-material'; +import { Button, Hidden } from '@mui/material'; +import useIsPro from 'auth/useIsPro'; +import ProButton from 'components/ProButton'; +import styled from '@emotion/styled'; +import { useCallback } from 'react'; +import useUser from 'auth/useUser'; +import { useMatch, useNavigate } from 'react-router-dom'; +import useSidePanel from 'views/panel/useSidePanel'; +import useIsInitialised from 'auth/useIsInitialised'; +import Invite from './Invite'; +import AccountMenu from 'auth/AccountMenu'; +import { useTranslation } from 'react-i18next'; +import ProPill from 'components/ProPill'; + +const Title = styled(Typography)` + color: white; +`; + +export function Header() { + const user = useUser(); + const isPro = useIsPro(); + const navigate = useNavigate(); + const displayGoPro = !isPro && user && user.accountType !== 'anonymous'; + const goToHome = useCallback(() => navigate('/'), [navigate]); + const { toggle: togglePanel } = useSidePanel(); + const isInitialised = useIsInitialised(); + const isOnGamePage = !!useMatch('game/:gameId/*'); + const { t } = useTranslation(); + return ( + + + + + + + + + + + + Retrospected  + + + + + {displayGoPro ? ( + + + + + + + + ) : null} + + {isOnGamePage ? : null} + {isInitialised ? ( + + ) : ( + {t('Main.loading')} + )} + + + ); +} + +const MainTitle = styled(Title)` + cursor: pointer; + margin-right: 10px; + @media screen and (max-width: 600px) { + display: none; + } +`; + +const HomeButton = styled.div` + margin-right: 10px; +`; + +const ProPillContainer = styled.div``; + +const GoProContainer = styled.div` + margin-left: 20px; +`; + +const Initialising = styled.div``; + +const Spacer = styled.div` + flex: 1; +`;