diff --git a/worklenz-frontend/src/components/CustomAvatar.tsx b/worklenz-frontend/src/components/CustomAvatar.tsx index ef63448e..03f98d1c 100644 --- a/worklenz-frontend/src/components/CustomAvatar.tsx +++ b/worklenz-frontend/src/components/CustomAvatar.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import Tooltip from 'antd/es/tooltip'; -import Avatar from 'antd/es/avatar'; - import { AvatarNamesMap } from '../shared/constants'; +import { Avatar, Tooltip } from '@/shared/antd-imports'; interface CustomAvatarProps { avatarName: string; diff --git a/worklenz-frontend/src/components/TawkTo.tsx b/worklenz-frontend/src/components/TawkTo.tsx deleted file mode 100644 index c447a050..00000000 --- a/worklenz-frontend/src/components/TawkTo.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useEffect } from 'react'; - -// Add TypeScript declarations for Tawk_API -declare global { - interface Window { - Tawk_API?: any; - Tawk_LoadStart?: Date; - } -} - -interface TawkToProps { - propertyId: string; - widgetId: string; -} - -const TawkTo: React.FC = ({ propertyId, widgetId }) => { - useEffect(() => { - // Initialize tawk.to chat - const s1 = document.createElement('script'); - s1.async = true; - s1.src = `https://embed.tawk.to/${propertyId}/${widgetId}`; - s1.setAttribute('crossorigin', '*'); - - const s0 = document.getElementsByTagName('script')[0]; - s0.parentNode?.insertBefore(s1, s0); - - return () => { - // Clean up when the component unmounts - // Remove the script tag - const tawkScript = document.querySelector(`script[src*="tawk.to/${propertyId}"]`); - if (tawkScript && tawkScript.parentNode) { - tawkScript.parentNode.removeChild(tawkScript); - } - - // Remove the tawk.to iframe - const tawkIframe = document.getElementById('tawk-iframe'); - if (tawkIframe) { - tawkIframe.remove(); - } - - // Reset Tawk globals - delete window.Tawk_API; - delete window.Tawk_LoadStart; - }; - }, [propertyId, widgetId]); - - return null; -}; - -export default TawkTo; diff --git a/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx b/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx index 11a3282e..b28e46af 100644 --- a/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx +++ b/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx @@ -1,11 +1,8 @@ -import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; import { fetchStorageInfo } from '@/features/admin-center/admin-center.slice'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { useAppSelector } from '@/hooks/useAppSelector'; import { SUBSCRIPTION_STATUS } from '@/shared/constants'; -import { IBillingAccountStorage } from '@/types/admin-center/admin-center.types'; -import logger from '@/utils/errorLogger'; -import { Card, Progress, Typography } from 'antd/es'; +import { Card, Progress, Typography } from '@/shared/antd-imports'; import { useEffect, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx b/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx index d86ea72d..8317ab07 100644 --- a/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx +++ b/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx @@ -1,8 +1,7 @@ import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; import logger from '@/utils/errorLogger'; import { EnterOutlined, EditOutlined } from '@/shared/antd-imports'; -import { Card, Button, Tooltip, Typography } from '@/shared/antd-imports'; -import TextArea from 'antd/es/input/TextArea'; +import { Card, Button, Tooltip, Typography, TextArea } from '@/shared/antd-imports'; import { TFunction } from 'i18next'; import { useState, useEffect } from 'react'; diff --git a/worklenz-frontend/src/components/board/board-assignee-selector/board-assignee-selector.tsx b/worklenz-frontend/src/components/board/board-assignee-selector/board-assignee-selector.tsx index 27604a44..5b8cb529 100644 --- a/worklenz-frontend/src/components/board/board-assignee-selector/board-assignee-selector.tsx +++ b/worklenz-frontend/src/components/board/board-assignee-selector/board-assignee-selector.tsx @@ -1,31 +1,32 @@ -import { InputRef } from 'antd/es/input'; -import Card from 'antd/es/card'; -import Checkbox from 'antd/es/checkbox'; -import Divider from 'antd/es/divider'; -import Dropdown from 'antd/es/dropdown'; -import Empty from 'antd/es/empty'; -import Flex from 'antd/es/flex'; -import Input from 'antd/es/input'; -import List from 'antd/es/list'; -import Typography from 'antd/es/typography'; -import Button from 'antd/es/button'; - import { useMemo, useRef, useState } from 'react'; +import { + InputRef, + PlusOutlined, + UsergroupAddOutlined, + Card, + Flex, + Input, + List, + Typography, + Checkbox, + Divider, + Button, + Empty, + Dropdown, + CheckboxChangeEvent, +} from '@/shared/antd-imports'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; -import { toggleProjectMemberDrawer } from '../../../features/projects/singleProject/members/projectMembersSlice'; -import { colors } from '../../../styles/colors'; -import { PlusOutlined, UsergroupAddOutlined } from '@/shared/antd-imports'; +import { toggleProjectMemberDrawer } from '@features/projects/singleProject/members/projectMembersSlice'; +import { colors } from '@/styles/colors'; import { useTranslation } from 'react-i18next'; import SingleAvatar from '@/components/common/single-avatar/single-avatar'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; import { ITeamMembersViewModel } from '@/types/teamMembers/teamMembersViewModel.types'; -import { sortByBooleanField, sortBySelection, sortTeamMembers } from '@/utils/sort-team-members'; +import { sortTeamMembers } from '@/utils/sort-team-members'; import { useAuthService } from '@/hooks/useAuth'; import { useSocket } from '@/socket/socketContext'; import { SocketEvents } from '@/shared/socket-events'; -import { getTeamMembers } from '@/features/team-members/team-members.slice'; interface BoardAssigneeSelectorProps { task: IProjectTask; diff --git a/worklenz-frontend/src/components/common/invite-team-members-modal/InviteTeamMembersModal.tsx b/worklenz-frontend/src/components/common/invite-team-members-modal/InviteTeamMembersModal.tsx new file mode 100644 index 00000000..b5f50e62 --- /dev/null +++ b/worklenz-frontend/src/components/common/invite-team-members-modal/InviteTeamMembersModal.tsx @@ -0,0 +1,280 @@ +import { Button, Flex, Input, message, Modal, Select, Space, Typography, List, Avatar } from '@/shared/antd-imports'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { + toggleInviteMemberDrawer, + triggerTeamMembersRefresh, +} from '../../../features/settings/member/memberSlice'; +import { useTranslation } from 'react-i18next'; +import { useState, useEffect, useCallback, useMemo, memo } from 'react'; +import { ITeamMemberCreateRequest } from '@/types/teamMembers/team-member-create-request'; +import { DeleteOutlined, UserOutlined } from '@ant-design/icons'; + +interface MemberEntry { + email: string; + access: 'member' | 'admin' | 'guest'; +} + + +const InviteTeamMembersModal = () => { + const [newMembers, setNewMembers] = useState([]); + const [emailInput, setEmailInput] = useState(''); + const [loading, setLoading] = useState(false); + + const { t } = useTranslation('settings/team-members'); + const isModalOpen = useAppSelector(state => state.memberReducer.isInviteMemberDrawerOpen); + const dispatch = useAppDispatch(); + + + useEffect(() => { + if (isModalOpen) { + // Reset state when modal opens + setNewMembers([]); + setEmailInput(''); + // Focus on email input when modal opens + setTimeout(() => { + const emailInput = document.querySelector('input[type="text"]'); + if (emailInput) { + (emailInput as HTMLElement).focus(); + } + }, 100); + } + }, [isModalOpen]); + + const handleFormSubmit = async () => { + try { + setLoading(true); + + if (newMembers.length === 0) { + message.error('Please add at least one member'); + return; + } + + // Send invitations for each member + const promises = newMembers.map(member => { + const body: ITeamMemberCreateRequest = { + emails: [member.email], + is_admin: member.access === 'admin', + is_guest: member.access === 'guest', + }; + return teamMembersApiService.createTeamMember(body); + }); + + const results = await Promise.allSettled(promises); + const successful = results.filter(r => r.status === 'fulfilled').length; + + if (successful > 0) { + message.success(`${successful} invitation(s) sent successfully`); + setNewMembers([]); + setEmailInput(''); + dispatch(triggerTeamMembersRefresh()); + dispatch(toggleInviteMemberDrawer()); + } + + const failed = results.length - successful; + if (failed > 0) { + message.error(`${failed} invitation(s) failed`); + } + } catch (error) { + message.error(t('createMemberErrorMessage')); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + setNewMembers([]); + setEmailInput(''); + dispatch(toggleInviteMemberDrawer()); + }; + + const handleEmailKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + + const trimmedEmail = emailInput.trim(); + + // Don't show error for empty input, just ignore + if (!trimmedEmail) { + return; + } + + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!emailPattern.test(trimmedEmail)) { + message.error('Please enter a valid email address'); + return; + } + + // Check if email already exists + if (newMembers.find(m => m.email === trimmedEmail)) { + message.warning('Email already added'); + return; + } + + // Add new member + setNewMembers([...newMembers, { email: trimmedEmail, access: 'member' }]); + setEmailInput(''); + } + }; + + const updateMemberAccess = (index: number, access: 'member' | 'admin' | 'guest') => { + const updated = [...newMembers]; + updated[index].access = access; + setNewMembers(updated); + }; + + const removeMember = (index: number) => { + setNewMembers(newMembers.filter((_, i) => i !== index)); + }; + + + + + const accessOptions = useMemo(() => [ + { value: 'member', label: t('memberText') }, + { value: 'admin', label: t('adminText') }, + { value: 'guest', label: t('guestText') }, + ], [t]); + + const renderContent = () => ( +
+
+ setEmailInput(e.target.value)} + onKeyPress={handleEmailKeyPress} + size="middle" + autoFocus + style={{ + borderRadius: 8, + fontSize: 14 + }} + /> + + Press Enter to add • Multiple emails can be added + +
+ + {newMembers.length > 0 && ( +
+ + Members to invite ({newMembers.length}) + +
+ ( + updateMemberAccess(index, value)} + options={accessOptions} + style={{ width: 90 }} + variant="outlined" + />, +
+
+ )} +
+ ); + + + return ( + + {t('inviteTeamMembersModalTitle')} + + } + open={isModalOpen} + onCancel={handleClose} + width={520} + destroyOnClose + bodyStyle={{ padding: '16px 20px' }} + footer={ + + + + + + + } + > + {renderContent()} + + ); +}; + +export default memo(InviteTeamMembersModal); \ No newline at end of file diff --git a/worklenz-frontend/src/components/common/invite-team-members-modal/index.ts b/worklenz-frontend/src/components/common/invite-team-members-modal/index.ts new file mode 100644 index 00000000..4a1061cf --- /dev/null +++ b/worklenz-frontend/src/components/common/invite-team-members-modal/index.ts @@ -0,0 +1 @@ +export { default } from './InviteTeamMembersModal'; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index ae1e3c35..3452dea8 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -1,26 +1,32 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Card, Empty } from '@/shared/antd-imports'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '@/app/store'; -import '../EnhancedKanbanBoard.css'; -import '../EnhancedKanbanGroup.css'; -import '../EnhancedKanbanTaskCard.css'; -import ImprovedTaskFilters from '../../task-management/improved-task-filters'; -import Card from 'antd/es/card'; -import Spin from 'antd/es/spin'; -import Empty from 'antd/es/empty'; -import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhancedKanbanTasks, fetchEnhancedKanbanLabels, fetchEnhancedKanbanGroups, fetchEnhancedKanbanTaskAssignees } from '@/features/enhanced-kanban/enhanced-kanban.slice'; -import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; import { useAppSelector } from '@/hooks/useAppSelector'; -import KanbanGroup from './KanbanGroup'; -import EnhancedKanbanCreateSection from '../EnhancedKanbanCreateSection'; -import { useSocket } from '@/socket/socketContext'; -import { SocketEvents } from '@/shared/socket-events'; import { useAuthService } from '@/hooks/useAuth'; +import { useSocket } from '@/socket/socketContext'; +import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; import alertService from '@/services/alerts/alertService'; import logger from '@/utils/errorLogger'; +import { + reorderGroups, + reorderEnhancedKanbanGroups, + reorderTasks, + reorderEnhancedKanbanTasks, + fetchEnhancedKanbanLabels, + fetchEnhancedKanbanGroups, + fetchEnhancedKanbanTaskAssignees, +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; import { checkTaskDependencyStatus } from '@/utils/check-task-dependency-status'; -import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; +import KanbanGroup from './KanbanGroup'; +import EnhancedKanbanCreateSection from '../EnhancedKanbanCreateSection'; +import ImprovedTaskFilters from '../../task-management/improved-task-filters'; +import { SocketEvents } from '@/shared/socket-events'; +import '../EnhancedKanbanBoard.css'; +import '../EnhancedKanbanGroup.css'; +import '../EnhancedKanbanTaskCard.css'; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const dispatch = useDispatch(); diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx index 82f79c55..db49df49 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx @@ -1,37 +1,38 @@ import React, { useCallback, useMemo, useState } from 'react'; +import { + ForkOutlined, + CaretDownFilled, + CaretRightFilled, + Tag, + Flex, + Tooltip, + Progress, + Typography, + Button, + Divider, + List, + Skeleton, + PlusOutlined, + Dayjs, + dayjs, +} from '@/shared/antd-imports'; import { useSortable, defaultAnimateLayoutChanges } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { useTranslation } from 'react-i18next'; import { useAppSelector } from '@/hooks/useAppSelector'; -import './EnhancedKanbanTaskCard.css'; -import Flex from 'antd/es/flex'; -import Tag from 'antd/es/tag'; -import Tooltip from 'antd/es/tooltip'; -import Progress from 'antd/es/progress'; -import Button from 'antd/es/button'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { setShowTaskDrawer, setSelectedTaskId } from '@/features/task-drawer/task-drawer.slice'; -import PrioritySection from '../board/taskCard/priority-section/priority-section'; -import Typography from 'antd/es/typography'; -import CustomDueDatePicker from '../board/custom-due-date-picker'; -import { themeWiseColor } from '@/utils/themeWiseColor'; -import { ForkOutlined } from '@/shared/antd-imports'; -import { Dayjs } from 'dayjs'; -import dayjs from 'dayjs'; -import { CaretDownFilled, CaretRightFilled } from '@/shared/antd-imports'; import { fetchBoardSubTasks, toggleTaskExpansion, } from '@/features/enhanced-kanban/enhanced-kanban.slice'; -import { Divider } from '@/shared/antd-imports'; -import { List } from '@/shared/antd-imports'; -import { Skeleton } from '@/shared/antd-imports'; -import { PlusOutlined } from '@/shared/antd-imports'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import './EnhancedKanbanTaskCard.css'; +import LazyAssigneeSelectorWrapper from '../task-management/lazy-assignee-selector'; +import CustomDueDatePicker from '../board/custom-due-date-picker'; import BoardSubTaskCard from '@/pages/projects/projectView/board/board-section/board-sub-task-card/board-sub-task-card'; -import BoardCreateSubtaskCard from '@/pages/projects/projectView/board/board-section/board-sub-task-card/board-create-sub-task-card'; -import { useTranslation } from 'react-i18next'; import EnhancedKanbanCreateSubtaskCard from './EnhancedKanbanCreateSubtaskCard'; -import LazyAssigneeSelectorWrapper from '@/components/task-management/lazy-assignee-selector'; import AvatarGroup from '@/components/AvatarGroup'; interface EnhancedKanbanTaskCardProps { diff --git a/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx b/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx index 29aefae5..f76f4a8d 100644 --- a/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx +++ b/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx @@ -1,6 +1,4 @@ -import { SettingOutlined } from '@/shared/antd-imports'; -import Tooltip from 'antd/es/tooltip'; -import Button from 'antd/es/button'; +import { SettingOutlined, Tooltip, Button } from '@/shared/antd-imports'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { toggleDrawer } from '../../../features/projects/status/StatusSlice'; import { colors } from '@/styles/colors'; diff --git a/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx b/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx index f51925cf..b87106d4 100644 --- a/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx +++ b/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx @@ -1,20 +1,16 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; +import { Form, Input, Select, Button, Drawer, Flex, Badge } from '@/shared/antd-imports'; import { useTranslation } from 'react-i18next'; -import Flex from 'antd/es/flex'; -import Badge from 'antd/es/badge'; -import Drawer from 'antd/es/drawer'; -import Form from 'antd/es/form'; -import Input from 'antd/es/input'; -import Select from 'antd/es/select'; -import Button from 'antd/es/button/button'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { toggleDrawer } from '@/features/projects/status/StatusSlice'; -import './create-status-drawer.css'; - -import { createStatus, fetchStatusesCategories, fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; +import { + createStatus, + fetchStatusesCategories, + fetchStatuses, +} from '@/features/taskAttributes/taskStatusSlice'; import { ITaskStatusCategory } from '@/types/status.types'; import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; import useTabSearchParam from '@/hooks/useTabSearchParam'; @@ -22,6 +18,8 @@ import { evt_project_board_create_status } from '@/shared/worklenz-analytics-eve import { fetchTaskGroups } from '@/features/tasks/tasks.slice'; import { fetchBoardTaskGroups } from '@/features/board/board-slice'; +import './create-status-drawer.css'; + const StatusDrawer: React.FC = () => { const dispatch = useAppDispatch(); const { trackMixpanelEvent } = useMixpanelTracking(); diff --git a/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx b/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx index 3b6b7fd0..4942b144 100644 --- a/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx +++ b/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; +import { Drawer, Alert, Card, Select, Button, Typography, Badge, Form } from '@/shared/antd-imports'; import { useTranslation } from 'react-i18next'; -import Form from 'antd/es/form'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { fetchStatuses, fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; @@ -9,9 +9,7 @@ import useTabSearchParam from '@/hooks/useTabSearchParam'; import { fetchTaskGroups } from '@/features/tasks/tasks.slice'; import { deleteStatusToggleDrawer } from '@/features/projects/status/DeleteStatusSlice'; -import { Drawer, Alert, Card, Select, Button, Typography, Badge } from '@/shared/antd-imports'; import { DownOutlined } from '@/shared/antd-imports'; -import { useSelector } from 'react-redux'; import { deleteSection, IGroupBy, @@ -21,7 +19,6 @@ import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service import logger from '@/utils/errorLogger'; import { fetchEnhancedKanbanGroups } from '@/features/enhanced-kanban/enhanced-kanban.slice'; const { Title, Text } = Typography; -const { Option } = Select; const DeleteStatusDrawer: React.FC = () => { const [currentStatus, setCurrentStatus] = useState(''); diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx index 4969a406..53abed33 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx @@ -1,7 +1,6 @@ import { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { CaretDownFilled } from '@/shared/antd-imports'; -import { ConfigProvider, Flex, Dropdown, Button } from 'antd/es'; +import { CaretDownFilled, Dropdown, Button, Flex, ConfigProvider } from '@/shared/antd-imports'; import { useSearchParams } from 'react-router-dom'; import ConfigPhaseButton from '@features/projects/singleProject/phase/ConfigPhaseButton'; diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx index c4ae5b44..bfb3edb6 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx @@ -1,14 +1,16 @@ -import { CaretDownFilled } from '@/shared/antd-imports'; -import Badge from 'antd/es/badge'; -import Button from 'antd/es/button'; -import Card from 'antd/es/card'; -import Checkbox from 'antd/es/checkbox'; -import Dropdown from 'antd/es/dropdown'; -import Empty from 'antd/es/empty'; -import Flex from 'antd/es/flex'; -import Input, { InputRef } from 'antd/es/input'; -import List from 'antd/es/list'; -import Space from 'antd/es/space'; +import { + CaretDownFilled, + Card, + Flex, + Input, + List, + Checkbox, + Dropdown, + Button, + Empty, + Space, + InputRef, +} from '@/shared/antd-imports'; import { useSearchParams } from 'react-router-dom'; import { useMemo, useRef, useState, useEffect } from 'react'; diff --git a/worklenz-frontend/src/features/navbar/navbar.tsx b/worklenz-frontend/src/features/navbar/navbar.tsx index 1630c25e..3d2bb6ad 100644 --- a/worklenz-frontend/src/features/navbar/navbar.tsx +++ b/worklenz-frontend/src/features/navbar/navbar.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { Col, ConfigProvider, Flex, Menu, MenuProps, Alert } from '@/shared/antd-imports'; import { createPortal } from 'react-dom'; -import InviteTeamMembers from '../../components/common/invite-team-members/invite-team-members'; +import InviteTeamMembersModal from '../../components/common/invite-team-members-modal'; import InviteButton from './invite/InviteButton'; import MobileMenuButton from './mobileMenu/MobileMenuButton'; import NavbarLogo from './navbar-logo'; @@ -179,7 +179,7 @@ const Navbar = () => { - {isOwnerOrAdmin && createPortal(, document.body, 'invite-team-members')} + {isOwnerOrAdmin && createPortal(, document.body, 'invite-team-members-modal')} {createPortal(, document.body, 'notification-drawer')} ); diff --git a/worklenz-frontend/src/shared/antd-imports.ts b/worklenz-frontend/src/shared/antd-imports.ts index 9f532aa2..2325d808 100644 --- a/worklenz-frontend/src/shared/antd-imports.ts +++ b/worklenz-frontend/src/shared/antd-imports.ts @@ -67,6 +67,8 @@ import { Radio, } from 'antd/es'; +import TextArea from 'antd/es/input/TextArea'; + // Icons - Import commonly used ones export { EditOutlined, @@ -240,6 +242,7 @@ export { Timeline, Mentions, Radio, + TextArea }; // TypeScript Types - Import commonly used ones @@ -263,6 +266,7 @@ export type { PaginationProps, CollapseProps, TablePaginationConfig, + CheckboxChangeEvent } from 'antd/es'; // Dayjs diff --git a/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts b/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts index d1191d57..15cf6944 100644 --- a/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts +++ b/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts @@ -4,4 +4,5 @@ export interface ITeamMemberCreateRequest extends ITeamMember { job_title?: string | null; emails?: string | string[]; is_admin?: boolean; + is_guest?: boolean; }