diff --git a/frontend/src/components/App/App.jsx b/frontend/src/components/App/App.jsx index ec894c6..7e0d62a 100644 --- a/frontend/src/components/App/App.jsx +++ b/frontend/src/components/App/App.jsx @@ -55,6 +55,7 @@ import { PopupDialog, PopupEditAvatar, PopupAskToRegister, + PopupShareCalendar, } from '../Popups'; const locales = { @@ -83,6 +84,7 @@ function App() { const [chosenCalendars, setChosenCalendars] = useState([]); const [editableCalendar, setEditableCalendar] = useState({}); const [editableEvent, setEditableEvent] = useState({}); + const [team, setTeam] = useState([]); // Popups const [visiblePopupLogin, setVisiblePopupLogin] = useState(false); @@ -97,6 +99,8 @@ function App() { useState(false); const [visiblePopupAskToRegister, setVisiblePopupAskToRegister] = useState(false); + const [visiblePopupShareCalendar, setVisiblePopupShareCalendar] = + useState(false); // Helpers const [showMessage, setShowMessage] = useState(false); @@ -147,6 +151,7 @@ function App() { setCurrentUser({}); setAllUserCalendars([]); setAllUserEvents([]); + setTeam([]); setChosenCalendars(holidaysCalendar.map((c) => c.id)); closeAllPopups(); @@ -157,7 +162,9 @@ function App() { const handleErrors = useCallback( ({ error, res }) => { - if (error.code === 'token_not_valid') { + console.log({ error, res }); + + if (error.code && error.code === 'token_not_valid') { logout(); showDialog(ErrorMessage.UNAUTHORIZED, true); } else if (res.status === 401) { @@ -302,6 +309,7 @@ function App() { setEditableCalendar, editableEvent, setEditableEvent, + team, }), [ holidays, @@ -310,6 +318,7 @@ function App() { chosenCalendars, editableCalendar, editableEvent, + team, ] ); @@ -660,6 +669,73 @@ function App() { }); }; + const handleShareCalendar = (data) => { + const newMate = { + email: data.email, + infoIcon: 'load', + }; + setTeam([...team, newMate]); + + calendarApi + .shareCalendar(data) + .then(() => { + newMate.infoIcon = 'success'; + + const index = team.findIndex((m) => m.email === data.email); + if (index !== -1) { + team.splice(index, 1); + } + + setTeam([...team, newMate]); + }) + .catch((/* { error, res } */) => { + newMate.infoIcon = 'denied'; + + const index = team.findIndex((m) => m.email === data.email); + if (index !== -1) { + team.splice(index, 1); + } + + setTeam([...team, newMate]); + // handleErrors({ error, res }); + }); + }; + + const handleDeleteMate = ({ id, email }) => { + setIsLoading(true); + calendarApi + .deleteSharedCalendar({ id, email }) + .then(() => { + const index = team.findIndex((m) => m.email === email); + if (index !== -1) { + team.splice(index, 1); + } + }) + .catch(({ err, res }) => { + handleErrors({ err, res }); + }) + .finally(() => { + setIsLoading(false); + }); + }; + + const handleShowSharePopup = (id) => { + setIsLoading(true); + calendarApi + .getSharedCalendarById(id) + .then((res) => { + setTeam(res.users); + setVisiblePopupShareCalendar(true); + }) + .catch(({ error, res }) => { + console.log({ error, res }); + handleErrors({ error, res }); + }) + .finally(() => { + setIsLoading(false); + }); + }; + return ( @@ -683,6 +759,7 @@ function App() { onEventDoubleClick={setVisiblePopupEditEvent} onNewCalendarClick={setVisiblePopupNewCalendar} onEditCalendarClick={setVisiblePopupEditCalendar} + onShareCalendarClick={handleShowSharePopup} onNewEventClickUnauth={setVisiblePopupAskToRegister} onEditEvent={handleEditEvent} /> @@ -757,6 +834,13 @@ function App() { setIsFormLogin={setIsFormLogin} /> + + { + const handleClick = (calendar, isEdit) => { setEditableCalendar(calendar); - onEditCalendarClick(true); + if (isEdit) { + onEditCalendarClick(true); + } else { + onShareCalendarClick(calendar.id); + } }; // TODO: потом сюда добявятся пошаренные календари @@ -57,4 +61,5 @@ export function CalendarSelect({ onEditCalendarClick }) { CalendarSelect.propTypes = { onEditCalendarClick: PropTypes.func.isRequired, + onShareCalendarClick: PropTypes.func.isRequired, }; diff --git a/frontend/src/components/CalendarSelect/CalendarSelect.module.css b/frontend/src/components/CalendarSelect/CalendarSelect.module.css index 774c8ad..c92aecc 100644 --- a/frontend/src/components/CalendarSelect/CalendarSelect.module.css +++ b/frontend/src/components/CalendarSelect/CalendarSelect.module.css @@ -52,7 +52,7 @@ .list { display: grid; - grid-template-columns: 16px 1fr 16px; + grid-template-columns: 16px 1fr 44px; grid-column-gap: 8px; align-items: start; cursor: pointer; @@ -107,8 +107,14 @@ label input:checked ~ .checkbox::before { cursor: pointer; padding: 0; transition: all 0.2s ease-out; + width: 16px; + height: 16px; } .edit:hover { color: var(--surface-200); } + +.edit:first-child { + margin-right: 12px; +} diff --git a/frontend/src/components/CalendarSelect/CalendarsBlock.jsx b/frontend/src/components/CalendarSelect/CalendarsBlock.jsx index e59253b..8645cfb 100644 --- a/frontend/src/components/CalendarSelect/CalendarsBlock.jsx +++ b/frontend/src/components/CalendarSelect/CalendarsBlock.jsx @@ -48,13 +48,30 @@ export function CalendarBlock(props) { /> {calendar.name} {editButton && ( - +
+ + +
)} ))} diff --git a/frontend/src/components/Forms/FormShareCalendar.jsx b/frontend/src/components/Forms/FormShareCalendar.jsx new file mode 100644 index 0000000..083b997 --- /dev/null +++ b/frontend/src/components/Forms/FormShareCalendar.jsx @@ -0,0 +1,208 @@ +/* eslint-disable no-nested-ternary */ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'primereact/button'; +import { Avatar } from 'primereact/avatar'; +import { InputText } from 'primereact/inputtext'; +import { Tooltip } from 'primereact/tooltip'; +import { useForm, Controller } from 'react-hook-form'; +import { classNames as cn } from 'primereact/utils'; +import styles from './Forms.module.css'; +import { CalendarsContext } from '../../context'; +import { PICTURE_URL } from '../../utils/api/commonApi'; + +export function FormShareCalendar({ + onShareCalendar, + setVisible, + handleDeleteMate, +}) { + const { editableCalendar, team } = useContext(CalendarsContext); + const { id, name, color } = editableCalendar; + + const defaultValues = { + email: '', + }; + + const { + control, + formState: { errors, isValid }, + handleSubmit, + reset, + } = useForm({ defaultValues, mode: 'onChange' }); + + const onSubmit = (data) => { + const result = { + ...data, + id, + name, + color, + }; + onShareCalendar(result); + reset(); + }; + + const getFormErrorMessage = (errorName) => + errors[errorName] && ( + {errors[errorName].message} + ); + + return ( +
+
+
+
+

Поделиться календарём

+ + + + +
+ +
+
+

{name}

+
+ + {team.length > 0 && ( +
+ {team.map((mate) => ( +
+
+ + +

+ {mate.email} +

+ + {mate.infoIcon ? ( + mate.infoIcon === 'load' ? ( + + ) : mate.infoIcon === 'success' ? ( + + ) : ( + + ) + ) : ( + + )} +
+ + {mate.infoIcon ? ( + mate.infoIcon === 'success' && ( + + ) + ) : ( + + )} +
+ ))} +
+ )} + +
+
+
+ + ( + + )} + /> + {/* eslint jsx-a11y/label-has-associated-control: ["error", { assert: "either" } ] */} + + + {getFormErrorMessage('email')} +
+ +
+ +
+
+
+ ); +} + +FormShareCalendar.propTypes = { + onShareCalendar: PropTypes.func.isRequired, + setVisible: PropTypes.func.isRequired, + handleDeleteMate: PropTypes.func.isRequired, +}; diff --git a/frontend/src/components/Forms/Forms.module.css b/frontend/src/components/Forms/Forms.module.css index bc2f949..c62fef9 100644 --- a/frontend/src/components/Forms/Forms.module.css +++ b/frontend/src/components/Forms/Forms.module.css @@ -1,6 +1,6 @@ /* ОБЩИЕ СТИЛИ */ .card { - width: 20rem; + width: 21rem; } .form { @@ -8,6 +8,10 @@ } .title { + font-size: 26px; + font-family: Roboto; + font-weight: 600; + word-wrap: break-word; margin: 0 0 2.5rem; } @@ -111,3 +115,89 @@ .avatar .dangerBtn { margin-top: 3rem; } + +/* FormShareCalendar */ +.inputBox { + display: grid; + grid-template-columns: 6fr 1fr; + gap: 1rem; +} + +.circle { + width: 18px; + height: 18px; + border-radius: 25px; +} + +.nameCalendar { + margin: 0 0 0 12px; + padding-top: 1px; +} + +.guestsTable { + padding: 1.2rem; + border: 1px solid; + border-radius: 0.3rem; + border-color: var(--surface-border); + display: flex; + flex-direction: column; + gap: 12px; + column-gap: 1.1rem; +} + +.guest { + width: 100%; + border-radius: 0.3rem; + border-color: var(--surface-border); + display: grid; + grid-template-columns: auto 24px; + column-gap: 1.1rem; + align-items: center; +} + +.guestInfo { + max-width: 270px; + height: 32px; + border-radius: 20px; + background-color: var(--highlight-bg); + margin-left: 5px; +} + +.guestIcon { + margin-left: auto; + margin-right: 8px; +} + +.guestAvatar { + margin-left: 2px; + width: 28px; + height: 28px; + background-color: violet; +} + +.guestEmail { + max-width: 170px; + font-size: 16px; + font-style: normal; + font-weight: 300; + line-height: 150%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.delButton { + justify-self: center; + border: none; + background-color: transparent; + color: var(--text-color); + cursor: pointer; + padding: 0; + transition: all 0.2s ease-out; + width: 16px; + height: 16px; +} + +.delButton:hover { + color: var(--surface-200); +} diff --git a/frontend/src/components/Main/Main.jsx b/frontend/src/components/Main/Main.jsx index 5da23d7..34b38e9 100644 --- a/frontend/src/components/Main/Main.jsx +++ b/frontend/src/components/Main/Main.jsx @@ -16,6 +16,7 @@ export function Main({ onNewEventClick, onNewCalendarClick, onEditCalendarClick, + onShareCalendarClick, onEventDoubleClick, onNewEventClickUnauth, onEditEvent, @@ -57,6 +58,7 @@ export function Main({
setVisible(false)}> + + + ); +} + +PopupShareCalendar.propTypes = { + visible: PropTypes.bool.isRequired, + setVisible: PropTypes.func.isRequired, + handleShare: PropTypes.func.isRequired, + handleDeleteMate: PropTypes.func.isRequired, +}; diff --git a/frontend/src/components/Popups/index.js b/frontend/src/components/Popups/index.js index 541eaf5..fc39710 100644 --- a/frontend/src/components/Popups/index.js +++ b/frontend/src/components/Popups/index.js @@ -8,6 +8,7 @@ import { PopupEditEvent } from './PopupEditEvent'; import { PopupDialog } from './PopupDialog'; import { PopupEditAvatar } from './PopupEditAvatar'; import { PopupAskToRegister } from './PopupAskToRegister'; +import { PopupShareCalendar } from './PopupShareCalendar'; export { PopupChangePassword, @@ -20,4 +21,5 @@ export { PopupDialog, PopupEditAvatar, PopupAskToRegister, + PopupShareCalendar, }; diff --git a/frontend/src/components/Sidebar/Sidebar.jsx b/frontend/src/components/Sidebar/Sidebar.jsx index 74fce8a..f979b66 100644 --- a/frontend/src/components/Sidebar/Sidebar.jsx +++ b/frontend/src/components/Sidebar/Sidebar.jsx @@ -18,6 +18,7 @@ import { CalendarSelect } from '../CalendarSelect/CalendarSelect'; export function Sidebar({ onEditCalendarClick, + onShareCalendarClick, onNewEventClick, onNewCalendarClick, visibleProdCalendar, @@ -76,7 +77,10 @@ export function Sidebar({ /> {loggedIn && ( <> - +