diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ad7c937..4f41416 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -6,15 +6,14 @@ module.exports = { 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', + 'prettier', ], ignorePatterns: ['dist', 'netlify', '.eslintrc.cjs'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, settings: { react: { version: '18.2' } }, - plugins: ['react-refresh'], + plugins: ['react-refresh', 'prettier'], rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + 'prettier/prettier': 'error', }, -}; +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..43fe2ee --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "parser": "flow", + "useTabs": false, + "printWidth": 90, + "tabWidth": 2, + "trailingComma": "es5", + "singleQuote": true, + "bracketSameLine": false, + "semi": false +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..07e9276 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "vivaxy.vscode-conventional-commits" + ] +} diff --git a/index.html b/index.html index d065b2c..9ec03a6 100644 --- a/index.html +++ b/index.html @@ -16,9 +16,10 @@ 3:16 John - +
+ diff --git a/netlify/functions/sendFeedback.js b/netlify/functions/sendFeedback.js index 6b0b83c..c4555da 100644 --- a/netlify/functions/sendFeedback.js +++ b/netlify/functions/sendFeedback.js @@ -1,6 +1,6 @@ const axios = require('axios'); -exports.handler = async function (event) { +export async function handler(event) { let sendInfo; try { sendInfo = JSON.parse(event.body); diff --git a/package.json b/package.json index c7f1601..0de1835 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "@headlessui/react": "^1.7.16", "axios": "^1.4.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-swipeable": "^7.0.1" }, "devDependencies": { "@types/react": "^18.2.15", @@ -32,6 +33,7 @@ "react-i18next": "^13.1.2", "recoil": "^0.7.7", "tailwindcss": "^3.3.3", - "vite": "^4.4.5" + "vite": "^4.4.5", + "vite-plugin-svgr": "^4.1.0" } } diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png index daa0bf3..8ba7e73 100644 Binary files a/public/android-chrome-192x192.png and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png index f960c41..c1440d8 100644 Binary files a/public/android-chrome-512x512.png and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index 42390b4..faaaa39 100644 Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png index eb58220..0a5743a 100644 Binary files a/public/favicon-16x16.png and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png index 172a942..917cbd8 100644 Binary files a/public/favicon-32x32.png and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 7b76bff..7722502 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 10c26cd..f5ed6ba 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,14 +1,14 @@ { "AboutProject": "About project", - "AboutProjectText": "English. Lorem ipsum dolor sit amet consectetur. Massa nunc tempor velit vivamus vitae auctor. A sagittis sit nunc sed blandit. Ipsum eu orci magna sed ipsum tristique nec. Eget mi faucibus est nunc arcu viverra.", + "AboutProjectText": "John 3:16 is a project aimed at making a well-known verse available in all languages of the world.", "By": "by", "ContactUs": "Contact us", "Country": "Country", - "TranslatedLanguage": "Translated language out of", - "TranslatedLanguage_few": "Translated {{count}} languages out of", - "TranslatedLanguage_many": "Translated {{count}} languages out of", - "TranslatedLanguage_one": "Translated {{count}} languages out of", - "TranslatedLanguage_other": "Translated {{count}} languages out of", + "CountVerse": "Just one verse translation", + "CountVerse_few": "Total {{count}} verse translations", + "CountVerse_many": "Total {{count}} verse translations", + "CountVerse_one": "Total {{count}} verse translation", + "CountVerse_other": "Total {{count}} verse translations", "Language": "Language", "Language_few": "{{count}} languages", "Language_many": "{{count}} languages", @@ -18,9 +18,14 @@ "Message": "Message", "NotAllFieldFull": "Not all fields are filled in", "OurGoal": "Our goal", - "OurGoalText": "Our goal is to have John 3:16 translated into all the languages of the world by 2033.", + "OurGoalText": "Our goal is to support the vision of translating the Bible until 2033 into all languages of the world.", "PrivacyPolicyMessage": "By clicking the 'Submit' button, you agree to the privacy policy.", "Send": "Send", + "TranslatedLanguage": "Translated language out of", + "TranslatedLanguage_few": "Translated {{count}} languages out of", + "TranslatedLanguage_many": "Translated {{count}} languages out of", + "TranslatedLanguage_one": "Translated {{count}} languages out of", + "TranslatedLanguage_other": "Translated {{count}} languages out of", "YourEmail": "Your email", "YourMessageHasBeenSent": "Your message has been sent", "YourName": "Your name" diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index b1d2e3a..5a2446d 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -1,14 +1,14 @@ { "AboutProject": "О проекте", - "AboutProjectText": "На русском. Lorem ipsum dolor sit amet consectetur. Massa nunc tempor velit vivamus vitae auctor. A sagittis sit nunc sed blandit. Ipsum eu orci magna sed ipsum tristique nec. Eget mi faucibus est nunc arcu viverra.", + "AboutProjectText": "Иоанна 3:16 - проект, цель которого сделать доступным хорошо известный стих на все языки мира.", "By": "от", "ContactUs": "Свяжитесь с нами", "Country": "Страна", - "TranslatedLanguage": "Переведен язык из", - "TranslatedLanguage_few": "Переведены {{count}} языка из", - "TranslatedLanguage_many": "Переведено {{count}} языков из", - "TranslatedLanguage_one": "Переведен {{count}} язык из", - "TranslatedLanguage_other": "Переведены {{count}} языка из", + "CountVerse": "Всего один перевод стиха", + "CountVerse_few": "Всего {{count}} перевода стиха", + "CountVerse_many": "Всего {{count}} переводов стиха", + "CountVerse_one": "Всего {{count}} перевод стих", + "CountVerse_other": "Всего {{count}} переводов стиха", "Language": "Язык", "Language_few": "{{count}} языка", "Language_many": "{{count}} языков", @@ -18,9 +18,14 @@ "Message": "Сообщение", "NotAllFieldFull": "Не все поля заполнены", "OurGoal": "Наша цель", - "OurGoalText": "Наша цель - до 2033 года перевести стих Иоанна 3:16 на все языки мира.", + "OurGoalText": "Наша цель - поддержать видение по переводу Библии до 2033 года на все языки мира.", "PrivacyPolicyMessage": "Нажимая кнопку 'Отправить', вы соглашаетесь с политикой конфиденциальности.", "Send": "Отправить", + "TranslatedLanguage": "Переведен язык из", + "TranslatedLanguage_few": "Переведены {{count}} языка из", + "TranslatedLanguage_many": "Переведено {{count}} языков из", + "TranslatedLanguage_one": "Переведен {{count}} язык из", + "TranslatedLanguage_other": "Переведены {{count}} языка из", "YourEmail": "Ваш email", "YourMessageHasBeenSent": "Ваше сообщение отправлено", "YourName": "Ваше имя" diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png index ca0fe6b..06ee1bd 100644 Binary files a/public/mstile-150x150.png and b/public/mstile-150x150.png differ diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg index 4f5a11e..23a776e 100644 --- a/public/safari-pinned-tab.svg +++ b/public/safari-pinned-tab.svg @@ -2,44 +2,133 @@ Created by potrace 1.14, written by Peter Selinger 2001-2017 - - - + + + + + + + + + diff --git a/src/App.jsx b/src/App.jsx index 252de3a..5234b26 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,7 +1,7 @@ -import LanguageSelect from './components/LanguageSelect'; -import Header from './components/Header'; -import VerseSlider from './components/VerseSlider'; -import About from './components/About'; +import LanguageSelect from './components/LanguageSelect' +import Header from './components/Header' +import VerseSlider from './components/VerseSlider' +import About from './components/About' function App() { return ( @@ -11,7 +11,7 @@ function App() { - ); + ) } -export default App; +export default App diff --git a/src/atoms.js b/src/atoms.js index dcc1214..091e652 100644 --- a/src/atoms.js +++ b/src/atoms.js @@ -1,6 +1,16 @@ -import { atom } from 'recoil'; +import { atom } from "recoil"; export const languageIndexState = atom({ - key: 'languageIndexState', + key: "languageIndexState", default: 0, }); + +export const translateIndexState = atom({ + key: "translateIndexState", + default: 0, +}); + +export const darkModeState = atom({ + key: "darkModeState", + default: false, +}); diff --git a/src/components/About.jsx b/src/components/About.jsx index 0ec014d..f06671b 100644 --- a/src/components/About.jsx +++ b/src/components/About.jsx @@ -1,46 +1,65 @@ -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useState } from 'react' +import { useTranslation } from 'react-i18next' -import Feedback from './Feedback'; -import Modal from './Modal'; -import PrevButton from './PrevButton'; -import Header from './Header'; - -import versesData from '../verses.json'; +import Feedback from './Feedback' +import Modal from './Modal' +import PrevButton from './PrevButton' +import Header from './Header' +import { languageGroups, countVerse } from '../helper' +import Youtube from '../youtube.svg?react' function About() { - const { t } = useTranslation(); - const [isOpen, setIsOpen] = useState(false); + const { t } = useTranslation() + const [isOpen, setIsOpen] = useState(false) + return (
-
setIsOpen(false)}> -
+
-
- setIsOpen(false)} classes="w-4 h-4" /> +
+ setIsOpen(false)} + className="w-10 h-10 bg-zinc-100 dark:bg-widget hover:bg-zinc-200 active:bg-zinc-300 dark:hover:bg-hover dark:active:bg-zinc-600" + />
-
-
-
+
+
+
{t('AboutProject')}
-

{t('AboutProjectText')}

-

{`${t('TranslatedLanguage', { - count: versesData.length, - })} 7000`}

+

{t('AboutProjectText')}

+

+ {`${t('TranslatedLanguage', { + count: Object.keys(languageGroups).length, + })} 7000`} +

+

+ {`${t('CountVerse', { + count: countVerse, + })}`} +

+
+ -
+
{t('OurGoal')}
-

{t('OurGoalText')}

+

{t('OurGoalText')}

@@ -48,7 +67,7 @@ function About() {
- ); + ) } -export default About; +export default About diff --git a/src/components/DarkModeToggle.jsx b/src/components/DarkModeToggle.jsx new file mode 100644 index 0000000..4b9316c --- /dev/null +++ b/src/components/DarkModeToggle.jsx @@ -0,0 +1,38 @@ +import { useEffect } from 'react' +import { useRecoilState } from 'recoil' +import { darkModeState } from '../atoms' +import Moon from '../moon.svg?react' +import Sun from '../sun.svg?react' + +function DarkModeToggle() { + const [isDarkMode, setIsDarkMode] = useRecoilState(darkModeState) + const toggleDarkMode = () => { + const newMode = !isDarkMode + setIsDarkMode(newMode) + document.documentElement.classList.toggle('dark', newMode) + localStorage.setItem('darkMode', newMode) + } + + useEffect(() => { + const savedDarkMode = localStorage.getItem('darkMode') + if (savedDarkMode === 'true') { + setIsDarkMode(true) + document.documentElement.classList.add('dark') + } else { + setIsDarkMode(false) + document.documentElement.classList.remove('dark') + } + }, [setIsDarkMode]) + + return ( + + ) +} + +export default DarkModeToggle diff --git a/src/components/Feedback.jsx b/src/components/Feedback.jsx index 869f9db..6251636 100644 --- a/src/components/Feedback.jsx +++ b/src/components/Feedback.jsx @@ -1,31 +1,33 @@ -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import axios from 'axios'; +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import axios from 'axios' function Feedback() { - const { t } = useTranslation(); - const [name, setName] = useState(''); - const [email, setEmail] = useState(''); - const [country, setCountry] = useState(''); - const [message, setMessage] = useState(''); - const [error, setError] = useState(''); - const [isSended, setIsSended] = useState(false); + const { t } = useTranslation() + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [country, setCountry] = useState('') + const [message, setMessage] = useState('') + const [error, setError] = useState('') + const [isSended, setIsSended] = useState(false) + const [isFormValid, setIsFormValid] = useState(false) const validation = () => { - let error = null; + let error = null if (!name || !country || !email || !message) { - error = 'NotAllFieldFull'; + error = 'NotAllFieldFull' } - return { error }; - }; + setIsFormValid(!error) + return { error } + } const handleSubmit = (e) => { - e.preventDefault(); - setError(''); - const { error } = validation(); + e.preventDefault() + setError('') + const { error } = validation() if (error) { - setError(error); - return; + setError(error) + return } axios .post( @@ -33,70 +35,68 @@ function Feedback() { JSON.stringify({ name, country, message, email }) ) .then((res) => { - console.log({ res }); - setIsSended(true); - setCountry(''); - setName(''); - setEmail(''); - setMessage(''); + console.log({ res }) + setIsSended(true) + setCountry('') + setName('') + setEmail('') + setMessage('') }) .catch((err) => { - console.log({ err }); - }); - }; + console.log({ err }) + }) + } useEffect(() => { setTimeout(() => { - setIsSended(false); - }, 2000); - }, [isSended]); + setIsSended(false) + }, 2000) + }, [isSended]) return ( - <> -
-
{t('ContactUs')}
- {!isSended ? ( -
- setName(e.target.value)} - /> - setEmail(e.target.value)} - /> - setCountry(e.target.value)} - /> -