Pull request #2231: ADG-8368 Frontend rewritten in TypeScript, added Node 18 support
Merge in DNS/adguard-home from ADG-8368-typescript-node-18 to master Squashed commit of the following: commit daa288ae0d76178af24595cc807055902e6f09ab Merge:4c89cf7201085d59a6Author: Igor Lobanov <bniwredyc@gmail.com> Date: Mon Jun 10 17:22:20 2024 +0200 merge commit4c89cf7209Author: Ildar Kamalov <ik@adguard.com> Date: Thu Jun 6 13:27:18 2024 +0300 remove install from initial state commitb943f2011fAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 23:10:55 2024 +0200 frontend production build fix commitcd1be2d66dAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 20:23:14 2024 +0200 production build quickfix commit7b8ac01fc2Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Jun 5 19:57:31 2024 +0300 all: upd node docker commit02afed66d5Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 18:23:12 2024 +0200 changelog fixes commit9c0f736f0cMerge:62c4fbf1ee04775c4fAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 18:18:29 2024 +0200 merge commit62c4fbf1e3Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:22:22 2024 +0200 empty line in changelog commit76b1e44a93Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:20:37 2024 +0200 changelog commitf783e90040Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:19:13 2024 +0200 filters.js -> filters.ts commit3d4ce6554cAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:18:03 2024 +0200 generated file removed commite35ba58f2aAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 15:45:21 2024 +0200 rollback unwanted changes commit1f30d4216dAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 15:27:36 2024 +0200 review fix commit6cd4e44f07Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 11:55:39 2024 +0200 missing generated file restoresd commit2ab738b303Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 11:40:32 2024 +0200 Frontend rewritten in TypeScript, added Node 18 support
This commit is contained in:
168
client/src/components/ui/Footer.tsx
Normal file
168
client/src/components/ui/Footer.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
|
||||
import { REPOSITORY, PRIVACY_POLICY_LINK, THEMES } from '../../helpers/constants';
|
||||
import { LANGUAGES } from '../../helpers/twosky';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
import Version from './Version';
|
||||
import './Footer.css';
|
||||
import './Select.css';
|
||||
|
||||
import { setHtmlLangAttr, setUITheme } from '../../helpers/helpers';
|
||||
|
||||
import { changeTheme } from '../../actions';
|
||||
import { RootState } from '../../initialState';
|
||||
|
||||
const linksData = [
|
||||
{
|
||||
href: REPOSITORY.URL,
|
||||
name: 'homepage',
|
||||
},
|
||||
{
|
||||
href: PRIVACY_POLICY_LINK,
|
||||
name: 'privacy_policy',
|
||||
},
|
||||
{
|
||||
href: REPOSITORY.ISSUES,
|
||||
className: 'btn btn-outline-primary btn-sm footer__link--report',
|
||||
name: 'report_an_issue',
|
||||
},
|
||||
];
|
||||
|
||||
const Footer = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const currentTheme = useSelector((state: RootState) => (state.dashboard ? state.dashboard.theme : THEMES.auto));
|
||||
const profileName = useSelector((state: RootState) => (state.dashboard ? state.dashboard.name : ''));
|
||||
const isLoggedIn = profileName !== '';
|
||||
const [currentThemeLocal, setCurrentThemeLocal] = useState(THEMES.auto);
|
||||
|
||||
const getYear = () => {
|
||||
const today = new Date();
|
||||
return today.getFullYear();
|
||||
};
|
||||
|
||||
const changeLanguage = (event: any) => {
|
||||
const { value } = event.target;
|
||||
i18n.changeLanguage(value);
|
||||
setHtmlLangAttr(value);
|
||||
};
|
||||
|
||||
const onThemeChange = (value: any) => {
|
||||
if (isLoggedIn) {
|
||||
dispatch(changeTheme(value));
|
||||
} else {
|
||||
setUITheme(value);
|
||||
setCurrentThemeLocal(value);
|
||||
}
|
||||
};
|
||||
|
||||
const renderCopyright = () => (
|
||||
<div className="footer__column">
|
||||
<div className="footer__copyright">
|
||||
{t('copyright')} © {getYear()}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://link.adtidy.org/forward.html?action=home&from=ui&app=home">
|
||||
AdGuard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderLinks = (linksData: any) =>
|
||||
linksData.map(({ name, href, className = '' }: any) => (
|
||||
<a
|
||||
key={name}
|
||||
href={href}
|
||||
className={cn('footer__link', className)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{t(name)}
|
||||
</a>
|
||||
));
|
||||
|
||||
const renderThemeButtons = () => {
|
||||
const currentValue = isLoggedIn ? currentTheme : currentThemeLocal;
|
||||
|
||||
const content = {
|
||||
auto: {
|
||||
desc: t('theme_auto_desc'),
|
||||
icon: '#auto',
|
||||
},
|
||||
dark: {
|
||||
desc: t('theme_dark_desc'),
|
||||
icon: '#dark',
|
||||
},
|
||||
light: {
|
||||
desc: t('theme_light_desc'),
|
||||
icon: '#light',
|
||||
},
|
||||
};
|
||||
|
||||
return Object.values(THEMES)
|
||||
|
||||
.map((theme: any) => (
|
||||
<button
|
||||
key={theme}
|
||||
type="button"
|
||||
className="btn btn-sm btn-secondary footer__theme-button"
|
||||
onClick={() => onThemeChange(theme)}
|
||||
title={content[theme].desc}>
|
||||
<svg className={cn('footer__theme-icon', { 'footer__theme-icon--active': currentValue === theme })}>
|
||||
<use xlinkHref={content[theme].icon} />
|
||||
</svg>
|
||||
</button>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<footer className="footer">
|
||||
<div className="container">
|
||||
<div className="footer__row">
|
||||
<div className="footer__column footer__column--links">{renderLinks(linksData)}</div>
|
||||
|
||||
<div className="footer__column footer__column--theme">
|
||||
<div className="footer__themes">
|
||||
<div className="btn-group">{renderThemeButtons()}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer__column footer__column--language">
|
||||
<select
|
||||
className="form-control select select--language"
|
||||
value={i18n.language}
|
||||
onChange={changeLanguage}>
|
||||
{Object.keys(LANGUAGES).map((lang) => (
|
||||
<option key={lang} value={lang}>
|
||||
{LANGUAGES[lang]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div className="footer">
|
||||
<div className="container">
|
||||
<div className="footer__row">
|
||||
{renderCopyright()}
|
||||
|
||||
<div className="footer__column footer__column--language">
|
||||
<Version />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
Reference in New Issue
Block a user