Skip to content

Commit

Permalink
feat: add profiles to user menu
Browse files Browse the repository at this point in the history
  • Loading branch information
naumovski-filip committed Jul 13, 2023
1 parent 9bb2fa5 commit b63e7e7
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 69 deletions.
6 changes: 5 additions & 1 deletion public/locales/en/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@
},
"nav": {
"account": "Account",
"add_profile": "Add new profile",
"favorites": "Favorites",
"logout": "Log out",
"payments": "Payments"
"payments": "Payments",
"profile": "Profile",
"settings": "Settings",
"switch_profiles": "Switch profiles"
},
"payment": {
"access_granted": "access granted",
Expand Down
6 changes: 5 additions & 1 deletion public/locales/es/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@
},
"nav": {
"account": "Cuenta",
"add_profile": "",
"favorites": "Favoritos",
"logout": "Cerrar sesión",
"payments": "Pagos"
"payments": "Pagos",
"profile": "",
"settings": "",
"switch_profiles": ""
},
"payment": {
"access_granted": "acceso concedido",
Expand Down
5 changes: 0 additions & 5 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import Popover from '#components/Popover/Popover';
import UserMenu from '#components/UserMenu/UserMenu';
import useBreakpoint, { Breakpoint } from '#src/hooks/useBreakpoint';
import IconButton from '#components/IconButton/IconButton';
import { useAccountStore } from '#src/stores/AccountStore';
import { useConfigStore } from '#src/stores/ConfigStore';
import Language from '#src/icons/Language';
import LanguageMenu from '#components/LanguageMenu/LanguageMenu';
import type { LanguageDefinition } from '#src/i18n/config';
Expand Down Expand Up @@ -76,8 +74,6 @@ const Header: React.FC<Props> = ({
onLanguageClick,
}) => {
const { t } = useTranslation('menu');
const { accessModel } = useConfigStore();
const { canManageProfiles, profile } = useAccountStore();
const [logoLoaded, setLogoLoaded] = useState(false);
const breakpoint = useBreakpoint();
const headerClassName = classNames(styles.header, styles[headerType], {
Expand Down Expand Up @@ -131,7 +127,6 @@ const Header: React.FC<Props> = ({
<Panel>
<UserMenu onClick={closeUserMenu} showPaymentsItem={showPaymentsMenuItem} small />
</Panel>
{canManageProfiles && accessModel === 'SVOD' && <h2 onClick={() => (userMenuOpen ? closeUserMenu() : openUserMenu())}>Hi, {profile}</h2>}
</Popover>
</React.Fragment>
) : (
Expand Down
4 changes: 4 additions & 0 deletions src/components/MenuButton/MenuButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
opacity: 0.7;
transition: background 0.1s ease;

> span {
width: max-content;
}

&.small {
padding: 0 calc(#{variables.$base-spacing} * 1.5);
font-size: 16px;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const Root: FC = () => {
const IS_DEMO_OR_PREVIEW = IS_DEMO_MODE || IS_PREVIEW_MODE;

// Show the spinner while loading except in demo mode (the demo config shows its own loading status)
if (settingsQuery.isLoading || (!IS_DEMO_OR_PREVIEW && configQuery.isLoading)) {
if (userData.loading || settingsQuery.isLoading || (!IS_DEMO_OR_PREVIEW && configQuery.isLoading)) {
return <LoadingOverlay />;
}

Expand Down
36 changes: 36 additions & 0 deletions src/components/UserMenu/UserMenu.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@use 'src/styles/theme';

.menuItems {
width: auto;
margin: 0;
padding: 0;
list-style-type: none;
Expand All @@ -27,3 +28,38 @@
border-top: 1px solid rgba(255, 255, 255, 0.32);
}
}

.sectionHeader {
width: 100%;
padding: 0 0 12px 24px;
color: variables.$slate-gray;
font-weight: 700;
font-size: 12px;
text-transform: uppercase;
}

.profileButton {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
padding: 6px 18px;
gap: 12px;
color: variables.$white;
font-size: 16px;
transition: all 0.1s ease-out;
}

.profileButton:hover {
background: rgba(variables.$white, 0.12);
}

.profileButton:active {
background: rgba(variables.$white, 0.24);
}

.profileIcon {
width: 24px;
height: 24px;
border-radius: 50%;
}
57 changes: 50 additions & 7 deletions src/components/UserMenu/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import MenuButton from '#components/MenuButton/MenuButton';
import { logout } from '#src/stores/AccountController';
import { useConfigStore } from '#src/stores/ConfigStore';
import { useAccountStore } from '#src/stores/AccountStore';
import ArrowLeftRight from '#src/icons/ArrowLeftRight';
import { useHandleProfileSelection, useListProfiles } from '#src/containers/Profiles/utils';
import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
import Plus from '#src/icons/Plus';

type Props = {
small?: boolean;
Expand All @@ -25,7 +27,12 @@ const UserMenu = ({ showPaymentsItem, small = false, onClick }: Props) => {
const { t } = useTranslation('user');
const navigate = useNavigate();
const { accessModel } = useConfigStore();
const { canManageProfiles } = useAccountStore();
const { canManageProfiles, profile: currentProfile } = useAccountStore();

const { data, isFetching } = useListProfiles();
const profiles = data?.responseData.collection;

const selectProfile = useHandleProfileSelection();

const onLogout = useCallback(async () => {
if (onClick) {
Expand All @@ -38,14 +45,50 @@ const UserMenu = ({ showPaymentsItem, small = false, onClick }: Props) => {

return (
<ul className={styles.menuItems}>
{accessModel === 'SVOD' && canManageProfiles && (
<>
<div className={styles.sectionHeader}>{t('nav.switch_profiles')}</div>
{selectProfile.isLoading || isFetching ? (
<LoadingOverlay inline />
) : (
profiles?.map((profile) => (
<li key={profile.id}>
<MenuButton
active={profile.id === currentProfile?.id}
small={small}
onClick={() => selectProfile.mutate({ id: profile.id, navigate })}
label={profile.name}
startIcon={<img className={styles.profileIcon} src={profile.avatar_url} alt={profile.name} />}
/>
</li>
))
)}
{(profiles?.length || 0) < 4 && (
<li>
<MenuButton small={small} onClick={() => navigate('/u/profiles/create')} label={t('nav.add_profile')} startIcon={<Plus />} />
</li>
)}
<hr
className={classNames(styles.divider, {
[styles.small]: small,
})}
/>
</>
)}
<div className={styles.sectionHeader}>{t('nav.settings')}</div>
<li>
<MenuButton
small={small}
onClick={onClick}
to="/u/profiles"
label={t('nav.profile')}
startIcon={<img className={styles.profileIcon} src={currentProfile?.avatar_url} alt={currentProfile?.name} />}
/>
</li>
<li>
<MenuButton small={small} onClick={onClick} to="/u/my-account" label={t('nav.account')} startIcon={<AccountCircle />} />
</li>
{accessModel === 'SVOD' && canManageProfiles && (
<li>
<MenuButton small={small} onClick={onClick} to="/u/profiles" label="Switch profile" startIcon={<ArrowLeftRight />} />
</li>
)}

<li>
<MenuButton small={small} onClick={onClick} to="/u/favorites" label={t('nav.favorites')} startIcon={<Favorite />} />
</li>
Expand Down
12 changes: 6 additions & 6 deletions src/containers/Profiles/CreateProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React, { useEffect, useState } from 'react';
import { useQuery, UseQueryResult } from 'react-query';
import { useNavigate } from 'react-router';

import profileStyles from './Profiles.module.scss';
import Form from './Form';
import type { ProfileFormValues } from './types';
import { useListProfiles } from './utils';

import styles from '#src/pages/User/User.module.scss';
import { useAccountStore } from '#src/stores/AccountStore';
import type { ListProfilesResponse } from '#types/account';
import { createProfile, listProfiles } from '#src/stores/AccountController';
import LoadingOverlay from '#src/components/LoadingOverlay/LoadingOverlay';
import type { UseFormOnSubmitHandler } from '#src/hooks/useForm';
import { createProfile } from '#src/stores/AccountController';

const AVATARS = [
'https://gravatar.com/avatar/5e62c8c13582f94b74ae21cfeb83e28a?s=400&d=robohash&r=x',
Expand All @@ -30,8 +29,8 @@ const CreateProfile = () => {
}, [canManageProfiles, navigate]);

// this is only needed so we can set different avatar url which will be temporary
const { data, isLoading }: UseQueryResult<ListProfilesResponse> = useQuery(['listProfiles'], () => listProfiles(), { staleTime: 0 });
const activeProfiles = data?.collection?.length || 0;
const listProfiles = useListProfiles();
const activeProfiles = listProfiles.data?.responseData.collection?.length || 0;

const initialValues = {
name: '',
Expand All @@ -50,6 +49,7 @@ const CreateProfile = () => {
})
)?.responseData;
if (profile?.id) {
listProfiles.refetch();
setSubmitting(false);
navigate('/u/profiles');
} else {
Expand All @@ -62,7 +62,7 @@ const CreateProfile = () => {
}
};

if (isLoading) return <LoadingOverlay inline />;
if (listProfiles.isLoading) return <LoadingOverlay inline />;

return (
<div className={styles.user}>
Expand Down
5 changes: 5 additions & 0 deletions src/containers/Profiles/DeleteProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router';

import styles from './Profiles.module.scss';
import { useListProfiles } from './utils';

import Button from '#src/components/Button/Button';
import Dialog from '#src/components/Dialog/Dialog';
Expand All @@ -19,9 +20,12 @@ const DeleteProfile = () => {
const [view, setView] = useState(viewParam);
const [isDeleting, setIsDeleting] = useState<boolean>(false);

const listProfiles = useListProfiles();

const closeHandler = () => {
navigate(removeQueryParam(location, 'action'));
};

const deleteHandler = async () => {
try {
setIsDeleting(true);
Expand All @@ -31,6 +35,7 @@ const DeleteProfile = () => {
const response = await deleteProfile({ id });
if (response?.errors.length === 0) {
closeHandler();
listProfiles.refetch();
setIsDeleting(false);
navigate('/u/profiles');
}
Expand Down
47 changes: 5 additions & 42 deletions src/containers/Profiles/Profiles.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router';
import { useQuery } from 'react-query';
import shallow from 'zustand/shallow';

import styles from './Profiles.module.scss';
import { useHandleProfileSelection, useListProfiles } from './utils';

import * as persist from '#src/utils/persist';
import ProfileBox from '#src/components/ProfileBox/ProfileBox';
import { useAccountStore } from '#src/stores/AccountStore';
import { enterProfile, initializeAccount, listProfiles } from '#src/stores/AccountController';
import type { AuthData, Profile } from '#types/account';
import type { Profile } from '#types/account';
import AddNewProfile from '#src/components/ProfileBox/AddNewProfile';
import LoadingOverlay from '#src/components/LoadingOverlay/LoadingOverlay';
import Button from '#src/components/Button/Button';
import { useFavoritesStore } from '#src/stores/FavoritesStore';
import { useWatchHistoryStore } from '#src/stores/WatchHistoryStore';

const MAX_PROFILES = 4;
const PERSIST_KEY_ACCOUNT = 'auth';
const PERSIST_PROFILE = 'profile';

type Props = {
editMode?: boolean;
Expand All @@ -33,41 +26,11 @@ const Profiles = ({ editMode = false }: Props) => {
if (!canManageProfiles) navigate('/');
}, [canManageProfiles, navigate]);

const { data, isLoading, isFetching } = useQuery(['listProfiles'], listProfiles, {
staleTime: 0,
});
const { data, isLoading, isFetching } = useListProfiles();
const activeProfiles = data?.responseData.collection.length || 0;
const canAddNew = activeProfiles < MAX_PROFILES;

const handleProfileSelection = async (id: string) => {
try {
useAccountStore.setState({ loading: true });
const response = await enterProfile({ id });
const profile = response?.responseData;

if (profile?.credentials?.access_token) {
const authData: AuthData = {
jwt: profile.credentials.access_token,
refreshToken: '',
};
persist.setItem(PERSIST_KEY_ACCOUNT, authData);
persist.setItem(PERSIST_PROFILE, profile.name);
persist.setItemStorage('inplayer_token', {
expires: profile.credentials.expires,
token: profile.credentials.access_token,
refreshToken: '',
});
// useAccountStore.setState({ auth: authData });
useFavoritesStore.setState({ favorites: [] });
useWatchHistoryStore.setState({ watchHistory: [] });
await initializeAccount().finally(() => {
navigate('/');
});
}
} catch {
throw new Error('Unable to enter profile.');
}
};
const selectProfile = useHandleProfileSelection();

if (loading || isLoading || isFetching) return <LoadingOverlay inline />;

Expand All @@ -86,7 +49,7 @@ const Profiles = ({ editMode = false }: Props) => {
<ProfileBox
editMode={editMode}
onEdit={() => navigate(`/u/profiles/edit/${profile.id}`)}
onClick={() => handleProfileSelection(profile.id)}
onClick={() => selectProfile.mutate({ id: profile.id, navigate })}
key={profile.id}
name={profile.name}
adult={profile.adult}
Expand Down
Loading

0 comments on commit b63e7e7

Please sign in to comment.