From 820a441be83e4fc51321c3953736812e9ed673bd Mon Sep 17 00:00:00 2001 From: DeDxYk594 Date: Sun, 15 Dec 2024 19:19:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B3=D0=BB=D0=B0=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=20=D0=B4=D0=BE=D1=81?= =?UTF-8?q?=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 20 ++++---- src/api/cardDetails.ts | 21 +++++++++ src/api/members.ts | 81 ++++++++++++++++++++++++++++++-- src/api/responseTypes.ts | 18 ++++++- src/containers/BoardSettings.tsx | 18 ++++++- src/containers/CardDetails.tsx | 6 ++- src/containers/cardDetails.scss | 5 -- src/routes/routes.ts | 11 ----- src/routes/routesFlag.ts | 38 +++++++++++++-- src/screens/MainApp.tsx | 70 ++++++++++++++++++++++++++- src/screens/mainApp.scss | 20 ++++++++ src/stores/meStore.ts | 3 +- src/stores/previewStore.ts | 43 +++++++++++++++++ src/stores/routerStore.ts | 5 ++ 14 files changed, 322 insertions(+), 37 deletions(-) delete mode 100644 src/routes/routes.ts create mode 100644 src/screens/mainApp.scss create mode 100644 src/stores/previewStore.ts diff --git a/src/App.tsx b/src/App.tsx index 64aed00..aa597b1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,8 @@ import { getFlagRoutes } from './routes/routesFlag'; import { CsatPoll } from './screens/CsatPoll'; import { CsatResults } from './screens/CsatResults'; import { setCsatStore, useCsatStore } from './stores/csatStore'; +import { loadBoard, useActiveBoardStore } from './stores/activeBoardStore'; +import { useDndStore } from './stores/dndStore'; const App: IComponentFunction = () => { const routerStore = useRouterStore(); @@ -37,7 +39,6 @@ setTimeout(() => { window.addEventListener('message', (ev: MessageEvent) => { if (ev.data === 'close_csat') { - console.log('should output'); setTimeout(() => { setCsatStore({ ...useCsatStore(), isOpened: false }); }, 0); @@ -62,11 +63,14 @@ if ('serviceWorker' in navigator) { console.error('Service Worker is not available'); } -// const REFETCH_DELAY = 5000; +const REFETCH_DELAY = 5000; -// setInterval(() => { -// const activeBoard = useActiveBoardStore(); -// if (activeBoard !== undefined) { -// loadBoard(activeBoard.id, true); -// } -// }, REFETCH_DELAY); +setInterval(() => { + const activeBoard = useActiveBoardStore(); + const dnd = useDndStore(); + if (dnd === undefined) { + if (activeBoard !== undefined) { + loadBoard(activeBoard.board.id, true); + } + } +}, REFETCH_DELAY); diff --git a/src/api/cardDetails.ts b/src/api/cardDetails.ts index e289bab..44f8e11 100644 --- a/src/api/cardDetails.ts +++ b/src/api/cardDetails.ts @@ -12,6 +12,7 @@ import { apiPost, apiPut, HTTP_STATUS_CREATED, + HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK, } from './apiHelper'; import { @@ -25,6 +26,7 @@ import { CardDetailsResponse, CheckListFieldResponse, CommentResponse, + SharedCardResponse, UserResponse, } from './responseTypes'; import { @@ -191,3 +193,22 @@ export const addAttachment = async ( showToast('Ошибка при добавлении файла', 'error'); } }; + +export const getCardByLink = async ( + cardUuid: string +): Promise => { + const response = await apiGet(`/sharedCard/${cardUuid}`); + switch (response.status) { + case HTTP_STATUS_CREATED: + case HTTP_STATUS_OK: { + const res = response.body as SharedCardResponse; + return res; + } + case HTTP_STATUS_NOT_FOUND: + showToast('Карточка удалена или ссылка повреждена', 'error'); + return undefined; + default: + showToast('Неизвестная ошибка при получении карточки', 'error'); + return undefined; + } +}; diff --git a/src/api/members.ts b/src/api/members.ts index c7dc739..340f4bf 100644 --- a/src/api/members.ts +++ b/src/api/members.ts @@ -1,13 +1,16 @@ -import { UserToBoard } from '@/types/types'; +import { Board, UserToBoard } from '@/types/types'; import { apiDelete, apiGet, + apiPost, apiPut, + HTTP_STATUS_CREATED, + HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK, } from './apiHelper'; import { showToast } from '@/stores/toastNotificationStore'; -import { MemberWithPermissionsResponse } from './responseTypes'; -import { decodeMember } from './decode'; +import { BoardResponse, MemberWithPermissionsResponse } from './responseTypes'; +import { decodeBoard, decodeMember } from './decode'; export const getBoardPermissions = async ( boardId: number @@ -57,3 +60,75 @@ export const updateMember = async ( throw new Error('Неизвестная ошибка'); } }; + +export const createInviteLink = async ( + boardId: number +): Promise => { + const response = await apiPut(`/inviteLink/board_${boardId}`); + switch (response.status) { + case HTTP_STATUS_OK: + case HTTP_STATUS_CREATED: + return response.body.inviteLinkUuid as string; + default: + showToast('Ошибка при задании ссылки-приглашения', 'error'); + return; + } +}; + +export const deleteInviteLink = async (boardId: number): Promise => { + const response = await apiDelete(`/inviteLink/board_${boardId}`); + switch (response.status) { + case HTTP_STATUS_OK: + case HTTP_STATUS_CREATED: + return true; + default: + showToast('Ошибка при задании ссылки-приглашения', 'error'); + return false; + } +}; + +export const fetchInviteLink = async ( + inviteLinkUuid: string +): Promise => { + const response = await apiGet(`/joinBoard/${inviteLinkUuid}`); + switch (response.status) { + case HTTP_STATUS_OK: + case HTTP_STATUS_CREATED: + return decodeBoard(response.body as BoardResponse); + case HTTP_STATUS_NOT_FOUND: + showToast( + 'Возможно, ссылка-приглашение была удалена или повреждена', + 'error' + ); + return undefined; + default: + showToast( + 'Неизвестная ошибка при получении приглашения на доску', + 'error' + ); + return undefined; + } +}; + +export const joinInviteLink = async ( + inviteLinkUuid: string +): Promise => { + const response = await apiPost(`/joinBoard/${inviteLinkUuid}`); + switch (response.status) { + case HTTP_STATUS_OK: + case HTTP_STATUS_CREATED: + return decodeBoard(response.body as BoardResponse); + case HTTP_STATUS_NOT_FOUND: + showToast( + 'Возможно, ссылка-приглашение была удалена или повреждена', + 'error' + ); + return undefined; + default: + showToast( + 'Неизвестная ошибка при получении приглашения на доску', + 'error' + ); + return undefined; + } +}; diff --git a/src/api/responseTypes.ts b/src/api/responseTypes.ts index ede7978..c7ab29c 100644 --- a/src/api/responseTypes.ts +++ b/src/api/responseTypes.ts @@ -1,4 +1,4 @@ -import { CsatQuestion } from "@/types/types"; +import { CsatQuestion } from '@/types/types'; export interface BoardContentResponse { myRole: 'viewer' | 'editor' | 'editor_chief' | 'admin'; @@ -92,3 +92,19 @@ export interface CardDetailsResponse { comments: CommentResponse[]; assignedUsers: UserResponse[]; } + +export type SharedCardResponse = + | MySharedCardResponse + | ForeignSharedCardResponse; + +export interface MySharedCardResponse { + type: 'my'; + boardId: number; + cardId: number; +} + +export interface ForeignSharedCardResponse { + type: 'foreign'; + board: BoardResponse; + card: CardDetailsResponse; +} diff --git a/src/containers/BoardSettings.tsx b/src/containers/BoardSettings.tsx index bd46b07..e72dd85 100644 --- a/src/containers/BoardSettings.tsx +++ b/src/containers/BoardSettings.tsx @@ -7,7 +7,7 @@ import { useActiveBoardStore, } from '@/stores/activeBoardStore'; import { Input } from '@/components/Input'; -import { removeMember, updateMember } from '@/api/members'; +import { createInviteLink, removeMember, updateMember } from '@/api/members'; import { setMembersStore, useMembersStore } from '@/stores/members'; import { showToast } from '@/stores/toastNotificationStore'; import { useMeStore } from '@/stores/meStore'; @@ -142,7 +142,7 @@ export const BoardSettings = () => {

Ссылка-приглашение

) : ( @@ -151,6 +151,20 @@ export const BoardSettings = () => { key="create_invite_link" text="Создать ссылку-приглашение" icon="bi-link-45deg" + callback={() => { + createInviteLink(activeBoard.board.id).then( + (linkUuid) => { + if (linkUuid !== undefined) { + showToast( + 'Успешно задана ссылка-приглашение!', + 'success' + ); + activeBoard.board.myInviteLinkUuid = linkUuid; + setActiveBoardStore(activeBoard); + } + } + ); + }} /> )} diff --git a/src/containers/CardDetails.tsx b/src/containers/CardDetails.tsx index c296787..de7a349 100644 --- a/src/containers/CardDetails.tsx +++ b/src/containers/CardDetails.tsx @@ -276,7 +276,7 @@ export const CardDetailsContainer = (props: ComponentProps) => { )}
-

Комментарии

+ {cardDetails.comments.length &&

Комментарии

} {commentInput ? ( <> { />
-

Назначенные пользователи

+ {cardDetails.assignedUsers.length && ( +

Назначенные пользователи

+ )} {cardDetails.assignedUsers.map((u) => { return (
diff --git a/src/containers/cardDetails.scss b/src/containers/cardDetails.scss index db57ff6..6042aa9 100644 --- a/src/containers/cardDetails.scss +++ b/src/containers/cardDetails.scss @@ -5,11 +5,6 @@ gap: 20px; } -.card-details_block { - min-height: 140px; - max-width: 450px; -} - .card-details__left-section { width: 60%; display: flex; diff --git a/src/routes/routes.ts b/src/routes/routes.ts deleted file mode 100644 index 6966280..0000000 --- a/src/routes/routes.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const routes = { - home: '/', - login: '/login', - register: '/register', - app: '/app', - board: '/app/boardId', - method: '/app/boardId/method', // method: kanban, list ... - boardSettings: '/app/boardId/method/settings', - profile: '/app/profile', - search: '/app/boardId/method/search', -}; diff --git a/src/routes/routesFlag.ts b/src/routes/routesFlag.ts index 06fc0d0..6bf49f8 100644 --- a/src/routes/routesFlag.ts +++ b/src/routes/routesFlag.ts @@ -1,33 +1,65 @@ import { loadBoard } from '@/stores/activeBoardStore'; import { updateBoards } from '@/stores/boardsStore'; +import { + loadBoardInvitePreview, + loadCardPreview, +} from '@/stores/previewStore'; export interface RouterFlags { isHome: boolean; isApp: boolean; isPoll: boolean; + isPreview: boolean; // Является ли просмотром карточки по ссылке или просмотром приглашения + isCardPreview: boolean; + isBoardPreview: boolean; + cardUuid: string | undefined; + boardInviteUuid: string | undefined; isCsatResults: boolean; boardId: number | undefined; } export const getFlagRoutes = (currentPath: string): RouterFlags => { let boardId: number | undefined = undefined; - if (currentPath.startsWith('/app')) { + let cardUuid: string | undefined; + let boardInviteUuid: string | undefined; + + const isBoardPreview = currentPath.startsWith('/inviteBoard'); + const isCardPreview = currentPath.startsWith('/card'); + const isApp = + currentPath.startsWith('/app') || isBoardPreview || isCardPreview; + if (isApp) { if (currentPath.startsWith('/app/board_')) { boardId = parseInt(currentPath.slice('/app/board_'.length)); } else { boardId = undefined; } } - if (currentPath.startsWith('/app')) { + + if (isApp && !(isCardPreview || isBoardPreview)) { updateBoards(); loadBoard(boardId); } + if (isCardPreview) { + cardUuid = currentPath.slice('/card/'.length); + loadCardPreview(cardUuid); + } + + if (isBoardPreview) { + boardInviteUuid = currentPath.slice('/inviteBoard/'.length); + loadBoardInvitePreview(boardInviteUuid); + } + return { isHome: ['/login', '/register', '/'].indexOf(currentPath) !== -1, isCsatResults: currentPath === '/csat_results', isPoll: currentPath === '/csat_poll', - isApp: currentPath.startsWith('/app'), + isApp, boardId, + boardInviteUuid, + cardUuid, + isPreview: isBoardPreview || isCardPreview, + isBoardPreview, + isCardPreview, }; }; diff --git a/src/screens/MainApp.tsx b/src/screens/MainApp.tsx index e5d78f7..8673e0f 100644 --- a/src/screens/MainApp.tsx +++ b/src/screens/MainApp.tsx @@ -17,15 +17,23 @@ import { useCardDetailsStore, } from '@/stores/cardDetailsStore'; import { CardDetailsContainer } from '@/containers/CardDetails'; +import { setPreviewStore, usePreviewStore } from '@/stores/previewStore'; +import { goToUrl, useRouterStore } from '@/stores/routerStore'; +import { Button } from '@/components/Button'; +import './mainApp.scss'; +import { joinInviteLink } from '@/api/members'; +import { useMeStore } from '@/stores/meStore'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export const MainApp = (props: ComponentProps) => { const [leftPanelOpened, setLeftPanelOpened] = useState(false); const modalDialogsStore = useModalDialogsStore(); const activeBoard = useActiveBoardStore(); + const preview = usePreviewStore(); const boards = useBoardsStore(); const csat = useCsatStore(); const cardDetails = useCardDetailsStore(); + const me = useMeStore(); if (boards === undefined) { getBoards().then((newBoards: Board[]) => { @@ -106,6 +114,59 @@ export const MainApp = (props: ComponentProps) => { )} + {preview !== undefined && preview.type === 'board' && ( + { + setPreviewStore(undefined); + goToUrl('/app'); + }} + > +
+
{preview.board.title}
+ +
+
+
+
+ )}
{activeBoard !== undefined && ( @@ -115,7 +176,14 @@ export const MainApp = (props: ComponentProps) => { alt="" /> )} - {activeBoard === undefined && ( + {preview !== undefined && ( + + )} + {activeBoard === undefined && preview === undefined && (
diff --git a/src/screens/mainApp.scss b/src/screens/mainApp.scss new file mode 100644 index 0000000..1edb986 --- /dev/null +++ b/src/screens/mainApp.scss @@ -0,0 +1,20 @@ +.board-invite__image { + max-width: 100%; + max-height: 300px; + object-fit: cover; + border-radius: 3px; +} + +.board-invite__buttons { + display: flex; + flex-direction: row; + justify-content: end; + column-gap: 10px; +} + +.board-invite__title { + font-size: 1.5rem; + font-weight: bold; + width: 100%; + text-align: center; +} diff --git a/src/stores/meStore.ts b/src/stores/meStore.ts index 8aeb9cf..8c45b6c 100644 --- a/src/stores/meStore.ts +++ b/src/stores/meStore.ts @@ -10,12 +10,13 @@ export const [useMeStore, setMeStore] = defineStore( export const updateMe = () => { setTimeout(() => { + const router = useRouterStore(); getUserMe().then((user) => { if (user !== undefined) { setMeStore(user); } else { setMeStore(undefined); - if (useRouterStore().isApp) { + if (router.isApp && !router.isPreview) { goToUrl('/'); } } diff --git a/src/stores/previewStore.ts b/src/stores/previewStore.ts new file mode 100644 index 0000000..126d5bb --- /dev/null +++ b/src/stores/previewStore.ts @@ -0,0 +1,43 @@ +import { getCardByLink } from '@/api/cardDetails'; +import { decodeBoard, decodeCardDetails } from '@/api/decode'; +import { fetchInviteLink } from '@/api/members'; +import { defineStore } from '@/jsxCore/hooks'; +import { Board, CardDetails } from '@/types/types'; + +type PreviewStore = CardPreviewStore | BoardPreviewStore; + +interface CardPreviewStore { + type: 'card'; + board: Board; + cardDetails: CardDetails; +} +interface BoardPreviewStore { + type: 'board'; + board: Board; +} + +export const [usePreviewStore, setPreviewStore] = defineStore< + PreviewStore | undefined +>('previewStore', undefined); + +export const loadBoardInvitePreview = (inviteLinkUuid: string) => { + fetchInviteLink(inviteLinkUuid).then((result) => { + if (result !== undefined) { + setPreviewStore({ type: 'board', board: result }); + } + }); +}; + +export const loadCardPreview = (cardUuid: string) => { + getCardByLink(cardUuid).then((card) => { + if (card !== undefined) { + if (card.type === 'foreign') { + setPreviewStore({ + type: 'card', + board: decodeBoard(card.board), + cardDetails: decodeCardDetails(card.card), + }); + } + } + }); +}; diff --git a/src/stores/routerStore.ts b/src/stores/routerStore.ts index 949f5fc..3f32a38 100644 --- a/src/stores/routerStore.ts +++ b/src/stores/routerStore.ts @@ -40,5 +40,10 @@ export const [useRouterStore, setRouterStore] = defineStore( boardId: undefined, isCsatResults: false, isPoll: false, + isBoardPreview: false, + isCardPreview: false, + isPreview: false, + cardUuid: undefined, + boardInviteUuid: undefined, } );