feat: settings modal add profile and lang selector

This commit is contained in:
Sonny
2023-11-26 04:03:42 +01:00
parent f64476de37
commit fe288c69b1
15 changed files with 327 additions and 20 deletions

118
package-lock.json generated
View File

@@ -25,6 +25,7 @@
"react-i18next": "^13.5.0",
"react-icons": "^4.12.0",
"react-select": "^5.8.0",
"react-tabs": "^6.0.2",
"sass": "^1.69.5",
"sharp": "^0.32.6",
"yup": "^1.3.2"
@@ -6969,6 +6970,18 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-tabs": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz",
"integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==",
"dependencies": {
"clsx": "^2.0.0",
"prop-types": "^15.5.0"
},
"peerDependencies": {
"react": "^18.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -8438,6 +8451,111 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz",
"integrity": "sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz",
"integrity": "sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz",
"integrity": "sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz",
"integrity": "sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz",
"integrity": "sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz",
"integrity": "sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz",
"integrity": "sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
}
}
}

View File

@@ -28,6 +28,7 @@
"react-i18next": "^13.5.0",
"react-icons": "^4.12.0",
"react-select": "^5.8.0",
"react-tabs": "^6.0.2",
"sass": "^1.69.5",
"sharp": "^0.32.6",
"yup": "^1.3.2"

View File

@@ -35,5 +35,11 @@
"language": {
"fr": "French",
"en": "English"
}
},
"lang": "Language",
"settings": "Settings",
"profile": "Profile",
"select-your-lang": "Change the language",
"name": "Name",
"email": "Email"
}

View File

@@ -35,5 +35,11 @@
"language": {
"fr": "Français",
"en": "Anglais"
}
},
"lang": "Langage",
"settings": "Paramètres",
"profile": "Profil",
"select-your-lang": "Modifier la langue",
"name": "Nom",
"email": "Email"
}

View File

@@ -31,7 +31,7 @@
& h2 {
color: $blue;
margin-bottom: 15px;
margin-bottom: 0.25em;
font-weight: 500;
& .links-count {

View File

@@ -28,12 +28,13 @@
.modal-header {
width: 100%;
margin-bottom: 1.5em;
margin-bottom: 0.75em;
display: flex;
align-items: center;
justify-content: space-between;
& button.btn-close {
cursor: pointer;
color: $blue;
background-color: transparent;
border: 0;

View File

@@ -3,8 +3,8 @@ import { useSession } from 'next-auth/react';
import PATHS from 'constants/paths';
import styles from './navbar.module.scss';
import { useTranslation } from 'next-i18next';
import Image from 'next/image';
import { TFunctionParam } from 'types/i18next';
import RoundedImage from '../RoundedImage/RoundedImage';
export default function Navbar() {
const { data, status } = useSession();
@@ -13,6 +13,7 @@ export default function Navbar() {
const avatarLabel = t('common:avatar', {
name: data?.user?.name,
} as TFunctionParam);
return (
<nav className={styles['navbar']}>
<ul className='reset'>
@@ -28,12 +29,9 @@ export default function Navbar() {
{status === 'authenticated' ? (
<>
<li className={styles['user']}>
<Image
<RoundedImage
src={data.user.image}
width={24}
height={24}
alt={avatarLabel}
title={avatarLabel}
/>
{data.user.name}
</li>

View File

@@ -13,8 +13,4 @@
display: flex;
gap: 0.25em;
justify-content: center;
& img {
border-radius: 50%;
}
}

View File

@@ -0,0 +1,38 @@
import { useSession } from 'next-auth/react';
import { useTranslation } from 'next-i18next';
import { TFunctionParam } from 'types/i18next';
import RoundedImage from '../RoundedImage/RoundedImage';
import styles from './profile.module.scss';
import clsx from 'clsx';
export default function Profile() {
const { data } = useSession();
const { t } = useTranslation('common');
const avatarLabel = t('common:avatar', {
name: data?.user?.name,
} as TFunctionParam);
return (
<ul className={clsx('reset', styles['profile'])}>
<li
className={styles['avatar']}
style={{ textAlign: 'center' }}
>
<RoundedImage
src={data.user.image}
width={96}
height={96}
alt={avatarLabel}
title={avatarLabel}
/>
</li>
<li className={styles['name']}>
<b>{t('common:name')}</b> {data.user.name}
</li>
<li className={styles['email']}>
<b>{t('common:email')}</b> {data.user.email}
</li>
</ul>
);
}

View File

@@ -0,0 +1,14 @@
.profile {
display: flex;
gap: 0.75em;
flex-direction: column;
& .avatar {
text-align: center;
}
& li {
display: flex;
flex-direction: column;
}
}

View File

@@ -0,0 +1,21 @@
import Image from 'next/image';
import styles from './rounded-image.module.scss';
export default function RoundedImage({
src,
width = 24,
height = 24,
alt,
}: (typeof Image)['defaultProps']) {
return (
<div className={styles['rounded-image']}>
<Image
src={src}
width={width}
height={height}
alt={alt}
title={alt}
/>
</div>
);
}

View File

@@ -0,0 +1,3 @@
.rounded-image img {
border-radius: 50%;
}

View File

@@ -3,10 +3,20 @@ import useModal from 'hooks/useModal';
import Modal from '../Modal/Modal';
import * as Keys from 'constants/keys';
import { useHotkeys } from 'react-hotkeys-hook';
import { IoSettingsOutline } from 'react-icons/io5';
import { IoLogOutOutline, IoSettingsOutline } from 'react-icons/io5';
import LangSelector from '../LangSelector';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { TfiWorld } from 'react-icons/tfi';
import { signOut } from 'next-auth/react';
import PATHS from 'constants/paths';
import styles from './settings-modal.module.scss';
import clsx from 'clsx';
import { useTranslation } from 'next-i18next';
import { FaUser } from 'react-icons/fa';
import Profile from '../Profile/Profile';
export default function SettingsModal() {
const { t } = useTranslation('common');
const modal = useModal();
useHotkeys(Keys.CLOSE_SEARCH_KEY, modal.close, {
enabled: modal.isShowing,
@@ -25,12 +35,48 @@ export default function SettingsModal() {
<AnimatePresence>
{modal.isShowing && (
<Modal
title='Settings'
title={t('common:settings')}
close={modal.close}
>
<LangSelector />
<p>about tab with all links related to MyLinks</p>
<button>disconnect</button>
<Tabs className={styles['tabs']}>
<TabList className={clsx('reset', styles['tab-list'])}>
<Tab
className={styles['tab']}
selectedClassName={styles['tab-selected']}
disabledClassName={styles['tab-disabled']}
>
<FaUser size={18} /> {t('common:profile')}
</Tab>
<Tab
className={styles['tab']}
selectedClassName={styles['tab-selected']}
disabledClassName={styles['tab-disabled']}
>
<TfiWorld size={18} /> {t('common:lang')}
</Tab>
<button
className={clsx('reset', styles['tab'])}
style={{ color: 'red' }}
onClick={() => signOut({ callbackUrl: PATHS.LOGIN })}
>
<IoLogOutOutline size={18} /> {t('common:logout')}
</button>
</TabList>
<TabPanel
className={styles['tab-panel']}
selectedClassName={styles['tab-panel-selected']}
>
<Profile />
</TabPanel>
<TabPanel
className={styles['tab-panel']}
selectedClassName={styles['tab-panel-selected']}
>
<p>{t('common:select-your-lang')}</p>
<LangSelector />
</TabPanel>
</Tabs>
</Modal>
)}
</AnimatePresence>

View File

@@ -0,0 +1,59 @@
@import 'styles/colors.scss';
.tabs {
width: 100%;
display: flex;
gap: 2em;
flex: 1;
justify-content: center;
& .tab-list {
width: 150px;
display: flex;
gap: 0.25em;
flex-direction: column;
}
& .tab {
height: 40px;
width: 100%;
font-size: 1em;
cursor: pointer;
padding: 0.5em 0.75em;
display: flex;
gap: 0.5em;
align-items: center;
transition: 0.05s;
&:hover,
&-selected {
background: $white;
border-color: $grey;
border-radius: 0.25em;
}
&-selected {
color: $blue;
}
&-disabled {
color: transparent;
cursor: default;
}
&:focus {
outline: none;
}
}
& .tab-panel {
display: none;
&-selected {
display: flex;
gap: 0.5em;
flex: 1;
flex-direction: column;
}
}
}

View File

@@ -37,8 +37,8 @@ export interface SearchItem {
id: number;
name: string;
url: string;
type: "category" | "link";
category?: undefined | Link["category"];
type: 'category' | 'link';
category?: undefined | Link['category'];
}
export interface Favicon {