From 43f6aefe8f1456384b4e1a1685eb71cdcf7bf27d Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Fri, 30 Jun 2023 11:01:42 +0300 Subject: [PATCH 1/8] fix: rework backToTopButton box --- .../src/components/BackToTopButton/BackToTopButton.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/BackToTopButton/BackToTopButton.module.css b/frontend/src/components/BackToTopButton/BackToTopButton.module.css index 1184fb5..a597e9b 100644 --- a/frontend/src/components/BackToTopButton/BackToTopButton.module.css +++ b/frontend/src/components/BackToTopButton/BackToTopButton.module.css @@ -22,6 +22,7 @@ left: 24px; height: 50px; width: 60px; + visibility: hidden; } .button:hover { From ca3f69a72880bcbe4d37593016390e040c687601 Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Mon, 3 Jul 2023 13:45:56 +0300 Subject: [PATCH 2/8] feat: add invite popup --- frontend/src/components/App/App.jsx | 15 +++ .../BackToTopButton.module.css | 4 +- .../CalendarSelect/CalendarSelect.jsx | 11 +- .../CalendarSelect/CalendarSelect.module.css | 8 +- .../CalendarSelect/CalendarsBlock.jsx | 29 +++- .../components/Forms/FormShareCalendar.jsx | 126 ++++++++++++++++++ .../src/components/Forms/Forms.module.css | 24 +++- frontend/src/components/Main/Main.jsx | 3 + .../components/Popups/PopupShareCalendar.jsx | 21 +++ frontend/src/components/Popups/index.js | 2 + frontend/src/components/Sidebar/Sidebar.jsx | 7 +- 11 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 frontend/src/components/Forms/FormShareCalendar.jsx create mode 100644 frontend/src/components/Popups/PopupShareCalendar.jsx diff --git a/frontend/src/components/App/App.jsx b/frontend/src/components/App/App.jsx index e2268c5..22ba245 100644 --- a/frontend/src/components/App/App.jsx +++ b/frontend/src/components/App/App.jsx @@ -50,6 +50,7 @@ import { PopupDialog, PopupEditAvatar, PopupAskToRegister, + PopupShareCalendar, } from '../Popups'; const locales = { @@ -92,6 +93,8 @@ function App() { useState(false); const [visiblePopupAskToRegister, setVisiblePopupAskToRegister] = useState(false); + const [visiblePopupShareCalendar, setVisiblePopupShareCalendar] = + useState(false); // Helpers const [showMessage, setShowMessage] = useState(false); @@ -736,6 +739,11 @@ function App() { } }; + const handleShareCalendar = () => { + // eslint-disable-next-line + console.log('поделился (^-^)'); + }; + return ( @@ -759,6 +767,7 @@ function App() { onEventDoubleClick={setVisiblePopupEditEvent} onNewCalendarClick={setVisiblePopupNewCalendar} onEditCalendarClick={setVisiblePopupEditCalendar} + onShareCalendarClick={setVisiblePopupShareCalendar} onNewEventClickUnauth={setVisiblePopupAskToRegister} /> @@ -831,6 +840,12 @@ function App() { setIsFormLogin={setIsFormLogin} /> + + { + const handleClick = (calendar, isEdit) => { setEditableCalendar(calendar); - onEditCalendarClick(true); + if (isEdit) { + onEditCalendarClick(true); + } else { + onShareCalendarClick(true); + } }; // 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..b799ca4 100644 --- a/frontend/src/components/CalendarSelect/CalendarsBlock.jsx +++ b/frontend/src/components/CalendarSelect/CalendarsBlock.jsx @@ -48,13 +48,28 @@ 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..aa2ba30 --- /dev/null +++ b/frontend/src/components/Forms/FormShareCalendar.jsx @@ -0,0 +1,126 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'primereact/button'; +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'; + +export function FormShareCalendar({ onShareCalendar, setVisible }) { + const { editableCalendar } = useContext(CalendarsContext); + const { id, name, color } = editableCalendar; + + const defaultValues = { + id, + name, + color, + }; + + const { + control, + formState: { errors, isValid, isDirty }, + handleSubmit, + reset, + } = useForm({ defaultValues, mode: 'onChange' }); + + const onSubmit = (data) => { + onShareCalendar(data); + reset(); + }; + + const getFormErrorMessage = (errorName) => + errors[errorName] && ( + {errors[errorName].message} + ); + + return ( +
+
+
+
+

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

+ + + + +
+ +
+
+

Акселератор

+
+ +
+
+
+ + ( + + )} + /> + + {getFormErrorMessage('title')} +
+ +
+ +
+
+
+ ); +} + +FormShareCalendar.propTypes = { + onShareCalendar: PropTypes.func.isRequired, + setVisible: PropTypes.func.isRequired, +}; diff --git a/frontend/src/components/Forms/Forms.module.css b/frontend/src/components/Forms/Forms.module.css index bc2f949..cc8aa36 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,21 @@ .avatar .dangerBtn { margin-top: 3rem; } + +.inputBox { + display: grid; + grid-template-columns: 6fr 1fr; + gap: 8px; +} + +.circle { + width: 18px; + height: 18px; + border-radius: 25px; + background: red; +} + +.nameCalendar { + margin: 0 0 0 12px; + padding-top: 1px; +} diff --git a/frontend/src/components/Main/Main.jsx b/frontend/src/components/Main/Main.jsx index 08284f6..77e7963 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, }) { @@ -55,6 +56,7 @@ export function Main({
setVisible(false)}> + + + ); +} + +PopupShareCalendar.propTypes = { + visible: PropTypes.bool.isRequired, + setVisible: PropTypes.func.isRequired, + handleShare: 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 && ( <> - +
+
+
+
+ + +

+ hoba@ya.ru +

+ + + {/* */} +
+ + +
+
+
-
+
)} /> + {/* eslint jsx-a11y/label-has-associated-control: ["error", { assert: "either" } ] */} + {getFormErrorMessage('title')}
@@ -102,8 +160,7 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) { icon="pi pi-plus" disabled={!isValid || !isDirty} className={cn( - 'p-button-rounded p-button-outlined', - styles.dangerBtn + 'p-button-rounded p-button-outlined' )} />
diff --git a/frontend/src/components/Forms/Forms.module.css b/frontend/src/components/Forms/Forms.module.css index cc8aa36..0557d6f 100644 --- a/frontend/src/components/Forms/Forms.module.css +++ b/frontend/src/components/Forms/Forms.module.css @@ -116,10 +116,11 @@ margin-top: 3rem; } +/* FormShareCalendar */ .inputBox { display: grid; grid-template-columns: 6fr 1fr; - gap: 8px; + gap: 1rem; } .circle { @@ -133,3 +134,71 @@ 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); +} From aef0b97e93ad14ff61cc6114980f50a36bd2ede2 Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Thu, 6 Jul 2023 15:20:12 +0300 Subject: [PATCH 4/8] feat: add new api requests for sharing calendars --- frontend/src/utils/api/calendars.js | 116 ++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/frontend/src/utils/api/calendars.js b/frontend/src/utils/api/calendars.js index bf4742d..3042932 100644 --- a/frontend/src/utils/api/calendars.js +++ b/frontend/src/utils/api/calendars.js @@ -127,3 +127,119 @@ export const deleteCalendar = (id) => authorization: getAccessToken(), }, }); + +/* + ЗАПРОСЫ НА ШЕРИНГ КАЛЕНДАРЯ +*/ + +/* + Поделиться календарём с польщователем + Вернётся: + { + "owner": "user@example.com", + "user": "user@example.com", + "calendar": "string", + "custom_name": "string", + "custom_color": "#fDd449" + } +*/ +export const shareCalendar = ({ id, user, name, color }) => + fetch(`${BASE_URL}/v1/calendars/${id}/share`, { + method: 'POST', + headers: { + ...HEADERS, + authorization: getAccessToken(), + }, + body: JSON.stringify({ + user, + custom_name: name, + custom_color: color, + }), + }).then(getJson); + +/* + Получение календарей, которыми ТЫ поделился с пользователями + Вернётся массив объектов: + { + "owner": "user@example.com", + "users": [ + "user1@user.com", + "user2@user.com", + ], + "calendar": { + "id": 1, + "name": "string", + "color": "#000", + } + } +*/ +export const getAllSharedToOthers = () => + fetch(`${BASE_URL}/v1/calendars/shared_to_user`, { + headers: { + authorization: getAccessToken(), + }, + }).then(getJson); + +/* + Получение календарей, которыми поделились с тобой + Вернётся массив объектов: + { + "id": 0, + "owner": "user@example.com", + "calendar": { + "id": 1, + "name": "string", + "color": "#000", + }, + "custom_name": "string", + "custom_color": "#2Bd" + } +*/ +export const getAllSharedToMe = () => + fetch(`${BASE_URL}/v1/calendars/shared_to_me`, { + headers: { + authorization: getAccessToken(), + }, + }).then(getJson); + +/* + Изменить свою копию календаря + Вернётся: + { + "owner": "user@example.com", + "user": "user@example.com", + "calendar": "string", + "custom_name": "string", + "custom_color": "#2C6" + } +*/ +export const changeSharedCalendar = ({ id, name, color }) => + fetch(`${BASE_URL}/v1/calendars/${id}/share`, { + method: 'PATCH', + headers: { + ...HEADERS, + authorization: getAccessToken(), + }, + body: JSON.stringify({ + custom_name: name, + custom_color: color, + }), + }).then(getJson); + +/* + TODO: Спросить у бэков, работает это с двух сторон одиноково или нет + + Удалить доступ к календарю + Вернётся 204)))) +*/ + +export const deleteSharedCalendar = ({ id, user }) => + fetch(`${BASE_URL}/v1/calendars/${id}/share/`, { + method: 'DELETE', + headers: { + authorization: getAccessToken(), + }, + body: JSON.stringify({ + user, + }) + }); From 13cac77eb2ca90e09446b37cabc741d4e0ab5b27 Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Thu, 6 Jul 2023 17:17:58 +0300 Subject: [PATCH 5/8] feat: add base function of sharing --- frontend/src/components/App/App.jsx | 5 +- .../components/Forms/FormShareCalendar.jsx | 123 +++++++----------- .../src/components/Forms/Forms.module.css | 51 ++++---- frontend/src/utils/api/calendars.js | 20 +-- 4 files changed, 86 insertions(+), 113 deletions(-) diff --git a/frontend/src/components/App/App.jsx b/frontend/src/components/App/App.jsx index 22ba245..714a149 100644 --- a/frontend/src/components/App/App.jsx +++ b/frontend/src/components/App/App.jsx @@ -739,9 +739,10 @@ function App() { } }; - const handleShareCalendar = () => { + const handleShareCalendar = (data) => { // eslint-disable-next-line - console.log('поделился (^-^)'); + // TODO: добавить сюда вызов запроса на шаринг календариком + console.log(data); }; return ( diff --git a/frontend/src/components/Forms/FormShareCalendar.jsx b/frontend/src/components/Forms/FormShareCalendar.jsx index 996ec4c..fff4d80 100644 --- a/frontend/src/components/Forms/FormShareCalendar.jsx +++ b/frontend/src/components/Forms/FormShareCalendar.jsx @@ -14,9 +14,7 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) { const { id, name, color } = editableCalendar; const defaultValues = { - id, - name, - color, + email: '', }; const { @@ -27,7 +25,11 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) { } = useForm({ defaultValues, mode: 'onChange' }); const onSubmit = (data) => { - onShareCalendar(data); + const result = { ...data }; + result.id = id; + result.name = name; + result.color = color; + onShareCalendar(result); reset(); }; @@ -58,59 +60,35 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) {
-
-

Акселератор

+
+

{name}

-
-
-
- +
+
+
+ -

- hoba@ya.ru -

+

hoba@ya.ru

- - {/* */} -
+ + {/* */} +
- -
-
+ +
+
( @@ -135,33 +113,28 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) { id={field.name} {...field} autoFocus - type="text" - className={cn( - { - 'p-invalid': fieldState.invalid, - }, - )} + className={cn({ + 'p-invalid': fieldState.invalid, + })} /> )} /> - {/* eslint jsx-a11y/label-has-associated-control: ["error", { assert: "either" } ] */} - + {/* eslint jsx-a11y/label-has-associated-control: ["error", { assert: "either" } ] */} + - {getFormErrorMessage('title')} + {getFormErrorMessage('email')}
diff --git a/frontend/src/components/Forms/Forms.module.css b/frontend/src/components/Forms/Forms.module.css index 0557d6f..c62fef9 100644 --- a/frontend/src/components/Forms/Forms.module.css +++ b/frontend/src/components/Forms/Forms.module.css @@ -127,7 +127,6 @@ width: 18px; height: 18px; border-radius: 25px; - background: red; } .nameCalendar { @@ -137,58 +136,58 @@ .guestsTable { padding: 1.2rem; - border: 1px solid; + border: 1px solid; border-radius: 0.3rem; border-color: var(--surface-border); display: flex; - flex-direction: column; - gap: 12px; + flex-direction: column; + gap: 12px; column-gap: 1.1rem; } .guest { - width: 100%; + 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; + align-items: center; } .guestInfo { - max-width: 270px; - height: 32px; - border-radius: 20px; - background-color: var(--highlight-bg); - margin-left: 5px; + max-width: 270px; + height: 32px; + border-radius: 20px; + background-color: var(--highlight-bg); + margin-left: 5px; } .guestIcon { - margin-left: auto; - margin-right: 8px; + margin-left: auto; + margin-right: 8px; } .guestAvatar { - margin-left: 2px; - width: 28px; - height: 28px; - background-color: violet; + 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; + 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; + justify-self: center; border: none; background-color: transparent; color: var(--text-color); diff --git a/frontend/src/utils/api/calendars.js b/frontend/src/utils/api/calendars.js index 3042932..19a694d 100644 --- a/frontend/src/utils/api/calendars.js +++ b/frontend/src/utils/api/calendars.js @@ -151,10 +151,10 @@ export const shareCalendar = ({ id, user, name, color }) => authorization: getAccessToken(), }, body: JSON.stringify({ - user, - custom_name: name, - custom_color: color, - }), + user, + custom_name: name, + custom_color: color, + }), }).then(getJson); /* @@ -221,9 +221,9 @@ export const changeSharedCalendar = ({ id, name, color }) => authorization: getAccessToken(), }, body: JSON.stringify({ - custom_name: name, - custom_color: color, - }), + custom_name: name, + custom_color: color, + }), }).then(getJson); /* @@ -239,7 +239,7 @@ export const deleteSharedCalendar = ({ id, user }) => headers: { authorization: getAccessToken(), }, - body: JSON.stringify({ - user, - }) + body: JSON.stringify({ + user, + }), }); From dbbe835d0b8e7bee1052793c6b841000b5d978cb Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Sun, 9 Jul 2023 15:42:36 +0300 Subject: [PATCH 6/8] feat: WIP team array problem --- frontend/src/components/App/App.jsx | 47 ++++++++++++-- .../CalendarSelect/CalendarSelect.jsx | 2 +- .../components/Forms/FormShareCalendar.jsx | 64 +++++++++++-------- .../components/Popups/PopupShareCalendar.jsx | 10 ++- frontend/src/utils/api/calendars.js | 64 +++++++++++++------ 5 files changed, 135 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/App/App.jsx b/frontend/src/components/App/App.jsx index 34f04b1..263dffa 100644 --- a/frontend/src/components/App/App.jsx +++ b/frontend/src/components/App/App.jsx @@ -83,6 +83,8 @@ function App() { const [chosenCalendars, setChosenCalendars] = useState([]); const [editableCalendar, setEditableCalendar] = useState({}); const [editableEvent, setEditableEvent] = useState({}); + const [team, setTeam] = useState([]); + const [temp, setTemp] = useState([]); // Popups const [visiblePopupLogin, setVisiblePopupLogin] = useState(false); @@ -149,6 +151,7 @@ function App() { setCurrentUser({}); setAllUserCalendars([]); setAllUserEvents([]); + setTeam([]); setChosenCalendars(holidaysCalendar.map((c) => c.id)); closeAllPopups(); @@ -159,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) { @@ -304,6 +309,10 @@ function App() { setEditableCalendar, editableEvent, setEditableEvent, + team, + setTeam, + temp, + setTemp, }), [ holidays, @@ -312,6 +321,8 @@ function App() { chosenCalendars, editableCalendar, editableEvent, + team, + temp, ] ); @@ -663,9 +674,34 @@ function App() { }; const handleShareCalendar = (data) => { - // eslint-disable-next-line - // TODO: добавить сюда вызов запроса на шаринг календариком - console.log(data); + setIsLoading(true); + calendarApi + .shareCalendar(data) + .then((res) => setTeam([res.user, ...team])) + .catch(({ error, res }) => { + console.log(error, res); + handleErrors({ error, 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 ( @@ -691,7 +727,7 @@ function App() { onEventDoubleClick={setVisiblePopupEditEvent} onNewCalendarClick={setVisiblePopupNewCalendar} onEditCalendarClick={setVisiblePopupEditCalendar} - onShareCalendarClick={setVisiblePopupShareCalendar} + onShareCalendarClick={handleShowSharePopup} onNewEventClickUnauth={setVisiblePopupAskToRegister} onEditEvent={handleEditEvent} /> @@ -769,6 +805,7 @@ function App() { visible={visiblePopupShareCalendar} setVisible={setVisiblePopupShareCalendar} handleShare={handleShareCalendar} + colleagues={team} /> diff --git a/frontend/src/components/CalendarSelect/CalendarSelect.jsx b/frontend/src/components/CalendarSelect/CalendarSelect.jsx index 533e74d..271a748 100644 --- a/frontend/src/components/CalendarSelect/CalendarSelect.jsx +++ b/frontend/src/components/CalendarSelect/CalendarSelect.jsx @@ -32,7 +32,7 @@ export function CalendarSelect({ onEditCalendarClick, onShareCalendarClick }) { if (isEdit) { onEditCalendarClick(true); } else { - onShareCalendarClick(true); + onShareCalendarClick(calendar.id); } }; diff --git a/frontend/src/components/Forms/FormShareCalendar.jsx b/frontend/src/components/Forms/FormShareCalendar.jsx index fff4d80..6b4dd8f 100644 --- a/frontend/src/components/Forms/FormShareCalendar.jsx +++ b/frontend/src/components/Forms/FormShareCalendar.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { Button } from 'primereact/button'; -import { Avatar } from 'primereact/avatar'; +// import { Avatar } from 'primereact/avatar'; import { InputText } from 'primereact/inputtext'; import { Tooltip } from 'primereact/tooltip'; import { useForm, Controller } from 'react-hook-form'; @@ -9,26 +9,30 @@ import { classNames as cn } from 'primereact/utils'; import styles from './Forms.module.css'; import { CalendarsContext } from '../../context'; -export function FormShareCalendar({ onShareCalendar, setVisible }) { +export function FormShareCalendar({ onShareCalendar, setVisible, colleagues }) { const { editableCalendar } = useContext(CalendarsContext); const { id, name, color } = editableCalendar; + // console.log({colleagues}); + const defaultValues = { email: '', }; const { control, - formState: { errors, isValid, isDirty }, + formState: { errors, isValid }, handleSubmit, reset, } = useForm({ defaultValues, mode: 'onChange' }); const onSubmit = (data) => { - const result = { ...data }; - result.id = id; - result.name = name; - result.color = color; + const result = { + ...data, + id, + name, + color, + }; onShareCalendar(result); reset(); }; @@ -69,26 +73,34 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) {

{name}

-
-
-
- + {/* {colleagues.length > 0 && + (
+ {colleagues.map((colleague) => ( +
+
+ -

hoba@ya.ru

+

{colleague}

- - {/* */} -
+ - -
-
+
+ + +
+ ))} +
) + } */} @@ -153,4 +165,6 @@ export function FormShareCalendar({ onShareCalendar, setVisible }) { FormShareCalendar.propTypes = { onShareCalendar: PropTypes.func.isRequired, setVisible: PropTypes.func.isRequired, + // eslint-disable-next-line react/forbid-prop-types + colleagues: PropTypes.array.isRequired, }; diff --git a/frontend/src/components/Popups/PopupShareCalendar.jsx b/frontend/src/components/Popups/PopupShareCalendar.jsx index e40321b..748c019 100644 --- a/frontend/src/components/Popups/PopupShareCalendar.jsx +++ b/frontend/src/components/Popups/PopupShareCalendar.jsx @@ -3,12 +3,18 @@ import PropTypes from 'prop-types'; import { Dialog } from 'primereact/dialog'; import { FormShareCalendar } from '../Forms/FormShareCalendar'; -export function PopupShareCalendar({ visible, setVisible, handleShare }) { +export function PopupShareCalendar({ + visible, + setVisible, + handleShare, + colleagues, +}) { return ( setVisible(false)}> ); @@ -18,4 +24,6 @@ PopupShareCalendar.propTypes = { visible: PropTypes.bool.isRequired, setVisible: PropTypes.func.isRequired, handleShare: PropTypes.func.isRequired, + // eslint-disable-next-line react/forbid-prop-types + colleagues: PropTypes.array.isRequired, }; diff --git a/frontend/src/utils/api/calendars.js b/frontend/src/utils/api/calendars.js index 2b946ad..b621ca2 100644 --- a/frontend/src/utils/api/calendars.js +++ b/frontend/src/utils/api/calendars.js @@ -122,7 +122,7 @@ export const deleteCalendar = (id) => */ /* - Поделиться календарём с польщователем + Поделиться календарём с пользователем Вернётся: { "owner": "user@example.com", @@ -132,42 +132,66 @@ export const deleteCalendar = (id) => "custom_color": "#fDd449" } */ -export const shareCalendar = ({ id, user, name, color }) => - fetch(`${BASE_URL}/v1/calendars/${id}/share`, { +export const shareCalendar = ({ id, email, name, color }) => + fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { method: 'POST', headers: { ...HEADERS, authorization: getAccessToken(), }, body: JSON.stringify({ - user, + user: email, custom_name: name, custom_color: color, }), - }).then(getJson); + }); /* + NOTE: НЕ ИСПОЛЬЗУЕМ Получение календарей, которыми ТЫ поделился с пользователями Вернётся массив объектов: { "owner": "user@example.com", "users": [ - "user1@user.com", - "user2@user.com", + "user1@user.com", + "user2@user.com", ], "calendar": { - "id": 1, - "name": "string", - "color": "#000", + "id": 1, + "name": "string", + "color": "#000", } } */ export const getAllSharedToOthers = () => - fetch(`${BASE_URL}/v1/calendars/shared_to_user`, { + fetchWithRefresh(`${BASE_URL}/v1/calendars/shared_to_user/`, { + headers: { + authorization: getAccessToken(), + }, + }); + +/* + Получение данных конкретного календаря, которым ты поделился + Вернётся объект: + { + "owner": "user@example.com", + "users": [ + "user1@user.com", + "user2@user.com", + ], + "calendar": { + "id": 1, + "name": "string", + "color": "#000", + } + } +*/ +export const getSharedCalendarById = (id) => + fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { headers: { authorization: getAccessToken(), }, - }).then(getJson); + }); /* Получение календарей, которыми поделились с тобой @@ -176,20 +200,20 @@ export const getAllSharedToOthers = () => "id": 0, "owner": "user@example.com", "calendar": { - "id": 1, - "name": "string", - "color": "#000", + "id": 1, + "name": "string", + "color": "#000", }, "custom_name": "string", "custom_color": "#2Bd" } */ export const getAllSharedToMe = () => - fetch(`${BASE_URL}/v1/calendars/shared_to_me`, { + fetchWithRefresh(`${BASE_URL}/v1/calendars/shared_to_me/`, { headers: { authorization: getAccessToken(), }, - }).then(getJson); + }); /* Изменить свою копию календаря @@ -203,7 +227,7 @@ export const getAllSharedToMe = () => } */ export const changeSharedCalendar = ({ id, name, color }) => - fetch(`${BASE_URL}/v1/calendars/${id}/share`, { + fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { method: 'PATCH', headers: { ...HEADERS, @@ -213,7 +237,7 @@ export const changeSharedCalendar = ({ id, name, color }) => custom_name: name, custom_color: color, }), - }).then(getJson); + }); /* TODO: Спросить у бэков, работает это с двух сторон одиноково или нет @@ -223,7 +247,7 @@ export const changeSharedCalendar = ({ id, name, color }) => */ export const deleteSharedCalendar = ({ id, user }) => - fetch(`${BASE_URL}/v1/calendars/${id}/share/`, { + fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { method: 'DELETE', headers: { authorization: getAccessToken(), From 561e3021f079af0e708790c9b6a62636020f8004 Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Wed, 19 Jul 2023 12:06:34 +0300 Subject: [PATCH 7/8] feat: rework loader for sharing --- frontend/src/components/App/App.jsx | 47 ++++++++++++++----- .../components/Forms/FormShareCalendar.jsx | 40 +++++++++------- .../components/Popups/PopupShareCalendar.jsx | 7 ++- frontend/src/utils/api/calendars.js | 1 + 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/App/App.jsx b/frontend/src/components/App/App.jsx index 263dffa..d8d2e62 100644 --- a/frontend/src/components/App/App.jsx +++ b/frontend/src/components/App/App.jsx @@ -84,7 +84,6 @@ function App() { const [editableCalendar, setEditableCalendar] = useState({}); const [editableEvent, setEditableEvent] = useState({}); const [team, setTeam] = useState([]); - const [temp, setTemp] = useState([]); // Popups const [visiblePopupLogin, setVisiblePopupLogin] = useState(false); @@ -108,6 +107,7 @@ function App() { const [isDialogError, setIsDialogError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isFormLogin, setIsFormLogin] = useState(true); + // const [isShareLoading, setIsShareLoading] = useState(false); const today = new Date(); const start = useRef([today.getFullYear(), '-01-01'].join('')); @@ -310,9 +310,6 @@ function App() { editableEvent, setEditableEvent, team, - setTeam, - temp, - setTemp, }), [ holidays, @@ -322,7 +319,6 @@ function App() { editableCalendar, editableEvent, team, - temp, ] ); @@ -674,17 +670,38 @@ function App() { }; const handleShareCalendar = (data) => { - setIsLoading(true); + /* + здесь нужно включить новый лоадер, + который также нужно передавать через контекст + после чего сразу добавитть пользака в список + а уже после ответа заменять лоадер на галочку или на крестик + */ + const newMate = { + // TODO: что то придумать с id + email: data.email, + infoIcon: 'load', + } + setTeam([...team, newMate]); + calendarApi .shareCalendar(data) - .then((res) => setTeam([res.user, ...team])) + .then(() => { + newMate.infoIcon = 'success'; + + const index = team.findIndex((m) => m.email === data.email); + team.splice(index, 1); + + setTeam([...team, newMate]); + }) .catch(({ error, res }) => { - console.log(error, res); + newMate.infoIcon = 'denied'; + + const index = team.findIndex((m) => m.email === data.email); + team.splice(index, 1); + + setTeam([...team, newMate]); handleErrors({ error, res }); }) - .finally(() => { - setIsLoading(false); - }); }; const handleShowSharePopup = (id) => { @@ -692,7 +709,11 @@ function App() { calendarApi .getSharedCalendarById(id) .then((res) => { - setTeam(res.users); + // в случае, если календарь не расшарен, возвращается пустой объект и 200 + // возможно нужно попросить бэков вернуть + // const isEmpty = Object.keys(res).length === 0 && res.constructor === Object; + // setTeam(isEmpty ? [] : res.users) + setTeam(res.users) setVisiblePopupShareCalendar(true); }) .catch(({ error, res }) => { @@ -805,7 +826,7 @@ function App() { visible={visiblePopupShareCalendar} setVisible={setVisiblePopupShareCalendar} handleShare={handleShareCalendar} - colleagues={team} + // isShareLoading={isShareLoading} /> diff --git a/frontend/src/components/Forms/FormShareCalendar.jsx b/frontend/src/components/Forms/FormShareCalendar.jsx index 6b4dd8f..53729dc 100644 --- a/frontend/src/components/Forms/FormShareCalendar.jsx +++ b/frontend/src/components/Forms/FormShareCalendar.jsx @@ -1,7 +1,8 @@ +/* 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 { Avatar } from 'primereact/avatar'; import { InputText } from 'primereact/inputtext'; import { Tooltip } from 'primereact/tooltip'; import { useForm, Controller } from 'react-hook-form'; @@ -9,12 +10,10 @@ import { classNames as cn } from 'primereact/utils'; import styles from './Forms.module.css'; import { CalendarsContext } from '../../context'; -export function FormShareCalendar({ onShareCalendar, setVisible, colleagues }) { - const { editableCalendar } = useContext(CalendarsContext); +export function FormShareCalendar({ onShareCalendar, setVisible, /* isShareLoading */ }) { + const { editableCalendar, team } = useContext(CalendarsContext); const { id, name, color } = editableCalendar; - // console.log({colleagues}); - const defaultValues = { email: '', }; @@ -73,21 +72,31 @@ export function FormShareCalendar({ onShareCalendar, setVisible, colleagues }) {

{name}

- {/* {colleagues.length > 0 && + {team.length > 0 && (
- {colleagues.map((colleague) => ( + {team.map((mate) => (
-

{colleague}

- - - +

{mate.email}

+ + { + (mate.infoIcon) + ? ( + mate.infoIcon === 'load' ? ( + + ) : mate.infoIcon === 'success' ? ( + + ) : ( + + ) + ) + : + }
) - } */} + } setVisible(false)}> ); @@ -24,6 +24,5 @@ PopupShareCalendar.propTypes = { visible: PropTypes.bool.isRequired, setVisible: PropTypes.func.isRequired, handleShare: PropTypes.func.isRequired, - // eslint-disable-next-line react/forbid-prop-types - colleagues: PropTypes.array.isRequired, + // isShareLoading: PropTypes.bool.isRequired }; diff --git a/frontend/src/utils/api/calendars.js b/frontend/src/utils/api/calendars.js index b621ca2..2e38a78 100644 --- a/frontend/src/utils/api/calendars.js +++ b/frontend/src/utils/api/calendars.js @@ -132,6 +132,7 @@ export const deleteCalendar = (id) => "custom_color": "#fDd449" } */ +// TODO: Здесь нужно получать id пользака export const shareCalendar = ({ id, email, name, color }) => fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { method: 'POST', From a80b0bea5e90e36bd0f53a161a383332974c36cb Mon Sep 17 00:00:00 2001 From: e-zybkin Date: Wed, 19 Jul 2023 17:33:36 +0300 Subject: [PATCH 8/8] feat: add invite and kick functions --- frontend/src/components/App/App.jsx | 70 +++++++----- .../BackToTopButton.module.css | 2 +- .../CalendarSelect/CalendarsBlock.jsx | 4 +- .../components/Forms/FormShareCalendar.jsx | 104 +++++++++++------- .../components/Popups/PopupShareCalendar.jsx | 6 +- frontend/src/utils/api/calendars.js | 8 +- 6 files changed, 120 insertions(+), 74 deletions(-) diff --git a/frontend/src/components/App/App.jsx b/frontend/src/components/App/App.jsx index 6b3c2ec..7e0d62a 100644 --- a/frontend/src/components/App/App.jsx +++ b/frontend/src/components/App/App.jsx @@ -108,7 +108,6 @@ function App() { const [isDialogError, setIsDialogError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isFormLogin, setIsFormLogin] = useState(true); - // const [isShareLoading, setIsShareLoading] = useState(false); const today = new Date(); const start = useRef([today.getFullYear(), '-01-01'].join('')); @@ -671,38 +670,53 @@ function App() { }; const handleShareCalendar = (data) => { - /* - здесь нужно включить новый лоадер, - который также нужно передавать через контекст - после чего сразу добавитть пользака в список - а уже после ответа заменять лоадер на галочку или на крестик - */ - const newMate = { - // TODO: что то придумать с id - email: data.email, - infoIcon: 'load', - } - setTeam([...team, newMate]); + const newMate = { + email: data.email, + infoIcon: 'load', + }; + setTeam([...team, newMate]); calendarApi .shareCalendar(data) .then(() => { - newMate.infoIcon = 'success'; + newMate.infoIcon = 'success'; - const index = team.findIndex((m) => m.email === data.email); - team.splice(index, 1); + 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'; + 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); + } - const index = team.findIndex((m) => m.email === data.email); - team.splice(index, 1); + setTeam([...team, newMate]); + // handleErrors({ error, res }); + }); + }; - 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) => { @@ -710,11 +724,7 @@ function App() { calendarApi .getSharedCalendarById(id) .then((res) => { - // в случае, если календарь не расшарен, возвращается пустой объект и 200 - // возможно нужно попросить бэков вернуть - // const isEmpty = Object.keys(res).length === 0 && res.constructor === Object; - // setTeam(isEmpty ? [] : res.users) - setTeam(res.users) + setTeam(res.users); setVisiblePopupShareCalendar(true); }) .catch(({ error, res }) => { @@ -828,7 +838,7 @@ function App() { visible={visiblePopupShareCalendar} setVisible={setVisiblePopupShareCalendar} handleShare={handleShareCalendar} - // isShareLoading={isShareLoading} + handleDeleteMate={handleDeleteMate} /> diff --git a/frontend/src/components/BackToTopButton/BackToTopButton.module.css b/frontend/src/components/BackToTopButton/BackToTopButton.module.css index 8d13bb1..1b798e4 100644 --- a/frontend/src/components/BackToTopButton/BackToTopButton.module.css +++ b/frontend/src/components/BackToTopButton/BackToTopButton.module.css @@ -21,7 +21,7 @@ top: 92vh; right: 0px; height: 50px; - width: 60px; + width: 56px; visibility: hidden; } diff --git a/frontend/src/components/CalendarSelect/CalendarsBlock.jsx b/frontend/src/components/CalendarSelect/CalendarsBlock.jsx index b799ca4..8645cfb 100644 --- a/frontend/src/components/CalendarSelect/CalendarsBlock.jsx +++ b/frontend/src/components/CalendarSelect/CalendarsBlock.jsx @@ -65,7 +65,9 @@ export function CalendarBlock(props) { onClick={() => handleClick(calendar, false)} > diff --git a/frontend/src/components/Forms/FormShareCalendar.jsx b/frontend/src/components/Forms/FormShareCalendar.jsx index 53729dc..083b997 100644 --- a/frontend/src/components/Forms/FormShareCalendar.jsx +++ b/frontend/src/components/Forms/FormShareCalendar.jsx @@ -9,8 +9,13 @@ 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, /* isShareLoading */ }) { +export function FormShareCalendar({ + onShareCalendar, + setVisible, + handleDeleteMate, +}) { const { editableCalendar, team } = useContext(CalendarsContext); const { id, name, color } = editableCalendar; @@ -72,44 +77,69 @@ export function FormShareCalendar({ onShareCalendar, setVisible, /* isShareLoadi

{name}

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

{mate.email}

+

+ {mate.email} +

- { - (mate.infoIcon) - ? ( - mate.infoIcon === 'load' ? ( - - ) : mate.infoIcon === 'success' ? ( - - ) : ( - - ) - ) - : - } -
+ {mate.infoIcon ? ( + mate.infoIcon === 'load' ? ( + + ) : mate.infoIcon === 'success' ? ( + + ) : ( + + ) + ) : ( + + )} +
- -
- ))} -
) - } + {mate.infoIcon ? ( + mate.infoIcon === 'success' && ( + + ) + ) : ( + + )} +
+ ))} +
+ )} setVisible(false)}> ); @@ -24,5 +24,5 @@ PopupShareCalendar.propTypes = { visible: PropTypes.bool.isRequired, setVisible: PropTypes.func.isRequired, handleShare: PropTypes.func.isRequired, - // isShareLoading: PropTypes.bool.isRequired + handleDeleteMate: PropTypes.func.isRequired, }; diff --git a/frontend/src/utils/api/calendars.js b/frontend/src/utils/api/calendars.js index 2e38a78..69ea094 100644 --- a/frontend/src/utils/api/calendars.js +++ b/frontend/src/utils/api/calendars.js @@ -167,6 +167,7 @@ export const shareCalendar = ({ id, email, name, color }) => export const getAllSharedToOthers = () => fetchWithRefresh(`${BASE_URL}/v1/calendars/shared_to_user/`, { headers: { + ...HEADERS, authorization: getAccessToken(), }, }); @@ -190,6 +191,7 @@ export const getAllSharedToOthers = () => export const getSharedCalendarById = (id) => fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { headers: { + ...HEADERS, authorization: getAccessToken(), }, }); @@ -212,6 +214,7 @@ export const getSharedCalendarById = (id) => export const getAllSharedToMe = () => fetchWithRefresh(`${BASE_URL}/v1/calendars/shared_to_me/`, { headers: { + ...HEADERS, authorization: getAccessToken(), }, }); @@ -247,13 +250,14 @@ export const changeSharedCalendar = ({ id, name, color }) => Вернётся 204)))) */ -export const deleteSharedCalendar = ({ id, user }) => +export const deleteSharedCalendar = ({ id, email }) => fetchWithRefresh(`${BASE_URL}/v1/calendars/${id}/share/`, { method: 'DELETE', headers: { + ...HEADERS, authorization: getAccessToken(), }, body: JSON.stringify({ - user, + user: email, }), });