mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 07:03:25 +00:00
feat: settings modal add profile and lang selector
This commit is contained in:
118
package-lock.json
generated
118
package-lock.json
generated
@@ -25,6 +25,7 @@
|
|||||||
"react-i18next": "^13.5.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
|
"react-tabs": "^6.0.2",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
"yup": "^1.3.2"
|
"yup": "^1.3.2"
|
||||||
@@ -6969,6 +6970,18 @@
|
|||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"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": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
@@ -8438,6 +8451,111 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"react-i18next": "^13.5.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
|
"react-tabs": "^6.0.2",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
"yup": "^1.3.2"
|
"yup": "^1.3.2"
|
||||||
|
|||||||
@@ -35,5 +35,11 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"fr": "French",
|
"fr": "French",
|
||||||
"en": "English"
|
"en": "English"
|
||||||
}
|
},
|
||||||
|
"lang": "Language",
|
||||||
|
"settings": "Settings",
|
||||||
|
"profile": "Profile",
|
||||||
|
"select-your-lang": "Change the language",
|
||||||
|
"name": "Name",
|
||||||
|
"email": "Email"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,5 +35,11 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"en": "Anglais"
|
"en": "Anglais"
|
||||||
}
|
},
|
||||||
|
"lang": "Langage",
|
||||||
|
"settings": "Paramètres",
|
||||||
|
"profile": "Profil",
|
||||||
|
"select-your-lang": "Modifier la langue",
|
||||||
|
"name": "Nom",
|
||||||
|
"email": "Email"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
& h2 {
|
& h2 {
|
||||||
color: $blue;
|
color: $blue;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 0.25em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
& .links-count {
|
& .links-count {
|
||||||
|
|||||||
@@ -28,12 +28,13 @@
|
|||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: 0.75em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
& button.btn-close {
|
& button.btn-close {
|
||||||
|
cursor: pointer;
|
||||||
color: $blue;
|
color: $blue;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { useSession } from 'next-auth/react';
|
|||||||
import PATHS from 'constants/paths';
|
import PATHS from 'constants/paths';
|
||||||
import styles from './navbar.module.scss';
|
import styles from './navbar.module.scss';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import Image from 'next/image';
|
|
||||||
import { TFunctionParam } from 'types/i18next';
|
import { TFunctionParam } from 'types/i18next';
|
||||||
|
import RoundedImage from '../RoundedImage/RoundedImage';
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const { data, status } = useSession();
|
const { data, status } = useSession();
|
||||||
@@ -13,6 +13,7 @@ export default function Navbar() {
|
|||||||
const avatarLabel = t('common:avatar', {
|
const avatarLabel = t('common:avatar', {
|
||||||
name: data?.user?.name,
|
name: data?.user?.name,
|
||||||
} as TFunctionParam);
|
} as TFunctionParam);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={styles['navbar']}>
|
<nav className={styles['navbar']}>
|
||||||
<ul className='reset'>
|
<ul className='reset'>
|
||||||
@@ -28,12 +29,9 @@ export default function Navbar() {
|
|||||||
{status === 'authenticated' ? (
|
{status === 'authenticated' ? (
|
||||||
<>
|
<>
|
||||||
<li className={styles['user']}>
|
<li className={styles['user']}>
|
||||||
<Image
|
<RoundedImage
|
||||||
src={data.user.image}
|
src={data.user.image}
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
alt={avatarLabel}
|
alt={avatarLabel}
|
||||||
title={avatarLabel}
|
|
||||||
/>
|
/>
|
||||||
{data.user.name}
|
{data.user.name}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -13,8 +13,4 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
& img {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
src/components/Profile/Profile.tsx
Normal file
38
src/components/Profile/Profile.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/components/Profile/profile.module.scss
Normal file
14
src/components/Profile/profile.module.scss
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.profile {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75em;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
& .avatar {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
& li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/components/RoundedImage/RoundedImage.tsx
Normal file
21
src/components/RoundedImage/RoundedImage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
src/components/RoundedImage/rounded-image.module.scss
Normal file
3
src/components/RoundedImage/rounded-image.module.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.rounded-image img {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
@@ -3,10 +3,20 @@ import useModal from 'hooks/useModal';
|
|||||||
import Modal from '../Modal/Modal';
|
import Modal from '../Modal/Modal';
|
||||||
import * as Keys from 'constants/keys';
|
import * as Keys from 'constants/keys';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { IoSettingsOutline } from 'react-icons/io5';
|
import { IoLogOutOutline, IoSettingsOutline } from 'react-icons/io5';
|
||||||
import LangSelector from '../LangSelector';
|
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() {
|
export default function SettingsModal() {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
useHotkeys(Keys.CLOSE_SEARCH_KEY, modal.close, {
|
useHotkeys(Keys.CLOSE_SEARCH_KEY, modal.close, {
|
||||||
enabled: modal.isShowing,
|
enabled: modal.isShowing,
|
||||||
@@ -25,12 +35,48 @@ export default function SettingsModal() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{modal.isShowing && (
|
{modal.isShowing && (
|
||||||
<Modal
|
<Modal
|
||||||
title='Settings'
|
title={t('common:settings')}
|
||||||
close={modal.close}
|
close={modal.close}
|
||||||
>
|
>
|
||||||
<LangSelector />
|
<Tabs className={styles['tabs']}>
|
||||||
<p>about tab with all links related to MyLinks</p>
|
<TabList className={clsx('reset', styles['tab-list'])}>
|
||||||
<button>disconnect</button>
|
<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>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
59
src/components/Settings/settings-modal.module.scss
Normal file
59
src/components/Settings/settings-modal.module.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/types/types.d.ts
vendored
4
src/types/types.d.ts
vendored
@@ -37,8 +37,8 @@ export interface SearchItem {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
type: "category" | "link";
|
type: 'category' | 'link';
|
||||||
category?: undefined | Link["category"];
|
category?: undefined | Link['category'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Favicon {
|
export interface Favicon {
|
||||||
|
|||||||
Reference in New Issue
Block a user