From 2ca30a359667b538437c895e7ae07917992bbb08 Mon Sep 17 00:00:00 2001 From: Siju Moncy <72241997+sijumoncy@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:01:05 +0530 Subject: [PATCH] Ft/login page UI (#243) * login right UI done * login page design are added for left login and right login content * fixes --- renderer/public/illustrations/AudioEditor.svg | 9 + renderer/public/illustrations/BibleEditor.svg | 9 + renderer/public/illustrations/OBSEditor.svg | 9 + .../src/components/ImageSlider/Slider.jsx | 85 ++++ .../src/components/ImageSlider/SliderItem.jsx | 40 ++ .../src/components/Login/LeftLogin copy 2.js | 435 ++++++++++++++++++ .../src/components/Login/LeftLogin org.js | 432 +++++++++++++++++ renderer/src/components/Login/LeftLogin.js | 73 +-- renderer/src/components/Login/RightLogin.js | 85 ++-- 9 files changed, 1096 insertions(+), 81 deletions(-) create mode 100644 renderer/public/illustrations/AudioEditor.svg create mode 100644 renderer/public/illustrations/BibleEditor.svg create mode 100644 renderer/public/illustrations/OBSEditor.svg create mode 100644 renderer/src/components/ImageSlider/Slider.jsx create mode 100644 renderer/src/components/ImageSlider/SliderItem.jsx create mode 100644 renderer/src/components/Login/LeftLogin copy 2.js create mode 100644 renderer/src/components/Login/LeftLogin org.js diff --git a/renderer/public/illustrations/AudioEditor.svg b/renderer/public/illustrations/AudioEditor.svg new file mode 100644 index 000000000..bf2094a41 --- /dev/null +++ b/renderer/public/illustrations/AudioEditor.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/renderer/public/illustrations/BibleEditor.svg b/renderer/public/illustrations/BibleEditor.svg new file mode 100644 index 000000000..90b066aec --- /dev/null +++ b/renderer/public/illustrations/BibleEditor.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/renderer/public/illustrations/OBSEditor.svg b/renderer/public/illustrations/OBSEditor.svg new file mode 100644 index 000000000..89ba7f635 --- /dev/null +++ b/renderer/public/illustrations/OBSEditor.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/renderer/src/components/ImageSlider/Slider.jsx b/renderer/src/components/ImageSlider/Slider.jsx new file mode 100644 index 000000000..459a24835 --- /dev/null +++ b/renderer/src/components/ImageSlider/Slider.jsx @@ -0,0 +1,85 @@ +// minimum 3 data +import React, { useEffect, useState } from 'react'; +import SliderItem from './SliderItem'; + +function Slider({ + data, +}) { + const [activeIndex, setActiveIndex] = useState(0); + const [direction, setDirection] = useState(true); // true -> right false -> left + + function handleNextItemBtn() { + setActiveIndex((prev) => (prev + 1 < data.length ? prev + 1 : prev)); + } + + function handlePrevItemBtn() { + setActiveIndex((prev) => (prev - 1 >= 0 ? prev - 1 : prev)); + } + + useEffect(() => { + const interval = setInterval(() => { + if (direction) { + if (activeIndex + 1 >= data.length) { + setDirection(false); + } + handleNextItemBtn(); + } else { + if (activeIndex - 1 <= 0) { + setDirection(true); + } + handlePrevItemBtn(); + } + }, [2500]); + + return () => { + clearInterval(interval); + }; + }); + + return ( +
+
+ {activeIndex > 0 && ( + + )} + {data?.map((item, index) => ( + + {item.img} + + ))} + {activeIndex < data.length - 1 && ( + + )} +
+ +
+

{data[activeIndex].title}

+

{data[activeIndex].content}

+
+
+ ); +} + +export default Slider; diff --git a/renderer/src/components/ImageSlider/SliderItem.jsx b/renderer/src/components/ImageSlider/SliderItem.jsx new file mode 100644 index 000000000..173e4c1a3 --- /dev/null +++ b/renderer/src/components/ImageSlider/SliderItem.jsx @@ -0,0 +1,40 @@ +import React from 'react'; + +function SliderItem({ index, activeIndex, children }) { + const offset = (index - activeIndex) / 4; + const direction = Math.sign(index - activeIndex); + const absOffset = Math.abs(offset); + + const cssTransformProperties = ` + rotateY(calc(${offset} * 55deg)) + scaleY(calc(1 + ${absOffset} * -0.5)) + translateX(calc( ${direction} * -3.5rem)) + translateZ(calc( ${absOffset} * -35rem)) + `; + + const cssOpacity = ` + ${Math.abs(index - activeIndex) >= 3 ? '0' : '1'}`; + + const cssDisplay = ` + ${Math.abs(index - activeIndex) >= 3 ? 'none' : 'block'}, + `; + + return ( +
+ {children} +
+ ); +} + +export default SliderItem; diff --git a/renderer/src/components/Login/LeftLogin copy 2.js b/renderer/src/components/Login/LeftLogin copy 2.js new file mode 100644 index 000000000..2d6950d00 --- /dev/null +++ b/renderer/src/components/Login/LeftLogin copy 2.js @@ -0,0 +1,435 @@ +import { Dialog, Tab, Transition } from '@headlessui/react'; +import React, { + Fragment, useContext, useEffect, useState, +} from 'react'; +import * as localForage from 'localforage'; +import { TrashIcon } from '@heroicons/react/24/outline'; +import { Restore } from '@material-ui/icons'; +import { createUser, handleLogin, writeToFile } from '../../core/Login/handleLogin'; +import { isElectron } from '../../core/handleElectron'; +import * as logger from '../../logger'; +import { AuthenticationContext } from './AuthenticationContextProvider'; + +const LeftLogin = () => { + const [isOpen, setIsOpen] = useState(false); + const [open, setOpen] = useState(false); + const [values, setValues] = useState({}); + const [text, setText] = useState(''); + const [newOpen, setNewOpen] = useState(false); + const [users, setUsers] = useState([]); + const { + action: { generateToken }, + } = useContext(AuthenticationContext); + // eslint-disable-next-line no-unused-vars + const [valid, setValid] = useState({ + username: false, + password: false, + }); + const [showArchived, setShowArchived] = useState(false); + const [userNameError, setUserNameError] = useState(false); + + /* Checking if the users array is empty, if it is, it is getting the users from localForage and + setting the users array to the users from localForage. */ + useEffect(() => { + const checkUsers = async () => { + if (users.length === 0) { + const user = await localForage.getItem('users'); + if (user) { + setUsers(user); + } + } + }; + checkUsers(); + }, [users]); + + function closeModal() { + setIsOpen(false); + setShowArchived(false); + } + function openModal() { + setIsOpen(true); + } + function closeAccountModal() { + setOpen(false); + setValues({}); + } + function openAccountModal() { + setOpen(true); + } + const handleChange = (event) => { + setValues({ ...values, username: event.target.value }); + setUserNameError(false); + }; + + /** + * If the username is not empty, set the username error to false. If the username is empty, set the + * username error to true. + * @param values - the values of the form + * @returns The return value is a boolean. + */ + const handleValidation = (values) => { + let user; + if (values.username) { + user = true; + setUserNameError(false); + setNewOpen(false); + } else if (values.username === '') { + user = false; + setUserNameError(true); + } else { + user = false; + setUserNameError(true); + } + return user; + }; + /* Sorting the users array by the lastSeen property. */ + const sortedUsers = [...users].sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)); + /** + * Checks if the user is existing or not, if not then it creates a new user and generates a token + * for the user. + * @param values - { + */ + const handleSubmit = async (values) => { + localForage.setItem('appMode', 'offline'); + logger.debug('Login.js', 'In handleSubmit'); + if (isElectron()) { + // router.push('/main'); + // The below code is commented for UI dev purpose. + if (handleValidation(values)) { + const fs = window.require('fs'); + logger.debug( + 'LeftLogin.js', + 'Triggers handleLogin to check whether the user is existing or not', + ); + const user = await handleLogin(users, values); + if (user) { + logger.debug( + 'LeftLogin.js', + 'Triggers generateToken to generate a Token for the user', + ); + generateToken(user); + } else { + logger.debug( + 'LeftLogin.js', + 'Triggers createUser for creating a new user', + ); + const user = await createUser(values, fs); + logger.debug( + 'LeftLogin.js', + 'Triggers generateToken to generate a Token for the user', + ); + generateToken(user); + } + } + } + }; + /** + * When the form is submitted, prevent the default action, then call the handleSubmit function with + * the values from the form, then reset the form values. + * @param event - the event object + */ + const displayError = (errorText) => { + setNewOpen(true); + setTimeout(() => { + setNewOpen(false); + }, 2000); + setText(errorText); + }; + function formSubmit(event) { + event.preventDefault(); + if (values.username.length < 3 || values.username.length > 15) { + displayError('The input has to be between 3 and 15 characters long'); + } else if (users.length > 0 && users.find((item) => (item.username.toLowerCase() === values.username.toLowerCase().trim()))) { + displayError('User exists, Check archived and active tab by click on view more.'); + } else { + handleSubmit(values); + setValues({}); + } + } + function classNames(...classes) { + return classes.filter(Boolean).join(' '); + } + + function archiveUser(users, selectedUser) { + const archivedUsers = users.map((user) => { + if (user.username === selectedUser.username) { + return { ...user, isArchived: true }; + } + return user; + }); + + setUsers(archivedUsers); + localForage.setItem('users', archivedUsers); + writeToFile(archivedUsers); + } + function restoreUser(users, selectedUser) { + const activeUsers = users.map((user) => { + if (user.username === selectedUser.username) { + return { ...user, isArchived: false }; + } + return user; + }); + setUsers(activeUsers); + localForage.setItem('users', activeUsers); + writeToFile(activeUsers); + } + const filterUsers = (user) => { + if (user.isArchived === showArchived || (user.isArchived === undefined && showArchived === false)) { + return true; + } + return false; + }; + return ( + //
+
+

+ {sortedUsers.length === 0 ? 'Welcome' : 'Welcome Back!'} +

+ {/*

+ Welcome back! Login to access Scribe Scripture +

*/} +
+
+ {sortedUsers?.filter(filterUsers).slice(0, 5).map((user) => ( +
{ + handleSubmit({ username: user?.username }); + }} + > + {user.username} +
+ ))} +
+ {sortedUsers.length === 0 ? (
) : ( +
+ +
+)} + + + +
+ +
+
+ + + setShowArchived((value) => !value)}> + + classNames( + 'w-full text-md items-center justify-center outline-none font-bold py-4 leading-5 rounded-t-lg', + '', + selected + ? 'text-primary bg-gray-200' + : 'text-gray-400 hover:text-gray-500 border-b bg-white', + )} + > + Active + + classNames( + 'w-full text-md items-center justify-center outline-none font-bold py-4 leading-5 rounded-t-lg', + selected + ? ' text-error bg-gray-200 ' + : 'text-gray-400 hover:text-gray-500 border-b bg-white ', + )} + > + Archived + + + + + +
+ {sortedUsers.filter(filterUsers).map((user) => ( +
+
{ handleSubmit({ username: user.username }); }} + className="w-full p-4 py-3 text-sm rounded-lg cursor-pointer bg-[#F9F9F9] hover:bg-primary hover:text-white border border-[#E3E3E3] font-semibold" + > + +

+ {user.username} +

+
+ +
+ ))} +
+
+ +
+ {sortedUsers.filter(filterUsers).map((user) => ( +
+
+

+ {user.username} +

+
+ + +
+ ))} +
+
+
+
+
+
+
+
+
+
+
+ + + + +
+ + +
+
+ + + + Create New User* + + +
+ + {userNameError && ( + + Required + + )} +
+ {newOpen && ( + {text} + )} +
+ + +
+
+
+
+
+
+
+
+ {/*
+ event.preventDefault()}> + EN(US) + + event.preventDefault()}> + ABOUT + + event.preventDefault()}> + PRIVACY + + event.preventDefault()}> + TERMS + +
*/} +
+
+ ); +}; + +export default LeftLogin; diff --git a/renderer/src/components/Login/LeftLogin org.js b/renderer/src/components/Login/LeftLogin org.js new file mode 100644 index 000000000..c05dacc54 --- /dev/null +++ b/renderer/src/components/Login/LeftLogin org.js @@ -0,0 +1,432 @@ +import { Dialog, Tab, Transition } from '@headlessui/react'; +import React, { + Fragment, useContext, useEffect, useState, +} from 'react'; +import * as localForage from 'localforage'; +import { TrashIcon } from '@heroicons/react/24/outline'; +import { Restore } from '@material-ui/icons'; +import { createUser, handleLogin, writeToFile } from '../../core/Login/handleLogin'; +import { isElectron } from '../../core/handleElectron'; +import * as logger from '../../logger'; +import { AuthenticationContext } from './AuthenticationContextProvider'; + +const LeftLogin = () => { + const [isOpen, setIsOpen] = useState(false); + const [open, setOpen] = useState(false); + const [values, setValues] = useState({}); + const [text, setText] = useState(''); + const [newOpen, setNewOpen] = useState(false); + const [users, setUsers] = useState([]); + const { + action: { generateToken }, + } = useContext(AuthenticationContext); + // eslint-disable-next-line no-unused-vars + const [valid, setValid] = useState({ + username: false, + password: false, + }); + const [showArchived, setShowArchived] = useState(false); + const [userNameError, setUserNameError] = useState(false); + + /* Checking if the users array is empty, if it is, it is getting the users from localForage and + setting the users array to the users from localForage. */ + useEffect(() => { + const checkUsers = async () => { + if (users.length === 0) { + const user = await localForage.getItem('users'); + if (user) { + setUsers(user); + } + } + }; + checkUsers(); + }, [users]); + + function closeModal() { + setIsOpen(false); + setShowArchived(false); + } + function openModal() { + setIsOpen(true); + } + function closeAccountModal() { + setOpen(false); + setValues({}); + } + function openAccountModal() { + setOpen(true); + } + const handleChange = (event) => { + setValues({ ...values, username: event.target.value }); + setUserNameError(false); + }; + + /** + * If the username is not empty, set the username error to false. If the username is empty, set the + * username error to true. + * @param values - the values of the form + * @returns The return value is a boolean. + */ + const handleValidation = (values) => { + let user; + if (values.username) { + user = true; + setUserNameError(false); + setNewOpen(false); + } else if (values.username === '') { + user = false; + setUserNameError(true); + } else { + user = false; + setUserNameError(true); + } + return user; + }; + /* Sorting the users array by the lastSeen property. */ + const sortedUsers = [...users].sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)); + /** + * Checks if the user is existing or not, if not then it creates a new user and generates a token + * for the user. + * @param values - { + */ + const handleSubmit = async (values) => { + localForage.setItem('appMode', 'offline'); + logger.debug('Login.js', 'In handleSubmit'); + if (isElectron()) { + // router.push('/main'); + // The below code is commented for UI dev purpose. + if (handleValidation(values)) { + const fs = window.require('fs'); + logger.debug( + 'LeftLogin.js', + 'Triggers handleLogin to check whether the user is existing or not', + ); + const user = await handleLogin(users, values); + if (user) { + logger.debug( + 'LeftLogin.js', + 'Triggers generateToken to generate a Token for the user', + ); + generateToken(user); + } else { + logger.debug( + 'LeftLogin.js', + 'Triggers createUser for creating a new user', + ); + const user = await createUser(values, fs); + logger.debug( + 'LeftLogin.js', + 'Triggers generateToken to generate a Token for the user', + ); + generateToken(user); + } + } + } + }; + /** + * When the form is submitted, prevent the default action, then call the handleSubmit function with + * the values from the form, then reset the form values. + * @param event - the event object + */ + const displayError = (errorText) => { + setNewOpen(true); + setTimeout(() => { + setNewOpen(false); + }, 2000); + setText(errorText); + }; + function formSubmit(event) { + event.preventDefault(); + if (values.username.length < 3 || values.username.length > 15) { + displayError('The input has to be between 3 and 15 characters long'); + } else if (users.length > 0 && users.find((item) => (item.username.toLowerCase() === values.username.toLowerCase().trim()))) { + displayError('User exists, Check archived and active tab by click on view more.'); + } else { + handleSubmit(values); + setValues({}); + } + } + function classNames(...classes) { + return classes.filter(Boolean).join(' '); + } + + function archiveUser(users, selectedUser) { + const archivedUsers = users.map((user) => { + if (user.username === selectedUser.username) { + return { ...user, isArchived: true }; + } + return user; + }); + + setUsers(archivedUsers); + localForage.setItem('users', archivedUsers); + writeToFile(archivedUsers); + } + function restoreUser(users, selectedUser) { + const activeUsers = users.map((user) => { + if (user.username === selectedUser.username) { + return { ...user, isArchived: false }; + } + return user; + }); + setUsers(activeUsers); + localForage.setItem('users', activeUsers); + writeToFile(activeUsers); + } + const filterUsers = (user) => { + if (user.isArchived === showArchived || (user.isArchived === undefined && showArchived === false)) { + return true; + } + return false; + }; + return ( +
+

Welcome!

+

+ Welcome back! Login to access Scribe Scripture +

+
+
+ {sortedUsers?.filter(filterUsers).slice(0, 5).map((user) => ( +
{ + handleSubmit({ username: user?.username }); + }} + > + {user.username} +
+ ))} +
+ {sortedUsers.length === 0 ? (
) : ( +
+ +
+)} + + + +
+ +
+
+ + + setShowArchived((value) => !value)}> + + classNames( + 'w-full text-md items-center justify-center outline-none font-bold py-4 leading-5 rounded-t-lg', + '', + selected + ? 'text-primary bg-gray-200' + : 'text-gray-400 hover:text-gray-500 border-b bg-white', + )} + > + Active + + classNames( + 'w-full text-md items-center justify-center outline-none font-bold py-4 leading-5 rounded-t-lg', + selected + ? ' text-error bg-gray-200 ' + : 'text-gray-400 hover:text-gray-500 border-b bg-white ', + )} + > + Archived + + + + + +
+ {sortedUsers.filter(filterUsers).map((user) => ( +
+
{ handleSubmit({ username: user.username }); }} + className="w-full p-4 py-3 text-sm rounded-lg cursor-pointer bg-[#F9F9F9] hover:bg-primary hover:text-white border border-[#E3E3E3] font-semibold" + > + +

+ {user.username} +

+
+ +
+ ))} +
+
+ +
+ {sortedUsers.filter(filterUsers).map((user) => ( +
+
+

+ {user.username} +

+
+ + +
+ ))} +
+
+
+
+
+
+
+
+
+
+
+ + + + +
+ + +
+
+ + + + Create New User* + + +
+ + {userNameError && ( + + Required + + )} +
+ {newOpen && ( + {text} + )} +
+ + +
+
+
+
+
+
+
+
+ {/* */} +
+
+ ); +}; + +export default LeftLogin; diff --git a/renderer/src/components/Login/LeftLogin.js b/renderer/src/components/Login/LeftLogin.js index 2a7d6aa4b..9f26a327f 100644 --- a/renderer/src/components/Login/LeftLogin.js +++ b/renderer/src/components/Login/LeftLogin.js @@ -5,6 +5,7 @@ import React, { import * as localForage from 'localforage'; import { TrashIcon } from '@heroicons/react/24/outline'; import { Restore } from '@material-ui/icons'; +import LogoIcon from '@/icons/logo.svg'; import { createUser, handleLogin, writeToFile } from '../../core/Login/handleLogin'; import { isElectron } from '../../core/handleElectron'; import * as logger from '../../logger'; @@ -180,18 +181,34 @@ const LeftLogin = () => { return false; }; return ( -
-

Welcome!

-

- Welcome back! Login to access Scribe Scripture -

-
-
+
+ +
+
+ +

{sortedUsers.length === 0 ? 'Welcome!' : 'Welcome Back !'}

+ +
+ +
{sortedUsers?.filter(filterUsers).slice(0, 5).map((user) => (
{ @@ -202,6 +219,7 @@ const LeftLogin = () => {
))}
+ {sortedUsers.length === 0 ? (
) : (
-)} + )} + {
+
@@ -411,21 +433,20 @@ const LeftLogin = () => {
- + + {/*
*/} + +
+ + +
); }; diff --git a/renderer/src/components/Login/RightLogin.js b/renderer/src/components/Login/RightLogin.js index bc11c4dfd..b89a85b39 100644 --- a/renderer/src/components/Login/RightLogin.js +++ b/renderer/src/components/Login/RightLogin.js @@ -1,4 +1,7 @@ -import LogoIcon from '@/icons/logo.svg'; +import Slider from '../ImageSlider/Slider'; +import AudioEditorIcon from '@/illustrations/AudioEditor.svg'; +import BibleEditorIcon from '@/illustrations/BibleEditor.svg'; +import OBSEditorIcon from '@/illustrations/OBSEditor.svg'; // import GroupIcon from '@/illustrations/group.svg'; // import VectorOne from '@/illustrations/vector-one.svg'; @@ -7,68 +10,40 @@ import LogoIcon from '@/icons/logo.svg'; export default function RightLogin() { return ( -
+
-
-
- -
- -
- {/* */} -
- -
-
- {/* */} -
- -
-

A Bible translation editor that is owned by and developed for the community which uses modern technology to solve the practical problems faced on the field in the current Bible translation context.

-

- FEATURE 1 - - -

-
+
+
+

A Bible translation editor that is owned by and developed for the community which uses modern technology to solve the practical problems faced on the field in the current Bible translation context.

-
-
- - -
-
- {/* */} -
-
-
- - - +
+ , + title: 'USER CENTRIC', + content: 'Starting with little or no training, the application lets beginners and experts achieve their full potential!', + }, + { + id: 2, + img: , + title: 'Oral Bible Translation', + content: 'a flexible manner has proven immensely useful in completing multiple OBT projects across various locations, regardless of the presence of scripts', + }, + { + id: 3, + img: , + title: 'Collaborate', + content: 'On projects, it is possible for multiple people to collaborate together, both offline and online.', + }, + ]} + />
-
- {/* */} - -
-
); }