From 913a9970096481c278a89bbd86505f8608866287 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 17:46:06 +0300 Subject: [PATCH 01/58] remove useless code on privateRoute.tsx --- src/utils/privateRoute.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/privateRoute.tsx b/src/utils/privateRoute.tsx index eddcb90a..6af49fcf 100644 --- a/src/utils/privateRoute.tsx +++ b/src/utils/privateRoute.tsx @@ -17,9 +17,6 @@ export default function PrivateRoute({ [router.pathname] ); - const isObjectNotEmpty = (obj: Record): boolean => { - return Object.keys(obj).length > 0; - }; useEffect(() => { if (!isCentricRoute) { const storedToken = StorageService.readLocalStorage('user'); From 075e9cec784d67e50a2d88852862603dd5778652 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 17:48:57 +0300 Subject: [PATCH 02/58] remove useless code on index.tsx route and remove old components --- .../pages/pageIndex/FooterSection.tsx | 138 ------------------ .../pages/pageIndex/HeaderSection.tsx | 33 ----- src/pages/index.tsx | 11 -- 3 files changed, 182 deletions(-) delete mode 100644 src/components/pages/pageIndex/FooterSection.tsx delete mode 100644 src/components/pages/pageIndex/HeaderSection.tsx diff --git a/src/components/pages/pageIndex/FooterSection.tsx b/src/components/pages/pageIndex/FooterSection.tsx deleted file mode 100644 index be4b8392..00000000 --- a/src/components/pages/pageIndex/FooterSection.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import Image from 'next/image'; - -import graph from '../../../assets/svg/graph.svg'; -import members from '../../../assets/svg/members.svg'; -import metrics from '../../../assets/svg/metrics.svg'; -import arrowBottom from '../../../assets/svg/arrowBottom.svg'; -import benchmark from '../../../assets/svg/benchmark.svg'; - -import { BsClockHistory } from 'react-icons/bs'; -import { HiOutlineArrowRight } from 'react-icons/hi'; -import Link from 'next/link'; - -export const FooterSection = (): JSX.Element => { - return ( - <> -
-
-
-

- Spot value-adding members in your community -

-
- Image Alt -
-
-
-

- Use data to improve onboarding -

-
- Image Alt -
-
-
-
-

- Explore all the metrics that determine
the health of your - community -

-
- Picture of the author -
- Read our research on - -
-
-
-

- Monitor members who disengage and take action to bring them back -

-
- Image Alt -
-
-
-

- Benchmark your metrics and learn from others -

-
- Image Alt -
-
-
-
- - ); -}; diff --git a/src/components/pages/pageIndex/HeaderSection.tsx b/src/components/pages/pageIndex/HeaderSection.tsx deleted file mode 100644 index c0620b51..00000000 --- a/src/components/pages/pageIndex/HeaderSection.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -import { ImArrowDown } from 'react-icons/im'; - -export const HeaderSection = (): JSX.Element => { - return ( - <> -
-
-
-

- The new way to manage your community -

-
-
-

- We believe communities are the beating heart of DAOs. But there - was no way to assess and improve. We assembled a team of - scientists to empower you with deep, actionable insights. -

-

- And while the team is busy building a suite of tools, below is a - - small appetizer to get you started - - -

-
-
-
- - ); -}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6bcb0683..3aae153d 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,7 +1,5 @@ import { defaultLayout } from '../layouts/defaultLayout'; import SEO from '../components/global/SEO'; -import { useState } from 'react'; -import { StorageService } from '../services/StorageService'; import EmptyState from '../components/global/EmptyState'; import Image from 'next/image'; import emptyState from '../assets/svg/empty-state.svg'; @@ -13,17 +11,8 @@ import { ChannelProvider } from '../context/ChannelContext'; import { useToken } from '../context/TokenContext'; function Dashboard(): JSX.Element { - const [alertStateOpen, setAlertStateOpen] = useState(false); const { community } = useToken(); - const toggleAnalysisState = () => { - StorageService.writeLocalStorage('analysis_state', { - isRead: true, - visible: false, - }); - setAlertStateOpen(false); - }; - if (!community || community?.platforms?.length === 0) { return ( <> From cce04bd5343ff42e8ef58fabcd4e5702fa400de0 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 17:51:28 +0300 Subject: [PATCH 03/58] remove old settings components --- .../pages/settings/ChannelSelection.tsx | 400 ------------------ .../settings/ConfirmStartProcessing.spec.tsx | 85 ---- .../pages/settings/ConfirmStartProcessing.tsx | 75 ---- .../pages/settings/ConnectCommunities.tsx | 260 ------------ .../settings/ConnectedCommunitiesItem.tsx | 91 ---- .../settings/ConnectedCommunitiesList.tsx | 159 ------- .../pages/settings/ConnectedTwitter.tsx | 80 ---- .../pages/settings/DataAnalysis.tsx | 171 -------- .../pages/settings/IntegrateDiscord.tsx | 14 - 9 files changed, 1335 deletions(-) delete mode 100644 src/components/pages/settings/ChannelSelection.tsx delete mode 100644 src/components/pages/settings/ConfirmStartProcessing.spec.tsx delete mode 100644 src/components/pages/settings/ConfirmStartProcessing.tsx delete mode 100644 src/components/pages/settings/ConnectCommunities.tsx delete mode 100644 src/components/pages/settings/ConnectedCommunitiesItem.tsx delete mode 100644 src/components/pages/settings/ConnectedCommunitiesList.tsx delete mode 100644 src/components/pages/settings/ConnectedTwitter.tsx delete mode 100644 src/components/pages/settings/DataAnalysis.tsx delete mode 100644 src/components/pages/settings/IntegrateDiscord.tsx diff --git a/src/components/pages/settings/ChannelSelection.tsx b/src/components/pages/settings/ChannelSelection.tsx deleted file mode 100644 index fdd03676..00000000 --- a/src/components/pages/settings/ChannelSelection.tsx +++ /dev/null @@ -1,400 +0,0 @@ -import { - Accordion, - AccordionDetails, - AccordionSummary, - Dialog, -} from '@mui/material'; -import React, { useEffect, useState } from 'react'; -import { IoClose } from 'react-icons/io5'; -import useAppStore from '../../../store/useStore'; -import ChannelList from '../login/ChannelList'; -import { StorageService } from '../../../services/StorageService'; -import { - IGuild, - IGuildChannels, - IUser, - ISubChannels, - IChannelWithoutId, -} from '../../../utils/types'; -import { BiError } from 'react-icons/bi'; -import CustomButton from '../../global/CustomButton'; -import { FiRefreshCcw } from 'react-icons/fi'; -import Loading from '../../global/Loading'; -import { MdExpandMore } from 'react-icons/md'; -import ConfirmStartProcessing from './ConfirmStartProcessing'; -import clsx from 'clsx'; - -type IProps = { - emitable?: boolean; - submit?: (selectedChannels: IChannelWithoutId[]) => unknown; -}; -export default function ChannelSelection({ emitable, submit }: IProps) { - const [open, setOpen] = useState(false); - const [openProcessing, SetOpenProcessing] = useState(false); - - const [fullWidth, setFullWidth] = React.useState(true); - const [guild, setGuild] = useState(); - const [channels, setChannels] = useState>([]); - const [selectedChannels, setSelectedChannels] = useState< - Array - >([]); - - const { - guildChannels, - guildInfo, - updateSelectedChannels, - getUserGuildInfo, - guilds, - isRefetchLoading, - refetchGuildChannels, - } = useAppStore(); - - useEffect(() => { - const user = StorageService.readLocalStorage('user'); - if (user) { - setGuild(user.guild); - } - - const activeChannles = - guildInfo && guildInfo.selectedChannels - ? guildInfo.selectedChannels.map( - (channel: { - channelId: string; - channelName: string; - _id: string; - }) => { - return channel.channelId; - } - ) - : []; - - const channels = guildChannels.map( - (guild: IGuildChannels, _index: number) => { - const selected: Record = {}; - - guild.subChannels.forEach((subChannel: ISubChannels) => { - if (activeChannles.includes(subChannel.channelId)) { - selected[subChannel.channelId] = true; - } else { - selected[subChannel.channelId] = false; - } - }); - - return { ...guild, selected: selected }; - } - ); - - const subChannelsStatus = channels.map((channel: IGuildChannels) => { - return channel.selected; - }); - - const selectedChannelsStatus = Object.assign({}, ...subChannelsStatus); - let activeChannel: string[] = []; - for (const key in selectedChannelsStatus) { - if (selectedChannelsStatus[key]) { - activeChannel.push(key); - } - } - - const result = ([] as IChannelWithoutId[]).concat( - ...channels.map((channel: IGuildChannels) => { - return channel.subChannels - .filter((subChannel: ISubChannels) => { - if (activeChannel.includes(subChannel.channelId)) { - return subChannel; - } - }) - .map((filterdItem: ISubChannels) => { - return { - channelId: filterdItem.channelId, - channelName: filterdItem.name, - }; - }); - }) - ); - setSelectedChannels(result); - setChannels(channels); - }, [guildChannels]); - - const onChange = ( - channelId: string, - subChannelId: string, - status: boolean - ) => { - setChannels((preChannels) => { - return preChannels.map((preChannel) => { - if (preChannel.channelId !== channelId) return preChannel; - - const selected = preChannel.selected ?? {}; - selected[subChannelId] = status; - - return { ...preChannel, selected }; - }); - }); - }; - const handleCheckAll = (guild: IGuildChannels, status: boolean) => { - const selectedGuild = channels.find( - (channel) => channel.channelId === guild.channelId - ); - if (!selectedGuild) return; - - const updatedChannels = channels.map((channel: IGuildChannels) => { - if (channel === selectedGuild) { - const selected = { ...channel.selected }; - Object.keys(selected).forEach((key) => (selected[key] = status)); - return { ...channel, selected }; - } - return channel; - }); - - setChannels(updatedChannels); - }; - - const refetchChannels = () => { - refetchGuildChannels(guild?.guildId); - }; - - const submitChannels = () => { - const subChannelsStatus = channels.map((channel: IGuildChannels) => { - return channel.selected; - }); - - const selectedChannelsStatus = Object.assign({}, ...subChannelsStatus); - let activeChannel: string[] = []; - for (const key in selectedChannelsStatus) { - if (selectedChannelsStatus[key]) { - activeChannel.push(key); - } - } - - const result = ([] as IChannelWithoutId[]).concat( - ...channels.map((channel: IGuildChannels) => { - return channel.subChannels - .filter((subChannel: ISubChannels) => { - if ( - activeChannel.includes(subChannel.channelId) && - subChannel.canReadMessageHistoryAndViewChannel - ) { - return subChannel; - } - }) - .map((filterdItem: ISubChannels) => { - return { - channelId: filterdItem.channelId, - channelName: filterdItem.name, - }; - }); - }) - ); - - setSelectedChannels(result); - if (emitable) { - if (submit) submit(result); - setOpen(false); - } else { - setOpen(false); - SetOpenProcessing(true); - } - }; - - const handleClose = () => { - setOpen(false); - }; - - const handleCloseProcessingModal = () => { - SetOpenProcessing(false); - }; - const handleToProcess = () => { - updateSelectedChannels(guild?.guildId, selectedChannels).then( - (_res: unknown) => { - SetOpenProcessing(false); - getUserGuildInfo(guild?.guildId); - } - ); - }; - - if (guilds.length === 0) { - return ( -
- Selected channels: {selectedChannels.length}{' '} - setOpen(true)} - > - Show Channels - -

- - - There is no community connected at the moment. To be able to change - channels, please
connect your community first. -
-

-
- ); - } - - return ( -
-

- Selected channels:{' '} - - {guildInfo && guildInfo.isDisconnected ? 0 : selectedChannels.length} - {' '} - setOpen(true)} - > - Show Channels - -

- {guildInfo && guildInfo.isInProgress ? ( -
- -

- We are processing data from selected channels. It might take up to 6 - hours to complete. -

-
- ) : ( - '' - )} - -
-
-
-

- Import activities from channels -

- -
-

- Select channels to import activity in this workspace. Please give - Together Crew access to all selected private channels by updating - the channels permissions in Discord. Discord permission will - affect the channels the bot can see. -

-
-
- {isRefetchLoading ? ( - - ) : ( -
-
- } - size="large" - variant="outlined" - onClick={refetchChannels} - /> -
- {channels && channels.length > 0 - ? channels.map((guild: IGuildChannels, index: number) => { - return ( -
- -
- ); - }) - : ''} -
- )} -
- - - } - > -

- How to give access to the channel you want to import? -

-
- -
-
    -
  1. - Navigate to the channel you want to import on{' '} - - Discord - -
  2. -
  3. - Go to the settings for that specific channel (select the - wheel on the right of the channel name) -
  4. -
  5. - Select Permissions (left sidebar), and then in the - middle of the screen check Advanced permissions -
  6. -
  7. - With the TogetherCrew Bot selected, under Advanced - Permissions, make sure that [View channel] and [Read message - history] are marked as [✓] -
  8. -
  9. - Select the plus sign to the right of Roles/Members and under - members select TogetherCrew bot -
  10. -
  11. - Click on the Refresh List button on this window and - select the new channels -
  12. -
-
-
-
-
- -
-
-
- {' '} -
- ); -} - -ChannelSelection.defaultProps = { - emitable: false, -}; diff --git a/src/components/pages/settings/ConfirmStartProcessing.spec.tsx b/src/components/pages/settings/ConfirmStartProcessing.spec.tsx deleted file mode 100644 index 7d6ed312..00000000 --- a/src/components/pages/settings/ConfirmStartProcessing.spec.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import ConfirmStartProcessing from './ConfirmStartProcessing'; - -describe('ConfirmStartProcessing', () => { - const onClose = jest.fn(); - const onSubmitProcess = jest.fn(); - - beforeEach(() => { - onClose.mockClear(); - onSubmitProcess.mockClear(); - }); - - it('renders the correct text', () => { - render( - - ); - - expect( - screen.getByText( - /Data from selected channels may take some time to process/i - ) - ).toBeInTheDocument(); - expect( - screen.getByText( - /Please confirm you want to start data processing. It might take up to 6 hours to complete. Once it is done we will send you a message on Discord./i - ) - ).toBeInTheDocument(); - expect( - screen.getByText( - /During this period, it will not be possible to change your imported channels./i - ) - ).toBeInTheDocument(); - expect(screen.getByText(/Cancel/i)).toBeInTheDocument(); - expect(screen.getByText('Start data processing')).toBeInTheDocument(); - }); - - it('calls onClose when cancel button is clicked', () => { - render( - - ); - - const cancelButton = screen.getByText(/Cancel/i); - fireEvent.click(cancelButton); - - expect(onClose).toHaveBeenCalledTimes(1); - }); - - it('calls onSubmitProcess when start button is clicked', () => { - render( - - ); - - const startButton = screen.getByText('Start data processing'); - fireEvent.click(startButton); - - expect(onSubmitProcess).toHaveBeenCalledTimes(1); - }); - - it('calls onClose when close icon is clicked', () => { - render( - - ); - - const closeIcon = screen.getByTestId('close-modal-icon'); - fireEvent.click(closeIcon); - - expect(onClose).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/components/pages/settings/ConfirmStartProcessing.tsx b/src/components/pages/settings/ConfirmStartProcessing.tsx deleted file mode 100644 index 2e952cb4..00000000 --- a/src/components/pages/settings/ConfirmStartProcessing.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Dialog, DialogTitle } from '@mui/material'; -import React from 'react'; -import { BsClockHistory } from 'react-icons/bs'; -import CustomButton from '../../global/CustomButton'; -import { IoClose } from 'react-icons/io5'; - -interface ConfirmStartProcessingProps { - open: boolean; - onClose: () => void; - onSubmitProcess: () => void; -} - -function ConfirmStartProcessing(props: ConfirmStartProcessingProps) { - const { open, onClose, onSubmitProcess } = props; - return ( - - - - -
- -

- Data from selected channels may take some time to process -

-

- Please confirm you want to start data processing. It might take up to - 6 hours to complete. Once it is done we will send you a message on - Discord. -

-

- During this period, it will not be possible to change your imported - channels. -

-
- - -
-
-
- ); -} - -export default ConfirmStartProcessing; diff --git a/src/components/pages/settings/ConnectCommunities.tsx b/src/components/pages/settings/ConnectCommunities.tsx deleted file mode 100644 index 19a3f4b3..00000000 --- a/src/components/pages/settings/ConnectCommunities.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import { Paper, Tooltip, Typography } from '@mui/material'; -import { useEffect, useState } from 'react'; -import { FaDiscord } from 'react-icons/fa'; -import { GoPlus } from 'react-icons/go'; -import CustomButton from '../../global/CustomButton'; -import DatePeriodRange from '../../global/DatePeriodRange'; -import CustomModal from '../../global/CustomModal'; -import ChannelSelection from './ChannelSelection'; -import { BsClockHistory, BsTwitter } from 'react-icons/bs'; -import useAppStore from '../../../store/useStore'; -import { useRouter } from 'next/router'; -import moment from 'moment'; -import { StorageService } from '../../../services/StorageService'; -import { IUser } from '../../../utils/types'; - -import { - setAmplitudeUserIdFromToken, - trackAmplitudeEvent, -} from '../../../helpers/amplitudeHelper'; -import { decodeUserTokenDiscordId } from '../../../helpers/helper'; - -export default function ConnectCommunities() { - const router = useRouter(); - - const user = StorageService.readLocalStorage('user'); - - const [open, setOpen] = useState(false); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); - const [guildId, setGuildId] = useState(''); - const [activePeriod, setActivePeriod] = useState(1); - const [datePeriod, setDatePeriod] = useState(''); - const [selectedChannels, setSelectedChannels] = useState([]); - - const { - guilds, - connectNewGuild, - patchGuildById, - getUserGuildInfo, - authorizeTwitter, - } = useAppStore(); - - if (typeof window !== 'undefined') { - useEffect(() => { - if (Object.keys(router?.query).length > 0 && router.query.isSuccessful) { - const { guildId, guildName } = router?.query; - let user: any = StorageService.readLocalStorage('user'); - user = { token: user.token, guild: { guildId, guildName } }; - StorageService.writeLocalStorage('user', user); - setGuildId(guildId); - toggleModal(true); - setDatePeriod( - moment().subtract('35', 'days').format('YYYY-MM-DDTHH:mm:ss[Z]') - ); - } - }, [router]); - } - - const updateSelectedChannels = (channels: any) => { - setSelectedChannels(channels); - }; - - const handleActivePeriod = (dateRangeType: number | string) => { - let dateTime = ''; - switch (dateRangeType) { - case 1: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('35', 'days') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 2: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('3', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 3: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('6', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 4: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('1', 'year') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - default: - break; - } - setDatePeriod(dateTime); - }; - - const submitGuild = async () => { - await patchGuildById(guildId, datePeriod, selectedChannels).then( - (_res: any) => { - setOpen(false); - toggleConfirmModal(true); - } - ); - }; - - const toggleModal = (e: boolean) => { - setOpen(e); - }; - - const toggleConfirmModal = (e: boolean) => { - setConfirmModalOpen(e); - router.replace({ - pathname: '/settings', - }); - }; - - const handleConnectedGuild = () => { - const user: IUser | undefined = - StorageService.readLocalStorage('user'); - - setAmplitudeUserIdFromToken(); - - trackAmplitudeEvent({ - eventType: 'update_connected_guild_on_settings', - eventProperties: { - guild: user?.guild, - }, - }); - getUserGuildInfo(guildId); - setConfirmModalOpen(false); - }; - - const handleAuthorizeTwitter = () => { - authorizeTwitter(decodeUserTokenDiscordId(user)); - }; - const isAllTwitterPropertiesNull = - user && - user.twitter && - Object.values(user.twitter).every((value) => value == null); - - return ( - <> - -
- -

{"Perfect, you're all set!"}

-

- Data import just started. It might take up to 6 hours to finish. - Once it is done we will send you a message on Discord. -

- { - handleConnectedGuild(); - }} - /> -
-
- -
-

- Choose date period for data analysis -

-

- You will be able to change date period and selected channels in the - future. -

- -
-
-

- Confirm your imported channels -

- updateSelectedChannels(channels)} - /> -
- { - submitGuild(); - }} - /> -
-
-
-
-
-

- Connect your communities -

-
- {isAllTwitterPropertiesNull ? ( -
- handleAuthorizeTwitter()} - > -

Twitter

- -
- -

Connect

-
-
-
- ) : ( - <> - )} -
- {guilds.length >= 1 ? ( - - It will be possible to connect more communities soon. - - } - arrow - placement="right" - > - -

Discord

- -
- -

Connect

-
-
-
- ) : ( - connectNewGuild()} - > -

Discord

- -
- -

Connect

-
-
- )} -
-
-
-
- - ); -} diff --git a/src/components/pages/settings/ConnectedCommunitiesItem.tsx b/src/components/pages/settings/ConnectedCommunitiesItem.tsx deleted file mode 100644 index 787156e5..00000000 --- a/src/components/pages/settings/ConnectedCommunitiesItem.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Paper, Tooltip, Typography } from '@mui/material'; -import { FaDiscord } from 'react-icons/fa'; -import Image from 'next/image'; -import moment from 'moment'; - -type IProps = { - guild: any; - onClick: (guildId: string) => void; -}; -export default function ConnectedCommunitiesItem({ guild, onClick }: IProps) { - return ( -
- -
-
-

Discord

- {!guild.isInProgress || guild.isDisconnected ? ( - - {guild.isDisconnected - ? 'We don’t have access to your server anymore. Please make sure the Bot is installed properly.' - : !guild.isInProgress - ? 'Discord is connected' - : 'The Discord bot has been connected, and we need time to analyze your data'} - - } - arrow - placement="right" - > - - - ) : ( - - )} -
- -
-
- {guild.guildId && guild.icon ? ( - {guild.name - ) : ( -
- )} -
-

{guild.name}

-

- {!guild.isInProgress || guild.isDisconnected - ? `Connected ${moment(guild.connectedAt).format('DD MMM yyyy')}` - : 'Data import in progress'} -

-
-
-
onClick(guild.guildId)} - > - Disconnect -
- -
- ); -} diff --git a/src/components/pages/settings/ConnectedCommunitiesList.tsx b/src/components/pages/settings/ConnectedCommunitiesList.tsx deleted file mode 100644 index f1972bb2..00000000 --- a/src/components/pages/settings/ConnectedCommunitiesList.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { useState } from 'react'; -import CustomModal from '../../global/CustomModal'; -import CustomButton from '../../global/CustomButton'; -import ConnectedCommunitiesItem from './ConnectedCommunitiesItem'; -import { toast } from 'react-toastify'; -import { FaRegCheckCircle } from 'react-icons/fa'; -import { Paper } from '@mui/material'; -import useAppStore from '../../../store/useStore'; -import { DISCONNECT_TYPE } from '../../../store/types/ISetting'; -import { StorageService } from '../../../services/StorageService'; -import { ITwitter, IUser } from '../../../utils/types'; - -import { - setAmplitudeUserIdFromToken, - trackAmplitudeEvent, -} from '../../../helpers/amplitudeHelper'; -import ConnectedTwitter from './ConnectedTwitter'; - -export default function ConnectedCommunitiesList({ guilds }: any) { - const { disconnecGuildById, getGuilds } = useAppStore(); - const [open, setOpen] = useState(false); - const [guildId, setGuildId] = useState(''); - const toggleModal = (e: boolean) => { - setOpen(e); - }; - let user: IUser | undefined = StorageService.readLocalStorage('user'); - const notify = () => { - toast('The integration has been disconnected succesfully.', { - position: 'top-center', - autoClose: 3000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: false, - progress: undefined, - closeButton: false, - theme: 'light', - icon: , - }); - }; - - const disconnectGuild = (discconectType: DISCONNECT_TYPE) => { - disconnecGuildById(guildId, discconectType).then((_res: any) => { - notify(); - getGuilds(); - - setAmplitudeUserIdFromToken(); - - trackAmplitudeEvent({ - eventType: 'disconnect_guild_on_setting', - eventProperties: { - guild: user?.guild, - }, - }); - - if (user) { - user = { token: user.token, guild: { guildId: '', guildName: '' } }; - StorageService.writeLocalStorage('user', user); - } - }); - }; - - function isAllTwitterPropertiesNull(twitter: ITwitter): boolean { - return ( - twitter.twitterConnectedAt === null && - twitter.twitterId === null && - twitter.twitterProfileImageUrl === null && - twitter.twitterUsername === null - ); - } - - return ( - <> - {guilds && guilds.length > 0 ? ( -
-

Connected communities

-
- {guilds && guilds.length > 0 - ? guilds.map((guild: any) => ( -
- { - setGuildId(guildId), setOpen(true); - }} - /> -
- )) - : ''} - {user?.twitter && !isAllTwitterPropertiesNull(user.twitter) ? ( -
- -
- ) : ( - <> - )} -
-
- ) : ( - '' - )} - -
-

- Are you sure you want to disconnect{' '} -
your community? -

-
- -
-

Disconnect and delete data

-

- Importing activities and members will be stopped. Historical - activities will be deleted. -

-
- { - disconnectGuild('hard'); - }} - /> -
- -
-

Disconnect only

-

- Importing activities and members will be stopped. Historical - activities will not be affected. -

-
- { - disconnectGuild('soft'); - }} - /> -
-
-
-
- - ); -} diff --git a/src/components/pages/settings/ConnectedTwitter.tsx b/src/components/pages/settings/ConnectedTwitter.tsx deleted file mode 100644 index de5be7b0..00000000 --- a/src/components/pages/settings/ConnectedTwitter.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Avatar, Paper } from '@mui/material'; -import React from 'react'; -import { ITwitter } from '../../../utils/types'; -import useAppStore from '../../../store/useStore'; -import { BsTwitter } from 'react-icons/bs'; -import moment from 'moment'; -import { StorageService } from '../../../services/StorageService'; -import clsx from 'clsx'; - -interface IConnectedTwitter { - twitter?: ITwitter; -} - -function ConnectedTwitter({ twitter }: IConnectedTwitter) { - const { disconnectTwitter, getUserInfo } = useAppStore(); - - const handleDisconnect = async () => { - try { - await disconnectTwitter(); - - const userInfo = await getUserInfo(); - const { - twitterConnectedAt, - twitterId, - twitterProfileImageUrl, - twitterUsername, - } = userInfo; - - StorageService.updateLocalStorageWithObject('user', 'twitter', { - twitterConnectedAt, - twitterId, - twitterProfileImageUrl, - twitterUsername, - }); - - StorageService.removeLocalStorage('lastTwitterMetricsRefreshDate'); - } catch (error) { - console.error('Error handling disconnect:', error); - } - }; - - return ( -
- -
-
-

Twitter

- -
- -
-
- -
-

{twitter?.twitterUsername}

-

{`Connected ${moment( - twitter?.twitterConnectedAt - ).format('DD MMM yyyy')}`}

-
-
-
- Disconnect -
-
-
- ); -} - -export default ConnectedTwitter; diff --git a/src/components/pages/settings/DataAnalysis.tsx b/src/components/pages/settings/DataAnalysis.tsx deleted file mode 100644 index cd80b222..00000000 --- a/src/components/pages/settings/DataAnalysis.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import CustomModal from '../../global/CustomModal'; -import CustomButton from '../../global/CustomButton'; -import DatePeriodRange from '../../global/DatePeriodRange'; -import { BsClockHistory } from 'react-icons/bs'; -import useAppStore from '../../../store/useStore'; -import { FiInfo } from 'react-icons/fi'; -import { BiError } from 'react-icons/bi'; -import moment from 'moment'; -import { StorageService } from '../../../services/StorageService'; -import { IGuild, IUser } from '../../../utils/types'; - -export default function DataAnalysis() { - const [activePeriod, setActivePeriod] = useState(1); - const [guild, setGuild] = useState(); - const [analysisStateDate, setAnalysisStartDate] = useState(''); - const [open, setOpen] = useState(false); - const [datePeriod, setDatePeriod] = useState(''); - const [isDisabled, toggleDisabled] = useState(true); - const { guildInfo, updateAnalysisDatePeriod, getUserGuildInfo, guilds } = - useAppStore(); - - const handleActivePeriod = (dateRangeType: number | string) => { - let dateTime = ''; - switch (dateRangeType) { - case 1: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('35', 'days') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 2: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('3', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 3: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('6', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 4: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('1', 'year') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - default: - break; - } - setDatePeriod(dateTime); - toggleDisabled(false); - }; - - useEffect(() => { - const user = StorageService.readLocalStorage('user'); - if (user) { - setGuild(user.guild); - } - const start = moment(guildInfo.period, 'YYYY-MM-DD'); - const end = moment(); - - const datePeriod = Math.round(moment.duration(end.diff(start)).asMonths()); - - if (datePeriod <= 1) { - setActivePeriod(1); - } else if (datePeriod <= 3) { - setActivePeriod(2); - } else if (datePeriod > 3 && datePeriod <= 6) { - setActivePeriod(3); - } else { - setActivePeriod(4); - } - - setAnalysisStartDate(guildInfo.period); - }, [guildInfo]); - - const toggleModal = (e: boolean) => { - setOpen(e); - }; - - const submitNewDatePeriod = () => { - updateAnalysisDatePeriod(guild?.guildId, datePeriod).then((_res: any) => { - getUserGuildInfo(guild?.guildId); - setOpen(false); - }); - }; - - if (guilds.length === 0) { - return ( -
-

- It might take up to 6 hours to finish new data import. Once it is done - we will
send you a message on Discord. -

-

- - - There is no community connected at the moment. To be able to select - the date period, -
please connect your community - first. -
-

-
- - toggleModal(true)} - /> -
-
- ); - } - - return ( -
-

- It might take up to 6 hours to finish new data import. Once it is done - we will
send you a message on Discord. -

-

- - - Data analysis runs from:{' '} - {moment(analysisStateDate).format('DD MMMM yyyy')} - -

- -
- toggleModal(true)} - /> -
- -
- -

- We are changing date period for data analysis now -

-

- It might take up to 6 hours to finish new data import.{' '} -
Once it is done we will send you a - message on Discord. -

- -
-
-
- ); -} diff --git a/src/components/pages/settings/IntegrateDiscord.tsx b/src/components/pages/settings/IntegrateDiscord.tsx deleted file mode 100644 index 951025aa..00000000 --- a/src/components/pages/settings/IntegrateDiscord.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import useAppStore from '../../../store/useStore'; -import ConnectCommunities from './ConnectCommunities'; -import ConnectedCommunitiesList from './ConnectedCommunitiesList'; - -export default function IntegrateDiscord() { - const { guilds } = useAppStore(); - - return ( -
- - -
- ); -} From a4afd2488bf2670356f9799acb6fec498c62170c Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 18:05:40 +0300 Subject: [PATCH 04/58] remove settingSlice & authSlice --- src/store/slices/authSlice.ts | 73 --------------------- src/store/slices/settingSlice.ts | 108 ------------------------------- src/store/types/IAuth.ts | 34 ---------- src/store/types/ISetting.ts | 59 ----------------- src/store/useStore.ts | 4 -- 5 files changed, 278 deletions(-) delete mode 100644 src/store/slices/authSlice.ts delete mode 100644 src/store/slices/settingSlice.ts delete mode 100644 src/store/types/IAuth.ts delete mode 100644 src/store/types/ISetting.ts diff --git a/src/store/slices/authSlice.ts b/src/store/slices/authSlice.ts deleted file mode 100644 index 0e116464..00000000 --- a/src/store/slices/authSlice.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { StateCreator } from 'zustand'; -import IAuth, { IUser } from '../types/IAuth'; -import { conf } from '../../configs'; -import { axiosInstance } from '../../axiosInstance'; -import { StorageService } from '../../services/StorageService'; - -const BASE_URL = conf.API_BASE_URL; - -const createAuthSlice: StateCreator = (set, get) => ({ - isLoggedIn: false, - isLoading: false, - user: {}, - guildChannels: [], - - signUp: () => { - location.replace(`${BASE_URL}/auth/try-now`); - }, - - login: () => { - location.replace(`${BASE_URL}/auth/login`); - }, - - loginWithDiscord: (user: IUser) => - set(() => { - StorageService.writeLocalStorage('user', { - guild: { - guildId: user.guildId, - guildName: user.guildName, - }, - token: { - accessToken: user.accessToken, - accessExp: user.accessExp, - refreshToken: user.refreshToken, - refreshExp: user.refreshExp, - }, - }); - - return { user }; - }), - - fetchGuildChannels: async (guild_id: string) => { - try { - set(() => ({ isLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guild_id}/channels`); - set({ guildChannels: [...data], isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - - updateGuildById: async (guildId, period, selectedChannels) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.patch(`/guilds/${guildId}`, { - period, - selectedChannels: selectedChannels, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - - changeEmail: async (emailAddress: string) => { - try { - await axiosInstance.patch(`/users/@me`, { - email: emailAddress, - }); - } catch (error) {} - }, -}); - -export default createAuthSlice; diff --git a/src/store/slices/settingSlice.ts b/src/store/slices/settingSlice.ts deleted file mode 100644 index 175ede5d..00000000 --- a/src/store/slices/settingSlice.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { StateCreator } from 'zustand'; -import { axiosInstance } from '../../axiosInstance'; -import ISetting from '../types/ISetting'; -import { conf } from '../../configs'; - -const BASE_URL = conf.API_BASE_URL; - -const createSettingSlice: StateCreator = (set, get) => ({ - isLoading: false, - isRefetchLoading: false, - guildInfo: {}, - userInfo: {}, - guildInfoByDiscord: {}, - guilds: [], - guildChannels: [], - getUserGuildInfo: async (guildId: string) => { - try { - set(() => ({ isLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guildId}`); - - set({ guildInfo: data, isLoading: false }); - } catch (error) { - set(() => ({ guildInfo: {}, isLoading: false })); - } - }, - getUserInfo: async () => { - try { - const { data } = await axiosInstance.get('/users/@me'); - set({ userInfo: data }); - return data; - } catch (error) {} - }, - getGuildInfoByDiscord: async (guildId) => { - try { - set(() => ({ isLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guildId}`); - set({ guildInfoByDiscord: data, isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - updateSelectedChannels: async (guildId, selectedChannels) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.patch(`/guilds/${guildId}`, { - selectedChannels: selectedChannels, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - patchGuildById: async (guildId, period, selectedChannels) => { - try { - await axiosInstance.patch(`/guilds/${guildId}`, { - period, - selectedChannels: selectedChannels, - }); - } catch (error) {} - }, - updateAnalysisDatePeriod: async (guildId, period) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.patch(`/guilds/${guildId}`, { - period, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - getGuilds: async () => { - try { - const { data } = await axiosInstance.get(`/guilds?isDisconnected=false`); - set({ - guilds: [...data.results], - }); - } catch (error) {} - }, - disconnecGuildById: async (guildId, disconnectType) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.post(`/guilds/${guildId}/disconnect`, { - disconnectType: disconnectType, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - connectNewGuild: async () => { - try { - location.replace(`${BASE_URL}/guilds/connect`); - } catch (error) {} - }, - - refetchGuildChannels: async (guild_id: string) => { - try { - set(() => ({ isRefetchLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guild_id}/channels`); - set({ guildChannels: [...data], isRefetchLoading: false }); - } catch (error) { - set(() => ({ isRefetchLoading: false })); - } - }, -}); - -export default createSettingSlice; diff --git a/src/store/types/IAuth.ts b/src/store/types/IAuth.ts deleted file mode 100644 index ab03f71c..00000000 --- a/src/store/types/IAuth.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { IChannelWithoutId, IGuildChannels } from '../../utils/types'; - -export type IUser = { - readonly accessToken: string; - readonly accessExp: string; - readonly guildId: string; - readonly guildName: string; - readonly refreshExp: string; - readonly refreshToken: string; -}; - -export type ISubChannels = { - readonly id: string; - readonly name: string; - readonly canReadMessageHistoryAndViewChannel: boolean; - readonly parent_id: string; -}; - -export default interface IAuth { - user: IUser | {}; - isLoading: boolean; - isLoggedIn: boolean; - guildChannels: IGuildChannels[]; - signUp: () => void; - login: () => void; - loginWithDiscord: (user: IUser) => void; - fetchGuildChannels: (guild_id: string) => void; - updateGuildById: ( - guildId: string, - period: string, - selectedChannels: IChannelWithoutId[] - ) => any; - changeEmail: (email: string) => any; -} diff --git a/src/store/types/ISetting.ts b/src/store/types/ISetting.ts deleted file mode 100644 index 5de57107..00000000 --- a/src/store/types/ISetting.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { IChannelWithoutId, IGuildChannels } from '../../utils/types'; - -export type IGuildInfo = { - id?: string; - guildId?: string; - ownerId?: string; - name?: boolean; - period?: string; - selectedChannels?: IChannelWithoutId[]; -}; - -export type DISCONNECT_TYPE = 'soft' | 'hard'; - -export interface IUserInfo { - discordId: string; - email: string; - verified: boolean; - avatar: string; - twitterConnectedAt: string; - twitterId: string; - twitterProfileImageUrl: string; - twitterUsername: string; - twitterIsInProgress: boolean; - id: string; -} - -export default interface IGuildList extends IGuildInfo { - isInProgress?: boolean; - isDisconnected?: boolean; - connectedAt?: string; -} -export default interface ISetting { - isLoading: boolean; - isRefetchLoading: boolean; - guildInfo?: IGuildInfo | {}; - userInfo: IUserInfo | {}; - guildInfoByDiscord: {}; - guilds: IGuildList[]; - guildChannels: IGuildChannels[]; - getUserGuildInfo: (guildId: string) => void; - getUserInfo: () => any; - getGuildInfoByDiscord: (guildId: string) => void; - updateSelectedChannels: ( - guildId: string, - selectedChannels: IChannelWithoutId[] - ) => void; - patchGuildById: ( - guildId: string, - period: string, - selectedChannels: IChannelWithoutId[] - ) => any; - updateAnalysisDatePeriod: (guildId: string, period: string) => void; - getGuilds: () => void; - disconnecGuildById: ( - guildId: string, - disconnectType: DISCONNECT_TYPE - ) => void; - refetchGuildChannels: (guild_id: string) => void; -} diff --git a/src/store/useStore.ts b/src/store/useStore.ts index 63526092..16a674c6 100644 --- a/src/store/useStore.ts +++ b/src/store/useStore.ts @@ -1,7 +1,5 @@ import { create } from 'zustand'; -import createAuthSlice from './slices/authSlice'; import createChartSlice from './slices/chartSlice'; -import createSettingSlice from './slices/settingSlice'; import createBreakdownsSlice from './slices/breakdownsSlice'; import createMemberInteractionSlice from './slices/memberInteractionSlice'; import communityHealthSlice from './slices/communityHealthSlice'; @@ -11,9 +9,7 @@ import platformSlice from './slices/platformSlice'; import userSlice from './slices/userSlice'; const useAppStore = create()((...a) => ({ - ...createAuthSlice(...a), ...createChartSlice(...a), - ...createSettingSlice(...a), ...createBreakdownsSlice(...a), ...createMemberInteractionSlice(...a), ...communityHealthSlice(...a), From 8363c08bb6c27550a16b302afcecf1a06c0b5919 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 18:08:54 +0300 Subject: [PATCH 05/58] remove old channelList component --- src/components/pages/login/ChannelList.tsx | 91 ---------------------- 1 file changed, 91 deletions(-) delete mode 100644 src/components/pages/login/ChannelList.tsx diff --git a/src/components/pages/login/ChannelList.tsx b/src/components/pages/login/ChannelList.tsx deleted file mode 100644 index 91c38db6..00000000 --- a/src/components/pages/login/ChannelList.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { FormControlLabel, Checkbox } from '@mui/material'; -import { FiAlertTriangle } from 'react-icons/fi'; -import { ISubChannels } from '../../../utils/types'; - -type IChannelListProps = { - guild: any; - showFlag: boolean; - onChange: (channelId: string, subChannelId: string, status: boolean) => void; - handleCheckAll: (guild: any, status: boolean) => void; -}; - -export default function ChannelList({ - guild, - onChange, - handleCheckAll, - showFlag, -}: IChannelListProps) { - const subChannelsList = ( - <> -

Channels

- {guild.subChannels.map((channel: ISubChannels, index: any) => ( -
-
- - onChange( - guild.channelId, - channel.channelId, - e.target.checked - ) - } - /> - } - label={channel.name} - /> - {showFlag && !channel.canReadMessageHistoryAndViewChannel ? ( -
- - - {!channel.canReadMessageHistoryAndViewChannel - ? 'Bot needs access' - : ''} - -
- ) : ( - '' - )} -
-
- ))} - - ); - - return ( -
-

{guild.title}

-
- item)} - color="secondary" - onChange={(e) => handleCheckAll(guild, e.target.checked)} - /> - } - /> - {subChannelsList} -
-
- ); -} - -ChannelList.defaultProps = { - showFlag: false, -}; From 1e9faab738a4b2466919682bd05df038af883f05 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 18:18:07 +0300 Subject: [PATCH 06/58] remove useless global component --- src/components/global/Accardion.tsx | 66 ------------------- src/components/global/Card.tsx | 28 -------- src/components/global/CustomDatePicker.tsx | 77 ---------------------- src/components/global/CustomModal.tsx | 60 ----------------- src/components/global/DatePeriodRange.tsx | 63 ------------------ tsconfig.json | 2 +- 6 files changed, 1 insertion(+), 295 deletions(-) delete mode 100644 src/components/global/Accardion.tsx delete mode 100644 src/components/global/Card.tsx delete mode 100644 src/components/global/CustomDatePicker.tsx delete mode 100644 src/components/global/CustomModal.tsx delete mode 100644 src/components/global/DatePeriodRange.tsx diff --git a/src/components/global/Accardion.tsx b/src/components/global/Accardion.tsx deleted file mode 100644 index e4400f2a..00000000 --- a/src/components/global/Accardion.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { ReactElement } from 'react'; -import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material'; -import { MdExpandMore } from 'react-icons/md'; - -type AcProps = { - readonly title?: string; - childs: AcChildProps[]; -}; - -type AcChildProps = { - title: string; - id: string; - icon?: ReactElement; - detailsComponent: ReactElement; -}; - -export default function Accardion({ title, childs }: AcProps) { - const [expanded, setExpanded] = React.useState(false); - - const handleChange = - (panel: string) => (_event: React.SyntheticEvent, isExpanded: boolean) => { - setExpanded(isExpanded ? panel : false); - }; - - return ( - <> -

{title}

- {childs.map((el) => ( - - - } - aria-controls={`${el.id}-content`} - id={el.id} - > -
-
- {el.icon} -
-

{el.title}

-
-
- - {el.detailsComponent} - -
- ))} - - ); -} diff --git a/src/components/global/Card.tsx b/src/components/global/Card.tsx deleted file mode 100644 index fc0ae5c2..00000000 --- a/src/components/global/Card.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import Image from "next/image"; - -type Props = { - className?: string; - title: string; - srcImage: string; - srcWidth: number; -}; - -export default function Card({ className, title, srcImage, srcWidth }: Props) { - return ( -
-

{title}

-
-
- Picture of the author -
-
-
- ); -} - -Card.defaultProps = { - title: "", - srcWidth: "400", -}; diff --git a/src/components/global/CustomDatePicker.tsx b/src/components/global/CustomDatePicker.tsx deleted file mode 100644 index 45ede02b..00000000 --- a/src/components/global/CustomDatePicker.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { FC, RefObject, useState } from 'react'; -import '@hassanmojab/react-modern-calendar-datepicker/lib/DatePicker.css'; -import DatePicker, { - DayRange, -} from '@hassanmojab/react-modern-calendar-datepicker'; -import { FiCalendar } from 'react-icons/fi'; -import clsx from 'clsx'; -import moment from 'moment'; - -interface IProps { - placeholder?: string; - className: string; - onClick: any; -} - -const CustomDatePicker: FC = ({ - placeholder, - className, - onClick, -}): JSX.Element => { - const [dayRange, setDayRange] = useState({ - from: null, - to: null, - }); - - const renderCustomInput = ({ - ref, - }: { - ref: RefObject | any; - }) => ( -
- - -
- ); - - return ( - setDayRange(date)} - renderInput={renderCustomInput} - colorPrimary="#35B9B7" // added this - colorPrimaryLight="#D0FBF8" // and this - calendarPopperPosition="bottom" - /> - ); -}; - -CustomDatePicker.defaultProps = { - placeholder: 'Specific date', -}; - -export default CustomDatePicker; diff --git a/src/components/global/CustomModal.tsx b/src/components/global/CustomModal.tsx deleted file mode 100644 index 3209fa9a..00000000 --- a/src/components/global/CustomModal.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Dialog, DialogTitle, DialogContent } from '@mui/material'; -import { IoClose } from 'react-icons/io5'; - -type IModalProps = { - isOpen: boolean; - toggleModal: (arg0: boolean) => void; - children: any; - hasClose: boolean; -}; -export default function ConfirmModal({ - isOpen, - toggleModal, - children, - hasClose, - ...props -}: IModalProps) { - const handleClose = () => { - toggleModal(false); - }; - return ( - <> - - {hasClose ? ( - - - - ) : ( - '' - )} - {children} - - - ); -} diff --git a/src/components/global/DatePeriodRange.tsx b/src/components/global/DatePeriodRange.tsx deleted file mode 100644 index 826d0b95..00000000 --- a/src/components/global/DatePeriodRange.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import clsx from 'clsx'; -import React, { useState } from 'react'; -import CustomDatePicker from './CustomDatePicker'; - -type dateItems = { - title: string; - icon?: JSX.Element; - value: any; -}; - -const datePeriod: dateItems[] = [ - { - title: 'Last 35 days', - value: 1, - }, - { - title: '3M', - value: 2, - }, - { - title: '6M', - value: 3, - }, - { - title: '1Y', - value: 4, - }, -]; - -type datePeriodRangeProps = { - activePeriod: string | number; - onChangeActivePeriod: (e: number) => void; -}; - -export default function DatePeriodRange({ - activePeriod, - onChangeActivePeriod, -}: datePeriodRangeProps) { - return ( -
-
    - {datePeriod.length > 0 - ? datePeriod.map((el) => ( -
  • onChangeActivePeriod(el.value)} - > - {el.icon ? el.icon : ''} -
    {el.title}
    -
  • - )) - : ''} -
-
- ); -} diff --git a/tsconfig.json b/tsconfig.json index b194c6be..e313683d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ "jest.config.js", "jest.setup.js" ], - "exclude": ["node_modules", "./src/components/global/CustomDatePicker.tsx"] + "exclude": ["node_modules"] } From 33d3cfaf94964733b7896edf7ba4dc5bcfc8a76d Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 17:46:06 +0300 Subject: [PATCH 07/58] remove useless code on privateRoute.tsx --- src/utils/privateRoute.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/privateRoute.tsx b/src/utils/privateRoute.tsx index eddcb90a..6af49fcf 100644 --- a/src/utils/privateRoute.tsx +++ b/src/utils/privateRoute.tsx @@ -17,9 +17,6 @@ export default function PrivateRoute({ [router.pathname] ); - const isObjectNotEmpty = (obj: Record): boolean => { - return Object.keys(obj).length > 0; - }; useEffect(() => { if (!isCentricRoute) { const storedToken = StorageService.readLocalStorage('user'); From ea3d5c486d79f828710e16a3606e9d25ff243d7d Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 17:48:57 +0300 Subject: [PATCH 08/58] remove useless code on index.tsx route and remove old components --- .../pages/pageIndex/FooterSection.tsx | 138 ------------------ .../pages/pageIndex/HeaderSection.tsx | 33 ----- src/pages/index.tsx | 11 -- 3 files changed, 182 deletions(-) delete mode 100644 src/components/pages/pageIndex/FooterSection.tsx delete mode 100644 src/components/pages/pageIndex/HeaderSection.tsx diff --git a/src/components/pages/pageIndex/FooterSection.tsx b/src/components/pages/pageIndex/FooterSection.tsx deleted file mode 100644 index be4b8392..00000000 --- a/src/components/pages/pageIndex/FooterSection.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import Image from 'next/image'; - -import graph from '../../../assets/svg/graph.svg'; -import members from '../../../assets/svg/members.svg'; -import metrics from '../../../assets/svg/metrics.svg'; -import arrowBottom from '../../../assets/svg/arrowBottom.svg'; -import benchmark from '../../../assets/svg/benchmark.svg'; - -import { BsClockHistory } from 'react-icons/bs'; -import { HiOutlineArrowRight } from 'react-icons/hi'; -import Link from 'next/link'; - -export const FooterSection = (): JSX.Element => { - return ( - <> -
-
-
-

- Spot value-adding members in your community -

-
- Image Alt -
-
-
-

- Use data to improve onboarding -

-
- Image Alt -
-
-
-
-

- Explore all the metrics that determine
the health of your - community -

-
- Picture of the author -
- Read our research on - -
-
-
-

- Monitor members who disengage and take action to bring them back -

-
- Image Alt -
-
-
-

- Benchmark your metrics and learn from others -

-
- Image Alt -
-
-
-
- - ); -}; diff --git a/src/components/pages/pageIndex/HeaderSection.tsx b/src/components/pages/pageIndex/HeaderSection.tsx deleted file mode 100644 index c0620b51..00000000 --- a/src/components/pages/pageIndex/HeaderSection.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -import { ImArrowDown } from 'react-icons/im'; - -export const HeaderSection = (): JSX.Element => { - return ( - <> -
-
-
-

- The new way to manage your community -

-
-
-

- We believe communities are the beating heart of DAOs. But there - was no way to assess and improve. We assembled a team of - scientists to empower you with deep, actionable insights. -

-

- And while the team is busy building a suite of tools, below is a - - small appetizer to get you started - - -

-
-
-
- - ); -}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6bcb0683..3aae153d 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,7 +1,5 @@ import { defaultLayout } from '../layouts/defaultLayout'; import SEO from '../components/global/SEO'; -import { useState } from 'react'; -import { StorageService } from '../services/StorageService'; import EmptyState from '../components/global/EmptyState'; import Image from 'next/image'; import emptyState from '../assets/svg/empty-state.svg'; @@ -13,17 +11,8 @@ import { ChannelProvider } from '../context/ChannelContext'; import { useToken } from '../context/TokenContext'; function Dashboard(): JSX.Element { - const [alertStateOpen, setAlertStateOpen] = useState(false); const { community } = useToken(); - const toggleAnalysisState = () => { - StorageService.writeLocalStorage('analysis_state', { - isRead: true, - visible: false, - }); - setAlertStateOpen(false); - }; - if (!community || community?.platforms?.length === 0) { return ( <> From f7d3b07515173337752f598f87cffdfad140e454 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 17:51:28 +0300 Subject: [PATCH 09/58] remove old settings components --- .../pages/settings/ChannelSelection.tsx | 400 ------------------ .../settings/ConfirmStartProcessing.spec.tsx | 85 ---- .../pages/settings/ConfirmStartProcessing.tsx | 75 ---- .../pages/settings/ConnectCommunities.tsx | 260 ------------ .../settings/ConnectedCommunitiesItem.tsx | 91 ---- .../settings/ConnectedCommunitiesList.tsx | 159 ------- .../pages/settings/ConnectedTwitter.tsx | 80 ---- .../pages/settings/DataAnalysis.tsx | 171 -------- .../pages/settings/IntegrateDiscord.tsx | 14 - 9 files changed, 1335 deletions(-) delete mode 100644 src/components/pages/settings/ChannelSelection.tsx delete mode 100644 src/components/pages/settings/ConfirmStartProcessing.spec.tsx delete mode 100644 src/components/pages/settings/ConfirmStartProcessing.tsx delete mode 100644 src/components/pages/settings/ConnectCommunities.tsx delete mode 100644 src/components/pages/settings/ConnectedCommunitiesItem.tsx delete mode 100644 src/components/pages/settings/ConnectedCommunitiesList.tsx delete mode 100644 src/components/pages/settings/ConnectedTwitter.tsx delete mode 100644 src/components/pages/settings/DataAnalysis.tsx delete mode 100644 src/components/pages/settings/IntegrateDiscord.tsx diff --git a/src/components/pages/settings/ChannelSelection.tsx b/src/components/pages/settings/ChannelSelection.tsx deleted file mode 100644 index fdd03676..00000000 --- a/src/components/pages/settings/ChannelSelection.tsx +++ /dev/null @@ -1,400 +0,0 @@ -import { - Accordion, - AccordionDetails, - AccordionSummary, - Dialog, -} from '@mui/material'; -import React, { useEffect, useState } from 'react'; -import { IoClose } from 'react-icons/io5'; -import useAppStore from '../../../store/useStore'; -import ChannelList from '../login/ChannelList'; -import { StorageService } from '../../../services/StorageService'; -import { - IGuild, - IGuildChannels, - IUser, - ISubChannels, - IChannelWithoutId, -} from '../../../utils/types'; -import { BiError } from 'react-icons/bi'; -import CustomButton from '../../global/CustomButton'; -import { FiRefreshCcw } from 'react-icons/fi'; -import Loading from '../../global/Loading'; -import { MdExpandMore } from 'react-icons/md'; -import ConfirmStartProcessing from './ConfirmStartProcessing'; -import clsx from 'clsx'; - -type IProps = { - emitable?: boolean; - submit?: (selectedChannels: IChannelWithoutId[]) => unknown; -}; -export default function ChannelSelection({ emitable, submit }: IProps) { - const [open, setOpen] = useState(false); - const [openProcessing, SetOpenProcessing] = useState(false); - - const [fullWidth, setFullWidth] = React.useState(true); - const [guild, setGuild] = useState(); - const [channels, setChannels] = useState>([]); - const [selectedChannels, setSelectedChannels] = useState< - Array - >([]); - - const { - guildChannels, - guildInfo, - updateSelectedChannels, - getUserGuildInfo, - guilds, - isRefetchLoading, - refetchGuildChannels, - } = useAppStore(); - - useEffect(() => { - const user = StorageService.readLocalStorage('user'); - if (user) { - setGuild(user.guild); - } - - const activeChannles = - guildInfo && guildInfo.selectedChannels - ? guildInfo.selectedChannels.map( - (channel: { - channelId: string; - channelName: string; - _id: string; - }) => { - return channel.channelId; - } - ) - : []; - - const channels = guildChannels.map( - (guild: IGuildChannels, _index: number) => { - const selected: Record = {}; - - guild.subChannels.forEach((subChannel: ISubChannels) => { - if (activeChannles.includes(subChannel.channelId)) { - selected[subChannel.channelId] = true; - } else { - selected[subChannel.channelId] = false; - } - }); - - return { ...guild, selected: selected }; - } - ); - - const subChannelsStatus = channels.map((channel: IGuildChannels) => { - return channel.selected; - }); - - const selectedChannelsStatus = Object.assign({}, ...subChannelsStatus); - let activeChannel: string[] = []; - for (const key in selectedChannelsStatus) { - if (selectedChannelsStatus[key]) { - activeChannel.push(key); - } - } - - const result = ([] as IChannelWithoutId[]).concat( - ...channels.map((channel: IGuildChannels) => { - return channel.subChannels - .filter((subChannel: ISubChannels) => { - if (activeChannel.includes(subChannel.channelId)) { - return subChannel; - } - }) - .map((filterdItem: ISubChannels) => { - return { - channelId: filterdItem.channelId, - channelName: filterdItem.name, - }; - }); - }) - ); - setSelectedChannels(result); - setChannels(channels); - }, [guildChannels]); - - const onChange = ( - channelId: string, - subChannelId: string, - status: boolean - ) => { - setChannels((preChannels) => { - return preChannels.map((preChannel) => { - if (preChannel.channelId !== channelId) return preChannel; - - const selected = preChannel.selected ?? {}; - selected[subChannelId] = status; - - return { ...preChannel, selected }; - }); - }); - }; - const handleCheckAll = (guild: IGuildChannels, status: boolean) => { - const selectedGuild = channels.find( - (channel) => channel.channelId === guild.channelId - ); - if (!selectedGuild) return; - - const updatedChannels = channels.map((channel: IGuildChannels) => { - if (channel === selectedGuild) { - const selected = { ...channel.selected }; - Object.keys(selected).forEach((key) => (selected[key] = status)); - return { ...channel, selected }; - } - return channel; - }); - - setChannels(updatedChannels); - }; - - const refetchChannels = () => { - refetchGuildChannels(guild?.guildId); - }; - - const submitChannels = () => { - const subChannelsStatus = channels.map((channel: IGuildChannels) => { - return channel.selected; - }); - - const selectedChannelsStatus = Object.assign({}, ...subChannelsStatus); - let activeChannel: string[] = []; - for (const key in selectedChannelsStatus) { - if (selectedChannelsStatus[key]) { - activeChannel.push(key); - } - } - - const result = ([] as IChannelWithoutId[]).concat( - ...channels.map((channel: IGuildChannels) => { - return channel.subChannels - .filter((subChannel: ISubChannels) => { - if ( - activeChannel.includes(subChannel.channelId) && - subChannel.canReadMessageHistoryAndViewChannel - ) { - return subChannel; - } - }) - .map((filterdItem: ISubChannels) => { - return { - channelId: filterdItem.channelId, - channelName: filterdItem.name, - }; - }); - }) - ); - - setSelectedChannels(result); - if (emitable) { - if (submit) submit(result); - setOpen(false); - } else { - setOpen(false); - SetOpenProcessing(true); - } - }; - - const handleClose = () => { - setOpen(false); - }; - - const handleCloseProcessingModal = () => { - SetOpenProcessing(false); - }; - const handleToProcess = () => { - updateSelectedChannels(guild?.guildId, selectedChannels).then( - (_res: unknown) => { - SetOpenProcessing(false); - getUserGuildInfo(guild?.guildId); - } - ); - }; - - if (guilds.length === 0) { - return ( -
- Selected channels: {selectedChannels.length}{' '} - setOpen(true)} - > - Show Channels - -

- - - There is no community connected at the moment. To be able to change - channels, please
connect your community first. -
-

-
- ); - } - - return ( -
-

- Selected channels:{' '} - - {guildInfo && guildInfo.isDisconnected ? 0 : selectedChannels.length} - {' '} - setOpen(true)} - > - Show Channels - -

- {guildInfo && guildInfo.isInProgress ? ( -
- -

- We are processing data from selected channels. It might take up to 6 - hours to complete. -

-
- ) : ( - '' - )} - -
-
-
-

- Import activities from channels -

- -
-

- Select channels to import activity in this workspace. Please give - Together Crew access to all selected private channels by updating - the channels permissions in Discord. Discord permission will - affect the channels the bot can see. -

-
-
- {isRefetchLoading ? ( - - ) : ( -
-
- } - size="large" - variant="outlined" - onClick={refetchChannels} - /> -
- {channels && channels.length > 0 - ? channels.map((guild: IGuildChannels, index: number) => { - return ( -
- -
- ); - }) - : ''} -
- )} -
- - - } - > -

- How to give access to the channel you want to import? -

-
- -
-
    -
  1. - Navigate to the channel you want to import on{' '} - - Discord - -
  2. -
  3. - Go to the settings for that specific channel (select the - wheel on the right of the channel name) -
  4. -
  5. - Select Permissions (left sidebar), and then in the - middle of the screen check Advanced permissions -
  6. -
  7. - With the TogetherCrew Bot selected, under Advanced - Permissions, make sure that [View channel] and [Read message - history] are marked as [✓] -
  8. -
  9. - Select the plus sign to the right of Roles/Members and under - members select TogetherCrew bot -
  10. -
  11. - Click on the Refresh List button on this window and - select the new channels -
  12. -
-
-
-
-
- -
-
-
- {' '} -
- ); -} - -ChannelSelection.defaultProps = { - emitable: false, -}; diff --git a/src/components/pages/settings/ConfirmStartProcessing.spec.tsx b/src/components/pages/settings/ConfirmStartProcessing.spec.tsx deleted file mode 100644 index 7d6ed312..00000000 --- a/src/components/pages/settings/ConfirmStartProcessing.spec.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import ConfirmStartProcessing from './ConfirmStartProcessing'; - -describe('ConfirmStartProcessing', () => { - const onClose = jest.fn(); - const onSubmitProcess = jest.fn(); - - beforeEach(() => { - onClose.mockClear(); - onSubmitProcess.mockClear(); - }); - - it('renders the correct text', () => { - render( - - ); - - expect( - screen.getByText( - /Data from selected channels may take some time to process/i - ) - ).toBeInTheDocument(); - expect( - screen.getByText( - /Please confirm you want to start data processing. It might take up to 6 hours to complete. Once it is done we will send you a message on Discord./i - ) - ).toBeInTheDocument(); - expect( - screen.getByText( - /During this period, it will not be possible to change your imported channels./i - ) - ).toBeInTheDocument(); - expect(screen.getByText(/Cancel/i)).toBeInTheDocument(); - expect(screen.getByText('Start data processing')).toBeInTheDocument(); - }); - - it('calls onClose when cancel button is clicked', () => { - render( - - ); - - const cancelButton = screen.getByText(/Cancel/i); - fireEvent.click(cancelButton); - - expect(onClose).toHaveBeenCalledTimes(1); - }); - - it('calls onSubmitProcess when start button is clicked', () => { - render( - - ); - - const startButton = screen.getByText('Start data processing'); - fireEvent.click(startButton); - - expect(onSubmitProcess).toHaveBeenCalledTimes(1); - }); - - it('calls onClose when close icon is clicked', () => { - render( - - ); - - const closeIcon = screen.getByTestId('close-modal-icon'); - fireEvent.click(closeIcon); - - expect(onClose).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/components/pages/settings/ConfirmStartProcessing.tsx b/src/components/pages/settings/ConfirmStartProcessing.tsx deleted file mode 100644 index 2e952cb4..00000000 --- a/src/components/pages/settings/ConfirmStartProcessing.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Dialog, DialogTitle } from '@mui/material'; -import React from 'react'; -import { BsClockHistory } from 'react-icons/bs'; -import CustomButton from '../../global/CustomButton'; -import { IoClose } from 'react-icons/io5'; - -interface ConfirmStartProcessingProps { - open: boolean; - onClose: () => void; - onSubmitProcess: () => void; -} - -function ConfirmStartProcessing(props: ConfirmStartProcessingProps) { - const { open, onClose, onSubmitProcess } = props; - return ( - - - - -
- -

- Data from selected channels may take some time to process -

-

- Please confirm you want to start data processing. It might take up to - 6 hours to complete. Once it is done we will send you a message on - Discord. -

-

- During this period, it will not be possible to change your imported - channels. -

-
- - -
-
-
- ); -} - -export default ConfirmStartProcessing; diff --git a/src/components/pages/settings/ConnectCommunities.tsx b/src/components/pages/settings/ConnectCommunities.tsx deleted file mode 100644 index 19a3f4b3..00000000 --- a/src/components/pages/settings/ConnectCommunities.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import { Paper, Tooltip, Typography } from '@mui/material'; -import { useEffect, useState } from 'react'; -import { FaDiscord } from 'react-icons/fa'; -import { GoPlus } from 'react-icons/go'; -import CustomButton from '../../global/CustomButton'; -import DatePeriodRange from '../../global/DatePeriodRange'; -import CustomModal from '../../global/CustomModal'; -import ChannelSelection from './ChannelSelection'; -import { BsClockHistory, BsTwitter } from 'react-icons/bs'; -import useAppStore from '../../../store/useStore'; -import { useRouter } from 'next/router'; -import moment from 'moment'; -import { StorageService } from '../../../services/StorageService'; -import { IUser } from '../../../utils/types'; - -import { - setAmplitudeUserIdFromToken, - trackAmplitudeEvent, -} from '../../../helpers/amplitudeHelper'; -import { decodeUserTokenDiscordId } from '../../../helpers/helper'; - -export default function ConnectCommunities() { - const router = useRouter(); - - const user = StorageService.readLocalStorage('user'); - - const [open, setOpen] = useState(false); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); - const [guildId, setGuildId] = useState(''); - const [activePeriod, setActivePeriod] = useState(1); - const [datePeriod, setDatePeriod] = useState(''); - const [selectedChannels, setSelectedChannels] = useState([]); - - const { - guilds, - connectNewGuild, - patchGuildById, - getUserGuildInfo, - authorizeTwitter, - } = useAppStore(); - - if (typeof window !== 'undefined') { - useEffect(() => { - if (Object.keys(router?.query).length > 0 && router.query.isSuccessful) { - const { guildId, guildName } = router?.query; - let user: any = StorageService.readLocalStorage('user'); - user = { token: user.token, guild: { guildId, guildName } }; - StorageService.writeLocalStorage('user', user); - setGuildId(guildId); - toggleModal(true); - setDatePeriod( - moment().subtract('35', 'days').format('YYYY-MM-DDTHH:mm:ss[Z]') - ); - } - }, [router]); - } - - const updateSelectedChannels = (channels: any) => { - setSelectedChannels(channels); - }; - - const handleActivePeriod = (dateRangeType: number | string) => { - let dateTime = ''; - switch (dateRangeType) { - case 1: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('35', 'days') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 2: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('3', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 3: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('6', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 4: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('1', 'year') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - default: - break; - } - setDatePeriod(dateTime); - }; - - const submitGuild = async () => { - await patchGuildById(guildId, datePeriod, selectedChannels).then( - (_res: any) => { - setOpen(false); - toggleConfirmModal(true); - } - ); - }; - - const toggleModal = (e: boolean) => { - setOpen(e); - }; - - const toggleConfirmModal = (e: boolean) => { - setConfirmModalOpen(e); - router.replace({ - pathname: '/settings', - }); - }; - - const handleConnectedGuild = () => { - const user: IUser | undefined = - StorageService.readLocalStorage('user'); - - setAmplitudeUserIdFromToken(); - - trackAmplitudeEvent({ - eventType: 'update_connected_guild_on_settings', - eventProperties: { - guild: user?.guild, - }, - }); - getUserGuildInfo(guildId); - setConfirmModalOpen(false); - }; - - const handleAuthorizeTwitter = () => { - authorizeTwitter(decodeUserTokenDiscordId(user)); - }; - const isAllTwitterPropertiesNull = - user && - user.twitter && - Object.values(user.twitter).every((value) => value == null); - - return ( - <> - -
- -

{"Perfect, you're all set!"}

-

- Data import just started. It might take up to 6 hours to finish. - Once it is done we will send you a message on Discord. -

- { - handleConnectedGuild(); - }} - /> -
-
- -
-

- Choose date period for data analysis -

-

- You will be able to change date period and selected channels in the - future. -

- -
-
-

- Confirm your imported channels -

- updateSelectedChannels(channels)} - /> -
- { - submitGuild(); - }} - /> -
-
-
-
-
-

- Connect your communities -

-
- {isAllTwitterPropertiesNull ? ( -
- handleAuthorizeTwitter()} - > -

Twitter

- -
- -

Connect

-
-
-
- ) : ( - <> - )} -
- {guilds.length >= 1 ? ( - - It will be possible to connect more communities soon. - - } - arrow - placement="right" - > - -

Discord

- -
- -

Connect

-
-
-
- ) : ( - connectNewGuild()} - > -

Discord

- -
- -

Connect

-
-
- )} -
-
-
-
- - ); -} diff --git a/src/components/pages/settings/ConnectedCommunitiesItem.tsx b/src/components/pages/settings/ConnectedCommunitiesItem.tsx deleted file mode 100644 index 787156e5..00000000 --- a/src/components/pages/settings/ConnectedCommunitiesItem.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Paper, Tooltip, Typography } from '@mui/material'; -import { FaDiscord } from 'react-icons/fa'; -import Image from 'next/image'; -import moment from 'moment'; - -type IProps = { - guild: any; - onClick: (guildId: string) => void; -}; -export default function ConnectedCommunitiesItem({ guild, onClick }: IProps) { - return ( -
- -
-
-

Discord

- {!guild.isInProgress || guild.isDisconnected ? ( - - {guild.isDisconnected - ? 'We don’t have access to your server anymore. Please make sure the Bot is installed properly.' - : !guild.isInProgress - ? 'Discord is connected' - : 'The Discord bot has been connected, and we need time to analyze your data'} - - } - arrow - placement="right" - > - - - ) : ( - - )} -
- -
-
- {guild.guildId && guild.icon ? ( - {guild.name - ) : ( -
- )} -
-

{guild.name}

-

- {!guild.isInProgress || guild.isDisconnected - ? `Connected ${moment(guild.connectedAt).format('DD MMM yyyy')}` - : 'Data import in progress'} -

-
-
-
onClick(guild.guildId)} - > - Disconnect -
- -
- ); -} diff --git a/src/components/pages/settings/ConnectedCommunitiesList.tsx b/src/components/pages/settings/ConnectedCommunitiesList.tsx deleted file mode 100644 index f1972bb2..00000000 --- a/src/components/pages/settings/ConnectedCommunitiesList.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { useState } from 'react'; -import CustomModal from '../../global/CustomModal'; -import CustomButton from '../../global/CustomButton'; -import ConnectedCommunitiesItem from './ConnectedCommunitiesItem'; -import { toast } from 'react-toastify'; -import { FaRegCheckCircle } from 'react-icons/fa'; -import { Paper } from '@mui/material'; -import useAppStore from '../../../store/useStore'; -import { DISCONNECT_TYPE } from '../../../store/types/ISetting'; -import { StorageService } from '../../../services/StorageService'; -import { ITwitter, IUser } from '../../../utils/types'; - -import { - setAmplitudeUserIdFromToken, - trackAmplitudeEvent, -} from '../../../helpers/amplitudeHelper'; -import ConnectedTwitter from './ConnectedTwitter'; - -export default function ConnectedCommunitiesList({ guilds }: any) { - const { disconnecGuildById, getGuilds } = useAppStore(); - const [open, setOpen] = useState(false); - const [guildId, setGuildId] = useState(''); - const toggleModal = (e: boolean) => { - setOpen(e); - }; - let user: IUser | undefined = StorageService.readLocalStorage('user'); - const notify = () => { - toast('The integration has been disconnected succesfully.', { - position: 'top-center', - autoClose: 3000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: false, - progress: undefined, - closeButton: false, - theme: 'light', - icon: , - }); - }; - - const disconnectGuild = (discconectType: DISCONNECT_TYPE) => { - disconnecGuildById(guildId, discconectType).then((_res: any) => { - notify(); - getGuilds(); - - setAmplitudeUserIdFromToken(); - - trackAmplitudeEvent({ - eventType: 'disconnect_guild_on_setting', - eventProperties: { - guild: user?.guild, - }, - }); - - if (user) { - user = { token: user.token, guild: { guildId: '', guildName: '' } }; - StorageService.writeLocalStorage('user', user); - } - }); - }; - - function isAllTwitterPropertiesNull(twitter: ITwitter): boolean { - return ( - twitter.twitterConnectedAt === null && - twitter.twitterId === null && - twitter.twitterProfileImageUrl === null && - twitter.twitterUsername === null - ); - } - - return ( - <> - {guilds && guilds.length > 0 ? ( -
-

Connected communities

-
- {guilds && guilds.length > 0 - ? guilds.map((guild: any) => ( -
- { - setGuildId(guildId), setOpen(true); - }} - /> -
- )) - : ''} - {user?.twitter && !isAllTwitterPropertiesNull(user.twitter) ? ( -
- -
- ) : ( - <> - )} -
-
- ) : ( - '' - )} - -
-

- Are you sure you want to disconnect{' '} -
your community? -

-
- -
-

Disconnect and delete data

-

- Importing activities and members will be stopped. Historical - activities will be deleted. -

-
- { - disconnectGuild('hard'); - }} - /> -
- -
-

Disconnect only

-

- Importing activities and members will be stopped. Historical - activities will not be affected. -

-
- { - disconnectGuild('soft'); - }} - /> -
-
-
-
- - ); -} diff --git a/src/components/pages/settings/ConnectedTwitter.tsx b/src/components/pages/settings/ConnectedTwitter.tsx deleted file mode 100644 index de5be7b0..00000000 --- a/src/components/pages/settings/ConnectedTwitter.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Avatar, Paper } from '@mui/material'; -import React from 'react'; -import { ITwitter } from '../../../utils/types'; -import useAppStore from '../../../store/useStore'; -import { BsTwitter } from 'react-icons/bs'; -import moment from 'moment'; -import { StorageService } from '../../../services/StorageService'; -import clsx from 'clsx'; - -interface IConnectedTwitter { - twitter?: ITwitter; -} - -function ConnectedTwitter({ twitter }: IConnectedTwitter) { - const { disconnectTwitter, getUserInfo } = useAppStore(); - - const handleDisconnect = async () => { - try { - await disconnectTwitter(); - - const userInfo = await getUserInfo(); - const { - twitterConnectedAt, - twitterId, - twitterProfileImageUrl, - twitterUsername, - } = userInfo; - - StorageService.updateLocalStorageWithObject('user', 'twitter', { - twitterConnectedAt, - twitterId, - twitterProfileImageUrl, - twitterUsername, - }); - - StorageService.removeLocalStorage('lastTwitterMetricsRefreshDate'); - } catch (error) { - console.error('Error handling disconnect:', error); - } - }; - - return ( -
- -
-
-

Twitter

- -
- -
-
- -
-

{twitter?.twitterUsername}

-

{`Connected ${moment( - twitter?.twitterConnectedAt - ).format('DD MMM yyyy')}`}

-
-
-
- Disconnect -
-
-
- ); -} - -export default ConnectedTwitter; diff --git a/src/components/pages/settings/DataAnalysis.tsx b/src/components/pages/settings/DataAnalysis.tsx deleted file mode 100644 index cd80b222..00000000 --- a/src/components/pages/settings/DataAnalysis.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import CustomModal from '../../global/CustomModal'; -import CustomButton from '../../global/CustomButton'; -import DatePeriodRange from '../../global/DatePeriodRange'; -import { BsClockHistory } from 'react-icons/bs'; -import useAppStore from '../../../store/useStore'; -import { FiInfo } from 'react-icons/fi'; -import { BiError } from 'react-icons/bi'; -import moment from 'moment'; -import { StorageService } from '../../../services/StorageService'; -import { IGuild, IUser } from '../../../utils/types'; - -export default function DataAnalysis() { - const [activePeriod, setActivePeriod] = useState(1); - const [guild, setGuild] = useState(); - const [analysisStateDate, setAnalysisStartDate] = useState(''); - const [open, setOpen] = useState(false); - const [datePeriod, setDatePeriod] = useState(''); - const [isDisabled, toggleDisabled] = useState(true); - const { guildInfo, updateAnalysisDatePeriod, getUserGuildInfo, guilds } = - useAppStore(); - - const handleActivePeriod = (dateRangeType: number | string) => { - let dateTime = ''; - switch (dateRangeType) { - case 1: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('35', 'days') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 2: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('3', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 3: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('6', 'months') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - case 4: - setActivePeriod(dateRangeType); - dateTime = moment() - .subtract('1', 'year') - .format('YYYY-MM-DDTHH:mm:ss[Z]'); - break; - default: - break; - } - setDatePeriod(dateTime); - toggleDisabled(false); - }; - - useEffect(() => { - const user = StorageService.readLocalStorage('user'); - if (user) { - setGuild(user.guild); - } - const start = moment(guildInfo.period, 'YYYY-MM-DD'); - const end = moment(); - - const datePeriod = Math.round(moment.duration(end.diff(start)).asMonths()); - - if (datePeriod <= 1) { - setActivePeriod(1); - } else if (datePeriod <= 3) { - setActivePeriod(2); - } else if (datePeriod > 3 && datePeriod <= 6) { - setActivePeriod(3); - } else { - setActivePeriod(4); - } - - setAnalysisStartDate(guildInfo.period); - }, [guildInfo]); - - const toggleModal = (e: boolean) => { - setOpen(e); - }; - - const submitNewDatePeriod = () => { - updateAnalysisDatePeriod(guild?.guildId, datePeriod).then((_res: any) => { - getUserGuildInfo(guild?.guildId); - setOpen(false); - }); - }; - - if (guilds.length === 0) { - return ( -
-

- It might take up to 6 hours to finish new data import. Once it is done - we will
send you a message on Discord. -

-

- - - There is no community connected at the moment. To be able to select - the date period, -
please connect your community - first. -
-

-
- - toggleModal(true)} - /> -
-
- ); - } - - return ( -
-

- It might take up to 6 hours to finish new data import. Once it is done - we will
send you a message on Discord. -

-

- - - Data analysis runs from:{' '} - {moment(analysisStateDate).format('DD MMMM yyyy')} - -

- -
- toggleModal(true)} - /> -
- -
- -

- We are changing date period for data analysis now -

-

- It might take up to 6 hours to finish new data import.{' '} -
Once it is done we will send you a - message on Discord. -

- -
-
-
- ); -} diff --git a/src/components/pages/settings/IntegrateDiscord.tsx b/src/components/pages/settings/IntegrateDiscord.tsx deleted file mode 100644 index 951025aa..00000000 --- a/src/components/pages/settings/IntegrateDiscord.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import useAppStore from '../../../store/useStore'; -import ConnectCommunities from './ConnectCommunities'; -import ConnectedCommunitiesList from './ConnectedCommunitiesList'; - -export default function IntegrateDiscord() { - const { guilds } = useAppStore(); - - return ( -
- - -
- ); -} From 95c80f917daeb5fb7488bf214ee341625cd68de6 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 18:05:40 +0300 Subject: [PATCH 10/58] remove settingSlice & authSlice --- src/store/slices/authSlice.ts | 73 --------------------- src/store/slices/settingSlice.ts | 108 ------------------------------- src/store/types/IAuth.ts | 34 ---------- src/store/types/ISetting.ts | 59 ----------------- src/store/useStore.ts | 4 -- 5 files changed, 278 deletions(-) delete mode 100644 src/store/slices/authSlice.ts delete mode 100644 src/store/slices/settingSlice.ts delete mode 100644 src/store/types/IAuth.ts delete mode 100644 src/store/types/ISetting.ts diff --git a/src/store/slices/authSlice.ts b/src/store/slices/authSlice.ts deleted file mode 100644 index 0e116464..00000000 --- a/src/store/slices/authSlice.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { StateCreator } from 'zustand'; -import IAuth, { IUser } from '../types/IAuth'; -import { conf } from '../../configs'; -import { axiosInstance } from '../../axiosInstance'; -import { StorageService } from '../../services/StorageService'; - -const BASE_URL = conf.API_BASE_URL; - -const createAuthSlice: StateCreator = (set, get) => ({ - isLoggedIn: false, - isLoading: false, - user: {}, - guildChannels: [], - - signUp: () => { - location.replace(`${BASE_URL}/auth/try-now`); - }, - - login: () => { - location.replace(`${BASE_URL}/auth/login`); - }, - - loginWithDiscord: (user: IUser) => - set(() => { - StorageService.writeLocalStorage('user', { - guild: { - guildId: user.guildId, - guildName: user.guildName, - }, - token: { - accessToken: user.accessToken, - accessExp: user.accessExp, - refreshToken: user.refreshToken, - refreshExp: user.refreshExp, - }, - }); - - return { user }; - }), - - fetchGuildChannels: async (guild_id: string) => { - try { - set(() => ({ isLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guild_id}/channels`); - set({ guildChannels: [...data], isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - - updateGuildById: async (guildId, period, selectedChannels) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.patch(`/guilds/${guildId}`, { - period, - selectedChannels: selectedChannels, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - - changeEmail: async (emailAddress: string) => { - try { - await axiosInstance.patch(`/users/@me`, { - email: emailAddress, - }); - } catch (error) {} - }, -}); - -export default createAuthSlice; diff --git a/src/store/slices/settingSlice.ts b/src/store/slices/settingSlice.ts deleted file mode 100644 index 175ede5d..00000000 --- a/src/store/slices/settingSlice.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { StateCreator } from 'zustand'; -import { axiosInstance } from '../../axiosInstance'; -import ISetting from '../types/ISetting'; -import { conf } from '../../configs'; - -const BASE_URL = conf.API_BASE_URL; - -const createSettingSlice: StateCreator = (set, get) => ({ - isLoading: false, - isRefetchLoading: false, - guildInfo: {}, - userInfo: {}, - guildInfoByDiscord: {}, - guilds: [], - guildChannels: [], - getUserGuildInfo: async (guildId: string) => { - try { - set(() => ({ isLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guildId}`); - - set({ guildInfo: data, isLoading: false }); - } catch (error) { - set(() => ({ guildInfo: {}, isLoading: false })); - } - }, - getUserInfo: async () => { - try { - const { data } = await axiosInstance.get('/users/@me'); - set({ userInfo: data }); - return data; - } catch (error) {} - }, - getGuildInfoByDiscord: async (guildId) => { - try { - set(() => ({ isLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guildId}`); - set({ guildInfoByDiscord: data, isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - updateSelectedChannels: async (guildId, selectedChannels) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.patch(`/guilds/${guildId}`, { - selectedChannels: selectedChannels, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - patchGuildById: async (guildId, period, selectedChannels) => { - try { - await axiosInstance.patch(`/guilds/${guildId}`, { - period, - selectedChannels: selectedChannels, - }); - } catch (error) {} - }, - updateAnalysisDatePeriod: async (guildId, period) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.patch(`/guilds/${guildId}`, { - period, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - getGuilds: async () => { - try { - const { data } = await axiosInstance.get(`/guilds?isDisconnected=false`); - set({ - guilds: [...data.results], - }); - } catch (error) {} - }, - disconnecGuildById: async (guildId, disconnectType) => { - try { - set(() => ({ isLoading: true })); - await axiosInstance.post(`/guilds/${guildId}/disconnect`, { - disconnectType: disconnectType, - }); - set({ isLoading: false }); - } catch (error) { - set(() => ({ isLoading: false })); - } - }, - connectNewGuild: async () => { - try { - location.replace(`${BASE_URL}/guilds/connect`); - } catch (error) {} - }, - - refetchGuildChannels: async (guild_id: string) => { - try { - set(() => ({ isRefetchLoading: true })); - const { data } = await axiosInstance.get(`/guilds/${guild_id}/channels`); - set({ guildChannels: [...data], isRefetchLoading: false }); - } catch (error) { - set(() => ({ isRefetchLoading: false })); - } - }, -}); - -export default createSettingSlice; diff --git a/src/store/types/IAuth.ts b/src/store/types/IAuth.ts deleted file mode 100644 index ab03f71c..00000000 --- a/src/store/types/IAuth.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { IChannelWithoutId, IGuildChannels } from '../../utils/types'; - -export type IUser = { - readonly accessToken: string; - readonly accessExp: string; - readonly guildId: string; - readonly guildName: string; - readonly refreshExp: string; - readonly refreshToken: string; -}; - -export type ISubChannels = { - readonly id: string; - readonly name: string; - readonly canReadMessageHistoryAndViewChannel: boolean; - readonly parent_id: string; -}; - -export default interface IAuth { - user: IUser | {}; - isLoading: boolean; - isLoggedIn: boolean; - guildChannels: IGuildChannels[]; - signUp: () => void; - login: () => void; - loginWithDiscord: (user: IUser) => void; - fetchGuildChannels: (guild_id: string) => void; - updateGuildById: ( - guildId: string, - period: string, - selectedChannels: IChannelWithoutId[] - ) => any; - changeEmail: (email: string) => any; -} diff --git a/src/store/types/ISetting.ts b/src/store/types/ISetting.ts deleted file mode 100644 index 5de57107..00000000 --- a/src/store/types/ISetting.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { IChannelWithoutId, IGuildChannels } from '../../utils/types'; - -export type IGuildInfo = { - id?: string; - guildId?: string; - ownerId?: string; - name?: boolean; - period?: string; - selectedChannels?: IChannelWithoutId[]; -}; - -export type DISCONNECT_TYPE = 'soft' | 'hard'; - -export interface IUserInfo { - discordId: string; - email: string; - verified: boolean; - avatar: string; - twitterConnectedAt: string; - twitterId: string; - twitterProfileImageUrl: string; - twitterUsername: string; - twitterIsInProgress: boolean; - id: string; -} - -export default interface IGuildList extends IGuildInfo { - isInProgress?: boolean; - isDisconnected?: boolean; - connectedAt?: string; -} -export default interface ISetting { - isLoading: boolean; - isRefetchLoading: boolean; - guildInfo?: IGuildInfo | {}; - userInfo: IUserInfo | {}; - guildInfoByDiscord: {}; - guilds: IGuildList[]; - guildChannels: IGuildChannels[]; - getUserGuildInfo: (guildId: string) => void; - getUserInfo: () => any; - getGuildInfoByDiscord: (guildId: string) => void; - updateSelectedChannels: ( - guildId: string, - selectedChannels: IChannelWithoutId[] - ) => void; - patchGuildById: ( - guildId: string, - period: string, - selectedChannels: IChannelWithoutId[] - ) => any; - updateAnalysisDatePeriod: (guildId: string, period: string) => void; - getGuilds: () => void; - disconnecGuildById: ( - guildId: string, - disconnectType: DISCONNECT_TYPE - ) => void; - refetchGuildChannels: (guild_id: string) => void; -} diff --git a/src/store/useStore.ts b/src/store/useStore.ts index 63526092..16a674c6 100644 --- a/src/store/useStore.ts +++ b/src/store/useStore.ts @@ -1,7 +1,5 @@ import { create } from 'zustand'; -import createAuthSlice from './slices/authSlice'; import createChartSlice from './slices/chartSlice'; -import createSettingSlice from './slices/settingSlice'; import createBreakdownsSlice from './slices/breakdownsSlice'; import createMemberInteractionSlice from './slices/memberInteractionSlice'; import communityHealthSlice from './slices/communityHealthSlice'; @@ -11,9 +9,7 @@ import platformSlice from './slices/platformSlice'; import userSlice from './slices/userSlice'; const useAppStore = create()((...a) => ({ - ...createAuthSlice(...a), ...createChartSlice(...a), - ...createSettingSlice(...a), ...createBreakdownsSlice(...a), ...createMemberInteractionSlice(...a), ...communityHealthSlice(...a), From 9a0efb7a8236160ec766f6e96fedcb55e68b87fd Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 18:08:54 +0300 Subject: [PATCH 11/58] remove old channelList component --- src/components/pages/login/ChannelList.tsx | 91 ---------------------- 1 file changed, 91 deletions(-) delete mode 100644 src/components/pages/login/ChannelList.tsx diff --git a/src/components/pages/login/ChannelList.tsx b/src/components/pages/login/ChannelList.tsx deleted file mode 100644 index 91c38db6..00000000 --- a/src/components/pages/login/ChannelList.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { FormControlLabel, Checkbox } from '@mui/material'; -import { FiAlertTriangle } from 'react-icons/fi'; -import { ISubChannels } from '../../../utils/types'; - -type IChannelListProps = { - guild: any; - showFlag: boolean; - onChange: (channelId: string, subChannelId: string, status: boolean) => void; - handleCheckAll: (guild: any, status: boolean) => void; -}; - -export default function ChannelList({ - guild, - onChange, - handleCheckAll, - showFlag, -}: IChannelListProps) { - const subChannelsList = ( - <> -

Channels

- {guild.subChannels.map((channel: ISubChannels, index: any) => ( -
-
- - onChange( - guild.channelId, - channel.channelId, - e.target.checked - ) - } - /> - } - label={channel.name} - /> - {showFlag && !channel.canReadMessageHistoryAndViewChannel ? ( -
- - - {!channel.canReadMessageHistoryAndViewChannel - ? 'Bot needs access' - : ''} - -
- ) : ( - '' - )} -
-
- ))} - - ); - - return ( -
-

{guild.title}

-
- item)} - color="secondary" - onChange={(e) => handleCheckAll(guild, e.target.checked)} - /> - } - /> - {subChannelsList} -
-
- ); -} - -ChannelList.defaultProps = { - showFlag: false, -}; From 7b99adbae36a78849d7dd95a8af2bb7e9b707d0b Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 22 Dec 2023 18:18:07 +0300 Subject: [PATCH 12/58] remove useless global component --- src/components/global/Accardion.tsx | 66 ------------------- src/components/global/Card.tsx | 28 -------- src/components/global/CustomDatePicker.tsx | 77 ---------------------- src/components/global/CustomModal.tsx | 60 ----------------- src/components/global/DatePeriodRange.tsx | 63 ------------------ tsconfig.json | 2 +- 6 files changed, 1 insertion(+), 295 deletions(-) delete mode 100644 src/components/global/Accardion.tsx delete mode 100644 src/components/global/Card.tsx delete mode 100644 src/components/global/CustomDatePicker.tsx delete mode 100644 src/components/global/CustomModal.tsx delete mode 100644 src/components/global/DatePeriodRange.tsx diff --git a/src/components/global/Accardion.tsx b/src/components/global/Accardion.tsx deleted file mode 100644 index e4400f2a..00000000 --- a/src/components/global/Accardion.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { ReactElement } from 'react'; -import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material'; -import { MdExpandMore } from 'react-icons/md'; - -type AcProps = { - readonly title?: string; - childs: AcChildProps[]; -}; - -type AcChildProps = { - title: string; - id: string; - icon?: ReactElement; - detailsComponent: ReactElement; -}; - -export default function Accardion({ title, childs }: AcProps) { - const [expanded, setExpanded] = React.useState(false); - - const handleChange = - (panel: string) => (_event: React.SyntheticEvent, isExpanded: boolean) => { - setExpanded(isExpanded ? panel : false); - }; - - return ( - <> -

{title}

- {childs.map((el) => ( - - - } - aria-controls={`${el.id}-content`} - id={el.id} - > -
-
- {el.icon} -
-

{el.title}

-
-
- - {el.detailsComponent} - -
- ))} - - ); -} diff --git a/src/components/global/Card.tsx b/src/components/global/Card.tsx deleted file mode 100644 index fc0ae5c2..00000000 --- a/src/components/global/Card.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import Image from "next/image"; - -type Props = { - className?: string; - title: string; - srcImage: string; - srcWidth: number; -}; - -export default function Card({ className, title, srcImage, srcWidth }: Props) { - return ( -
-

{title}

-
-
- Picture of the author -
-
-
- ); -} - -Card.defaultProps = { - title: "", - srcWidth: "400", -}; diff --git a/src/components/global/CustomDatePicker.tsx b/src/components/global/CustomDatePicker.tsx deleted file mode 100644 index 45ede02b..00000000 --- a/src/components/global/CustomDatePicker.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { FC, RefObject, useState } from 'react'; -import '@hassanmojab/react-modern-calendar-datepicker/lib/DatePicker.css'; -import DatePicker, { - DayRange, -} from '@hassanmojab/react-modern-calendar-datepicker'; -import { FiCalendar } from 'react-icons/fi'; -import clsx from 'clsx'; -import moment from 'moment'; - -interface IProps { - placeholder?: string; - className: string; - onClick: any; -} - -const CustomDatePicker: FC = ({ - placeholder, - className, - onClick, -}): JSX.Element => { - const [dayRange, setDayRange] = useState({ - from: null, - to: null, - }); - - const renderCustomInput = ({ - ref, - }: { - ref: RefObject | any; - }) => ( -
- - -
- ); - - return ( - setDayRange(date)} - renderInput={renderCustomInput} - colorPrimary="#35B9B7" // added this - colorPrimaryLight="#D0FBF8" // and this - calendarPopperPosition="bottom" - /> - ); -}; - -CustomDatePicker.defaultProps = { - placeholder: 'Specific date', -}; - -export default CustomDatePicker; diff --git a/src/components/global/CustomModal.tsx b/src/components/global/CustomModal.tsx deleted file mode 100644 index 3209fa9a..00000000 --- a/src/components/global/CustomModal.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Dialog, DialogTitle, DialogContent } from '@mui/material'; -import { IoClose } from 'react-icons/io5'; - -type IModalProps = { - isOpen: boolean; - toggleModal: (arg0: boolean) => void; - children: any; - hasClose: boolean; -}; -export default function ConfirmModal({ - isOpen, - toggleModal, - children, - hasClose, - ...props -}: IModalProps) { - const handleClose = () => { - toggleModal(false); - }; - return ( - <> - - {hasClose ? ( - - - - ) : ( - '' - )} - {children} - - - ); -} diff --git a/src/components/global/DatePeriodRange.tsx b/src/components/global/DatePeriodRange.tsx deleted file mode 100644 index 826d0b95..00000000 --- a/src/components/global/DatePeriodRange.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import clsx from 'clsx'; -import React, { useState } from 'react'; -import CustomDatePicker from './CustomDatePicker'; - -type dateItems = { - title: string; - icon?: JSX.Element; - value: any; -}; - -const datePeriod: dateItems[] = [ - { - title: 'Last 35 days', - value: 1, - }, - { - title: '3M', - value: 2, - }, - { - title: '6M', - value: 3, - }, - { - title: '1Y', - value: 4, - }, -]; - -type datePeriodRangeProps = { - activePeriod: string | number; - onChangeActivePeriod: (e: number) => void; -}; - -export default function DatePeriodRange({ - activePeriod, - onChangeActivePeriod, -}: datePeriodRangeProps) { - return ( -
-
    - {datePeriod.length > 0 - ? datePeriod.map((el) => ( -
  • onChangeActivePeriod(el.value)} - > - {el.icon ? el.icon : ''} -
    {el.title}
    -
  • - )) - : ''} -
-
- ); -} diff --git a/tsconfig.json b/tsconfig.json index b194c6be..e313683d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ "jest.config.js", "jest.setup.js" ], - "exclude": ["node_modules", "./src/components/global/CustomDatePicker.tsx"] + "exclude": ["node_modules"] } From 92d4f96d62b0aab9a7410a703ba2b814b90f8255 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 25 Dec 2023 13:10:50 +0300 Subject: [PATCH 13/58] create announcements initial route --- src/pages/announcement/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/pages/announcement/index.tsx diff --git a/src/pages/announcement/index.tsx b/src/pages/announcement/index.tsx new file mode 100644 index 00000000..a78b8b3a --- /dev/null +++ b/src/pages/announcement/index.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +function index() { + return
index
; +} + +export default index; From fc6fb47edf12529e98c14fd90a5ca256dd16a85b Mon Sep 17 00:00:00 2001 From: zuies Date: Tue, 26 Dec 2023 16:11:37 +0300 Subject: [PATCH 14/58] create shared tableContainer component --- src/components/layouts/Sidebar.tsx | 10 +++++ .../TcTableContainer/TcTableBody.spec.tsx | 30 +++++++++++++ .../shared/TcTableContainer/TcTableBody.tsx | 41 ++++++++++++++++++ .../TcTableContainer/TcTableCell.spec.tsx | 10 +++++ .../shared/TcTableContainer/TcTableCell.tsx | 25 +++++++++++ .../TcTableContainer.spec.tsx | 31 +++++++++++++ .../TcTableContainer/TcTableContainer.tsx | 40 +++++++++++++++++ .../TcTableContainer/TcTableHead.spec.tsx | 17 ++++++++ .../shared/TcTableContainer/TcTableHead.tsx | 23 ++++++++++ .../TcTableContainer/TcTableRow.spec.tsx | 14 ++++++ .../shared/TcTableContainer/TcTableRow.tsx | 35 +++++++++++++++ .../shared/TcTableContainer/index.ts | 3 ++ src/pages/announcement/index.tsx | 7 --- src/pages/announcements/index.tsx | 43 +++++++++++++++++++ src/styles/globals.css | 3 ++ 15 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 src/components/shared/TcTableContainer/TcTableBody.spec.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableBody.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableCell.spec.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableCell.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableContainer.spec.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableContainer.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableHead.spec.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableHead.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableRow.spec.tsx create mode 100644 src/components/shared/TcTableContainer/TcTableRow.tsx create mode 100644 src/components/shared/TcTableContainer/index.ts delete mode 100644 src/pages/announcement/index.tsx create mode 100644 src/pages/announcements/index.tsx diff --git a/src/components/layouts/Sidebar.tsx b/src/components/layouts/Sidebar.tsx index 3fb55c95..8d65e1fc 100644 --- a/src/components/layouts/Sidebar.tsx +++ b/src/components/layouts/Sidebar.tsx @@ -11,6 +11,7 @@ import { conf } from '../../configs/index'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUserGroup, faHeartPulse } from '@fortawesome/free-solid-svg-icons'; +import { MdOutlineAnnouncement } from 'react-icons/md'; import { useRouter } from 'next/router'; import Link from 'next/link'; @@ -60,6 +61,15 @@ const Sidebar = () => { /> ), }, + { + name: 'Announcements', + path: '/announcements', + icon: ( + + ), + }, { name: 'Community Settings', path: '/community-settings', diff --git a/src/components/shared/TcTableContainer/TcTableBody.spec.tsx b/src/components/shared/TcTableContainer/TcTableBody.spec.tsx new file mode 100644 index 00000000..2e8d1bb4 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableBody.spec.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TcTableBody from './TcTableBody'; + +describe('TcTableBody', () => { + const mockRowItems = [ + { Name: 'Alice', Age: 28, Location: 'New York' }, + { Name: 'Bob', Age: 34, Location: 'San Francisco' }, + ]; + + it('renders correctly with rowItems', () => { + render( + + +
+ ); + const tableRows = screen.getAllByRole('row'); + expect(tableRows.length).toBe(mockRowItems.length); + }); + + it('applies alternate background color for rows', () => { + render( + + +
+ ); + const firstRow = screen.getAllByRole('row')[0]; + expect(firstRow).toHaveClass('bg-gray-100'); + }); +}); diff --git a/src/components/shared/TcTableContainer/TcTableBody.tsx b/src/components/shared/TcTableContainer/TcTableBody.tsx new file mode 100644 index 00000000..67b08449 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableBody.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { TableBody, TableBodyProps } from '@mui/material'; +import TcTableRow from './TcTableRow'; + +interface ITcTableBodyProps extends TableBodyProps { + rowItems: { [key: string]: any }[]; +} + +/** + * TcTableBody Component + * + * Renders a Material-UI TableBody with custom row items. + * Each row is rendered using the TcTableRow component. + * + * Props: + * - rowItems: Array of objects, each representing data for a single row. + * + * @param {ITcTableBodyProps} props - Props including rowItems and other TableBodyProps + */ + +function TcTableBody({ rowItems, ...props }: ITcTableBodyProps) { + return ( + + {rowItems.map((row, index) => ( + + ))} + + ); +} + +TcTableBody.defaultProps = { + rowItems: [], +}; + +export default TcTableBody; diff --git a/src/components/shared/TcTableContainer/TcTableCell.spec.tsx b/src/components/shared/TcTableContainer/TcTableCell.spec.tsx new file mode 100644 index 00000000..a605d4a4 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableCell.spec.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TcTableCell from './TcTableCell'; + +describe('TcTableCell', () => { + it('renders the children content', () => { + render(Test Content); + expect(screen.getByText('Test Content')).toBeInTheDocument(); + }); +}); diff --git a/src/components/shared/TcTableContainer/TcTableCell.tsx b/src/components/shared/TcTableContainer/TcTableCell.tsx new file mode 100644 index 00000000..0717b3d2 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableCell.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { TableCell, TableCellProps } from '@mui/material'; + +interface ITcTableCellProps extends TableCellProps { + children: React.ReactNode; +} + +/** + * TcTableCell Component + * + * Custom TableCell component that extends Material-UI's TableCell. + * It can be used within Material-UI's Table components to display cell data. + * + * Props: + * - children: ReactNode - The content of the cell. + * - Other props inherited from Material-UI TableCellProps. + * + * @param {ITcTableCellProps} props - Props including children and TableCellProps + */ + +function TcTableCell({ children, ...props }: ITcTableCellProps) { + return {children}; +} + +export default TcTableCell; diff --git a/src/components/shared/TcTableContainer/TcTableContainer.spec.tsx b/src/components/shared/TcTableContainer/TcTableContainer.spec.tsx new file mode 100644 index 00000000..bca231fe --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableContainer.spec.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TcTableContainer from './TcTableContainer'; + +describe('TcTableContainer', () => { + const mockHeaders = ['Header 1', 'Header 2']; + const mockBodyRowItems = [ + { Column1: 'Row 1, Column 1', Column2: 'Row 1, Column 2' }, + { Column1: 'Row 2, Column 1', Column2: 'Row 2, Column 2' }, + ]; + + it('renders the table with body row items', () => { + render(); + + // Check if each row text content is present in the document + mockBodyRowItems.forEach((rowData) => { + Object.values(rowData).forEach((cellText) => { + const cell = screen.getByText(cellText); + expect(cell).toBeInTheDocument(); + }); + }); + }); + + it('applies custom classes for border separation and spacing', () => { + render( + + ); + const table = screen.getByRole('table'); + expect(table).toHaveClass('border-separate border-spacing-y-2'); + }); +}); diff --git a/src/components/shared/TcTableContainer/TcTableContainer.tsx b/src/components/shared/TcTableContainer/TcTableContainer.tsx new file mode 100644 index 00000000..9e580c73 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableContainer.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Table, TableProps } from '@mui/material'; +import TcTableHead from './TcTableHead'; +import TcTableBody from './TcTableBody'; + +interface ITcTableContainerProps extends TableProps { + headers?: string[]; + bodyRowItems?: any[]; +} + +/** + * TcTableContainer Component + * + * Custom Table component that extends Material-UI's Table. + * It can be used to display tabular data with optional custom border separation and spacing. + * + * Props: + * - headers: Array of strings - The table column headers. + * - bodyRowItems: Array of objects - The data for table rows. + * - Other props inherited from Material-UI TableProps. + * + * @param {ITcTableContainerProps} props - Props including headers, bodyRowItems, and TableProps + */ + +function TcTableContainer({ + headers, + bodyRowItems, + ...props +}: ITcTableContainerProps) { + return ( + + {headers && headers.length > 0 && } + {bodyRowItems && bodyRowItems.length > 0 && ( + + )} +
+ ); +} + +export default TcTableContainer; diff --git a/src/components/shared/TcTableContainer/TcTableHead.spec.tsx b/src/components/shared/TcTableContainer/TcTableHead.spec.tsx new file mode 100644 index 00000000..60d30645 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableHead.spec.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TcTableHead from './TcTableHead'; + +describe('TcTableHead', () => { + const mockHeaders = ['Header 1', 'Header 2']; + + it('renders the table head with headers', () => { + render(); + + // Check if each header text content is present in the document + mockHeaders.forEach((headerText) => { + const header = screen.getByText(headerText); + expect(header).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/shared/TcTableContainer/TcTableHead.tsx b/src/components/shared/TcTableContainer/TcTableHead.tsx new file mode 100644 index 00000000..ef6cc01b --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableHead.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { TableHead, TableHeadProps, TableRow, TableCell } from '@mui/material'; +import TcTableRow from './TcTableRow'; + +interface ITcTableHeadProps extends TableHeadProps { + headers: string[]; +} + +/** + * Component to render the table head with headers. + * + * @param {ITcTableHeadProps} props - The component props. + */ + +function TcTableHead({ headers, ...props }: ITcTableHeadProps) { + return ( + + + + ); +} + +export default TcTableHead; diff --git a/src/components/shared/TcTableContainer/TcTableRow.spec.tsx b/src/components/shared/TcTableContainer/TcTableRow.spec.tsx new file mode 100644 index 00000000..f9eaa7cb --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableRow.spec.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TcTableCell from './TcTableCell'; + +describe('TcTableCell', () => { + it('renders correctly with children', () => { + // Render the TcTableCell component with some children + render(Sample Content); + + // Check if the rendered content is present + const cellContent = screen.getByText('Sample Content'); + expect(cellContent).toBeInTheDocument(); + }); +}); diff --git a/src/components/shared/TcTableContainer/TcTableRow.tsx b/src/components/shared/TcTableContainer/TcTableRow.tsx new file mode 100644 index 00000000..ebe64525 --- /dev/null +++ b/src/components/shared/TcTableContainer/TcTableRow.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { TableRow, TableRowProps } from '@mui/material'; +import TcTableCell from './TcTableCell'; + +interface ITcTableRowProps extends TableRowProps { + rowItem: { [key: string]: any }; + customRenderers?: { [key: string]: (value: any) => React.ReactNode }; +} + +/** + * Component to render a table row with custom rendering options. + * + * @param {ITcTableRowProps} props - The component props. + */ + +function TcTableRow({ rowItem, customRenderers, ...props }: ITcTableRowProps) { + return ( + + {rowItem && + Object.entries(rowItem).map(([key, value], index) => { + const CustomRenderer = customRenderers?.[key]; + return ( + + {CustomRenderer ? CustomRenderer(value) : value} + + ); + })} + + ); +} + +export default TcTableRow; diff --git a/src/components/shared/TcTableContainer/index.ts b/src/components/shared/TcTableContainer/index.ts new file mode 100644 index 00000000..e1a070a6 --- /dev/null +++ b/src/components/shared/TcTableContainer/index.ts @@ -0,0 +1,3 @@ +import { default as TcTableContainer } from './TcTableContainer'; + +export default TcTableContainer; diff --git a/src/pages/announcement/index.tsx b/src/pages/announcement/index.tsx deleted file mode 100644 index a78b8b3a..00000000 --- a/src/pages/announcement/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -function index() { - return
index
; -} - -export default index; diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx new file mode 100644 index 00000000..71dd0fb3 --- /dev/null +++ b/src/pages/announcements/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { defaultLayout } from '../../layouts/defaultLayout'; +import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; +import SEO from '../../components/global/SEO'; +import TcText from '../../components/shared/TcText'; +import TcButton from '../../components/shared/TcButton'; +import { BsPlus } from 'react-icons/bs'; +import TcTableContainer from '../../components/shared/TcTableContainer'; + +const bodyRowItems = [ + { Name: 'Alice', Age: 28, Location: 'New York' }, + { Name: 'Bob', Age: 34, Location: 'San Francisco' }, + { Name: 'Carol', Age: 23, Location: 'Miami' }, +]; + +function Index() { + return ( + <> + +
+ +
+ + } + variant="outlined" + /> +
+ +
+ } + /> +
+ + ); +} + +Index.pageLayout = defaultLayout; + +export default Index; diff --git a/src/styles/globals.css b/src/styles/globals.css index a09541cc..e677196b 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -78,3 +78,6 @@ body { .highcharts-credits { pointer-events: none !important; } +.no-border td { + border: 0; +} From 138f9f569d72865ff68e08ebcb0e1268ae22815c Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 27 Dec 2023 13:25:13 +0300 Subject: [PATCH 15/58] public message container done --- .../create/TcIconContainer.spec.tsx | 14 +++ .../announcements/create/TcIconContainer.tsx | 31 +++++++ .../TcPrivateMessaageContainer.tsx | 86 +++++++++++++++++++ .../TcPrivateMessaageContainer/index.ts | 3 + .../TcPublicMessaageContainer.tsx | 86 +++++++++++++++++++ .../create/publicMessageContainer/index.ts | 3 + src/components/shared/TcSelect/TcSelect.tsx | 73 ++++++++-------- .../create-new-announcements.tsx | 73 ++++++++++++++++ src/pages/announcements/index.tsx | 4 + 9 files changed, 338 insertions(+), 35 deletions(-) create mode 100644 src/components/announcements/create/TcIconContainer.spec.tsx create mode 100644 src/components/announcements/create/TcIconContainer.tsx create mode 100644 src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx create mode 100644 src/components/announcements/create/TcPrivateMessaageContainer/index.ts create mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx create mode 100644 src/components/announcements/create/publicMessageContainer/index.ts create mode 100644 src/pages/announcements/create-new-announcements.tsx diff --git a/src/components/announcements/create/TcIconContainer.spec.tsx b/src/components/announcements/create/TcIconContainer.spec.tsx new file mode 100644 index 00000000..4c3f102b --- /dev/null +++ b/src/components/announcements/create/TcIconContainer.spec.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TcIconContainer from './TcIconContainer'; + +describe('TcIconContainer', () => { + it('renders its children', () => { + render( + +
Test Child
+
+ ); + expect(screen.getByText('Test Child')).toBeInTheDocument(); + }); +}); diff --git a/src/components/announcements/create/TcIconContainer.tsx b/src/components/announcements/create/TcIconContainer.tsx new file mode 100644 index 00000000..4b5c68a0 --- /dev/null +++ b/src/components/announcements/create/TcIconContainer.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +/** + * Interface defining the properties for TcIconContainer. + * @interface ITcIconContainerProps + */ +interface ITcIconContainerProps { + /** + * Children elements to be rendered inside the container. + * This should be a single React element, typically an icon or a small component. + * @type {React.ReactElement} + */ + children: React.ReactElement; +} + +/** + * A container component designed to display its children in a circular, centered fashion. + * Ideal for icons or small elements. + * + * @param {ITcIconContainerProps} props - The properties passed to the component. + * @returns {JSX.Element} A div element with applied styling and containing the children. + */ +function TcIconContainer({ children }: ITcIconContainerProps): JSX.Element { + return ( +
+ {children} +
+ ); +} + +export default TcIconContainer; diff --git a/src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx new file mode 100644 index 00000000..516a7eaa --- /dev/null +++ b/src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import TcText from '../../../shared/TcText'; +import { MdOutlineAnnouncement } from 'react-icons/md'; +import TcIconContainer from '../TcIconContainer'; +import TcButton from '../../../shared/TcButton'; +import TcSelect from '../../../shared/TcSelect'; +import { FormControl, InputLabel, SelectChangeEvent } from '@mui/material'; +import TcInput from '../../../shared/TcInput'; + +const mockPublicChannels = [ + { + label: 'test', + value: 1, + }, + { + label: 'test2', + value: 2, + }, +]; + +function TcPrivateMessaageContainer() { + const [selectedChannels, setSelectedChannels] = useState([]); + const [message, setMessage] = useState(''); + + const handleSelectChange = (event: SelectChangeEvent) => { + setSelectedChannels(event.target.value as number[]); + }; + + const handleChange = (event: React.ChangeEvent) => { + setMessage(event.target.value); + }; + + return ( +
+
+
+ + + + +
+ +
+
+ + Select Channels + + (selected as number[]) + .map( + (value) => + mockPublicChannels.find( + (channel) => channel.value === value + )?.label + ) + .join(', ') + } + onChange={(event) => handleSelectChange(event)} + /> + + + + +
+
+ ); +} + +export default TcPrivateMessaageContainer; diff --git a/src/components/announcements/create/TcPrivateMessaageContainer/index.ts b/src/components/announcements/create/TcPrivateMessaageContainer/index.ts new file mode 100644 index 00000000..800fd209 --- /dev/null +++ b/src/components/announcements/create/TcPrivateMessaageContainer/index.ts @@ -0,0 +1,3 @@ +import { default as TcPrivateMessaageContainer } from './TcPrivateMessaageContainer'; + +export default TcPrivateMessaageContainer; diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx new file mode 100644 index 00000000..013ef562 --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import TcText from '../../../shared/TcText'; +import { MdAnnouncement } from 'react-icons/md'; +import TcIconContainer from '../TcIconContainer'; +import TcButton from '../../../shared/TcButton'; +import TcSelect from '../../../shared/TcSelect'; +import { FormControl, InputLabel, SelectChangeEvent } from '@mui/material'; +import TcInput from '../../../shared/TcInput'; + +const mockPublicChannels = [ + { + label: 'test', + value: 1, + }, + { + label: 'test2', + value: 2, + }, +]; + +function TcPublicMessaageContainer() { + const [selectedChannels, setSelectedChannels] = useState([]); + const [message, setMessage] = useState(''); + + const handleSelectChange = (event: SelectChangeEvent) => { + setSelectedChannels(event.target.value as number[]); + }; + + const handleChange = (event: React.ChangeEvent) => { + setMessage(event.target.value); + }; + + return ( +
+
+
+ + + + +
+ +
+
+ + Select Channels + + (selected as number[]) + .map( + (value) => + mockPublicChannels.find( + (channel) => channel.value === value + )?.label + ) + .join(', ') + } + onChange={(event) => handleSelectChange(event)} + /> + + + + +
+
+ ); +} + +export default TcPublicMessaageContainer; diff --git a/src/components/announcements/create/publicMessageContainer/index.ts b/src/components/announcements/create/publicMessageContainer/index.ts new file mode 100644 index 00000000..55c8ddb5 --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/index.ts @@ -0,0 +1,3 @@ +import { default as TcPublicMessaageContainer } from './TcPublicMessaageContainer'; + +export default TcPublicMessaageContainer; diff --git a/src/components/shared/TcSelect/TcSelect.tsx b/src/components/shared/TcSelect/TcSelect.tsx index d911cd2d..abfa847f 100644 --- a/src/components/shared/TcSelect/TcSelect.tsx +++ b/src/components/shared/TcSelect/TcSelect.tsx @@ -1,45 +1,48 @@ +import { MenuItem, Select, SelectProps } from '@mui/material'; +import React, { ReactElement } from 'react'; +import { IconType } from 'react-icons'; +import TcText from '../TcText'; + /** - * TcSelect Component - * - * This component is a wrapper around Material-UI's Select component. - * It provides a dropdown select box functionality. - * - * Props: - * - options: Array of objects with 'value' and 'label' keys. These are used to populate the dropdown menu. - * - * Example Usage: - * + * Interface for TcSelect props */ - -import React, { useState } from 'react'; -import Select, { SelectChangeEvent } from '@mui/material/Select'; -import MenuItem from '@mui/material/MenuItem'; - -interface TcSelectProps { - options: { - value: string; +interface ITcSelectProps extends SelectProps { + /** + * options - Array of option objects for the select dropdown + * Each object can have: + * - value (string | number): The value of the option + * - label (string): The display label for the option + * - icon (ReactElement): Optional icon to display alongside the label + */ + options: Array<{ + value: string | number; label: string; - }[]; + icon?: ReactElement; + disabled?: boolean; + }>; } -function TcSelect({ options, ...props }: TcSelectProps) { - const [value, setValue] = useState(''); - - const handleChange = (event: SelectChangeEvent) => { - const newValue = event.target.value as string; - setValue(newValue); - }; +/** + * TcSelect is a custom select component built on Material-UI's Select component. + * It allows displaying a list of options with optional icons. + * + * @param {ITcSelectProps} props - The props for the component + * @returns {ReactElement} The TcSelect component + */ +function TcSelect({ options, ...props }: ITcSelectProps): ReactElement { return ( - + {options.map((option, index) => ( + +
+ {option.icon} + +
))} diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx new file mode 100644 index 00000000..f42b9be9 --- /dev/null +++ b/src/pages/announcements/create-new-announcements.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { defaultLayout } from '../../layouts/defaultLayout'; +import SEO from '../../components/global/SEO'; +import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; +import TcText from '../../components/shared/TcText'; +import TcSelect from '../../components/shared/TcSelect'; +import { FormControl, InputLabel } from '@mui/material'; +import { BsDiscord, BsTelegram } from 'react-icons/bs'; +import TcPublicMessaageContainer from '../../components/announcements/create/publicMessageContainer'; +import TcPrivateMessaageContainer from '../../components/announcements/create/TcPrivateMessaageContainer'; + +const announcementsPlatforms = [ + { + label: 'Discord', + value: '1', + icon: , + }, + { + label: 'Telegram(TBA)', + value: '2', + disabled: true, + icon: , + }, +]; + +function CreateNewAnnouncements() { + return ( + <> + +
+ +
+
+ + +
+ + + Select Platform + + + +
+ + +
+ } + /> +
+ + ); +} + +CreateNewAnnouncements.pageLayout = defaultLayout; + +export default CreateNewAnnouncements; diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index 71dd0fb3..cb74d1ea 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -6,6 +6,7 @@ import TcText from '../../components/shared/TcText'; import TcButton from '../../components/shared/TcButton'; import { BsPlus } from 'react-icons/bs'; import TcTableContainer from '../../components/shared/TcTableContainer'; +import router from 'next/router'; const bodyRowItems = [ { Name: 'Alice', Age: 28, Location: 'New York' }, @@ -27,6 +28,9 @@ function Index() { text="Create Announcement" startIcon={} variant="outlined" + onClick={() => + router.push('/announcements/create-new-announcements') + } /> From 2c21184657db225098b98ed73b28bb291b7f906e Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 27 Dec 2023 13:30:45 +0300 Subject: [PATCH 16/58] add TcSwitch component --- src/components/shared/TcSwitch.spec.tsx | 16 +++++++++++ src/components/shared/TcSwitch.tsx | 37 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/components/shared/TcSwitch.spec.tsx create mode 100644 src/components/shared/TcSwitch.tsx diff --git a/src/components/shared/TcSwitch.spec.tsx b/src/components/shared/TcSwitch.spec.tsx new file mode 100644 index 00000000..3988c15c --- /dev/null +++ b/src/components/shared/TcSwitch.spec.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import TcSwitch from './TcSwitch'; + +describe('TcSwitch', () => { + test('it should toggle switch', () => { + const handleChange = jest.fn(); + const { getByRole } = render(); + + const switchControl = getByRole('checkbox'); + expect(switchControl).not.toBeChecked(); + + fireEvent.click(switchControl); + expect(handleChange).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/shared/TcSwitch.tsx b/src/components/shared/TcSwitch.tsx new file mode 100644 index 00000000..6d980173 --- /dev/null +++ b/src/components/shared/TcSwitch.tsx @@ -0,0 +1,37 @@ +import { Switch, SwitchProps } from '@mui/material'; +import React from 'react'; + +interface ITcSwitchProps extends SwitchProps {} + +/** + * `TcSwitch` Component + * + * This component is a wrapper around Material-UI's `Switch` component. + * It can be used anywhere a Material-UI Switch would be used. It accepts all props + * that a standard Material-UI Switch accepts. + * + * Usage: + * + * + * Props: + * - All props available to Material-UI's `Switch` component. + * - `checked`: Boolean indicating whether the switch is on or off. + * - `onChange`: Function to handle the change event when the switch is toggled. + * + * Example: + * ``` + * { this.setState({ isChecked: e.target.checked }) }} + * /> + * ``` + * + * For more details on Material-UI's `Switch` props, + * see: https://mui.com/api/switch/ + */ + +function TcSwitch({ ...props }: ITcSwitchProps) { + return ; +} + +export default TcSwitch; From f6a7cb31263ae802392fe1e1c0b2e95c8ba4104b Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 3 Jan 2024 15:09:54 +0300 Subject: [PATCH 17/58] complete create-announcements --- package-lock.json | 389 ++++++++++++++---- package.json | 3 + .../TcPrivateMessaageContainer.tsx | 86 ---- .../TcPrivateMessaageContainer.spec.tsx | 43 ++ .../TcPrivateMessaageContainer.tsx | 195 +++++++++ .../index.ts | 0 .../TcPublicMessaageContainer.spec.tsx | 32 ++ .../TcPublicMessaageContainer.tsx | 17 +- .../TcDateTimePopover.tsx | 97 +++++ .../TcScheduleAnnouncement.spec.tsx | 11 + .../TcScheduleAnnouncement.tsx | 90 ++++ .../create/scheduleAnnouncement/index.ts | 3 + .../selectPlatform/TcSelectPlatform.tsx | 47 +++ .../create/selectPlatform/index.ts | 3 + src/components/shared/TcBreadcrumbs.tsx | 63 +-- src/components/shared/TcLink.tsx | 2 +- src/components/shared/TcTabs/TcTab/TcTab.tsx | 10 + src/components/shared/TcTabs/TcTab/index.ts | 3 + src/components/shared/TcTabs/TcTabs.tsx | 26 ++ src/components/shared/TcTabs/index.ts | 3 + .../create-new-announcements.tsx | 78 ++-- .../community-settings/platform/index.tsx | 1 + src/utils/theme.ts | 46 +-- 23 files changed, 968 insertions(+), 280 deletions(-) delete mode 100644 src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx create mode 100644 src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx create mode 100644 src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx rename src/components/announcements/create/{TcPrivateMessaageContainer => privateMessaageContainer}/index.ts (100%) create mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.spec.tsx create mode 100644 src/components/announcements/create/scheduleAnnouncement/TcDateTimePopover.tsx create mode 100644 src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx create mode 100644 src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx create mode 100644 src/components/announcements/create/scheduleAnnouncement/index.ts create mode 100644 src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx create mode 100644 src/components/announcements/create/selectPlatform/index.ts create mode 100644 src/components/shared/TcTabs/TcTab/TcTab.tsx create mode 100644 src/components/shared/TcTabs/TcTab/index.ts create mode 100644 src/components/shared/TcTabs/TcTabs.tsx create mode 100644 src/components/shared/TcTabs/index.ts diff --git a/package-lock.json b/package-lock.json index 18367f7c..9ea7cd8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@amplitude/analytics-browser": "^1.9.4", + "@date-io/date-fns": "^2.17.0", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@fortawesome/fontawesome-svg-core": "^6.2.0", @@ -19,6 +20,7 @@ "@hassanmojab/react-modern-calendar-datepicker": "^3.1.7", "@mui/lab": "^5.0.0-alpha.121", "@mui/material": "^5.10.13", + "@mui/x-date-pickers": "^6.18.6", "@sentry/nextjs": "^7.50.0", "@types/node": "18.11.9", "@types/react": "18.0.25", @@ -30,6 +32,7 @@ "axios": "^1.2.2", "clsx": "^1.2.1", "d3-force": "^3.0.0", + "date-fns": "^2.30.0", "eslint": "8.27.0", "eslint-config-next": "13.0.2", "eslint-config-prettier": "^8.6.0", @@ -600,11 +603,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -662,6 +665,27 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@date-io/core": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", + "integrity": "sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw==" + }, + "node_modules/@date-io/date-fns": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.17.0.tgz", + "integrity": "sha512-L0hWZ/mTpy3Gx/xXJ5tq5CzHo0L7ry6KEO9/w/JWiFWFLZgiNVo3ex92gOl3zmzjHqY/3Ev+5sehAr8UnGLEng==", + "dependencies": { + "@date-io/core": "^2.17.0" + }, + "peerDependencies": { + "date-fns": "^2.0.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + } + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.10.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", @@ -844,18 +868,39 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.1.tgz", - "integrity": "sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz", + "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } }, "node_modules/@floating-ui/dom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.1.tgz", - "integrity": "sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", "dependencies": { - "@floating-ui/core": "^1.2.1" + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", @@ -2077,11 +2122,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz", + "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2090,13 +2135,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.11.tgz", - "integrity": "sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==", + "version": "5.15.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.2.tgz", + "integrity": "sha512-6dGM9/guFKBlFRHA7/mbM+E7wE7CYDy9Ny4JLtD3J+NTyhi8nd8YxlzgAgTaTVqY0BpdQ2zdfB/q6+p2EdGM0w==", "dependencies": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.6", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -2105,10 +2149,120 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.6.tgz", + "integrity": "sha512-pqOrGPUDVY/1xXrM1hofqwgquno/SB9aG9CVS1m2Rs8hKF1VWRC+jYlEa1Qk08xKmvkia5g7NsdV/BBb+tHUZw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "date-fns": "^2.25.0", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/base": { + "version": "5.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.29.tgz", + "integrity": "sha512-OXfUssYrB6ch/xpBVHMKAjThPlI9VyGGKdvQLMXef2j39wXfcxPlUVQlwia/lmE3rxWIGvbwkZsDtNYzLMsDUg==", + "dependencies": { + "@babel/runtime": "^7.23.6", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.2", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" } }, "node_modules/@next/env": { @@ -2352,9 +2506,9 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -3175,9 +3329,9 @@ "dev": true }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { "version": "18.0.25", @@ -3197,18 +3351,10 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "dependencies": { "@types/react": "*" } @@ -4917,6 +5063,21 @@ "node": ">=12" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -10825,9 +10986,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -12630,11 +12791,11 @@ } }, "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -12680,6 +12841,19 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@date-io/core": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", + "integrity": "sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw==" + }, + "@date-io/date-fns": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.17.0.tgz", + "integrity": "sha512-L0hWZ/mTpy3Gx/xXJ5tq5CzHo0L7ry6KEO9/w/JWiFWFLZgiNVo3ex92gOl3zmzjHqY/3Ev+5sehAr8UnGLEng==", + "requires": { + "@date-io/core": "^2.17.0" + } + }, "@emotion/babel-plugin": { "version": "11.10.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", @@ -12822,18 +12996,35 @@ } }, "@floating-ui/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.1.tgz", - "integrity": "sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz", + "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==", + "requires": { + "@floating-ui/utils": "^0.1.3" + } }, "@floating-ui/dom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.1.tgz", - "integrity": "sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", "requires": { - "@floating-ui/core": "^1.2.1" + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" } }, + "@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "requires": { + "@floating-ui/dom": "^1.5.1" + } + }, + "@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "@fortawesome/fontawesome-common-types": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", @@ -13661,23 +13852,57 @@ } }, "@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz", + "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w==", "requires": {} }, "@mui/utils": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.11.tgz", - "integrity": "sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==", + "version": "5.15.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.2.tgz", + "integrity": "sha512-6dGM9/guFKBlFRHA7/mbM+E7wE7CYDy9Ny4JLtD3J+NTyhi8nd8YxlzgAgTaTVqY0BpdQ2zdfB/q6+p2EdGM0w==", "requires": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.6", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" } }, + "@mui/x-date-pickers": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.6.tgz", + "integrity": "sha512-pqOrGPUDVY/1xXrM1hofqwgquno/SB9aG9CVS1m2Rs8hKF1VWRC+jYlEa1Qk08xKmvkia5g7NsdV/BBb+tHUZw==", + "requires": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "@mui/base": { + "version": "5.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.29.tgz", + "integrity": "sha512-OXfUssYrB6ch/xpBVHMKAjThPlI9VyGGKdvQLMXef2j39wXfcxPlUVQlwia/lmE3rxWIGvbwkZsDtNYzLMsDUg==", + "requires": { + "@babel/runtime": "^7.23.6", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.2", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + } + }, + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" + } + } + }, "@next/env": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", @@ -13793,9 +14018,9 @@ } }, "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@remix-run/router": { "version": "1.3.2", @@ -14459,9 +14684,9 @@ "dev": true }, "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "@types/react": { "version": "18.0.25", @@ -14481,18 +14706,10 @@ "@types/react": "*" } }, - "@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "requires": { - "@types/react": "*" - } - }, "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "requires": { "@types/react": "*" } @@ -15725,6 +15942,14 @@ } } }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -20008,9 +20233,9 @@ } }, "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regexp.prototype.flags": { "version": "1.4.3", diff --git a/package.json b/package.json index 85d1b24e..39f5377e 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@amplitude/analytics-browser": "^1.9.4", + "@date-io/date-fns": "^2.17.0", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@fortawesome/fontawesome-svg-core": "^6.2.0", @@ -23,6 +24,7 @@ "@hassanmojab/react-modern-calendar-datepicker": "^3.1.7", "@mui/lab": "^5.0.0-alpha.121", "@mui/material": "^5.10.13", + "@mui/x-date-pickers": "^6.18.6", "@sentry/nextjs": "^7.50.0", "@types/node": "18.11.9", "@types/react": "18.0.25", @@ -34,6 +36,7 @@ "axios": "^1.2.2", "clsx": "^1.2.1", "d3-force": "^3.0.0", + "date-fns": "^2.30.0", "eslint": "8.27.0", "eslint-config-next": "13.0.2", "eslint-config-prettier": "^8.6.0", diff --git a/src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx deleted file mode 100644 index 516a7eaa..00000000 --- a/src/components/announcements/create/TcPrivateMessaageContainer/TcPrivateMessaageContainer.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useState } from 'react'; -import TcText from '../../../shared/TcText'; -import { MdOutlineAnnouncement } from 'react-icons/md'; -import TcIconContainer from '../TcIconContainer'; -import TcButton from '../../../shared/TcButton'; -import TcSelect from '../../../shared/TcSelect'; -import { FormControl, InputLabel, SelectChangeEvent } from '@mui/material'; -import TcInput from '../../../shared/TcInput'; - -const mockPublicChannels = [ - { - label: 'test', - value: 1, - }, - { - label: 'test2', - value: 2, - }, -]; - -function TcPrivateMessaageContainer() { - const [selectedChannels, setSelectedChannels] = useState([]); - const [message, setMessage] = useState(''); - - const handleSelectChange = (event: SelectChangeEvent) => { - setSelectedChannels(event.target.value as number[]); - }; - - const handleChange = (event: React.ChangeEvent) => { - setMessage(event.target.value); - }; - - return ( -
-
-
- - - - -
- -
-
- - Select Channels - - (selected as number[]) - .map( - (value) => - mockPublicChannels.find( - (channel) => channel.value === value - )?.label - ) - .join(', ') - } - onChange={(event) => handleSelectChange(event)} - /> - - - - -
-
- ); -} - -export default TcPrivateMessaageContainer; diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx new file mode 100644 index 00000000..e4827f8d --- /dev/null +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import TcPrivateMessageContainer from './TcPrivateMessaageContainer'; + +describe('TcPrivateMessageContainer Tests', () => { + test('renders the component without crashing', () => { + render(); + expect(screen.getByText('Private Message (optional)')).toBeInTheDocument(); + }); + + test('initial states are set correctly', () => { + render(); + }); + + test('toggles private message switch', () => { + render(); + const switchControl = screen.getByRole('checkbox'); + fireEvent.click(switchControl); + }); + + test('message type buttons respond to clicks', () => { + render(); + }); + + test('allows the user to enter a message', async () => { + render(); + + const privateMessageToggle = screen.getByRole('checkbox'); + fireEvent.click(privateMessageToggle); + + const messageInput = (await screen.findByPlaceholderText( + 'Write your message here' + )) as HTMLInputElement; + fireEvent.change(messageInput, { target: { value: 'Test Message' } }); + + expect(messageInput.value).toBe('Test Message'); + }); + + test('handles channel and username selection based on message type', () => { + render(); + }); +}); diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx new file mode 100644 index 00000000..a272e31c --- /dev/null +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; +import TcText from '../../../shared/TcText'; +import { MdOutlineAnnouncement } from 'react-icons/md'; +import TcIconContainer from '../TcIconContainer'; +import TcButton from '../../../shared/TcButton'; +import TcSelect from '../../../shared/TcSelect'; +import { + FormControl, + FormControlLabel, + InputLabel, + SelectChangeEvent, +} from '@mui/material'; +import TcInput from '../../../shared/TcInput'; +import TcSwitch from '../../../shared/TcSwitch'; +import TcIconWithTooltip from '../../../shared/TcIconWithTooltip'; +import TcButtonGroup from '../../../shared/TcButtonGroup'; +import clsx from 'clsx'; + +const mockPublicChannels = [ + { + label: 'test', + value: 1, + }, + { + label: 'test2', + value: 2, + }, +]; + +export enum MessageType { + Both = 'Both', + RoleOnly = 'Role Only', + UserOnly = 'User Only', +} + +function TcPrivateMessageContainer() { + const [privateMessage, setPrivateMessage] = useState(false); + const [messageType, setMessageType] = useState(MessageType.Both); + const [selectedChannels, setSelectedChannels] = useState([]); + const [message, setMessage] = useState(''); + + const handleSelectChange = (event: SelectChangeEvent) => { + setSelectedChannels(event.target.value as number[]); + }; + + const handleChange = (event: React.ChangeEvent) => { + setMessage(event.target.value); + }; + + const handlePrivateMessageChange = ( + event: React.ChangeEvent + ) => { + setPrivateMessage(event.target.checked); + }; + + const messageTypesArray = Object.values(MessageType); + + return ( +
+
+
+ + + + + } + label={ +
+ + +
+ } + /> +
+
+ + {messageTypesArray.map((el) => ( + setMessageType(el)} + /> + ))} + + +
+
+ {privateMessage && ( +
+
+ + Select Role(s) + + (selected as number[]) + .map( + (value) => + mockPublicChannels.find( + (channel) => channel.value === value + )?.label + ) + .join(', ') + } + onChange={(event) => handleSelectChange(event)} + /> + + + + Select Username(s) + + + (selected as number[]) + .map( + (value) => + mockPublicChannels.find( + (channel) => channel.value === value + )?.label + ) + .join(', ') + } + onChange={(event) => handleSelectChange(event)} + /> + +
+ + + +
+ )} +
+ ); +} + +export default TcPrivateMessageContainer; diff --git a/src/components/announcements/create/TcPrivateMessaageContainer/index.ts b/src/components/announcements/create/privateMessaageContainer/index.ts similarity index 100% rename from src/components/announcements/create/TcPrivateMessaageContainer/index.ts rename to src/components/announcements/create/privateMessaageContainer/index.ts diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.spec.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.spec.tsx new file mode 100644 index 00000000..50a4c3c9 --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.spec.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import TcPublicMessaageContainer from './TcPublicMessaageContainer'; + +describe('TcPublicMessaageContainer Tests', () => { + test('renders the component without crashing', () => { + render(); + expect(screen.getByText('Public Message')).toBeInTheDocument(); + }); + + test('initial state is set correctly', () => { + render(); + expect(screen.getByPlaceholderText('Write your message here')).toHaveValue( + '' + ); + }); + + test('allows the user to enter a message', () => { + render(); + const messageInput = screen.getByPlaceholderText( + 'Write your message here' + ) as HTMLInputElement; + fireEvent.change(messageInput, { target: { value: 'Test Message' } }); + expect(messageInput.value).toBe('Test Message'); + }); + + test('select channels dropdown is rendered', () => { + render(); + expect(screen.getByLabelText('Select Channels')).toBeInTheDocument(); + }); +}); diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx index 013ef562..5c851641 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx @@ -4,7 +4,12 @@ import { MdAnnouncement } from 'react-icons/md'; import TcIconContainer from '../TcIconContainer'; import TcButton from '../../../shared/TcButton'; import TcSelect from '../../../shared/TcSelect'; -import { FormControl, InputLabel, SelectChangeEvent } from '@mui/material'; +import { + FormControl, + FormHelperText, + InputLabel, + SelectChangeEvent, +} from '@mui/material'; import TcInput from '../../../shared/TcInput'; const mockPublicChannels = [ @@ -39,7 +44,11 @@ function TcPublicMessaageContainer() { - +
@@ -63,6 +72,10 @@ function TcPublicMessaageContainer() { } onChange={(event) => handleSelectChange(event)} /> + + The announcement will be sent by the a bot which will have access to + send the following message within the selected channels + void; + selectedDate: Date | null; + handleDateChange: (date: Date | null) => void; + selectedTime: Date | null; + handleTimeChange: (time: Date | null) => void; + activeTab: number; + setActiveTab: React.Dispatch>; +} + +function TcDateTimePopover({ + open, + anchorEl, + onClose, + selectedDate, + handleDateChange, + selectedTime, + handleTimeChange, + activeTab, + setActiveTab, +}: IDateTimePopoverProps) { + const disablePastDates = (date: Date): boolean => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + return date < today; + }; + + const tabContent = [ + + + , + + + , + ]; + + return ( + + {tabContent[activeTab]} + + setActiveTab(newValue)} + indicatorColor="secondary" + className="w-full border-t border-gray-200" + > + } + className="w-1/2" + data-testid="calendar-icon" + /> + } + className="w-1/2" + data-testid="time-icon" + /> + +
+ } + /> + ); +} + +export default TcDateTimePopover; diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx new file mode 100644 index 00000000..870106c8 --- /dev/null +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import TcScheduleAnnouncement from './TcScheduleAnnouncement'; + +describe('TcScheduleAnnouncement Tests', () => { + test('renders the component without crashing', () => { + render(); + expect(screen.getByText('Schedule Announcement')).toBeInTheDocument(); + }); +}); diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx new file mode 100644 index 00000000..3eed512e --- /dev/null +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -0,0 +1,90 @@ +import React, { useState } from 'react'; +import TcIconContainer from '../TcIconContainer'; +import { MdCalendarMonth } from 'react-icons/md'; +import TcText from '../../../shared/TcText'; +import TcButton from '../../../shared/TcButton'; +import moment from 'moment'; +import TcTcDateTimePopover from './TcDateTimePopover'; + +function TcScheduleAnnouncement() { + const [anchorEl, setAnchorEl] = useState(null); + const [activeTab, setActiveTab] = useState(0); + const [selectedDate, setSelectedDate] = useState(null); + const [selectedTime, setSelectedTime] = useState(null); + const [dateTimeDisplay, setDateTimeDisplay] = useState( + moment().format('D MMMM YYYY @ h A') + ); + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? 'date-time-popover' : undefined; + + const handleDateChange = (date: Date | null) => { + if (date) { + setSelectedDate(date); + setActiveTab(1); + } + }; + + const handleTimeChange = (time: Date | null) => { + if (time) { + setSelectedTime(time); + handleClose(); + + if (selectedDate) { + const fullDateTime = moment(selectedDate).set({ + hour: time.getHours(), + minute: time.getMinutes(), + }); + setDateTimeDisplay(fullDateTime.format('D MMMM YYYY @ h A')); + } + } + }; + + return ( +
+
+
+ + + + +
+ } + disableElevation={true} + className="border border-black bg-gray-100 shadow-md" + sx={{ color: 'black', height: '2.4rem' }} + aria-describedby={id} + onClick={handleOpen} + /> + +
+
+ ); +} + +export default TcScheduleAnnouncement; diff --git a/src/components/announcements/create/scheduleAnnouncement/index.ts b/src/components/announcements/create/scheduleAnnouncement/index.ts new file mode 100644 index 00000000..143415a6 --- /dev/null +++ b/src/components/announcements/create/scheduleAnnouncement/index.ts @@ -0,0 +1,3 @@ +import { default as TcScheduleAnnouncement } from './TcScheduleAnnouncement'; + +export default TcScheduleAnnouncement; diff --git a/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx b/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx new file mode 100644 index 00000000..56a5e615 --- /dev/null +++ b/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx @@ -0,0 +1,47 @@ +import { FormControl, InputLabel } from '@mui/material'; +import React from 'react'; +import TcSelect from '../../../shared/TcSelect'; +import TcText from '../../../shared/TcText'; +import { BsDiscord, BsTelegram } from 'react-icons/bs'; + +const announcementsPlatforms = [ + { + label: 'Discord', + value: '1', + icon: , + }, + { + label: 'Telegram(TBA)', + value: '2', + disabled: true, + icon: , + }, +]; + +function TcSelectPlatform() { + return ( +
+
+ + +
+ + Select Platform + + +
+ ); +} + +export default TcSelectPlatform; diff --git a/src/components/announcements/create/selectPlatform/index.ts b/src/components/announcements/create/selectPlatform/index.ts new file mode 100644 index 00000000..802608b8 --- /dev/null +++ b/src/components/announcements/create/selectPlatform/index.ts @@ -0,0 +1,3 @@ +import { default as TcSelectPlatform } from './TcSelectPlatform'; + +export default TcSelectPlatform; diff --git a/src/components/shared/TcBreadcrumbs.tsx b/src/components/shared/TcBreadcrumbs.tsx index 465b792b..526d32ab 100644 --- a/src/components/shared/TcBreadcrumbs.tsx +++ b/src/components/shared/TcBreadcrumbs.tsx @@ -1,37 +1,15 @@ -/** - * `TcBreadcrumbs` Component - * - * This component is used for displaying a breadcrumb navigation interface. It is built - * using Material-UI's `Breadcrumbs` and a custom `TcLink` component for navigation. - * - * Props: - * - `items`: An array of `BreadcrumbItem` objects. Each `BreadcrumbItem` should have: - * - `label` (string): The text displayed for the breadcrumb link. - * - `path` (string): The navigation path the breadcrumb link points to. - * - * Usage: - * - * - * This component renders breadcrumbs for the provided `items` array. Each item in the array - * represents a single breadcrumb link. The component uses flexbox for alignment and spacing, - * and includes a hover effect on the links for better user interaction. - */ - import React from 'react'; import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Link from '@mui/material/Link'; import { useRouter } from 'next/router'; import TcLink from './TcLink'; -import { MdOutlineKeyboardArrowLeft } from 'react-icons/md'; +import { ArrowDropDownIcon } from '@mui/x-date-pickers'; +import { MdChevronRight } from 'react-icons/md'; +import TcText from './TcText'; interface BreadcrumbItem { label: string; - path: string; + path?: string; } interface TcBreadcrumbsProps { @@ -50,22 +28,25 @@ function TcBreadcrumbs({ items }: TcBreadcrumbsProps) { }; return ( - - {items.map((item) => ( -
} + > + {items.map((item, index) => ( + handleClick(event, item.path || '')} + underline={'none'} + className={`${ + index === items.length - 1 + ? 'pointer-events-none text-black' + : 'text-gray-500' + }`} + to={item.path || '#'} > - - handleClick(event, item.path)} - > - {item.label} - -
+ + ))}
); diff --git a/src/components/shared/TcLink.tsx b/src/components/shared/TcLink.tsx index 192004b5..c7b2d7a3 100644 --- a/src/components/shared/TcLink.tsx +++ b/src/components/shared/TcLink.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { Link, LinkProps as MuiLinkProps } from '@mui/material'; interface CustomLinkProps extends MuiLinkProps { - to: string; + to?: string; } function TcLink({ children, to, ...props }: CustomLinkProps) { diff --git a/src/components/shared/TcTabs/TcTab/TcTab.tsx b/src/components/shared/TcTabs/TcTab/TcTab.tsx new file mode 100644 index 00000000..e05b7b2e --- /dev/null +++ b/src/components/shared/TcTabs/TcTab/TcTab.tsx @@ -0,0 +1,10 @@ +import { Tab, TabProps } from '@mui/material'; +import React from 'react'; + +interface ITcTabProps extends TabProps {} + +function TcTab({ ...props }: ITcTabProps) { + return ; +} + +export default TcTab; diff --git a/src/components/shared/TcTabs/TcTab/index.ts b/src/components/shared/TcTabs/TcTab/index.ts new file mode 100644 index 00000000..da866174 --- /dev/null +++ b/src/components/shared/TcTabs/TcTab/index.ts @@ -0,0 +1,3 @@ +import { default as TcTab } from './TcTab'; + +export default TcTab; diff --git a/src/components/shared/TcTabs/TcTabs.tsx b/src/components/shared/TcTabs/TcTabs.tsx new file mode 100644 index 00000000..46b53e53 --- /dev/null +++ b/src/components/shared/TcTabs/TcTabs.tsx @@ -0,0 +1,26 @@ +import { Tabs, TabsProps } from '@mui/material'; +import React from 'react'; + +interface ITcTabsProps extends TabsProps { + children: React.ReactElement | React.ReactElement[]; +} + +/** + * `TcTabs` is a functional React component that renders Material-UI's `Tabs` component + * along with any child components passed to it. This component allows for the standard + * functionality of MUI's `Tabs` while also enabling the insertion of `Tab` components + * or other custom elements as children. + * + * @param {ITcTabsProps} props - Includes standard properties of MUI's `Tabs` component + * and any additional props defined in `ITcTabsProps`. The `children` prop is explicitly + * typed to accept either a single React element or an array of React elements, which are + * typically `Tab` components. + * + * @returns {React.ReactElement} - A `Tabs` component from Material-UI, rendering the passed + * children within. + */ +function TcTabs({ children, ...props }: ITcTabsProps): React.ReactElement { + return {children}; +} + +export default TcTabs; diff --git a/src/components/shared/TcTabs/index.ts b/src/components/shared/TcTabs/index.ts new file mode 100644 index 00000000..6dd42fa9 --- /dev/null +++ b/src/components/shared/TcTabs/index.ts @@ -0,0 +1,3 @@ +import { default as TcTabs } from './TcTabs'; + +export default TcTabs; diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index f42b9be9..c5e70818 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -2,64 +2,52 @@ import React from 'react'; import { defaultLayout } from '../../layouts/defaultLayout'; import SEO from '../../components/global/SEO'; import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; -import TcText from '../../components/shared/TcText'; -import TcSelect from '../../components/shared/TcSelect'; -import { FormControl, InputLabel } from '@mui/material'; -import { BsDiscord, BsTelegram } from 'react-icons/bs'; import TcPublicMessaageContainer from '../../components/announcements/create/publicMessageContainer'; -import TcPrivateMessaageContainer from '../../components/announcements/create/TcPrivateMessaageContainer'; - -const announcementsPlatforms = [ - { - label: 'Discord', - value: '1', - icon: , - }, - { - label: 'Telegram(TBA)', - value: '2', - disabled: true, - icon: , - }, -]; +import TcPrivateMessaageContainer from '../../components/announcements/create/privateMessaageContainer'; +import TcButton from '../../components/shared/TcButton'; +import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement/'; +import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; +import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; function CreateNewAnnouncements() { return ( <>
+ -
-
- +
+ + + + +
+
+ +
+ -
- - - Select Platform - - -
- -
} /> diff --git a/src/pages/community-settings/platform/index.tsx b/src/pages/community-settings/platform/index.tsx index 716703ef..6c341877 100644 --- a/src/pages/community-settings/platform/index.tsx +++ b/src/pages/community-settings/platform/index.tsx @@ -14,6 +14,7 @@ function Index() { diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 38e0d8b1..9e96cf3d 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -110,29 +110,29 @@ export const theme = createTheme({ }, MuiTab: { styleOverrides: { - root: { - textTransform: 'none', - borderRadius: '10px 10px 0 0', - padding: '8px 24px', - width: '214px', - height: '40px', - gap: '10px', - borderBottom: 'none', - '&.Mui-selected': { - background: '#804EE1', - color: 'white', - border: 0, - borderBottom: 'none', - }, - '&$selected': { - borderBottom: 'none', - }, - '&:not(.Mui-selected)': { - backgroundColor: '#EDEDED', - color: '#222222', - }, - selected: {}, - }, + // root: { + // textTransform: 'none', + // borderRadius: '10px 10px 0 0', + // padding: '8px 24px', + // width: '214px', + // height: '40px', + // gap: '10px', + // borderBottom: 'none', + // '&.Mui-selected': { + // background: '#804EE1', + // color: 'white', + // border: 0, + // borderBottom: 'none', + // }, + // '&$selected': { + // borderBottom: 'none', + // }, + // '&:not(.Mui-selected)': { + // backgroundColor: '#EDEDED', + // color: '#222222', + // }, + // selected: {}, + // }, }, }, }, From fd9db254f632e8aae5d5d45dd157b10453dfab7d Mon Sep 17 00:00:00 2001 From: zuies Date: Thu, 4 Jan 2024 16:05:52 +0300 Subject: [PATCH 18/58] update announcements-list page --- .../announcements/TcTimeZone.spec.tsx | 17 ++ src/components/announcements/TcTimeZone.tsx | 122 ++++++++++++++ .../TcScheduleAnnouncement.tsx | 4 +- .../shared/TcPagination/TcPagination.spec.tsx | 42 +++++ .../shared/TcPagination/TcPagination.tsx | 60 +++++++ src/components/shared/TcPagination/index.ts | 3 + .../shared/TcTableContainer/TcTableHead.tsx | 5 +- .../shared/TcTableContainer/TcTableRow.tsx | 15 +- src/pages/announcements/index.tsx | 158 ++++++++++++++++-- 9 files changed, 404 insertions(+), 22 deletions(-) create mode 100644 src/components/announcements/TcTimeZone.spec.tsx create mode 100644 src/components/announcements/TcTimeZone.tsx create mode 100644 src/components/shared/TcPagination/TcPagination.spec.tsx create mode 100644 src/components/shared/TcPagination/TcPagination.tsx create mode 100644 src/components/shared/TcPagination/index.ts diff --git a/src/components/announcements/TcTimeZone.spec.tsx b/src/components/announcements/TcTimeZone.spec.tsx new file mode 100644 index 00000000..b30eaca2 --- /dev/null +++ b/src/components/announcements/TcTimeZone.spec.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import TcTimeZone from './TcTimeZone'; + +describe('TcTimeZone', () => { + test('renders TcTimeZone component', () => { + render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + test('opens popover on button click', () => { + render(); + const button = screen.getByRole('button'); + fireEvent.click(button); + expect(screen.getByText('Search timezone')).toBeInTheDocument(); + }); +}); diff --git a/src/components/announcements/TcTimeZone.tsx b/src/components/announcements/TcTimeZone.tsx new file mode 100644 index 00000000..517826e1 --- /dev/null +++ b/src/components/announcements/TcTimeZone.tsx @@ -0,0 +1,122 @@ +import React, { useState } from 'react'; +import TcButton from '../shared/TcButton'; +import { FaGlobeAmericas } from 'react-icons/fa'; +import TcPopover from '../shared/TcPopover'; + +import momentTZ from 'moment-timezone'; +import moment from 'moment'; +import 'moment-timezone'; +import TcInput from '../shared/TcInput'; +import { InputAdornment } from '@mui/material'; +import { MdSearch } from 'react-icons/md'; + +const timeZonesList = momentTZ.tz.names(); + +function TcTimeZone() { + const [activeZone, setActiveZone] = useState(moment.tz.guess()); + + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + setZones(timeZonesList); + }; + + const open = Boolean(anchorEl); + const id = open ? 'simple-popover' : undefined; + + const [zones, setZones] = useState(timeZonesList); + + const searchZones = (e: { target: { value: string } }) => { + const results = timeZonesList.filter((zone) => { + if (e.target.value === '') { + return timeZonesList; + } + return zone.toLowerCase().includes(e.target.value.toLowerCase()); + }); + setZones(results); + }; + + const handleTimeZoneSelect = (timeZone: string) => { + setActiveZone(timeZone); + setAnchorEl(null); + setZones(timeZonesList); + }; + + return ( +
+ } + aria-describedby={id} + onClick={handleClick} + /> + +
+ + + + ), + }} + onChange={searchZones} + /> +
+
    + {zones.length > 0 ? ( + zones.map((el) => ( +
  • handleTimeZoneSelect(el)} + > +
    {el}
    +
  • + )) + ) : ( +
    + Not founded +
    + )} +
+
+ } + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + /> +
+ ); +} + +export default TcTimeZone; diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index 3eed512e..bc3594fd 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -4,7 +4,7 @@ import { MdCalendarMonth } from 'react-icons/md'; import TcText from '../../../shared/TcText'; import TcButton from '../../../shared/TcButton'; import moment from 'moment'; -import TcTcDateTimePopover from './TcDateTimePopover'; +import TcDateTimePopover from './TcDateTimePopover'; function TcScheduleAnnouncement() { const [anchorEl, setAnchorEl] = useState(null); @@ -71,7 +71,7 @@ function TcScheduleAnnouncement() { aria-describedby={id} onClick={handleOpen} /> - { + const totalItems = 100; + const itemsPerPage = 10; + const currentPage = 1; + const onChangePage = jest.fn(); + + it('renders the pagination component correctly', () => { + const { getByText } = render( + + ); + + // Ensure the pagination component renders with the correct total pages and current page. + expect(getByText('1')).toBeInTheDocument(); + expect(getByText('10')).toBeInTheDocument(); + }); + + it('calls onChangePage when a page is clicked', () => { + const { getByText } = render( + + ); + + // Click on page 2 + fireEvent.click(getByText('2')); + + // Ensure onChangePage is called with the correct page number (2) + expect(onChangePage).toHaveBeenCalledWith(2); + }); +}); diff --git a/src/components/shared/TcPagination/TcPagination.tsx b/src/components/shared/TcPagination/TcPagination.tsx new file mode 100644 index 00000000..8a95a1d2 --- /dev/null +++ b/src/components/shared/TcPagination/TcPagination.tsx @@ -0,0 +1,60 @@ +import { Pagination, PaginationItem, PaginationProps } from '@mui/material'; + +interface ITcPaginationProps extends PaginationProps { + totalItems: number; + itemsPerPage: number; + currentPage: number; + onChangePage: (page: number) => void; +} + +/** + * TcPagination Component + * + * A pagination component using Material-UI's `Pagination` to handle page navigation. + * + * @component + * @param {ITcPaginationProps} props - The props for configuring the pagination. + * @param {number} props.totalItems - The total number of items to paginate. + * @param {number} props.itemsPerPage - The number of items per page. + * @param {number} props.currentPage - The current active page. + * @param {(page: number) => void} props.onChangePage - A callback function to handle page changes. + * @returns {JSX.Element} - The rendered pagination component. + * + * @example + * // Usage: + * handlePageChange(page)} + * /> + */ + +function TcPagination({ + onChangePage, + currentPage, + itemsPerPage, + totalItems, + ...props +}: ITcPaginationProps): JSX.Element { + const totalPages = Math.ceil(totalItems / itemsPerPage); + + const handleChangePage = (page: number) => { + if (page !== currentPage) { + onChangePage(page); + } + }; + + return ( + handleChangePage(page)} + {...props} + renderItem={(item) => } + /> + ); +} + +export default TcPagination; diff --git a/src/components/shared/TcPagination/index.ts b/src/components/shared/TcPagination/index.ts new file mode 100644 index 00000000..8995d5b0 --- /dev/null +++ b/src/components/shared/TcPagination/index.ts @@ -0,0 +1,3 @@ +import { default as TcPagination } from './TcPagination'; + +export default TcPagination; diff --git a/src/components/shared/TcTableContainer/TcTableHead.tsx b/src/components/shared/TcTableContainer/TcTableHead.tsx index ef6cc01b..f301c81e 100644 --- a/src/components/shared/TcTableContainer/TcTableHead.tsx +++ b/src/components/shared/TcTableContainer/TcTableHead.tsx @@ -15,7 +15,10 @@ interface ITcTableHeadProps extends TableHeadProps { function TcTableHead({ headers, ...props }: ITcTableHeadProps) { return ( - + ); } diff --git a/src/components/shared/TcTableContainer/TcTableRow.tsx b/src/components/shared/TcTableContainer/TcTableRow.tsx index ebe64525..bcadf15e 100644 --- a/src/components/shared/TcTableContainer/TcTableRow.tsx +++ b/src/components/shared/TcTableContainer/TcTableRow.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { TableRow, TableRowProps } from '@mui/material'; import TcTableCell from './TcTableCell'; +import clsx from 'clsx'; interface ITcTableRowProps extends TableRowProps { rowItem: { [key: string]: any }; + customTableCellClasses?: string; customRenderers?: { [key: string]: (value: any) => React.ReactNode }; } @@ -13,7 +15,12 @@ interface ITcTableRowProps extends TableRowProps { * @param {ITcTableRowProps} props - The component props. */ -function TcTableRow({ rowItem, customRenderers, ...props }: ITcTableRowProps) { +function TcTableRow({ + rowItem, + customRenderers, + customTableCellClasses, + ...props +}: ITcTableRowProps) { return ( {rowItem && @@ -22,7 +29,11 @@ function TcTableRow({ rowItem, customRenderers, ...props }: ITcTableRowProps) { return ( {CustomRenderer ? CustomRenderer(value) : value} diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index cb74d1ea..4467bd5d 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { defaultLayout } from '../../layouts/defaultLayout'; import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; import SEO from '../../components/global/SEO'; @@ -7,33 +7,157 @@ import TcButton from '../../components/shared/TcButton'; import { BsPlus } from 'react-icons/bs'; import TcTableContainer from '../../components/shared/TcTableContainer'; import router from 'next/router'; +import TcPagination from '../../components/shared/TcPagination'; +import TcTimeZone from '../../components/announcements/TcTimeZone'; +import TcDateTimePopover from '../../components/announcements/create/scheduleAnnouncement/TcDateTimePopover'; +import moment from 'moment'; +import { MdCalendarMonth } from 'react-icons/md'; -const bodyRowItems = [ - { Name: 'Alice', Age: 28, Location: 'New York' }, - { Name: 'Bob', Age: 34, Location: 'San Francisco' }, - { Name: 'Carol', Age: 23, Location: 'Miami' }, +const headers = ['Announcement', 'Channel', 'Handle', 'Role', 'Date']; +const bodyRowItems: any[] = [ + // { + // Announcement: 'Lorem Ipsum Announcement', + // Channel: 'General', + // Handle: 'JohnDoe', + // Role: 'Admin', + // Date: '2023-03-15', + // }, + // { + // Announcement: 'New Feature Release', + // Channel: 'Product Updates', + // Handle: 'JaneSmith', + // Role: 'User', + // Date: '2023-03-20', + // }, + // { + // Announcement: 'Upcoming Event', + // Channel: 'Events', + // Handle: 'EventHost', + // Role: 'Moderator', + // Date: '2023-04-05', + // }, ]; function Index() { + const [anchorEl, setAnchorEl] = useState(null); + const [activeTab, setActiveTab] = useState(0); + const [selectedDate, setSelectedDate] = useState(null); + const [selectedTime, setSelectedTime] = useState(null); + const [dateTimeDisplay, setDateTimeDisplay] = useState( + moment().format('D MMMM YYYY @ h A') + ); + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? 'date-time-popover' : undefined; + + const handleDateChange = (date: Date | null) => { + if (date) { + setSelectedDate(date); + setActiveTab(1); + } + }; + + const handleTimeChange = (time: Date | null) => { + if (time) { + setSelectedTime(time); + handleClose(); + + if (selectedDate) { + const fullDateTime = moment(selectedDate).set({ + hour: time.getHours(), + minute: time.getMinutes(), + }); + setDateTimeDisplay(fullDateTime.format('D MMMM YYYY @ h A')); + } + } + }; + return ( <>
-
- - } - variant="outlined" - onClick={() => - router.push('/announcements/create-new-announcements') - } - /> +
+
+
+ + } + variant="outlined" + onClick={() => + router.push('/announcements/create-new-announcements') + } + /> +
+
+ } + disableElevation={true} + className="border border-black bg-gray-100 shadow-md" + sx={{ color: 'black', height: '2.4rem' }} + aria-describedby={id} + onClick={handleOpen} + /> + + +
+ {bodyRowItems.length > 0 ? ( + + ) : ( +
+ + +
+ )} +
+ +
+ {bodyRowItems.length > 0 ? ( + + ) : ( + '' + )}
-
} /> From bdcf53a2bf58a1f1437042339d69d34a6cfcf81a Mon Sep 17 00:00:00 2001 From: zuies Date: Thu, 4 Jan 2024 16:11:52 +0300 Subject: [PATCH 19/58] make announcements-list page responsive --- src/components/announcements/TcTimeZone.tsx | 2 +- src/pages/announcements/index.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/announcements/TcTimeZone.tsx b/src/components/announcements/TcTimeZone.tsx index 517826e1..d37e357a 100644 --- a/src/components/announcements/TcTimeZone.tsx +++ b/src/components/announcements/TcTimeZone.tsx @@ -48,7 +48,7 @@ function TcTimeZone() { }; return ( -
+
-
+
-
+
) : ( -
+
Date: Thu, 4 Jan 2024 16:53:56 +0300 Subject: [PATCH 20/58] make create announcements page responsive --- .../TcPrivateMessaageContainer.tsx | 30 +++++++++++++------ .../TcPublicMessaageContainer.tsx | 12 ++++++-- .../TcScheduleAnnouncement.tsx | 2 +- .../selectPlatform/TcSelectPlatform.tsx | 3 +- .../create-new-announcements.tsx | 25 ++++++++++++---- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx index a272e31c..53141205 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -57,15 +57,14 @@ function TcPrivateMessageContainer() { return (
-
-
+
+
- } + className="mx-auto md:mx-0" + control={} label={
@@ -78,8 +77,12 @@ function TcPrivateMessageContainer() { } />
-
- +
+ {messageTypesArray.map((el) => ( setMessageType(el)} /> @@ -101,7 +107,13 @@ function TcPrivateMessageContainer() {
diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx index 5c851641..bfe725b5 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx @@ -37,8 +37,8 @@ function TcPublicMessaageContainer() { return (
-
-
+
+
@@ -47,7 +47,13 @@ function TcPublicMessaageContainer() {
diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index bc3594fd..ab71c975 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -50,7 +50,7 @@ function TcScheduleAnnouncement() { return (
-
+
diff --git a/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx b/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx index 56a5e615..35608a87 100644 --- a/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx +++ b/src/components/announcements/create/selectPlatform/TcSelectPlatform.tsx @@ -20,12 +20,13 @@ const announcementsPlatforms = [ function TcSelectPlatform() { return ( -
+
diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index c5e70818..2820464e 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -29,22 +29,37 @@ function CreateNewAnnouncements() {
-
+
-
+
From 3ab8322c63fb225e67d29cdb14d577de07c3e6b1 Mon Sep 17 00:00:00 2001 From: zuies Date: Thu, 4 Jan 2024 17:13:28 +0300 Subject: [PATCH 21/58] add slice for announcements --- .../create-new-announcements.tsx | 2 + src/store/slices/announcementsSlice.ts | 48 +++++++++++++++++++ src/store/types/IAnnouncements.ts | 1 + src/store/useStore.ts | 2 + 4 files changed, 53 insertions(+) create mode 100644 src/store/slices/announcementsSlice.ts create mode 100644 src/store/types/IAnnouncements.ts diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 2820464e..992dd003 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -8,6 +8,7 @@ import TcButton from '../../components/shared/TcButton'; import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement/'; import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; +import router from 'next/router'; function CreateNewAnnouncements() { return ( @@ -32,6 +33,7 @@ function CreateNewAnnouncements() {
router.push('/announcements')} variant="outlined" sx={{ maxWidth: { diff --git a/src/store/slices/announcementsSlice.ts b/src/store/slices/announcementsSlice.ts new file mode 100644 index 00000000..a8adaa5c --- /dev/null +++ b/src/store/slices/announcementsSlice.ts @@ -0,0 +1,48 @@ +import { StateCreator } from 'zustand'; +import { axiosInstance } from '../../axiosInstance'; +import IAnnouncements from '../types/IAnnouncements'; + +const createAnnouncementsSlice: StateCreator = (set, get) => ({ + retrieveAnnouncements: async () => { + try { + const { data } = await axiosInstance.get(`/announcements/`); + return data; + } catch (error) { + console.error('Failed to retrieve announcements:', error); + } + }, + retrieveAnnouncementById: async (id: string) => { + try { + const { data } = await axiosInstance.get(`/announcements/${id}`); + return data; + } catch (error) { + console.error('Failed to retrieve announcement:', error); + } + }, + createNewAnnouncements: async () => { + try { + const { data } = await axiosInstance.post(`/announcements/`); + return data; + } catch (error) { + console.error('Failed to create announcements:', error); + } + }, + patchExistingAnnouncement: async (id: string) => { + try { + const { data } = await axiosInstance.post(`/announcements/${id}`); + return data; + } catch (error) { + console.error('Failed to patch announcements:', error); + } + }, + deleteAnnouncements: async (id: string) => { + try { + const { data } = await axiosInstance.delete(`/platforms/${id}`); + return data; + } catch (error) { + console.error('Failed to delete announcements:', error); + } + }, +}); + +export default createAnnouncementsSlice; diff --git a/src/store/types/IAnnouncements.ts b/src/store/types/IAnnouncements.ts new file mode 100644 index 00000000..7691c89c --- /dev/null +++ b/src/store/types/IAnnouncements.ts @@ -0,0 +1 @@ +export default interface IAnnouncements {} diff --git a/src/store/useStore.ts b/src/store/useStore.ts index 16a674c6..4a6fd939 100644 --- a/src/store/useStore.ts +++ b/src/store/useStore.ts @@ -7,6 +7,7 @@ import twitterSlice from './slices/twitterSlice'; import centricSlice from './slices/centricSlice'; import platformSlice from './slices/platformSlice'; import userSlice from './slices/userSlice'; +import announcementsSlice from './slices/announcementsSlice'; const useAppStore = create()((...a) => ({ ...createChartSlice(...a), @@ -17,6 +18,7 @@ const useAppStore = create()((...a) => ({ ...centricSlice(...a), ...platformSlice(...a), ...userSlice(...a), + ...announcementsSlice(...a), })); export default useAppStore; From fbdb69926690d8f383d7c77e7160c7f4adee28c7 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 8 Jan 2024 14:12:09 +0300 Subject: [PATCH 22/58] add dialog for public message --- .../TcPublicMessaageContainer.tsx | 17 ++-- .../TcPublicMessagePreviewDialog.spec.tsx | 83 ++++++++++++++++ .../TcPublicMessagePreviewDialog.tsx | 95 +++++++++++++++++++ .../TcTableContainer/TcTableRow.spec.tsx | 34 ++++++- .../create-new-announcements.tsx | 2 +- 5 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.spec.tsx create mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx index bfe725b5..8c3146be 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx @@ -11,6 +11,7 @@ import { SelectChangeEvent, } from '@mui/material'; import TcInput from '../../../shared/TcInput'; +import TcPublicMessagePreviewDialog from './TcPublicMessagePreviewDialog'; const mockPublicChannels = [ { @@ -35,6 +36,9 @@ function TcPublicMessaageContainer() { setMessage(event.target.value); }; + const isPreviewDialogEnabled = + selectedChannels.length > 0 && message.length > 0; + return (
@@ -44,16 +48,9 @@ function TcPublicMessaageContainer() {
-
diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.spec.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.spec.tsx new file mode 100644 index 00000000..1a1e2f98 --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.spec.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import TcPublicMessagePreviewDialog from './TcPublicMessagePreviewDialog'; + +describe('TcPublicMessagePreviewDialog', () => { + const textMessage = 'This is a test message'; + + it('renders without crashing', () => { + render( + + ); + expect(screen.getByText('Preview')).toBeInTheDocument(); + }); + + it('opens dialog on preview button click', () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + expect(screen.getByText('Preview Public Message')).toBeInTheDocument(); + }); + + it('closes dialog on close icon click', async () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + fireEvent.click(screen.getByTestId('close-icon')); + + await waitFor(() => { + expect( + screen.queryByText('Preview Public Message') + ).not.toBeInTheDocument(); + }); + }); + + it('closes dialog on confirm button click', async () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + fireEvent.click(screen.getByText('Confirm')); + + await waitFor(() => { + expect( + screen.queryByText('Preview Public Message') + ).not.toBeInTheDocument(); + }); + }); + it('displays the correct text message', () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + expect(screen.getByText(textMessage)).toBeInTheDocument(); + }); + + it('preview button is disabled when isPreviewDialogEnabled is false', () => { + render( + + ); + const previewButton = screen.getByRole('button', { name: 'Preview' }); + expect(previewButton).toBeDisabled(); + }); +}); diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx new file mode 100644 index 00000000..a1407175 --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx @@ -0,0 +1,95 @@ +import React, { useState } from 'react'; +import TcDialog from '../../../shared/TcDialog'; +import TcButton from '../../../shared/TcButton'; +import { AiOutlineClose } from 'react-icons/ai'; +import TcText from '../../../shared/TcText'; + +interface ITcPublicMessagePreviewDialogProps { + textMessage: string; + isPreviewDialogEnabled: boolean; +} + +function TcPublicMessagePreviewDialog({ + textMessage, + isPreviewDialogEnabled, +}: ITcPublicMessagePreviewDialogProps) { + const [isPreviewDialogOpen, setPreviewDialogOpen] = useState(false); + return ( + <> + setPreviewDialogOpen(true)} + /> + +
+ setPreviewDialogOpen(false)} + /> +
+
+ +
+ + {['channel1', 'channel2'].map((channel, index, array) => ( + + {'#'} + + {index < array.length - 1 && ', '} + + ))} +
+ +
+ setPreviewDialogOpen(false)} + sx={{ width: '100%' }} + /> +
+
+ + } + open={isPreviewDialogOpen} + /> + + ); +} + +export default TcPublicMessagePreviewDialog; diff --git a/src/components/shared/TcTableContainer/TcTableRow.spec.tsx b/src/components/shared/TcTableContainer/TcTableRow.spec.tsx index f9eaa7cb..857840cc 100644 --- a/src/components/shared/TcTableContainer/TcTableRow.spec.tsx +++ b/src/components/shared/TcTableContainer/TcTableRow.spec.tsx @@ -1,14 +1,42 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import TcTableRow from './TcTableRow'; import TcTableCell from './TcTableCell'; describe('TcTableCell', () => { it('renders correctly with children', () => { - // Render the TcTableCell component with some children render(Sample Content); - - // Check if the rendered content is present const cellContent = screen.getByText('Sample Content'); expect(cellContent).toBeInTheDocument(); }); }); + +describe('TcTableRow', () => { + it('renders correctly with row data', () => { + const rowData = { column1: 'Data1', column2: 'Data2' }; + render(); + expect(screen.getByText('Data1')).toBeInTheDocument(); + expect(screen.getByText('Data2')).toBeInTheDocument(); + }); + + it('applies custom renderers', () => { + const rowData = { column1: 'Data1' }; + const customRenderers = { + column1: (value: any) => {value}, + }; + render(); + const renderedData = screen.getByText('Data1'); + expect(renderedData).toBeInTheDocument(); + expect(renderedData).toHaveProperty('nodeName', 'STRONG'); + }); + + it('applies custom table cell classes', () => { + const rowData = { column1: 'Data1' }; + const customClasses = 'test-class'; + render( + + ); + const cell = screen.getByText('Data1').closest('td'); + expect(cell).toHaveClass(customClasses); + }); +}); diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 992dd003..82947d3c 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -14,7 +14,7 @@ function CreateNewAnnouncements() { return ( <> -
+
Date: Mon, 8 Jan 2024 16:01:55 +0300 Subject: [PATCH 23/58] add private preview dialog and edit announcements --- .../TcPrivateMessaageContainer.tsx | 59 +++++--- .../TcPrivateMessagePreviewDialog.spec.tsx | 93 +++++++++++++ .../TcPrivateMessagePreviewDialog.tsx | 126 ++++++++++++++++++ .../TcPublicMessaageContainer.tsx | 12 +- .../TcPublicMessagePreviewDialog.tsx | 21 ++- .../selectPlatform/TcSelectPlatform.tsx | 12 +- .../create-new-announcements.tsx | 2 +- .../announcements/edit-announcements.tsx | 68 ++++++++++ 8 files changed, 364 insertions(+), 29 deletions(-) create mode 100644 src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.spec.tsx create mode 100644 src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx create mode 100644 src/pages/announcements/edit-announcements.tsx diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx index 53141205..96699d4a 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -15,6 +15,7 @@ import TcSwitch from '../../../shared/TcSwitch'; import TcIconWithTooltip from '../../../shared/TcIconWithTooltip'; import TcButtonGroup from '../../../shared/TcButtonGroup'; import clsx from 'clsx'; +import TcPrivateMessagePreviewDialog from './TcPrivateMessagePreviewDialog'; const mockPublicChannels = [ { @@ -36,11 +37,17 @@ export enum MessageType { function TcPrivateMessageContainer() { const [privateMessage, setPrivateMessage] = useState(false); const [messageType, setMessageType] = useState(MessageType.Both); - const [selectedChannels, setSelectedChannels] = useState([]); + const [selectedUsernames, setSelectedUsernames] = useState([]); + const [selectedRoles, setSelectedRoles] = useState([]); + const [message, setMessage] = useState(''); - const handleSelectChange = (event: SelectChangeEvent) => { - setSelectedChannels(event.target.value as number[]); + const handleSelectRolesChange = (event: SelectChangeEvent) => { + setSelectedRoles(event.target.value as number[]); + }; + + const handleSelectUsernamesChange = (event: SelectChangeEvent) => { + setSelectedUsernames(event.target.value as number[]); }; const handleChange = (event: React.ChangeEvent) => { @@ -55,6 +62,27 @@ function TcPrivateMessageContainer() { const messageTypesArray = Object.values(MessageType); + const isPreviewDialogEnabled = message.length > 0 && privateMessage == true; + + const getSelectedRolesLabels = () => { + return selectedRoles.map( + (roleId) => + mockPublicChannels.find((channel) => channel.value === roleId)?.label || + '' + ); + }; + + const getSelectedUsernamesLabels = () => { + return selectedUsernames.map( + (usernameId) => + mockPublicChannels.find((channel) => channel.value === usernameId) + ?.label || '' + ); + }; + + const selectedRolesLables = getSelectedRolesLabels(); + const selectedUsernamesLabels = getSelectedUsernamesLabels(); + return (
@@ -62,6 +90,7 @@ function TcPrivateMessageContainer() { + } @@ -104,17 +133,11 @@ function TcPrivateMessageContainer() { /> ))} -
@@ -137,7 +160,7 @@ function TcPrivateMessageContainer() { id="select-standard-label" label="Platform" options={mockPublicChannels} - value={selectedChannels} + value={selectedRoles} renderValue={(selected) => (selected as number[]) .map( @@ -148,7 +171,7 @@ function TcPrivateMessageContainer() { ) .join(', ') } - onChange={(event) => handleSelectChange(event)} + onChange={(event) => handleSelectRolesChange(event)} /> (selected as number[]) .map( @@ -180,7 +203,7 @@ function TcPrivateMessageContainer() { ) .join(', ') } - onChange={(event) => handleSelectChange(event)} + onChange={(event) => handleSelectUsernamesChange(event)} />
diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.spec.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.spec.tsx new file mode 100644 index 00000000..b8a8dec9 --- /dev/null +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.spec.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import TcPublicMessagePreviewDialog from './TcPrivateMessagePreviewDialog'; + +describe('TcPublicMessagePreviewDialog', () => { + const textMessage = 'This is a test message'; + const roles = ['Admin', 'User']; + const usernames = ['user1', 'user2']; + + it('renders without crashing', () => { + render( + + ); + expect(screen.getByText('Preview')).toBeInTheDocument(); + }); + + it('opens dialog on preview button click', () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + expect(screen.getByText('Preview Private Message')).toBeInTheDocument(); + }); + + it('closes dialog on close icon click', async () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + fireEvent.click(screen.getByTestId('close-icon')); + + await waitFor(() => { + expect( + screen.queryByText('Preview Private Message') + ).not.toBeInTheDocument(); + }); + }); + + it('closes dialog on confirm button click', async () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + fireEvent.click(screen.getByText('Confirm')); + + await waitFor(() => { + expect( + screen.queryByText('Preview Private Message') + ).not.toBeInTheDocument(); + }); + }); + + it('displays the correct text message', () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + expect(screen.getByText(textMessage)).toBeInTheDocument(); + }); + + it('displays roles and usernames when provided', () => { + render( + + ); + fireEvent.click(screen.getByText('Preview')); + roles.forEach((role) => { + expect(screen.getByText(role)).toBeInTheDocument(); + }); + usernames.forEach((username) => { + expect(screen.getByText(username)).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx new file mode 100644 index 00000000..4e7f865d --- /dev/null +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; +import TcDialog from '../../../shared/TcDialog'; +import TcButton from '../../../shared/TcButton'; +import { AiOutlineClose } from 'react-icons/ai'; +import TcText from '../../../shared/TcText'; + +interface ITcPublicMessagePreviewDialogProps { + textMessage: string; + selectedRoles?: string[]; + selectedUsernames?: string[]; + isPreviewDialogEnabled: boolean; +} + +function TcPublicMessagePreviewDialog({ + textMessage, + selectedRoles, + selectedUsernames, + isPreviewDialogEnabled, +}: ITcPublicMessagePreviewDialogProps) { + const [isPreviewDialogOpen, setPreviewDialogOpen] = useState(false); + return ( + <> + setPreviewDialogOpen(true)} + /> + +
+ setPreviewDialogOpen(false)} + /> +
+
+ +
+
+ + {selectedRoles && + selectedRoles.map((role, index, array) => ( + + {'#'} + + {index < array.length - 1 && ', '} + + ))} +
+
+ + {selectedUsernames && + selectedUsernames.map((username, index, array) => ( + + {'#'} + + {index < array.length - 1 && ', '} + + ))} +
+
+ +
+ setPreviewDialogOpen(false)} + sx={{ width: '100%' }} + /> +
+
+ + } + open={isPreviewDialogOpen} + /> + + ); +} + +export default TcPublicMessagePreviewDialog; diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx index 8c3146be..0cb87539 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import TcText from '../../../shared/TcText'; import { MdAnnouncement } from 'react-icons/md'; import TcIconContainer from '../TcIconContainer'; -import TcButton from '../../../shared/TcButton'; import TcSelect from '../../../shared/TcSelect'; import { FormControl, @@ -36,6 +35,16 @@ function TcPublicMessaageContainer() { setMessage(event.target.value); }; + const getSelectedChannelLabels = () => { + return selectedChannels.map( + (channelId) => + mockPublicChannels.find((channel) => channel.value === channelId) + ?.label || '' + ); + }; + + const selectedChannelLabels = getSelectedChannelLabels(); + const isPreviewDialogEnabled = selectedChannels.length > 0 && message.length > 0; @@ -51,6 +60,7 @@ function TcPublicMessaageContainer() {
diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx index a1407175..d4859d9b 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx @@ -6,11 +6,13 @@ import TcText from '../../../shared/TcText'; interface ITcPublicMessagePreviewDialogProps { textMessage: string; + selectedChannels: string[]; isPreviewDialogEnabled: boolean; } function TcPublicMessagePreviewDialog({ textMessage, + selectedChannels, isPreviewDialogEnabled, }: ITcPublicMessagePreviewDialogProps) { const [isPreviewDialogOpen, setPreviewDialogOpen] = useState(false); @@ -62,13 +64,18 @@ function TcPublicMessagePreviewDialog({ fontWeight={700} className="text-gray-500" /> - {['channel1', 'channel2'].map((channel, index, array) => ( - - {'#'} - - {index < array.length - 1 && ', '} - - ))} + {selectedChannels && + selectedChannels.map((channel, index, array) => ( + + {'#'} + + {index < array.length - 1 && ', '} + + ))}
- +
- + diff --git a/src/pages/announcements/edit-announcements.tsx b/src/pages/announcements/edit-announcements.tsx new file mode 100644 index 00000000..ba934626 --- /dev/null +++ b/src/pages/announcements/edit-announcements.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { defaultLayout } from '../../layouts/defaultLayout'; +import SEO from '../../components/global/SEO'; +import router from 'next/router'; +import TcPrivateMessaageContainer from '../../components/announcements/create/privateMessaageContainer'; +import TcPublicMessaageContainer from '../../components/announcements/create/publicMessageContainer'; +import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement'; +import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; +import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; +import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; +import TcButton from '../../components/shared/TcButton'; + +function EditAnnouncements() { + return ( + <> + +
+ + +
+ + + + +
+
+ router.push('/announcements')} + variant="outlined" + sx={{ + maxWidth: { + xs: '100%', + sm: '8rem', + }, + }} + /> +
+ +
+
+
+ } + /> +
+ + ); +} + +EditAnnouncements.pageLayout = defaultLayout; + +export default EditAnnouncements; From 8dca83d2f8edcdd8e489aa000fb1880bfe4a50f6 Mon Sep 17 00:00:00 2001 From: zuies Date: Tue, 9 Jan 2024 12:50:12 +0300 Subject: [PATCH 24/58] add confimDialog for create announcements --- ...nfirmSchaduledAnnouncementsDialog.spec.tsx | 80 +++++++++++++ .../TcConfirmSchaduledAnnouncementsDialog.tsx | 105 ++++++++++++++++++ .../create-new-announcements.tsx | 12 +- .../announcements/edit-announcements.tsx | 12 +- src/utils/theme.ts | 8 +- 5 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.spec.tsx create mode 100644 src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.spec.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.spec.tsx new file mode 100644 index 00000000..f2cafeed --- /dev/null +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.spec.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import TcConfirmSchaduledAnnouncementsDialog from './TcConfirmSchaduledAnnouncementsDialog'; + +describe('TcConfirmSchaduledAnnouncementsDialog', () => { + const defaultProps = { + buttonLabel: 'Test Button', + schaduledDate: 'July 12 at 13pm (CET)', + }; + + it('renders without crashing', () => { + render( + + ); + expect(screen.getByText('Test Button')).toBeInTheDocument(); + }); + + it('toggles dialog visibility on button click', async () => { + render( + + ); + const button = screen.getByText('Test Button'); + fireEvent.click(button); + + await waitFor(() => { + expect(screen.getByText('Confirm Schedule')).toBeInTheDocument(); + }); + + const closeButton = screen.getByTestId('close-icon'); + fireEvent.click(closeButton); + + await waitFor(() => { + expect(screen.queryByText('Confirm Schedule')).not.toBeInTheDocument(); + }); + }); + + it('displays the correct dialog content', () => { + render( + + ); + fireEvent.click(screen.getByText('Test Button')); + expect( + screen.getByText('Discord announcements scheduled for:') + ).toBeInTheDocument(); + expect(screen.getByText('Public Message to:')).toBeInTheDocument(); + expect( + screen.getByText('Private Message to these user(s):') + ).toBeInTheDocument(); + expect( + screen.getByText('Private Message to these role(s):') + ).toBeInTheDocument(); + }); + + it('closes the dialog when the close icon is clicked', async () => { + render( + + ); + + fireEvent.click(screen.getByText('Test Button')); + + fireEvent.click(screen.getByTestId('close-icon')); + + await waitFor(() => { + expect(screen.queryByText('Confirm Schedule')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx new file mode 100644 index 00000000..9410bd30 --- /dev/null +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import TcButton from '../shared/TcButton'; +import { AiOutlineClose } from 'react-icons/ai'; +import TcDialog from '../shared/TcDialog'; +import TcText from '../shared/TcText'; +import { FaDiscord } from 'react-icons/fa6'; + +interface ITcConfirmSchaduledAnnouncementsDialogProps { + buttonLabel: string; + selectedChannels: string[]; + selectedRoles?: string[]; + selectedUsernames?: string[]; + schaduledDate: string; +} + +function TcConfirmSchaduledAnnouncementsDialog({ + buttonLabel, +}: ITcConfirmSchaduledAnnouncementsDialogProps) { + const [confirmSchadulerDialog, setConfirmSchadulerDialog] = + useState(false); + + return ( + <> + setConfirmSchadulerDialog(true)} + /> + +
+ setConfirmSchadulerDialog(false)} + /> +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ setConfirmSchadulerDialog(false)} + sx={{ width: '100%' }} + /> +
+
+ + } + open={confirmSchadulerDialog} + /> + + ); +} + +export default TcConfirmSchaduledAnnouncementsDialog; diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 457b90fc..123da612 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -9,6 +9,7 @@ import TcScheduleAnnouncement from '../../components/announcements/create/schedu import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; import router from 'next/router'; +import TcConfirmSchaduledAnnouncementsDialog from '../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; function CreateNewAnnouncements() { return ( @@ -53,15 +54,8 @@ function CreateNewAnnouncements() { }, }} /> -
diff --git a/src/pages/announcements/edit-announcements.tsx b/src/pages/announcements/edit-announcements.tsx index ba934626..ee711383 100644 --- a/src/pages/announcements/edit-announcements.tsx +++ b/src/pages/announcements/edit-announcements.tsx @@ -9,6 +9,7 @@ import TcSelectPlatform from '../../components/announcements/create/selectPlatfo import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; import TcButton from '../../components/shared/TcButton'; +import TcConfirmSchaduledAnnouncementsDialog from '../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; function EditAnnouncements() { return ( @@ -43,16 +44,7 @@ function EditAnnouncements() { }} />
- +
diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 9e96cf3d..299635df 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -14,10 +14,10 @@ export const theme = createTheme({ components: { MuiButton: { styleOverrides: { - sizeMedium: { - width: '15rem', - padding: '0.5rem', - }, + // sizeMedium: { + // width: '15rem', + // padding: '0.5rem', + // }, root: { textTransform: 'none', borderRadius: '4px', From a280a1b7e7732f8487ab6d94877eaf9a8148effc Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 15 Jan 2024 14:31:11 +0300 Subject: [PATCH 25/58] implement basic create method --- .../TcPrivateMessaageContainer.tsx | 19 +- .../TcPublicMessaageContainer.spec.tsx | 32 --- .../TcPublicMessaageContainer.tsx | 112 ---------- .../TcPublicMessageContainer.spec.tsx | 89 ++++++++ .../TcPublicMessageContainer.tsx | 207 ++++++++++++++++++ .../TcPublicMessagePreviewDialog.tsx | 2 +- .../create/publicMessageContainer/index.ts | 4 +- .../TcScheduleAnnouncement.tsx | 19 +- .../shared/TcButtonGroup/TcButtonGroup.tsx | 6 +- src/components/shared/TcSelect/TcSelect.tsx | 35 +-- src/pages/_app.tsx | 21 +- .../create-new-announcements.tsx | 106 ++++++++- src/pages/announcements/index.tsx | 103 +++++---- src/pages/community-settings/index.tsx | 41 ++-- .../community-settings/platform/index.tsx | 23 +- src/pages/index.tsx | 23 +- src/store/slices/announcementsSlice.ts | 35 ++- src/store/types/IAnnouncements.ts | 18 +- src/utils/types.ts | 2 +- 19 files changed, 623 insertions(+), 274 deletions(-) delete mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.spec.tsx delete mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx create mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx create mode 100644 src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx index 96699d4a..a5d4cb08 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -90,13 +90,14 @@ function TcPrivateMessageContainer() { + } label={
- + {privateMessage && (
+
+ + +
+
+ + +
{ - test('renders the component without crashing', () => { - render(); - expect(screen.getByText('Public Message')).toBeInTheDocument(); - }); - - test('initial state is set correctly', () => { - render(); - expect(screen.getByPlaceholderText('Write your message here')).toHaveValue( - '' - ); - }); - - test('allows the user to enter a message', () => { - render(); - const messageInput = screen.getByPlaceholderText( - 'Write your message here' - ) as HTMLInputElement; - fireEvent.change(messageInput, { target: { value: 'Test Message' } }); - expect(messageInput.value).toBe('Test Message'); - }); - - test('select channels dropdown is rendered', () => { - render(); - expect(screen.getByLabelText('Select Channels')).toBeInTheDocument(); - }); -}); diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx deleted file mode 100644 index 0cb87539..00000000 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessaageContainer.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState } from 'react'; -import TcText from '../../../shared/TcText'; -import { MdAnnouncement } from 'react-icons/md'; -import TcIconContainer from '../TcIconContainer'; -import TcSelect from '../../../shared/TcSelect'; -import { - FormControl, - FormHelperText, - InputLabel, - SelectChangeEvent, -} from '@mui/material'; -import TcInput from '../../../shared/TcInput'; -import TcPublicMessagePreviewDialog from './TcPublicMessagePreviewDialog'; - -const mockPublicChannels = [ - { - label: 'test', - value: 1, - }, - { - label: 'test2', - value: 2, - }, -]; - -function TcPublicMessaageContainer() { - const [selectedChannels, setSelectedChannels] = useState([]); - const [message, setMessage] = useState(''); - - const handleSelectChange = (event: SelectChangeEvent) => { - setSelectedChannels(event.target.value as number[]); - }; - - const handleChange = (event: React.ChangeEvent) => { - setMessage(event.target.value); - }; - - const getSelectedChannelLabels = () => { - return selectedChannels.map( - (channelId) => - mockPublicChannels.find((channel) => channel.value === channelId) - ?.label || '' - ); - }; - - const selectedChannelLabels = getSelectedChannelLabels(); - - const isPreviewDialogEnabled = - selectedChannels.length > 0 && message.length > 0; - - return ( -
-
-
- - - - -
- -
-
- - Select Channels - - (selected as number[]) - .map( - (value) => - mockPublicChannels.find( - (channel) => channel.value === value - )?.label - ) - .join(', ') - } - onChange={(event) => handleSelectChange(event)} - /> - - The announcement will be sent by the a bot which will have access to - send the following message within the selected channels - - - - - -
-
- ); -} - -export default TcPublicMessaageContainer; diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx new file mode 100644 index 00000000..56b1abfb --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import TcPublicMessageContainer from './TcPublicMessageContainer'; +import { ChannelContext } from '../../../../context/ChannelContext'; + +const mockChannels = [ + { channelId: '1131241242', title: 'Channel 1', subChannels: [] }, + { channelId: '1242512553', title: 'Channel 2', subChannels: [] }, +]; + +const mockSelectedSubChannels = { + channel1: { '1131241242': true }, + channel2: { '1242512553': true }, +}; + +const mockChannelContext = { + channels: mockChannels, + selectedSubChannels: mockSelectedSubChannels, + loading: false, + refreshData: jest.fn(), + handleSubChannelChange: jest.fn(), + handleSelectAll: jest.fn(), + updateSelectedSubChannels: jest.fn(), +}; + +describe('TcPublicMessageContainer Tests', () => { + // Helper function to render the component with the necessary context + const renderComponent = (handlePublicAnnouncements = jest.fn()) => + render( + + + + ); + + test('renders the component without crashing', () => { + renderComponent(); + expect(screen.getByText('Public Message')).toBeInTheDocument(); + }); + + test('initial state is set correctly', () => { + renderComponent(); + expect(screen.getByPlaceholderText('Write your message here')).toHaveValue( + '' + ); + }); + + test('allows the user to enter a message', () => { + renderComponent(); + const messageInput = screen.getByPlaceholderText( + 'Write your message here' + ) as HTMLInputElement; + fireEvent.change(messageInput, { target: { value: 'Test Message' } }); + expect(messageInput.value).toBe('Test Message'); + }); + + test('select channels dropdown is rendered', () => { + renderComponent(); + expect(screen.getByLabelText('Select Channels')).toBeInTheDocument(); + }); + + test('handlePublicAnnouncements is called with correct data', () => { + const handlePublicAnnouncementsMock = jest.fn(); + renderComponent(handlePublicAnnouncementsMock); + + // Assuming there is a way to select channels in your UI, simulate that + // For example, if there's a button to confirm channel selection: + // fireEvent.click(screen.getByText('Confirm Channels')); + + // Simulate entering a message + const messageInput = screen.getByPlaceholderText( + 'Write your message here' + ) as HTMLInputElement; + fireEvent.change(messageInput, { target: { value: 'Test Message' } }); + + // Assuming the function is called on some action, like a form submission or button click + // fireEvent.click(screen.getByText('Submit')); + + // Check if handlePublicAnnouncementsMock was called correctly + // Expect the mock to have been called with expected message and channels data + // This will depend on how your component calls the handlePublicAnnouncements function + expect(handlePublicAnnouncementsMock).toHaveBeenCalledWith({ + message: 'Test Message', + selectedChannels: expect.anything(), // Replace with specific expectation + }); + }); +}); diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx new file mode 100644 index 00000000..34116b48 --- /dev/null +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -0,0 +1,207 @@ +import React, { useContext, useEffect, useState } from 'react'; +import TcText from '../../../shared/TcText'; +import { MdAnnouncement, MdExpandMore } from 'react-icons/md'; +import TcIconContainer from '../TcIconContainer'; +import TcSelect from '../../../shared/TcSelect'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + FormControl, + FormHelperText, + InputLabel, +} from '@mui/material'; +import TcInput from '../../../shared/TcInput'; +import TcPublicMessagePreviewDialog from './TcPublicMessagePreviewDialog'; +import { ChannelContext } from '../../../../context/ChannelContext'; +import TcPlatformChannelList from '../../../communitySettings/platform/TcPlatformChannelList'; +import { IGuildChannels } from '../../../../utils/types'; + +export interface FlattenedChannel { + id: string; + label: string; +} + +export interface ITcPublicMessageContainerProps { + handlePublicAnnouncements: ({ + message, + selectedChannels, + }: { + message: string; + selectedChannels: FlattenedChannel[]; + }) => void; +} + +function TcPublicMessageContainer({ + handlePublicAnnouncements, +}: ITcPublicMessageContainerProps) { + const channelContext = useContext(ChannelContext); + + const { channels, selectedSubChannels } = channelContext; + + const flattenChannels = (channels: IGuildChannels[]): FlattenedChannel[] => { + let flattened: FlattenedChannel[] = []; + + channels.forEach((channel) => { + if (channel.subChannels) { + channel.subChannels.forEach((subChannel) => { + if (selectedSubChannels[channel.channelId]?.[subChannel.channelId]) { + flattened.push({ + id: subChannel.channelId, + label: subChannel.name, + }); + } + }); + } + }); + + return flattened; + }; + + const [selectedChannels, setSelectedChannels] = useState( + [] + ); + + useEffect(() => { + setSelectedChannels(flattenChannels(channels)); + }, [channels, selectedSubChannels]); + + const [message, setMessage] = useState(''); + + const handleChange = (event: React.ChangeEvent) => { + setMessage(event.target.value); + }; + + const isPreviewDialogEnabled = + selectedChannels.length > 0 && message.length > 0; + + useEffect(() => { + handlePublicAnnouncements({ message, selectedChannels }); + }, [message, selectedChannels]); + + return ( +
+
+
+ + + + +
+ channel.label)} + /> +
+
+
+ + +
+ + Select Channels + + (selected as FlattenedChannel[]) + .map((channel) => `#${channel.label}`) + .join(', ') + } + > +
+
+ +
+
+ + + } + > + + + +
+ +
    +
  1. + Navigate to the channel you want to import on{' '} + + Discord + +
  2. +
  3. + Go to the settings for that specific channel (select + the wheel on the right of the channel name) +
  4. +
  5. + Select Permissions (left sidebar), and then in + the middle of the screen check{' '} + Advanced permissions +
  6. +
  7. + With the TogetherCrew Bot selected, under + Advanced Permissions, make sure that [View channel] + and [Write Access] are marked as [✓] +
  8. +
  9. + Select the plus sign to the right of Roles/Members and + under members select TogetherCrew bot +
  10. +
  11. + Click on the Refresh List button on this window + and select the new channels +
  12. +
+
+
+
+
+
+
+
+ + + + +
+
+ ); +} + +export default TcPublicMessageContainer; diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx index d4859d9b..15274c87 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessagePreviewDialog.tsx @@ -57,7 +57,7 @@ function TcPublicMessagePreviewDialog({ variant="h5" className="pb-4" /> -
+
void; +} + +function TcScheduleAnnouncement({ + handleSchaduledDate, +}: ITcScheduleAnnouncementProps) { const [anchorEl, setAnchorEl] = useState(null); const [activeTab, setActiveTab] = useState(0); const [selectedDate, setSelectedDate] = useState(null); @@ -48,6 +54,15 @@ function TcScheduleAnnouncement() { } }; + useEffect(() => { + if (!selectedTime) return; + + const formattedTime = selectedTime.toISOString(); + console.log({ formattedTime }); + + handleSchaduledDate({ selectedTime: formattedTime }); + }, [selectedTime]); + return (
diff --git a/src/components/shared/TcButtonGroup/TcButtonGroup.tsx b/src/components/shared/TcButtonGroup/TcButtonGroup.tsx index a6433c31..3ea16c9f 100644 --- a/src/components/shared/TcButtonGroup/TcButtonGroup.tsx +++ b/src/components/shared/TcButtonGroup/TcButtonGroup.tsx @@ -1,11 +1,11 @@ import { ButtonGroup, ButtonGroupProps } from '@mui/material'; -import React, { ReactElement, ReactNode } from 'react'; +import React, { ReactNode } from 'react'; -interface TcButtonGroup extends ButtonGroupProps { +interface ITcButtonGroup extends ButtonGroupProps { children: ReactNode; } -function TcButtonGroup({ children, ...props }: TcButtonGroup) { +function TcButtonGroup({ children, ...props }: ITcButtonGroup) { return {children}; } diff --git a/src/components/shared/TcSelect/TcSelect.tsx b/src/components/shared/TcSelect/TcSelect.tsx index abfa847f..124904ca 100644 --- a/src/components/shared/TcSelect/TcSelect.tsx +++ b/src/components/shared/TcSelect/TcSelect.tsx @@ -14,12 +14,13 @@ interface ITcSelectProps extends SelectProps { * - label (string): The display label for the option * - icon (ReactElement): Optional icon to display alongside the label */ - options: Array<{ + options?: Array<{ value: string | number; label: string; icon?: ReactElement; disabled?: boolean; }>; + children?: React.ReactNode; } /** @@ -30,21 +31,27 @@ interface ITcSelectProps extends SelectProps { * @returns {ReactElement} The TcSelect component */ -function TcSelect({ options, ...props }: ITcSelectProps): ReactElement { +function TcSelect({ + options, + children, + ...props +}: ITcSelectProps): ReactElement { return ( ); } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 39959383..ea9dc52d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -24,6 +24,7 @@ import Script from 'next/script'; import { usePageViewTracking } from '../helpers/amplitudeHelper'; import SafaryClubScript from '../components/global/SafaryClubScript'; import { TokenProvider } from '../context/TokenContext'; +import { ChannelProvider } from '../context/ChannelContext'; export default function App({ Component, pageProps }: ComponentWithPageLayout) { usePageViewTracking(); @@ -58,15 +59,17 @@ export default function App({ Component, pageProps }: ComponentWithPageLayout) { - {Component.pageLayout ? ( - - - - - - ) : ( - - )} + + {Component.pageLayout ? ( + + + + + + ) : ( + + )} + diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 123da612..9298382b 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { defaultLayout } from '../../layouts/defaultLayout'; import SEO from '../../components/global/SEO'; import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; -import TcPublicMessaageContainer from '../../components/announcements/create/publicMessageContainer'; +import TcPublicMessageContainer from '../../components/announcements/create/publicMessageContainer/TcPublicMessageContainer'; import TcPrivateMessaageContainer from '../../components/announcements/create/privateMessaageContainer'; import TcButton from '../../components/shared/TcButton'; import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement/'; @@ -10,8 +10,81 @@ import TcSelectPlatform from '../../components/announcements/create/selectPlatfo import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; import router from 'next/router'; import TcConfirmSchaduledAnnouncementsDialog from '../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; +import useAppStore from '../../store/useStore'; +import { useToken } from '../../context/TokenContext'; +import { ChannelContext } from '../../context/ChannelContext'; + +export type CreateAnnouncementsPayloadDataOptions = + | { channelIds: string[]; userIds?: string[]; roleIds?: string[] } + | { channelIds?: string[]; userIds: string[]; roleIds?: string[] } + | { channelIds?: string[]; userIds?: string[]; roleIds: string[] }; + +export interface CreateAnnouncementsPayloadData { + platformId: string; + template: string; + options: CreateAnnouncementsPayloadDataOptions; +} +export interface CreateAnnouncementsPayload { + title: string; + communityId: string; + scheduledAt: string; + draft: boolean; + data: CreateAnnouncementsPayloadData[]; +} function CreateNewAnnouncements() { + const { createNewAnnouncements, retrievePlatformById } = useAppStore(); + + const { community } = useToken(); + + const channelContext = useContext(ChannelContext); + + const { refreshData } = channelContext; + + const platformId = community?.platforms.find( + (platform) => platform.disconnectedAt === null + )?.id; + + const [publicAnnouncements, setPublicAnnouncements] = + useState(); + const [scheduledAt, setScheduledAt] = useState(); + + const fetchPlatformChannels = async () => { + try { + if (platformId) { + const data = await retrievePlatformById(platformId); + const { metadata } = data; + if (metadata) { + const { selectedChannels } = metadata; + await refreshData(platformId, 'channel', selectedChannels, true); + } else { + await refreshData(platformId); + } + } + } catch (error) { + } finally { + } + }; + + useEffect(() => { + if (!platformId) { + return; + } + + fetchPlatformChannels(); + }, [platformId]); + + const handleCreateAnnouncements = (isDrafted: boolean) => { + if (!community) return; + const announcementsPayload = { + communityId: community.id, + draft: isDrafted, + scheduledAt: scheduledAt, + data: [publicAnnouncements], + }; + + createNewAnnouncements(announcementsPayload); + }; return ( <> @@ -24,12 +97,32 @@ function CreateNewAnnouncements() { /> +
- + { + if (!platformId) return; + setPublicAnnouncements({ + platformId: platformId, + template: message, + options: { + channelIds: selectedChannels.map( + (channel) => channel.id + ), + }, + }); + }} + /> - + { + setScheduledAt(selectedTime); + }} + />
handleCreateAnnouncements(true)} />
diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index 215dd2cb..c56675d7 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { defaultLayout } from '../../layouts/defaultLayout'; import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; import SEO from '../../components/global/SEO'; @@ -12,33 +12,18 @@ import TcTimeZone from '../../components/announcements/TcTimeZone'; import TcDateTimePopover from '../../components/announcements/create/scheduleAnnouncement/TcDateTimePopover'; import moment from 'moment'; import { MdCalendarMonth } from 'react-icons/md'; +import useAppStore from '../../store/useStore'; +import { StorageService } from '../../services/StorageService'; +import { FetchedData, IDiscordModifiedCommunity } from '../../utils/interfaces'; const headers = ['Announcement', 'Channel', 'Handle', 'Role', 'Date']; -const bodyRowItems: any[] = [ - // { - // Announcement: 'Lorem Ipsum Announcement', - // Channel: 'General', - // Handle: 'JohnDoe', - // Role: 'Admin', - // Date: '2023-03-15', - // }, - // { - // Announcement: 'New Feature Release', - // Channel: 'Product Updates', - // Handle: 'JaneSmith', - // Role: 'User', - // Date: '2023-03-20', - // }, - // { - // Announcement: 'Upcoming Event', - // Channel: 'Events', - // Handle: 'EventHost', - // Role: 'Moderator', - // Date: '2023-04-05', - // }, -]; function Index() { + const { retrieveAnnouncements } = useAppStore(); + + const communityId = + StorageService.readLocalStorage('community')?.id; + const [anchorEl, setAnchorEl] = useState(null); const [activeTab, setActiveTab] = useState(0); const [selectedDate, setSelectedDate] = useState(null); @@ -47,6 +32,17 @@ function Index() { moment().format('D MMMM YYYY @ h A') ); + const [loading, setLoading] = useState(false); + const [fetchedAnnouncements, setFetchedAnnouncements] = useState( + { + limit: 10, + page: 1, + results: [], + totalPages: 0, + totalResults: 0, + } + ); + const handleOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -80,6 +76,41 @@ function Index() { } }; + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + + const data = await retrieveAnnouncements({ + page: 1, + limit: 10, + community: communityId, + }); + + setFetchedAnnouncements(data); + setLoading(false); + } catch (error) { + console.error('An error occurred while fetching platforms:', error); + setLoading(false); + } + }; + + fetchData(); + }, []); + + const formatAnnouncementsForTable = () => { + console.log(fetchedAnnouncements.results); + + return fetchedAnnouncements.results.map( + (announcement) => console.log(announcement.data.options) + + // { + // Announcement: announcement.title, + // Date: moment(announcement.scheduledAt).format('YYYY-MM-DD'), + // } + ); + }; + return ( <> @@ -123,10 +154,10 @@ function Index() { />
- {bodyRowItems.length > 0 ? ( + {fetchedAnnouncements.results.length > 0 ? ( ) : (
@@ -145,18 +176,14 @@ function Index() {
- {bodyRowItems.length > 0 ? ( - - ) : ( - '' - )} +
} diff --git a/src/pages/community-settings/index.tsx b/src/pages/community-settings/index.tsx index 4f7cbc10..a7835f67 100644 --- a/src/pages/community-settings/index.tsx +++ b/src/pages/community-settings/index.tsx @@ -8,7 +8,6 @@ import TcIntegrationDialog from '../../components/pages/communitySettings/TcInte import { useRouter } from 'next/router'; import TcSwitchCommunity from '../../components/communitySettings/switchCommunity/TcSwitchCommunity'; import SimpleBackdrop from '../../components/global/LoadingBackdrop'; -import { ChannelProvider } from '../../context/ChannelContext'; function index() { const router = useRouter(); @@ -48,29 +47,27 @@ function index() { return ( <> - - -
- - -
- - -
+ +
+ + +
+ +
- } - /> -
- + } /> - +
+ ); } diff --git a/src/pages/community-settings/platform/index.tsx b/src/pages/community-settings/platform/index.tsx index 6c341877..f1f6e5a3 100644 --- a/src/pages/community-settings/platform/index.tsx +++ b/src/pages/community-settings/platform/index.tsx @@ -2,24 +2,21 @@ import TcPlatform from '../../../components/communitySettings/platform'; import SEO from '../../../components/global/SEO'; import TcBreadcrumbs from '../../../components/shared/TcBreadcrumbs'; -import { ChannelProvider } from '../../../context/ChannelContext'; import { defaultLayout } from '../../../layouts/defaultLayout'; function Index() { return ( <> - - -
- - -
-
+ +
+ + +
); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3aae153d..a5613a93 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,7 +7,6 @@ import React from 'react'; import ActiveMemberComposition from '../components/pages/pageIndex/ActiveMemberComposition'; import HeatmapChart from '../components/pages/pageIndex/HeatmapChart'; import MemberInteractionGraph from '../components/pages/pageIndex/MemberInteractionGraph'; -import { ChannelProvider } from '../context/ChannelContext'; import { useToken } from '../context/TokenContext'; function Dashboard(): JSX.Element { @@ -25,20 +24,18 @@ function Dashboard(): JSX.Element { return ( <> - -
-
-

- Community Insights -

-
- - - -
+
+
+

+ Community Insights +

+
+ + +
- +
); } diff --git a/src/store/slices/announcementsSlice.ts b/src/store/slices/announcementsSlice.ts index a8adaa5c..96595163 100644 --- a/src/store/slices/announcementsSlice.ts +++ b/src/store/slices/announcementsSlice.ts @@ -1,11 +1,31 @@ import { StateCreator } from 'zustand'; import { axiosInstance } from '../../axiosInstance'; -import IAnnouncements from '../types/IAnnouncements'; +import IAnnouncements, { + IRetrieveAnnouncementsProps, +} from '../types/IAnnouncements'; +import { CreateAnnouncementsPayload } from '../../pages/announcements/create-new-announcements'; const createAnnouncementsSlice: StateCreator = (set, get) => ({ - retrieveAnnouncements: async () => { + retrieveAnnouncements: async ({ + page, + limit, + sortBy, + name, + community, + }: IRetrieveAnnouncementsProps) => { try { - const { data } = await axiosInstance.get(`/announcements/`); + const params = { + page, + limit, + sortBy, + ...(name ? { name } : {}), + }; + + const { data } = await axiosInstance.get( + `/announcements/?communityId=${community}`, + { params } + ); + return data; } catch (error) { console.error('Failed to retrieve announcements:', error); @@ -19,9 +39,14 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ console.error('Failed to retrieve announcement:', error); } }, - createNewAnnouncements: async () => { + createNewAnnouncements: async ( + announcementPayload: CreateAnnouncementsPayload + ) => { try { - const { data } = await axiosInstance.post(`/announcements/`); + const { data } = await axiosInstance.post( + `/announcements/`, + announcementPayload + ); return data; } catch (error) { console.error('Failed to create announcements:', error); diff --git a/src/store/types/IAnnouncements.ts b/src/store/types/IAnnouncements.ts index 7691c89c..916030ae 100644 --- a/src/store/types/IAnnouncements.ts +++ b/src/store/types/IAnnouncements.ts @@ -1 +1,17 @@ -export default interface IAnnouncements {} +export interface IRetrieveAnnouncementsProps { + page: number; + limit: number; + sortBy?: string; + name?: string; + community: string; +} + +export default interface IAnnouncements { + retrieveAnnouncements: ({ + page, + limit, + sortBy, + name, + community, + }: IRetrieveAnnouncementsProps) => void; +} diff --git a/src/utils/types.ts b/src/utils/types.ts index c5c59ae7..1b606fea 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -41,7 +41,7 @@ export type ISubChannels = { readonly channelId: string; readonly name: string; readonly canReadMessageHistoryAndViewChannel: boolean; - readonly parent_id: string; + readonly parent_id?: string; }; export type IChannel = { From b6cc0bbdf54e6d3d21ba7e453f68fcc3a72308ff Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 17 Jan 2024 05:12:41 +0300 Subject: [PATCH 26/58] complete create announcements integration --- src/axiosInstance.ts | 16 ++ .../TcConfirmSchaduledAnnouncementsDialog.tsx | 127 +++++++++---- .../TcPrivateMessaageContainer.tsx | 157 +++++++-------- .../TcPrivateMessagePreviewDialog.tsx | 10 +- .../TcRolesAutoComplete.tsx | 179 ++++++++++++++++++ .../TcUsersAutoComplete.tsx | 169 +++++++++++++++++ .../TcPublicMessageContainer.tsx | 1 - .../TcScheduleAnnouncement.tsx | 16 +- src/components/shared/TcAutocomplete.tsx | 35 ++++ src/context/ChannelContext.tsx | 18 +- .../create-new-announcements.tsx | 100 ++++++++-- src/store/types/IPlatform.ts | 2 +- src/utils/interfaces.ts | 9 + 13 files changed, 691 insertions(+), 148 deletions(-) create mode 100644 src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx create mode 100644 src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx create mode 100644 src/components/shared/TcAutocomplete.tsx diff --git a/src/axiosInstance.ts b/src/axiosInstance.ts index 4b118797..d3a0b4cc 100644 --- a/src/axiosInstance.ts +++ b/src/axiosInstance.ts @@ -169,6 +169,22 @@ axiosInstance.interceptors.response.use( }); window.location.href = '/'; + Sentry.captureException( + new Error( + `API responded with status code ${error.response.status}: ${error.response.data.message}` + ) + ); + break; + case 500: + toast.error(`${error.response.data.message}`, { + position: 'bottom-left', + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: 0, + }); Sentry.captureException( new Error( `API responded with status code ${error.response.status}: ${error.response.data.message}` diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx index 9410bd30..b411c4f2 100644 --- a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx @@ -4,17 +4,40 @@ import { AiOutlineClose } from 'react-icons/ai'; import TcDialog from '../shared/TcDialog'; import TcText from '../shared/TcText'; import { FaDiscord } from 'react-icons/fa6'; +import moment from 'moment'; +import { IRoles, IUser } from '../../utils/interfaces'; interface ITcConfirmSchaduledAnnouncementsDialogProps { buttonLabel: string; - selectedChannels: string[]; - selectedRoles?: string[]; - selectedUsernames?: string[]; + selectedChannels: { id: string; label: string }[]; + selectedRoles?: IRoles[]; + selectedUsernames?: IUser[]; schaduledDate: string; + isDisabled: boolean; + handleCreateAnnouncements: (isDrafted: boolean) => void; } +const formatDateToLocalTimezone = (scheduledDate: string) => { + if (!scheduledDate) { + console.error('Scheduled date is undefined or null'); + return 'Invalid Date'; + } + + const formattedDate = moment(scheduledDate).format('MMMM D [at] hA'); + + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + return `${formattedDate} (${timezone})`; +}; + function TcConfirmSchaduledAnnouncementsDialog({ buttonLabel, + schaduledDate, + selectedRoles, + selectedUsernames, + selectedChannels, + isDisabled = true, + handleCreateAnnouncements, }: ITcConfirmSchaduledAnnouncementsDialogProps) { const [confirmSchadulerDialog, setConfirmSchadulerDialog] = useState(false); @@ -24,6 +47,7 @@ function TcConfirmSchaduledAnnouncementsDialog({ setConfirmSchadulerDialog(true)} /> setConfirmSchadulerDialog(false)} />
-
- +
+
@@ -57,39 +85,72 @@ function TcConfirmSchaduledAnnouncementsDialog({ className="text-left" />
-
- - -
-
- - -
-
- - +
+
+ + +
+ {selectedChannels + .map((channel) => `#${channel.label}`) + .join(', ')}
+ {selectedUsernames && selectedUsernames.length > 0 ? ( +
+
+ + {' '} +
+ {selectedChannels + .map((channel) => `#${channel.label}`) + .join(', ')} +
+ ) : ( + '' + )} + {selectedRoles && selectedRoles.length > 0 ? ( +
+
+ + +
+ {selectedRoles.map((role) => `#${role.name}`).join(', ')} +
+ ) : ( + '' + )}
setConfirmSchadulerDialog(false)} + onClick={() => { + setConfirmSchadulerDialog(false); + handleCreateAnnouncements(false); + }} sx={{ width: '100%' }} />
diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx index a5d4cb08..78058436 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -1,32 +1,18 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import TcText from '../../../shared/TcText'; import { MdOutlineAnnouncement } from 'react-icons/md'; import TcIconContainer from '../TcIconContainer'; import TcButton from '../../../shared/TcButton'; -import TcSelect from '../../../shared/TcSelect'; -import { - FormControl, - FormControlLabel, - InputLabel, - SelectChangeEvent, -} from '@mui/material'; +import { FormControl, FormControlLabel } from '@mui/material'; import TcInput from '../../../shared/TcInput'; import TcSwitch from '../../../shared/TcSwitch'; import TcIconWithTooltip from '../../../shared/TcIconWithTooltip'; import TcButtonGroup from '../../../shared/TcButtonGroup'; import clsx from 'clsx'; import TcPrivateMessagePreviewDialog from './TcPrivateMessagePreviewDialog'; - -const mockPublicChannels = [ - { - label: 'test', - value: 1, - }, - { - label: 'test2', - value: 2, - }, -]; +import TcRolesAutoComplete from './TcRolesAutoComplete'; +import TcUsersAutoComplete from './TcUsersAutoComplete'; +import { IRoles, IUser } from '../../../../utils/interfaces'; export enum MessageType { Both = 'Both', @@ -34,22 +20,28 @@ export enum MessageType { UserOnly = 'User Only', } -function TcPrivateMessageContainer() { +export interface ITcPrivateMessageContainerProps { + handlePrivateAnnouncements: ({ + message, + selectedRoles, + selectedUsers, + }: { + message: string; + selectedRoles?: IRoles[]; + selectedUsers?: IUser[]; + }) => void; +} + +function TcPrivateMessageContainer({ + handlePrivateAnnouncements, +}: ITcPrivateMessageContainerProps) { const [privateMessage, setPrivateMessage] = useState(false); const [messageType, setMessageType] = useState(MessageType.Both); - const [selectedUsernames, setSelectedUsernames] = useState([]); - const [selectedRoles, setSelectedRoles] = useState([]); + const [selectedUsers, setSelectedUsers] = useState([]); + const [selectedRoles, setSelectedRoles] = useState([]); const [message, setMessage] = useState(''); - const handleSelectRolesChange = (event: SelectChangeEvent) => { - setSelectedRoles(event.target.value as number[]); - }; - - const handleSelectUsernamesChange = (event: SelectChangeEvent) => { - setSelectedUsernames(event.target.value as number[]); - }; - const handleChange = (event: React.ChangeEvent) => { setMessage(event.target.value); }; @@ -65,23 +57,49 @@ function TcPrivateMessageContainer() { const isPreviewDialogEnabled = message.length > 0 && privateMessage == true; const getSelectedRolesLabels = () => { - return selectedRoles.map( - (roleId) => - mockPublicChannels.find((channel) => channel.value === roleId)?.label || - '' - ); + return selectedRoles.map((role) => role.name || ''); }; - const getSelectedUsernamesLabels = () => { - return selectedUsernames.map( - (usernameId) => - mockPublicChannels.find((channel) => channel.value === usernameId) - ?.label || '' - ); + const selectedRolesLables = getSelectedRolesLabels(); + + const getSelectedUsersLabels = () => { + return selectedUsers.map((user) => user.ngu || ''); }; - const selectedRolesLables = getSelectedRolesLabels(); - const selectedUsernamesLabels = getSelectedUsernamesLabels(); + const selectedUsersLables = getSelectedUsersLabels(); + + useEffect(() => { + const prepareAndSendData = () => { + switch (messageType) { + case MessageType.Both: + handlePrivateAnnouncements({ message, selectedRoles, selectedUsers }); + break; + + case MessageType.RoleOnly: + handlePrivateAnnouncements({ message, selectedRoles }); + break; + + case MessageType.UserOnly: + handlePrivateAnnouncements({ message, selectedUsers }); + break; + + default: + handlePrivateAnnouncements({ message, selectedRoles, selectedUsers }); + break; + } + }; + + if (message && privateMessage) { + prepareAndSendData(); + } + }, [ + message, + selectedRoles, + selectedUsers, + messageType, + privateMessage, + handlePrivateAnnouncements, + ]); return (
@@ -91,7 +109,6 @@ function TcPrivateMessageContainer() { - } @@ -136,8 +153,8 @@ function TcPrivateMessageContainer() {
@@ -162,25 +179,12 @@ function TcPrivateMessageContainer() { messageType !== MessageType.RoleOnly } > - Select Role(s) - - (selected as number[]) - .map( - (value) => - mockPublicChannels.find( - (channel) => channel.value === value - )?.label - ) - .join(', ') + handleSelectRolesChange(event)} + handleSelectedUsers={setSelectedRoles} /> - - Select Username(s) - - - (selected as number[]) - .map( - (value) => - mockPublicChannels.find( - (channel) => channel.value === value - )?.label - ) - .join(', ') + handleSelectUsernamesChange(event)} + handleSelectedUsers={setSelectedUsers} />
diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx index 4e7f865d..3c8683e9 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessagePreviewDialog.tsx @@ -60,7 +60,7 @@ function TcPublicMessagePreviewDialog({ className="pb-4" />
-
+
{selectedRoles && selectedRoles.map((role, index, array) => ( - - {'#'} + + {'@'} ))}
-
+
void; +} + +function TcRolesAutoComplete({ + isDisabled, + handleSelectedUsers, +}: ITcRolesAutoCompleteProps) { + const { community } = useToken(); + + const platformId = community?.platforms.find( + (platform) => platform.disconnectedAt === null + )?.id; + + const { retrievePlatformProperties } = useAppStore(); + const [selectedRoles, setSelectedRoles] = useState([]); + + const [fetchedRoles, setFetchedRoles] = useState({ + limit: 8, + page: 1, + results: [], + totalPages: 0, + totalResults: 0, + }); + const [filteredRolesByName, setFilteredRolesByName] = useState(''); + + const fetchDiscordRoles = async ( + platformId: string, + page?: number, + limit?: number, + name?: string + ) => { + try { + const fetchedRoles = await retrievePlatformProperties({ + platformId, + name: name, + property: 'role', + page: page, + limit: limit, + }); + + if (name) { + setFilteredRolesByName(name); + setFetchedRoles(fetchedRoles); + } else { + setFetchedRoles((prevData: { results: any }) => { + const updatedResults = [ + ...prevData.results, + ...fetchedRoles.results, + ].filter( + (role, index, self) => + index === self.findIndex((r) => r.id === role.id) + ); + + return { + ...prevData, + ...fetchedRoles, + results: updatedResults, + }; + }); + } + } catch (error) {} + }; + + useEffect(() => { + if (!platformId) return; + fetchDiscordRoles(platformId, fetchedRoles.page, fetchedRoles.limit); + }, []); + + const debouncedFetchDiscordRoles = debounce(fetchDiscordRoles, 700); + + const handleSearchChange = (event: React.SyntheticEvent) => { + const target = event.target as HTMLInputElement; + const inputValue = target.value; + + if (!platformId) return; + + if (inputValue === '') { + setFilteredRolesByName(''); + setFetchedRoles({ + limit: 8, + page: 1, + results: [], + totalPages: 0, + totalResults: 0, + }); + + debouncedFetchDiscordRoles(platformId, 1, 8); + } else { + debouncedFetchDiscordRoles(platformId, 1, 8, inputValue); + } + }; + + const handleScroll = (event: React.UIEvent) => { + const listboxNode = event.currentTarget; + if ( + listboxNode.scrollTop + listboxNode.clientHeight === + listboxNode.scrollHeight + ) { + const nextPage = + Math.ceil(fetchedRoles.results.length / fetchedRoles.limit) + 1; + if (fetchedRoles.totalPages >= nextPage) { + if (!platformId) return; + fetchDiscordRoles(platformId, nextPage, fetchedRoles.limit); + } + } + }; + + const handleChange = ( + event: React.SyntheticEvent, + value: any[] + ): void => { + setSelectedRoles(value); + }; + + useEffect(() => { + if (!selectedRoles) return; + handleSelectedUsers(selectedRoles); + }, [selectedRoles]); + + return ( + option.name} + label={'Select Role(s)'} + multiple={true} + disabled={isDisabled} + value={selectedRoles} + onChange={handleChange} + onInputChange={handleSearchChange} + disableCloseOnSelect + renderTags={(value, getTagProps) => + value.map((option, index) => ( + + )) + } + textFieldProps={{ variant: 'filled' }} + ListboxProps={{ + onScroll: handleScroll, + style: { + maxHeight: '280px', + overflow: 'auto', + }, + }} + /> + ); +} + +export default TcRolesAutoComplete; diff --git a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx new file mode 100644 index 00000000..07d422f3 --- /dev/null +++ b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx @@ -0,0 +1,169 @@ +import React, { useEffect, useState } from 'react'; +import { useToken } from '../../../../context/TokenContext'; +import useAppStore from '../../../../store/useStore'; +import { FetchedData, IUser } from '../../../../utils/interfaces'; +import { debounce } from '../../../../helpers/helper'; +import TcAutocomplete from '../../../shared/TcAutocomplete'; +import { Chip } from '@mui/material'; + +interface ITcUsersAutoCompleteProps { + isDisabled: boolean; + handleSelectedUsers: (users: IUser[]) => void; +} + +function TcUsersAutoComplete({ + isDisabled, + handleSelectedUsers, +}: ITcUsersAutoCompleteProps) { + const { community } = useToken(); + + const platformId = community?.platforms.find( + (platform) => platform.disconnectedAt === null + )?.id; + + const { retrievePlatformProperties } = useAppStore(); + const [selectedUsers, setSelectedUsers] = useState([]); + + const [fetchedUsers, setFetchedUsers] = useState({ + limit: 8, + page: 1, + results: [], + totalPages: 0, + totalResults: 0, + }); + const [filteredRolesByName, setFilteredRolesByName] = useState(''); + + const fetchDiscordUsers = async ( + platformId: string, + page?: number, + limit?: number, + name?: string + ) => { + try { + const fetchedUsers = await retrievePlatformProperties({ + platformId, + name: name, + property: 'guildMember', + page: page, + limit: limit, + }); + + if (name) { + setFilteredRolesByName(name); + setFetchedUsers(fetchedUsers); + } else { + setFetchedUsers((prevData: { results: any }) => { + const updatedResults = [ + ...prevData.results, + ...fetchedUsers.results, + ].filter( + (role, index, self) => + index === self.findIndex((r) => r.discordId === role.discordId) + ); + + return { + ...prevData, + ...fetchedUsers, + results: updatedResults, + }; + }); + } + } catch (error) {} + }; + + useEffect(() => { + if (!platformId) return; + fetchDiscordUsers(platformId, fetchedUsers.page, fetchedUsers.limit); + }, []); + + const debouncedFetchDiscordUsers = debounce(fetchDiscordUsers, 700); + + const handleSearchChange = (event: React.SyntheticEvent) => { + const target = event.target as HTMLInputElement; + const inputValue = target.value; + + if (!platformId) return; + + if (inputValue === '') { + setFilteredRolesByName(''); + setFetchedUsers({ + limit: 8, + page: 1, + results: [], + totalPages: 0, + totalResults: 0, + }); + + debouncedFetchDiscordUsers(platformId, 1, 8); + } else { + debouncedFetchDiscordUsers(platformId, 1, 8, inputValue); + } + }; + + const handleScroll = (event: React.UIEvent) => { + const listboxNode = event.currentTarget; + if ( + listboxNode.scrollTop + listboxNode.clientHeight === + listboxNode.scrollHeight + ) { + const nextPage = + Math.ceil(fetchedUsers.results.length / fetchedUsers.limit) + 1; + if (fetchedUsers.totalPages >= nextPage) { + if (!platformId) return; + fetchDiscordUsers(platformId, nextPage, fetchedUsers.limit); + } + } + }; + + const handleChange = ( + event: React.SyntheticEvent, + value: any[] + ): void => { + setSelectedUsers(value); + }; + + useEffect(() => { + if (!selectedUsers) return; + handleSelectedUsers(selectedUsers); + }, [selectedUsers]); + + return ( + option.ngu} + label={'Select Role(s)'} + multiple={true} + disabled={isDisabled} + value={selectedUsers} + onChange={handleChange} + onInputChange={handleSearchChange} + disableCloseOnSelect + renderTags={(value, getTagProps) => + value.map((option, index) => ( + + )) + } + textFieldProps={{ variant: 'filled' }} + ListboxProps={{ + onScroll: handleScroll, + style: { + maxHeight: '280px', + overflow: 'auto', + }, + }} + /> + ); +} + +export default TcUsersAutoComplete; diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx index 34116b48..7c55c44c 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -8,7 +8,6 @@ import { AccordionDetails, AccordionSummary, FormControl, - FormHelperText, InputLabel, } from '@mui/material'; import TcInput from '../../../shared/TcInput'; diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index ba731a62..fcdb58fe 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -55,13 +55,19 @@ function TcScheduleAnnouncement({ }; useEffect(() => { - if (!selectedTime) return; + if (!selectedDate || !selectedTime) return; - const formattedTime = selectedTime.toISOString(); - console.log({ formattedTime }); + const fullDateTime = moment(selectedDate).set({ + hour: selectedTime.getHours(), + minute: selectedTime.getMinutes(), + }); - handleSchaduledDate({ selectedTime: formattedTime }); - }, [selectedTime]); + const fullDateTimeUTC = fullDateTime.utc(); + + const formattedUTCDate = fullDateTimeUTC.format(); + + handleSchaduledDate({ selectedTime: formattedUTCDate }); + }, [selectedDate, selectedTime]); return (
diff --git a/src/components/shared/TcAutocomplete.tsx b/src/components/shared/TcAutocomplete.tsx new file mode 100644 index 00000000..287497ea --- /dev/null +++ b/src/components/shared/TcAutocomplete.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import Autocomplete, { AutocompleteProps } from '@mui/material/Autocomplete'; +import TextField, { TextFieldProps } from '@mui/material/TextField'; + +interface TcAutocompleteProps + extends Omit, 'renderInput'> { + label?: string; + placeholder?: string; + textFieldProps?: TextFieldProps; +} + +function TcAutocomplete({ + options, + label, + placeholder, + textFieldProps, + ...props +}: TcAutocompleteProps) { + return ( + ( + + )} + {...props} + /> + ); +} + +export default TcAutocomplete; diff --git a/src/context/ChannelContext.tsx b/src/context/ChannelContext.tsx index a789f5e8..442188a4 100644 --- a/src/context/ChannelContext.tsx +++ b/src/context/ChannelContext.tsx @@ -30,7 +30,8 @@ interface ChannelContextProps { platformId: string, property?: 'channel', selectedChannels?: string[], - hideDeactiveSubchannels?: boolean + hideDeactiveSubchannels?: boolean, + allDefaultChecked?: boolean ) => Promise; handleSubChannelChange: (channelId: string, subChannelId: string) => void; handleSelectAll: (channelId: string, subChannels: SubChannel[]) => void; @@ -57,7 +58,8 @@ const initialChannelContextData: ChannelContextProps = { platformId: string, property?: 'channel', selectedChannels?: string[], - hideDeactiveSubchannels?: boolean + hideDeactiveSubchannels?: boolean, + allDefaultChecked?: boolean ) => {}, handleSubChannelChange: (channelId: string, subChannelId: string) => {}, handleSelectAll: (channelId: string, subChannels: SubChannel[]) => {}, @@ -84,7 +86,8 @@ export const ChannelProvider = ({ children }: ChannelProviderProps) => { platformId: string, property: 'channel' = 'channel', selectedChannels?: string[], - hideDeactiveSubchannels: boolean = false + hideDeactiveSubchannels: boolean = false, + allDefaultChecked: boolean = true ) => { setLoading(true); try { @@ -101,8 +104,13 @@ export const ChannelProvider = ({ children }: ChannelProviderProps) => { (acc: any, channel: any) => { acc[channel.channelId] = channel.subChannels.reduce( (subAcc: any, subChannel: any) => { - subAcc[subChannel.channelId] = - subChannel.canReadMessageHistoryAndViewChannel; + if (allDefaultChecked) { + subAcc[subChannel.channelId] = + subChannel.canReadMessageHistoryAndViewChannel; + } else { + subAcc[subChannel.channelId] = false; + } + return subAcc; }, {} as { [subChannelId: string]: boolean } diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 9298382b..233dd6ed 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -3,7 +3,7 @@ import { defaultLayout } from '../../layouts/defaultLayout'; import SEO from '../../components/global/SEO'; import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; import TcPublicMessageContainer from '../../components/announcements/create/publicMessageContainer/TcPublicMessageContainer'; -import TcPrivateMessaageContainer from '../../components/announcements/create/privateMessaageContainer'; +import TcPrivateMessageContainer from '../../components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer'; import TcButton from '../../components/shared/TcButton'; import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement/'; import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; @@ -13,6 +13,7 @@ import TcConfirmSchaduledAnnouncementsDialog from '../../components/announcement import useAppStore from '../../store/useStore'; import { useToken } from '../../context/TokenContext'; import { ChannelContext } from '../../context/ChannelContext'; +import { IRoles, IUser } from '../../utils/interfaces'; export type CreateAnnouncementsPayloadDataOptions = | { channelIds: string[]; userIds?: string[]; roleIds?: string[] } @@ -41,25 +42,27 @@ function CreateNewAnnouncements() { const { refreshData } = channelContext; + const [channels, setChannels] = useState([]); + const [roles, setRoles] = useState([]); + const [users, setUsers] = useState([]); + const platformId = community?.platforms.find( (platform) => platform.disconnectedAt === null )?.id; const [publicAnnouncements, setPublicAnnouncements] = useState(); + + const [privateAnnouncements, setPrivateAnnouncements] = + useState(); + const [scheduledAt, setScheduledAt] = useState(); const fetchPlatformChannels = async () => { try { if (platformId) { - const data = await retrievePlatformById(platformId); - const { metadata } = data; - if (metadata) { - const { selectedChannels } = metadata; - await refreshData(platformId, 'channel', selectedChannels, true); - } else { - await refreshData(platformId); - } + await retrievePlatformById(platformId); + await refreshData(platformId, 'channel', undefined, undefined, false); } } catch (error) { } finally { @@ -76,15 +79,24 @@ function CreateNewAnnouncements() { const handleCreateAnnouncements = (isDrafted: boolean) => { if (!community) return; + + const data = [publicAnnouncements]; + + if (privateAnnouncements && privateAnnouncements.length > 0) { + data.push(...privateAnnouncements); + } + const announcementsPayload = { communityId: community.id, draft: isDrafted, scheduledAt: scheduledAt, - data: [publicAnnouncements], + data: data, }; + console.log({ announcementsPayload }); createNewAnnouncements(announcementsPayload); }; + return ( <> @@ -106,6 +118,7 @@ function CreateNewAnnouncements() { selectedChannels, }) => { if (!platformId) return; + setChannels(selectedChannels); setPublicAnnouncements({ platformId: platformId, template: message, @@ -117,14 +130,63 @@ function CreateNewAnnouncements() { }); }} /> - + { + if (!platformId) return; + + let rolePrivateAnnouncements; + let userPrivateAnnouncements; + + const commonData = { + platformId: platformId, + template: message, + }; + + if (selectedRoles && selectedRoles.length > 0) { + setRoles(selectedRoles); + + rolePrivateAnnouncements = { + ...commonData, + options: { + roleIds: selectedRoles.map((role) => + role.id.toString() + ), + }, + }; + } + + if (selectedUsers && selectedUsers.length > 0) { + setUsers(selectedUsers); + + userPrivateAnnouncements = { + ...commonData, + options: { + userIds: selectedUsers.map((user) => user.discordId), + }, + }; + } + + const announcements = []; + if (rolePrivateAnnouncements) + announcements.push(rolePrivateAnnouncements); + if (userPrivateAnnouncements) + announcements.push(userPrivateAnnouncements); + + setPrivateAnnouncements(announcements); + }} + /> + { setScheduledAt(selectedTime); }} />
-
+
router.push('/announcements')} @@ -150,8 +212,18 @@ function CreateNewAnnouncements() { /> + handleCreateAnnouncements(e) + } />
diff --git a/src/store/types/IPlatform.ts b/src/store/types/IPlatform.ts index 6ad1e5f5..c03294de 100644 --- a/src/store/types/IPlatform.ts +++ b/src/store/types/IPlatform.ts @@ -14,7 +14,7 @@ export interface IRetrivePlatformRolesOrChannels { sortBy?: string; name?: string; platformId: string; - property: 'channel' | 'role'; + property: 'channel' | 'role' | 'guildMember'; } export interface IDeletePlatformProps { diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index d8218423..543828b3 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -171,3 +171,12 @@ export interface IDiscordModifiedCommunity extends Omit { platforms: ICommunityDiscordPlatfromProps[]; } + +export interface IUser { + discordId: string; + discriminator: string; + globalName: string | null; + ngu: string; + nickname: string | null; + username: string; +} From ed036731a623594809d664a865e9b84b9420cca3 Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 17 Jan 2024 17:39:41 +0300 Subject: [PATCH 27/58] add list integration --- .../announcements/TcAnnouncementsTable.tsx | 256 ++++++++++++++++++ src/components/announcements/TcTimeZone.tsx | 11 +- .../TcUsersAutoComplete.tsx | 2 +- .../TcScheduleAnnouncement.tsx | 2 +- src/components/shared/TcDatePickerPopover.tsx | 44 +++ .../create-new-announcements.tsx | 26 +- .../announcements/edit-announcements.tsx | 60 ---- .../edit-announcements/index.tsx | 102 +++++++ src/pages/announcements/index.tsx | 158 ++++++----- src/store/slices/announcementsSlice.ts | 8 +- src/store/types/IAnnouncements.ts | 3 + 11 files changed, 517 insertions(+), 155 deletions(-) create mode 100644 src/components/announcements/TcAnnouncementsTable.tsx create mode 100644 src/components/shared/TcDatePickerPopover.tsx delete mode 100644 src/pages/announcements/edit-announcements.tsx create mode 100644 src/pages/announcements/edit-announcements/index.tsx diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx new file mode 100644 index 00000000..ff9a8118 --- /dev/null +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -0,0 +1,256 @@ +import React, { useState } from 'react'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, + IconButton, + MenuItem, + Menu, +} from '@mui/material'; +import { BsThreeDotsVertical } from 'react-icons/bs'; +import Router from 'next/router'; +import TcDialog from '../shared/TcDialog'; +import TcButton from '../shared/TcButton'; +import { AiOutlineClose } from 'react-icons/ai'; +import TcText from '../shared/TcText'; +import useAppStore from '../../store/useStore'; +import { useSnackbar } from '../../context/SnackbarContext'; +import { MdModeEdit, MdDelete } from 'react-icons/md'; + +interface Channel { + channelId: string; + name: string; +} + +interface User { + discordId: string; + ngu: string; +} + +interface Role { + roleId: string; + color: string; + name: string; +} + +interface AnnouncementData { + platform: string; + template: string; + options: { + channels: Channel[]; + users?: User[]; + roles?: Role[]; + }; +} + +interface Announcement { + id: string; + title: string; + scheduledAt: string; + draft: boolean; + data: AnnouncementData[]; + community: string; +} + +interface AnnouncementsTableProps { + announcements: Announcement[]; + handleRefreshList: () => void; +} + +function TcAnnouncementsTable({ + announcements, + handleRefreshList, +}: AnnouncementsTableProps) { + const { deleteAnnouncements } = useAppStore(); + const { showMessage } = useSnackbar(); + const [anchorEl, setAnchorEl] = useState(null); + const [deleteConfirmDialogOpen, setDeleteConfirmDialogOpen] = + useState(false); + const [selectedAnnouncementId, setSelectedAnnouncementId] = useState< + string | null + >(null); + + const handleClick = ( + event: React.MouseEvent, + id: string + ) => { + setAnchorEl(event.currentTarget); + setSelectedAnnouncementId(id); + }; + + const handleClose = () => { + setAnchorEl(null); + setSelectedAnnouncementId(null); + }; + + const handleEdit = (id: string) => { + Router.push(`/announcements/edit-announcements/?announcementsId=${id}`); + handleClose(); + }; + + const handleDelete = () => { + setDeleteConfirmDialogOpen(true); + }; + + const handleDeleteAnnouncements = async (id: string) => { + try { + const data = await deleteAnnouncements(id); + + handleRefreshList(); + showMessage('Scheduled announcement removed successfully.', 'success'); + } catch (error) { + console.error('Error deleting announcement:', error); + showMessage('Error occurred while deleting the announcement.', 'error'); + } finally { + setDeleteConfirmDialogOpen(false); + setSelectedAnnouncementId(null); + } + }; + + return ( + <> + + + + Announcements + Channels + Handle + Role + Date + + + + + {announcements.map((announcement, index) => ( + + {announcement.data[0].template} + + {announcement.data.map( + (item) => + item.options.channels && + item.options.channels + .map((channel) => `#${channel.name}`) + .join(', ') + )} + + + {announcement.data.map((item) => + item.options.users + ? item.options.users + .map((user) => `@${user.ngu}`) + .join(', ') + : '' + )} + + + {announcement.data.map((item) => + item.options.roles + ? item.options.roles.map((role) => role.name).join(', ') + : '' + )} + + + {new Date(announcement.scheduledAt).toLocaleString()} + {' '} + + handleClick(event, announcement.id)} + > + + + + handleEdit(announcement.id)}> + + Edit + + + + Delete + + + + + ))} + +
+ +
+ setDeleteConfirmDialogOpen(false)} + /> +
+
+ +
+ +
+ setDeleteConfirmDialogOpen(false)} + /> + + selectedAnnouncementId && + handleDeleteAnnouncements(selectedAnnouncementId) + } + /> +
+
+
+ + } + open={deleteConfirmDialogOpen} + /> + + ); +} + +export default TcAnnouncementsTable; diff --git a/src/components/announcements/TcTimeZone.tsx b/src/components/announcements/TcTimeZone.tsx index d37e357a..180f7af8 100644 --- a/src/components/announcements/TcTimeZone.tsx +++ b/src/components/announcements/TcTimeZone.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import TcButton from '../shared/TcButton'; import { FaGlobeAmericas } from 'react-icons/fa'; import TcPopover from '../shared/TcPopover'; @@ -12,7 +12,10 @@ import { MdSearch } from 'react-icons/md'; const timeZonesList = momentTZ.tz.names(); -function TcTimeZone() { +interface ITcTimeZoneProps { + handleZone: (zone: string) => void; +} +function TcTimeZone({ handleZone }: ITcTimeZoneProps) { const [activeZone, setActiveZone] = useState(moment.tz.guess()); const [anchorEl, setAnchorEl] = useState(null); @@ -47,6 +50,10 @@ function TcTimeZone() { setZones(timeZonesList); }; + useEffect(() => { + handleZone(activeZone); + }, [activeZone]); + return (
option.ngu} - label={'Select Role(s)'} + label={'Select User(s)'} multiple={true} disabled={isDisabled} value={selectedUsers} diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index fcdb58fe..52c19986 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -18,7 +18,7 @@ function TcScheduleAnnouncement({ const [selectedDate, setSelectedDate] = useState(null); const [selectedTime, setSelectedTime] = useState(null); const [dateTimeDisplay, setDateTimeDisplay] = useState( - moment().format('D MMMM YYYY @ h A') + 'Select Date for Announcement' ); const handleOpen = (event: React.MouseEvent) => { diff --git a/src/components/shared/TcDatePickerPopover.tsx b/src/components/shared/TcDatePickerPopover.tsx new file mode 100644 index 00000000..d323a047 --- /dev/null +++ b/src/components/shared/TcDatePickerPopover.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import Popover from '@mui/material/Popover'; +import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + +interface ITcDatePickerPopoverProps { + open: boolean; + anchorEl: HTMLElement | null; + onClose: () => void; + selectedDate: Date | null; + onDateChange: (date: Date | null) => void; +} + +function TcDatePickerPopover({ + open, + anchorEl, + onClose, + selectedDate, + onDateChange, +}: ITcDatePickerPopoverProps) { + return ( + + + + + + ); +} + +export default TcDatePickerPopover; diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 233dd6ed..97df6983 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -8,12 +8,13 @@ import TcButton from '../../components/shared/TcButton'; import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement/'; import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; -import router from 'next/router'; import TcConfirmSchaduledAnnouncementsDialog from '../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; import useAppStore from '../../store/useStore'; import { useToken } from '../../context/TokenContext'; import { ChannelContext } from '../../context/ChannelContext'; import { IRoles, IUser } from '../../utils/interfaces'; +import { useSnackbar } from '../../context/SnackbarContext'; +import { useRouter } from 'next/router'; export type CreateAnnouncementsPayloadDataOptions = | { channelIds: string[]; userIds?: string[]; roleIds?: string[] } @@ -34,11 +35,13 @@ export interface CreateAnnouncementsPayload { } function CreateNewAnnouncements() { + const router = useRouter(); const { createNewAnnouncements, retrievePlatformById } = useAppStore(); const { community } = useToken(); const channelContext = useContext(ChannelContext); + const { showMessage } = useSnackbar(); const { refreshData } = channelContext; @@ -77,7 +80,7 @@ function CreateNewAnnouncements() { fetchPlatformChannels(); }, [platformId]); - const handleCreateAnnouncements = (isDrafted: boolean) => { + const handleCreateAnnouncements = async (isDrafted: boolean) => { if (!community) return; const data = [publicAnnouncements]; @@ -92,9 +95,16 @@ function CreateNewAnnouncements() { scheduledAt: scheduledAt, data: data, }; - console.log({ announcementsPayload }); - createNewAnnouncements(announcementsPayload); + try { + const data = await createNewAnnouncements(announcementsPayload); + if (data) { + showMessage('Announcement created successfully', 'success'); + router.push('/announcements'); + } + } catch (error) { + showMessage('Failed to create announcement', 'error'); + } }; return ( @@ -153,7 +163,7 @@ function CreateNewAnnouncements() { ...commonData, options: { roleIds: selectedRoles.map((role) => - role.id.toString() + role.roleId.toString() ), }, }; @@ -216,11 +226,7 @@ function CreateNewAnnouncements() { selectedRoles={roles} selectedUsernames={users} schaduledDate={scheduledAt || ''} - isDisabled={ - !scheduledAt || - !publicAnnouncements?.template || - !publicAnnouncements?.options.channelIds - } + isDisabled={!scheduledAt} handleCreateAnnouncements={(e) => handleCreateAnnouncements(e) } diff --git a/src/pages/announcements/edit-announcements.tsx b/src/pages/announcements/edit-announcements.tsx deleted file mode 100644 index ee711383..00000000 --- a/src/pages/announcements/edit-announcements.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { defaultLayout } from '../../layouts/defaultLayout'; -import SEO from '../../components/global/SEO'; -import router from 'next/router'; -import TcPrivateMessaageContainer from '../../components/announcements/create/privateMessaageContainer'; -import TcPublicMessaageContainer from '../../components/announcements/create/publicMessageContainer'; -import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement'; -import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; -import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; -import TcBreadcrumbs from '../../components/shared/TcBreadcrumbs'; -import TcButton from '../../components/shared/TcButton'; -import TcConfirmSchaduledAnnouncementsDialog from '../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; - -function EditAnnouncements() { - return ( - <> - -
- - -
- - - - -
-
- router.push('/announcements')} - variant="outlined" - sx={{ - maxWidth: { - xs: '100%', - sm: '8rem', - }, - }} - /> -
- -
-
-
- } - /> -
- - ); -} - -EditAnnouncements.pageLayout = defaultLayout; - -export default EditAnnouncements; diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx new file mode 100644 index 00000000..cd74aa84 --- /dev/null +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { defaultLayout } from '../../../layouts/defaultLayout'; +import SEO from '../../../components/global/SEO'; +import router from 'next/router'; +import TcPrivateMessaageContainer from '../../../components/announcements/create/privateMessaageContainer'; +import TcPublicMessaageContainer from '../../../components/announcements/create/publicMessageContainer'; +import TcScheduleAnnouncement from '../../../components/announcements/create/scheduleAnnouncement'; +import TcSelectPlatform from '../../../components/announcements/create/selectPlatform'; +import TcBoxContainer from '../../../components/shared/TcBox/TcBoxContainer'; +import TcBreadcrumbs from '../../../components/shared/TcBreadcrumbs'; +import TcButton from '../../../components/shared/TcButton'; +import TcConfirmSchaduledAnnouncementsDialog from '../../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; +import { FlattenedChannel } from '../../../components/announcements/create/publicMessageContainer/TcPublicMessageContainer'; +import { IRoles, IUser } from '../../../utils/interfaces'; + +function Index() { + return ( + <> + +
+ + +
+ + + + +
+
+ router.push('/announcements')} + variant="outlined" + sx={{ + maxWidth: { + xs: '100%', + sm: '8rem', + }, + }} + /> +
+ +
+
+
+ } + /> +
+ + ); +} + +Index.pageLayout = defaultLayout; + +export default Index; diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index c56675d7..58ecfcd4 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -5,18 +5,16 @@ import SEO from '../../components/global/SEO'; import TcText from '../../components/shared/TcText'; import TcButton from '../../components/shared/TcButton'; import { BsPlus } from 'react-icons/bs'; -import TcTableContainer from '../../components/shared/TcTableContainer'; import router from 'next/router'; import TcPagination from '../../components/shared/TcPagination'; import TcTimeZone from '../../components/announcements/TcTimeZone'; -import TcDateTimePopover from '../../components/announcements/create/scheduleAnnouncement/TcDateTimePopover'; import moment from 'moment'; import { MdCalendarMonth } from 'react-icons/md'; import useAppStore from '../../store/useStore'; import { StorageService } from '../../services/StorageService'; import { FetchedData, IDiscordModifiedCommunity } from '../../utils/interfaces'; - -const headers = ['Announcement', 'Channel', 'Handle', 'Role', 'Date']; +import TcAnnouncementsTable from '../../components/announcements/TcAnnouncementsTable'; +import TcDatePickerPopover from '../../components/shared/TcDatePickerPopover'; function Index() { const { retrieveAnnouncements } = useAppStore(); @@ -27,15 +25,15 @@ function Index() { const [anchorEl, setAnchorEl] = useState(null); const [activeTab, setActiveTab] = useState(0); const [selectedDate, setSelectedDate] = useState(null); - const [selectedTime, setSelectedTime] = useState(null); - const [dateTimeDisplay, setDateTimeDisplay] = useState( - moment().format('D MMMM YYYY @ h A') - ); + const [selectedZone, setSelectedZone] = useState(moment.tz.guess()); + const [dateTimeDisplay, setDateTimeDisplay] = useState('Filter Date'); const [loading, setLoading] = useState(false); + const [page, setPage] = useState(1); + const [fetchedAnnouncements, setFetchedAnnouncements] = useState( { - limit: 10, + limit: 8, page: 1, results: [], totalPages: 0, @@ -43,10 +41,6 @@ function Index() { } ); - const handleOpen = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const handleClose = () => { setAnchorEl(null); }; @@ -54,61 +48,63 @@ function Index() { const open = Boolean(anchorEl); const id = open ? 'date-time-popover' : undefined; + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleDateChange = (date: Date | null) => { if (date) { setSelectedDate(date); + const fullDateTime = moment(date); + setDateTimeDisplay(fullDateTime.format('D MMMM YYYY')); + setActiveTab(1); + setAnchorEl(null); } }; - const handleTimeChange = (time: Date | null) => { - if (time) { - setSelectedTime(time); - handleClose(); - - if (selectedDate) { - const fullDateTime = moment(selectedDate).set({ - hour: time.getHours(), - minute: time.getMinutes(), - }); - setDateTimeDisplay(fullDateTime.format('D MMMM YYYY @ h A')); + const fetchData = async (date?: Date | null, zone?: string) => { + try { + setLoading(true); + + let startDate, endDate; + if (date) { + startDate = moment(date) + .tz(zone || selectedZone) + .startOf('day') + .utcOffset(0, true) + .format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); + endDate = moment(date) + .tz(zone || selectedZone) + .endOf('day') + .utcOffset(0, true) + .format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); } + const data = await retrieveAnnouncements({ + page: page, + limit: 8, + timeZone: zone || selectedZone, + ...(startDate ? { startDate: startDate } : {}), + ...(endDate ? { endDate: endDate } : {}), + community: communityId, + }); + + setFetchedAnnouncements(data); + } catch (error) { + console.error('An error occurred:', error); + } finally { + setLoading(false); } }; useEffect(() => { - const fetchData = async () => { - try { - setLoading(true); - - const data = await retrieveAnnouncements({ - page: 1, - limit: 10, - community: communityId, - }); - - setFetchedAnnouncements(data); - setLoading(false); - } catch (error) { - console.error('An error occurred while fetching platforms:', error); - setLoading(false); - } - }; - - fetchData(); - }, []); - - const formatAnnouncementsForTable = () => { - console.log(fetchedAnnouncements.results); + console.log({ selectedDate }); - return fetchedAnnouncements.results.map( - (announcement) => console.log(announcement.data.options) + fetchData(selectedDate, selectedZone); + }, [selectedZone, selectedDate, page]); - // { - // Announcement: announcement.title, - // Date: moment(announcement.scheduledAt).format('YYYY-MM-DD'), - // } - ); + const handlePageChange = (selectedPage: number) => { + setPage(selectedPage); }; return ( @@ -132,32 +128,29 @@ function Index() {
} - disableElevation={true} - className="border border-black bg-gray-100 shadow-md" - sx={{ color: 'black', height: '2.4rem' }} + onClick={handleClick} + text={dateTimeDisplay} aria-describedby={id} - onClick={handleOpen} /> - - +
{fetchedAnnouncements.results.length > 0 ? ( - ) : (
@@ -175,16 +168,21 @@ function Index() { )}
-
- -
+ {fetchedAnnouncements.totalResults > 8 ? ( +
+ +
+ ) : ( + '' + )}
} /> diff --git a/src/store/slices/announcementsSlice.ts b/src/store/slices/announcementsSlice.ts index 96595163..5ef14cbf 100644 --- a/src/store/slices/announcementsSlice.ts +++ b/src/store/slices/announcementsSlice.ts @@ -11,6 +11,9 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ limit, sortBy, name, + timeZone, + startDate, + endDate, community, }: IRetrieveAnnouncementsProps) => { try { @@ -18,6 +21,9 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ page, limit, sortBy, + ...(timeZone ? { timeZone } : {}), + ...(startDate ? { startDate } : {}), + ...(endDate ? { endDate } : {}), ...(name ? { name } : {}), }; @@ -62,7 +68,7 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ }, deleteAnnouncements: async (id: string) => { try { - const { data } = await axiosInstance.delete(`/platforms/${id}`); + const { data } = await axiosInstance.delete(`/announcements/${id}`); return data; } catch (error) { console.error('Failed to delete announcements:', error); diff --git a/src/store/types/IAnnouncements.ts b/src/store/types/IAnnouncements.ts index 916030ae..e631ae48 100644 --- a/src/store/types/IAnnouncements.ts +++ b/src/store/types/IAnnouncements.ts @@ -4,6 +4,9 @@ export interface IRetrieveAnnouncementsProps { sortBy?: string; name?: string; community: string; + startDate?: string; + endDate?: string; + timeZone: string; } export default interface IAnnouncements { From 7926de5bd60ef644dff7440890a9f2b964448ef9 Mon Sep 17 00:00:00 2001 From: zuies Date: Thu, 18 Jan 2024 10:54:44 +0300 Subject: [PATCH 28/58] update create,list announcements --- .../announcements/TcAnnouncementsTable.tsx | 92 ++++++++++++++++--- src/components/shared/TcDatePickerPopover.tsx | 11 +++ .../create-new-announcements.tsx | 1 + src/pages/announcements/index.tsx | 9 +- 4 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index ff9a8118..122d3ffa 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -114,12 +114,76 @@ function TcAnnouncementsTable({ - Announcements - Channels - Handle - Role - Date - + + + + + + + + + + + + + + + + @@ -132,8 +196,10 @@ function TcAnnouncementsTable({ : 'border-1 border-solid border-gray-700' }`} > - {announcement.data[0].template} - + + {announcement.data[0].template} + + {announcement.data.map( (item) => item.options.channels && @@ -142,7 +208,7 @@ function TcAnnouncementsTable({ .join(', ') )} - + {announcement.data.map((item) => item.options.users ? item.options.users @@ -151,17 +217,17 @@ function TcAnnouncementsTable({ : '' )} - + {announcement.data.map((item) => item.options.roles ? item.options.roles.map((role) => role.name).join(', ') : '' )} - + {new Date(announcement.scheduledAt).toLocaleString()} - {' '} - + + void; selectedDate: Date | null; onDateChange: (date: Date | null) => void; + onResetDate: () => void; } function TcDatePickerPopover({ @@ -18,6 +20,7 @@ function TcDatePickerPopover({ onClose, selectedDate, onDateChange, + onResetDate, }: ITcDatePickerPopoverProps) { return ( +
+ +
); } diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 97df6983..60aa02cc 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -212,6 +212,7 @@ function CreateNewAnnouncements() { { + setSelectedDate(null); + setDateTimeDisplay('Filter Date'); + + setAnchorEl(null); + }; + const fetchData = async (date?: Date | null, zone?: string) => { try { setLoading(true); @@ -140,6 +146,7 @@ function Index() { onClose={handleClose} selectedDate={selectedDate} onDateChange={handleDateChange} + onResetDate={resetDateFilter} /> From 8242779b6cf63dec19e1f9d866a9dfd2c4a4bbcd Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 00:19:55 +0300 Subject: [PATCH 29/58] integrate edit announcements --- .../TcConfirmSchaduledAnnouncementsDialog.tsx | 18 +- .../TcPrivateMessaageContainer.tsx | 50 +++- .../TcRolesAutoComplete.tsx | 16 ++ .../TcUsersAutoComplete.tsx | 24 +- .../TcPublicMessageContainer.tsx | 23 ++ .../TcScheduleAnnouncement.tsx | 13 + .../pages/pageIndex/HeatmapChart.tsx | 10 + .../edit-announcements/index.tsx | 269 ++++++++++++++---- src/pages/announcements/index.tsx | 2 - src/store/slices/announcementsSlice.ts | 16 +- src/store/types/IAnnouncements.ts | 4 +- src/utils/interfaces.ts | 12 +- 12 files changed, 387 insertions(+), 70 deletions(-) diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx index b411c4f2..7e7c9cec 100644 --- a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx @@ -94,7 +94,11 @@ function TcConfirmSchaduledAnnouncementsDialog({ className="text-left text-gray-400" /> @@ -112,7 +116,11 @@ function TcConfirmSchaduledAnnouncementsDialog({ className="text-left text-gray-400" /> {' '} @@ -133,7 +141,11 @@ function TcConfirmSchaduledAnnouncementsDialog({ className="text-left text-gray-400" /> diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx index 78058436..e97ddcd6 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx @@ -13,6 +13,10 @@ import TcPrivateMessagePreviewDialog from './TcPrivateMessagePreviewDialog'; import TcRolesAutoComplete from './TcRolesAutoComplete'; import TcUsersAutoComplete from './TcUsersAutoComplete'; import { IRoles, IUser } from '../../../../utils/interfaces'; +import { + DiscordData, + DiscordPrivateOptions, +} from '../../../../pages/announcements/edit-announcements'; export enum MessageType { Both = 'Both', @@ -21,6 +25,8 @@ export enum MessageType { } export interface ITcPrivateMessageContainerProps { + isEdit?: boolean; + privateAnnouncementsData?: DiscordData[] | undefined; handlePrivateAnnouncements: ({ message, selectedRoles, @@ -34,6 +40,8 @@ export interface ITcPrivateMessageContainerProps { function TcPrivateMessageContainer({ handlePrivateAnnouncements, + isEdit = false, + privateAnnouncementsData, }: ITcPrivateMessageContainerProps) { const [privateMessage, setPrivateMessage] = useState(false); const [messageType, setMessageType] = useState(MessageType.Both); @@ -101,6 +109,37 @@ function TcPrivateMessageContainer({ handlePrivateAnnouncements, ]); + useEffect(() => { + if (isEdit && privateAnnouncementsData) { + const rolesArray: IRoles[] = []; + const usersArray: IUser[] = []; + let templateText = ''; + + privateAnnouncementsData.forEach((item) => { + if (item.type === 'discord_private') { + const privateOptions = item.options as DiscordPrivateOptions; + + if (privateOptions.roles) { + rolesArray.push(...privateOptions.roles); + } + + if (privateOptions.users) { + usersArray.push(...privateOptions.users); + } + + if (!templateText) { + templateText = item.template; + } + } + }); + + setPrivateMessage(true); + setSelectedRoles(rolesArray); + setSelectedUsers(usersArray); + setMessage(templateText); + } + }, [isEdit, privateAnnouncementsData]); + return (
@@ -111,7 +150,12 @@ function TcPrivateMessageContainer({ } + control={ + + } label={
@@ -184,6 +228,8 @@ function TcPrivateMessageContainer({ messageType !== MessageType.Both && messageType !== MessageType.RoleOnly } + isEdit={true} + privateSelectedRoles={selectedRoles} handleSelectedUsers={setSelectedRoles} /> @@ -201,6 +247,8 @@ function TcPrivateMessageContainer({ messageType !== MessageType.Both && messageType !== MessageType.UserOnly } + isEdit={true} + privateSelectedUsers={selectedUsers} handleSelectedUsers={setSelectedUsers} /> diff --git a/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx index d2f891c6..ff5eeb5a 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx @@ -7,11 +7,15 @@ import { useToken } from '../../../../context/TokenContext'; import { debounce, hexToRGBA, isDarkColor } from '../../../../helpers/helper'; interface ITcRolesAutoCompleteProps { + isEdit?: boolean; + privateSelectedRoles?: IRoles[]; isDisabled: boolean; handleSelectedUsers: (roles: IRoles[]) => void; } function TcRolesAutoComplete({ + isEdit = false, + privateSelectedRoles, isDisabled, handleSelectedUsers, }: ITcRolesAutoCompleteProps) { @@ -32,6 +36,7 @@ function TcRolesAutoComplete({ totalResults: 0, }); const [filteredRolesByName, setFilteredRolesByName] = useState(''); + const [isInitialized, setIsInitialized] = useState(false); const fetchDiscordRoles = async ( platformId: string, @@ -127,6 +132,17 @@ function TcRolesAutoComplete({ handleSelectedUsers(selectedRoles); }, [selectedRoles]); + useEffect(() => { + if (isEdit && !isInitialized) { + if (privateSelectedRoles !== undefined) { + setSelectedRoles(privateSelectedRoles); + } else { + setSelectedRoles([]); + } + setIsInitialized(true); + } + }, [privateSelectedRoles, isEdit, isInitialized]); + return ( void; } function TcUsersAutoComplete({ + isEdit = false, + privateSelectedUsers, isDisabled, handleSelectedUsers, }: ITcUsersAutoCompleteProps) { @@ -32,24 +36,25 @@ function TcUsersAutoComplete({ totalResults: 0, }); const [filteredRolesByName, setFilteredRolesByName] = useState(''); + const [isInitialized, setIsInitialized] = useState(false); const fetchDiscordUsers = async ( platformId: string, page?: number, limit?: number, - name?: string + ngu?: string ) => { try { const fetchedUsers = await retrievePlatformProperties({ platformId, - name: name, + ngu: ngu, property: 'guildMember', page: page, limit: limit, }); - if (name) { - setFilteredRolesByName(name); + if (ngu) { + setFilteredRolesByName(ngu); setFetchedUsers(fetchedUsers); } else { setFetchedUsers((prevData: { results: any }) => { @@ -127,6 +132,17 @@ function TcUsersAutoComplete({ handleSelectedUsers(selectedUsers); }, [selectedUsers]); + useEffect(() => { + if (isEdit && !isInitialized) { + if (privateSelectedUsers !== undefined) { + setSelectedUsers(privateSelectedUsers); + } else { + setSelectedUsers([]); + } + setIsInitialized(true); + } + }, [privateSelectedUsers, isEdit, isInitialized]); + return ( { + if (isEdit && publicAnnouncementsData) { + if ( + publicAnnouncementsData.type === 'discord_public' && + 'channels' in publicAnnouncementsData.options + ) { + const formattedChannels = publicAnnouncementsData.options.channels.map( + (channel) => ({ + id: channel.channelId, + label: channel.name, + }) + ); + setSelectedChannels(formattedChannels); + setMessage(publicAnnouncementsData.template); + } + } + }, [isEdit, publicAnnouncementsData]); + return (
diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index 52c19986..d8ddc3a0 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -7,10 +7,14 @@ import moment from 'moment'; import TcDateTimePopover from './TcDateTimePopover'; export interface ITcScheduleAnnouncementProps { + isEdit?: boolean; + preSelectedTime?: string; handleSchaduledDate: ({ selectedTime }: { selectedTime: string }) => void; } function TcScheduleAnnouncement({ + isEdit = false, + preSelectedTime, handleSchaduledDate, }: ITcScheduleAnnouncementProps) { const [anchorEl, setAnchorEl] = useState(null); @@ -69,6 +73,15 @@ function TcScheduleAnnouncement({ handleSchaduledDate({ selectedTime: formattedUTCDate }); }, [selectedDate, selectedTime]); + useEffect(() => { + if (isEdit) { + const date = moment(preSelectedTime); + setSelectedDate(date.toDate()); + setSelectedTime(date.toDate()); + setDateTimeDisplay(date.format('D MMMM YYYY @ h A')); + } + }, [isEdit]); + return (
diff --git a/src/components/pages/pageIndex/HeatmapChart.tsx b/src/components/pages/pageIndex/HeatmapChart.tsx index d578ea17..448db7f9 100644 --- a/src/components/pages/pageIndex/HeatmapChart.tsx +++ b/src/components/pages/pageIndex/HeatmapChart.tsx @@ -58,6 +58,8 @@ const HeatmapChart = () => { const fetchData = async () => { setLoading(true); try { + console.log({ selectedSubChannels }); + if (platformId) { const data = await fetchHeatmapData( platformId, @@ -94,11 +96,15 @@ const HeatmapChart = () => { }; useEffect(() => { +<<<<<<< Updated upstream const initializeSelectedChannels = async () => { await fetchPlatformChannels(); }; initializeSelectedChannels(); +======= + fetchPlatformChannels(); +>>>>>>> Stashed changes }, []); const handleSelectedZone = (zone: string) => { @@ -164,7 +170,11 @@ const HeatmapChart = () => { } else { await refreshData(platformId); } +<<<<<<< Updated upstream setPlatformFetched(true); +======= + await fetchData(); +>>>>>>> Stashed changes } } catch (error) { } finally { diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index cd74aa84..22f159c1 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -1,19 +1,162 @@ -import React from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import { defaultLayout } from '../../../layouts/defaultLayout'; import SEO from '../../../components/global/SEO'; -import router from 'next/router'; +import { useRouter } from 'next/router'; import TcPrivateMessaageContainer from '../../../components/announcements/create/privateMessaageContainer'; import TcPublicMessaageContainer from '../../../components/announcements/create/publicMessageContainer'; import TcScheduleAnnouncement from '../../../components/announcements/create/scheduleAnnouncement'; import TcSelectPlatform from '../../../components/announcements/create/selectPlatform'; import TcBoxContainer from '../../../components/shared/TcBox/TcBoxContainer'; import TcBreadcrumbs from '../../../components/shared/TcBreadcrumbs'; -import TcButton from '../../../components/shared/TcButton'; import TcConfirmSchaduledAnnouncementsDialog from '../../../components/announcements/TcConfirmSchaduledAnnouncementsDialog'; -import { FlattenedChannel } from '../../../components/announcements/create/publicMessageContainer/TcPublicMessageContainer'; +import useAppStore from '../../../store/useStore'; import { IRoles, IUser } from '../../../utils/interfaces'; +import { ChannelContext } from '../../../context/ChannelContext'; +import { useSnackbar } from '../../../context/SnackbarContext'; +import { useToken } from '../../../context/TokenContext'; +import { CreateAnnouncementsPayloadData } from '../create-new-announcements'; + +export interface DiscordChannel { + channelId: string; + name: string; +} + +interface DiscordUser { + discordId: string; + ngu: string; +} + +interface DiscordPublicOptions { + channels: DiscordChannel[]; +} + +export interface DiscordPrivateOptions { + roles?: IRoles[]; + users?: DiscordUser[]; +} + +type DiscordOptions = DiscordPublicOptions | DiscordPrivateOptions; + +export interface DiscordData { + platform: string; + template: string; + options: DiscordOptions; + type: 'discord_public' | 'discord_private'; +} + +export interface AnnouncementsDiscordResponseProps { + id: string; + scheduledAt: string; + draft: boolean; + data: DiscordData[]; + community: string; +} function Index() { + const { retrieveAnnouncementById, patchExistingAnnouncement } = useAppStore(); + + const router = useRouter(); + + const { community } = useToken(); + + const channelContext = useContext(ChannelContext); + const { refreshData } = channelContext; + + const { showMessage } = useSnackbar(); + + const [channels, setChannels] = useState([]); + const [roles, setRoles] = useState([]); + const [users, setUsers] = useState([]); + + const platformId = community?.platforms.find( + (platform) => platform.disconnectedAt === null + )?.id; + + const [publicAnnouncements, setPublicAnnouncements] = + useState(); + + const [privateAnnouncements, setPrivateAnnouncements] = + useState(); + + const [fetchedAnnouncements, setFetchedAnnouncements] = + useState(); + + const id = router.query.announcementsId as string; + + const [scheduledAt, setScheduledAt] = useState(); + + const publicSelectedAnnouncements = useMemo(() => { + return fetchedAnnouncements?.data.filter( + (item) => item.type === 'discord_public' + )[0]; + }, [fetchedAnnouncements]); + + const privateSelectedAnnouncements = useMemo(() => { + return fetchedAnnouncements?.data.filter( + (item) => item.type === 'discord_private' + ); + }, [fetchedAnnouncements]); + + const fetchPlatformChannels = async () => { + try { + if (platformId) { + let channelIds: string[] = []; + + if ( + publicSelectedAnnouncements?.type === 'discord_public' && + 'channels' in publicSelectedAnnouncements.options + ) { + channelIds = publicSelectedAnnouncements.options.channels.map( + (channel) => channel.channelId + ); + } + + await refreshData(platformId, 'channel', channelIds, undefined, false); + } + } catch (error) { + } finally { + } + }; + + useEffect(() => { + if (!id) return; + const fetchAnnouncement = async () => { + const data = await retrieveAnnouncementById(id); + setFetchedAnnouncements(data); + }; + + fetchAnnouncement(); + fetchPlatformChannels(); + }, [id]); + + const handleEditAnnouncements = async (isDrafted: boolean) => { + if (!community) return; + + const data = [publicAnnouncements]; + + if (privateAnnouncements && privateAnnouncements.length > 0) { + data.push(...privateAnnouncements); + } + + const announcementsPayload = { + draft: isDrafted, + scheduledAt: scheduledAt, + data: data, + }; + + try { + console.log(id, announcementsPayload); + + const data = await patchExistingAnnouncement(id, announcementsPayload); + if (data) { + showMessage('Announcement updated successfully', 'success'); + router.push('/announcements'); + } + } catch (error) { + showMessage('Failed to create announcement', 'error'); + } + }; + return ( <> @@ -30,64 +173,94 @@ function Index() {
{ + if (!platformId) return; + setChannels(selectedChannels); + setPublicAnnouncements({ + platformId: platformId, + template: message, + options: { + channelIds: selectedChannels.map( + (channel) => channel.id + ), + }, + }); }} /> { + if (!platformId) return; + + let rolePrivateAnnouncements; + let userPrivateAnnouncements; + + const commonData = { + platformId: platformId, + template: message, + }; + + if (selectedRoles && selectedRoles.length > 0) { + setRoles(selectedRoles); + + rolePrivateAnnouncements = { + ...commonData, + options: { + roleIds: selectedRoles.map((role) => + role.roleId.toString() + ), + }, + }; + } + + if (selectedUsers && selectedUsers.length > 0) { + setUsers(selectedUsers); + + userPrivateAnnouncements = { + ...commonData, + options: { + userIds: selectedUsers.map((user) => user.discordId), + }, + }; + } + + const announcements = []; + if (rolePrivateAnnouncements) + announcements.push(rolePrivateAnnouncements); + if (userPrivateAnnouncements) + announcements.push(userPrivateAnnouncements); + + setPrivateAnnouncements(announcements); }} /> { + setScheduledAt(selectedTime); }} />
-
- router.push('/announcements')} - variant="outlined" - sx={{ - maxWidth: { - xs: '100%', - sm: '8rem', - }, - }} +
+ handleEditAnnouncements(e)} /> -
- -
} diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index 2c84e671..3657be7e 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -104,8 +104,6 @@ function Index() { }; useEffect(() => { - console.log({ selectedDate }); - fetchData(selectedDate, selectedZone); }, [selectedZone, selectedDate, page]); diff --git a/src/store/slices/announcementsSlice.ts b/src/store/slices/announcementsSlice.ts index 5ef14cbf..1d2a06fb 100644 --- a/src/store/slices/announcementsSlice.ts +++ b/src/store/slices/announcementsSlice.ts @@ -10,7 +10,7 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ page, limit, sortBy, - name, + ngu, timeZone, startDate, endDate, @@ -24,7 +24,7 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ ...(timeZone ? { timeZone } : {}), ...(startDate ? { startDate } : {}), ...(endDate ? { endDate } : {}), - ...(name ? { name } : {}), + ...(ngu ? { name } : {}), }; const { data } = await axiosInstance.get( @@ -58,9 +58,17 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ console.error('Failed to create announcements:', error); } }, - patchExistingAnnouncement: async (id: string) => { + patchExistingAnnouncement: async ( + id: string, + announcementPayload: CreateAnnouncementsPayload + ) => { try { - const { data } = await axiosInstance.post(`/announcements/${id}`); + console.log({ id }); + + const { data } = await axiosInstance.patch( + `/announcements/${id}`, + announcementPayload + ); return data; } catch (error) { console.error('Failed to patch announcements:', error); diff --git a/src/store/types/IAnnouncements.ts b/src/store/types/IAnnouncements.ts index e631ae48..37937eac 100644 --- a/src/store/types/IAnnouncements.ts +++ b/src/store/types/IAnnouncements.ts @@ -2,7 +2,7 @@ export interface IRetrieveAnnouncementsProps { page: number; limit: number; sortBy?: string; - name?: string; + ngu?: string; community: string; startDate?: string; endDate?: string; @@ -14,7 +14,7 @@ export default interface IAnnouncements { page, limit, sortBy, - name, + ngu, community, }: IRetrieveAnnouncementsProps) => void; } diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index 543828b3..59165c1e 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -27,8 +27,8 @@ export interface IRoles { roleId: string; color: number | string; name: string; - deletedAt: string; - id: number | string; + deletedAt?: string; + id?: number | string; } export interface IUserProfile { @@ -174,9 +174,9 @@ export interface IDiscordModifiedCommunity export interface IUser { discordId: string; - discriminator: string; - globalName: string | null; + discriminator?: string; + globalName?: string | null; ngu: string; - nickname: string | null; - username: string; + nickname?: string | null; + username?: string; } From f353b41016b7963e3b77435671854bd04a0a9a23 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 04:06:49 +0300 Subject: [PATCH 30/58] complete announcement feature --- .../TcAnnouncementsAlert.spec.tsx | 29 ++ .../announcements/TcAnnouncementsAlert.tsx | 69 ++++ .../announcements/TcAnnouncementsTable.tsx | 296 +++++++++++++----- ...nfirmSchaduledAnnouncementsDialog.spec.tsx | 91 ++---- .../announcements/TcTimeZone.spec.tsx | 24 +- src/components/announcements/TcTimeZone.tsx | 2 +- .../TcPrivateMessaageContainer.spec.tsx | 43 --- ...iner.tsx => TcPrivateMessageContainer.tsx} | 0 .../TcRolesAutoComplete.tsx | 35 ++- .../TcUsersAutoComplete.tsx | 22 +- .../create/privateMessaageContainer/index.ts | 2 +- .../pages/pageIndex/HeatmapChart.tsx | 8 - .../TcTableContainer/TcTableRow.spec.tsx | 9 - .../create-new-announcements.tsx | 14 +- .../edit-announcements/index.tsx | 13 +- src/pages/announcements/index.tsx | 69 +++- src/pages/callback.tsx | 8 + src/store/slices/platformSlice.ts | 10 + src/store/types/IPlatform.ts | 11 + src/utils/enums.ts | 13 + src/utils/interfaces.ts | 22 ++ 21 files changed, 538 insertions(+), 252 deletions(-) create mode 100644 src/components/announcements/TcAnnouncementsAlert.spec.tsx create mode 100644 src/components/announcements/TcAnnouncementsAlert.tsx delete mode 100644 src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx rename src/components/announcements/create/privateMessaageContainer/{TcPrivateMessaageContainer.tsx => TcPrivateMessageContainer.tsx} (100%) diff --git a/src/components/announcements/TcAnnouncementsAlert.spec.tsx b/src/components/announcements/TcAnnouncementsAlert.spec.tsx new file mode 100644 index 00000000..2da01b59 --- /dev/null +++ b/src/components/announcements/TcAnnouncementsAlert.spec.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import TcAnnouncementsAlert from './TcAnnouncementsAlert'; + +const mockGrantWritePermissions = jest.fn(); + +jest.mock('../../store/useStore', () => () => ({ + grantWritePermissions: mockGrantWritePermissions, +})); + +jest.mock('../../context/TokenContext', () => ({ + useToken: () => ({ + community: { + platforms: [ + { id: '1', disconnectedAt: null, metadata: { id: '3123141414221' } }, + ], + }, + }), +})); + +describe('TcAnnouncementsAlert', () => { + it('renders correctly', () => { + render(); + expect( + screen.getByText(/Announcements needs write access at the server-level/i) + ).toBeInTheDocument(); + }); +}); diff --git a/src/components/announcements/TcAnnouncementsAlert.tsx b/src/components/announcements/TcAnnouncementsAlert.tsx new file mode 100644 index 00000000..be5f9b7b --- /dev/null +++ b/src/components/announcements/TcAnnouncementsAlert.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import TcAlert from '../shared/TcAlert'; +import TcCollapse from '../shared/TcCollapse'; +import TcText from '../shared/TcText'; +import TcButton from '../shared/TcButton'; +import useAppStore from '../../store/useStore'; +import { useToken } from '../../context/TokenContext'; + +function TcAnnouncementsAlert() { + const { grantWritePermissions } = useAppStore(); + + const { community } = useToken(); + + const handleGrantAccess = () => { + const guildId = community?.platforms.find( + (platform) => platform.disconnectedAt === null + )?.metadata.id; + + if (guildId) + grantWritePermissions({ + platformType: 'discord', + moduleType: 'Announcement', + id: guildId, + }); + }; + return ( + + +
+ + +
+
+
+ ); +} + +export default TcAnnouncementsAlert; diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index 122d3ffa..ace0bfe0 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -8,6 +8,7 @@ import { IconButton, MenuItem, Menu, + Chip, } from '@mui/material'; import { BsThreeDotsVertical } from 'react-icons/bs'; import Router from 'next/router'; @@ -18,6 +19,8 @@ import TcText from '../shared/TcText'; import useAppStore from '../../store/useStore'; import { useSnackbar } from '../../context/SnackbarContext'; import { MdModeEdit, MdDelete } from 'react-icons/md'; +import Loading from '../global/Loading'; +import { truncateCenter } from '../../helpers/helper'; interface Channel { channelId: string; @@ -38,6 +41,7 @@ interface Role { interface AnnouncementData { platform: string; template: string; + type: 'discord_public' | 'discord_private'; options: { channels: Channel[]; users?: User[]; @@ -56,11 +60,13 @@ interface Announcement { interface AnnouncementsTableProps { announcements: Announcement[]; + isLoading: boolean; handleRefreshList: () => void; } function TcAnnouncementsTable({ announcements, + isLoading, handleRefreshList, }: AnnouncementsTableProps) { const { deleteAnnouncements } = useAppStore(); @@ -96,8 +102,7 @@ function TcAnnouncementsTable({ const handleDeleteAnnouncements = async (id: string) => { try { - const data = await deleteAnnouncements(id); - + await deleteAnnouncements(id); handleRefreshList(); showMessage('Scheduled announcement removed successfully.', 'success'); } catch (error) { @@ -109,6 +114,212 @@ function TcAnnouncementsTable({ } }; + const getAnnouncementTypeLabel = (type: string) => { + if (type === 'discord_public') { + return 'Public'; + } else if (type === 'discord_private') { + return 'Private'; + } + return 'Unknown'; + }; + + const renderTableBody = () => { + if (isLoading) { + return ( + + + + + + ); + } + + return announcements.map((announcement, index) => ( + + +
+ + + {truncateCenter(announcement.data[0].template, 20)} + + } + variant="subtitle2" + /> + + {announcement.data + .reduce((unique: string[], item: AnnouncementData) => { + const itemType = item.type; + if (!unique.includes(itemType)) { + unique.push(itemType); + } + return unique; + }, [] as string[]) + .map((type, index) => ( + + + {getAnnouncementTypeLabel(type)} +
+ } + size="small" + sx={{ + borderRadius: '4px', + borderColor: '#D1D1D1', + backgroundColor: 'white', + color: 'black', + }} + /> + ))} + +
+ + +
+ { + const channels = item.options.channels; + if (channels && channels.length > 0) { + const displayedChannels = channels + .slice(0, 2) + .map((channel) => `#${channel.name}`) + .join(', '); + const moreChannelsIndicator = + channels.length > 2 ? '...' : ''; + return dataIndex > 0 + ? `, ${displayedChannels}${moreChannelsIndicator}` + : `${displayedChannels}${moreChannelsIndicator}`; + } + return ''; + }) + .filter((text) => text !== '') + .join('')} + variant="subtitle2" + /> +
+
+ +
+ { + const users = item.options.users; + if (users && users.length > 0) { + const displayedUsers = users + .slice(0, 2) + .map((user) => `@${user.ngu}`) + .join(', '); + const moreUsersIndicator = users.length > 2 ? '...' : ''; + return `${displayedUsers}${moreUsersIndicator}`; + } + return ''; + }) + .filter((text) => text !== '') + .join(', ')} + variant="subtitle2" + /> +
+
+ +
+ { + const roles = item.options.roles; + if (roles && roles.length > 0) { + const displayedRoles = roles + .slice(0, 2) + .map((role) => role.name) + .join(', '); + const moreRolesIndicator = roles.length > 2 ? '...' : ''; + return `${displayedRoles}${moreRolesIndicator}`; + } + return ''; + }) + .filter((text) => text !== '') + .join(', ')} + variant="subtitle2" + /> +
+
+ +
+ +
+
+ + handleClick(event, announcement.id)} + > + + + + handleEdit(announcement.id)}> + + Edit + + + + Delete + + + + + )); + }; + return ( <>
@@ -121,6 +332,7 @@ function TcAnnouncementsTable({ width: '20%', borderBottom: 'none', whiteSpace: 'nowrap', + padding: '0 1rem', }} align="left" > @@ -133,6 +345,7 @@ function TcAnnouncementsTable({ width: '20%', borderBottom: 'none', whiteSpace: 'nowrap', + padding: '0 1rem', }} align="left" > @@ -145,6 +358,7 @@ function TcAnnouncementsTable({ width: '20%', borderBottom: 'none', whiteSpace: 'nowrap', + padding: '0 1rem', }} align="left" > @@ -157,6 +371,7 @@ function TcAnnouncementsTable({ width: '20%', borderBottom: 'none', whiteSpace: 'nowrap', + padding: '0 1rem', }} align="left" > @@ -169,6 +384,7 @@ function TcAnnouncementsTable({ width: '20%', borderBottom: 'none', whiteSpace: 'nowrap', + padding: '0 1rem', }} align="left" > @@ -181,85 +397,13 @@ function TcAnnouncementsTable({ width: '20%', borderBottom: 'none', whiteSpace: 'nowrap', + padding: '0 1rem', }} align="right" > - - {announcements.map((announcement, index) => ( - - - {announcement.data[0].template} - - - {announcement.data.map( - (item) => - item.options.channels && - item.options.channels - .map((channel) => `#${channel.name}`) - .join(', ') - )} - - - {announcement.data.map((item) => - item.options.users - ? item.options.users - .map((user) => `@${user.ngu}`) - .join(', ') - : '' - )} - - - {announcement.data.map((item) => - item.options.roles - ? item.options.roles.map((role) => role.name).join(', ') - : '' - )} - - - {new Date(announcement.scheduledAt).toLocaleString()} - - - handleClick(event, announcement.id)} - > - - - - handleEdit(announcement.id)}> - - Edit - - - - Delete - - - - - ))} - + {renderTableBody()}
{ - const defaultProps = { - buttonLabel: 'Test Button', - schaduledDate: 'July 12 at 13pm (CET)', - }; +const mockHandleCreateAnnouncements = jest.fn(); - it('renders without crashing', () => { - render( - - ); - expect(screen.getByText('Test Button')).toBeInTheDocument(); - }); +const defaultProps = { + buttonLabel: 'Schedule Announcement', + selectedChannels: [{ id: '1', label: 'General' }], + schaduledDate: '2024-01-20T12:00:00', + isDisabled: false, + handleCreateAnnouncements: mockHandleCreateAnnouncements, +}; - it('toggles dialog visibility on button click', async () => { - render( - - ); - const button = screen.getByText('Test Button'); - fireEvent.click(button); +test('renders the dialog with button and calls handleCreateAnnouncements when confirmed', () => { + const { getByText, getByTestId } = render( + + ); - await waitFor(() => { - expect(screen.getByText('Confirm Schedule')).toBeInTheDocument(); - }); + const button = getByText('Schedule Announcement'); + expect(button).toBeInTheDocument(); - const closeButton = screen.getByTestId('close-icon'); - fireEvent.click(closeButton); + fireEvent.click(button); - await waitFor(() => { - expect(screen.queryByText('Confirm Schedule')).not.toBeInTheDocument(); - }); - }); + const dialogTitle = getByText('Confirm Schedule'); + expect(dialogTitle).toBeInTheDocument(); - it('displays the correct dialog content', () => { - render( - - ); - fireEvent.click(screen.getByText('Test Button')); - expect( - screen.getByText('Discord announcements scheduled for:') - ).toBeInTheDocument(); - expect(screen.getByText('Public Message to:')).toBeInTheDocument(); - expect( - screen.getByText('Private Message to these user(s):') - ).toBeInTheDocument(); - expect( - screen.getByText('Private Message to these role(s):') - ).toBeInTheDocument(); - }); + const confirmButton = getByText('Confirm'); + expect(confirmButton).toBeInTheDocument(); + fireEvent.click(confirmButton); - it('closes the dialog when the close icon is clicked', async () => { - render( - - ); - - fireEvent.click(screen.getByText('Test Button')); - - fireEvent.click(screen.getByTestId('close-icon')); - - await waitFor(() => { - expect(screen.queryByText('Confirm Schedule')).not.toBeInTheDocument(); - }); - }); + expect(mockHandleCreateAnnouncements).toHaveBeenCalledWith(false); }); diff --git a/src/components/announcements/TcTimeZone.spec.tsx b/src/components/announcements/TcTimeZone.spec.tsx index b30eaca2..351de2bc 100644 --- a/src/components/announcements/TcTimeZone.spec.tsx +++ b/src/components/announcements/TcTimeZone.spec.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; import TcTimeZone from './TcTimeZone'; -describe('TcTimeZone', () => { - test('renders TcTimeZone component', () => { - render(); - expect(screen.getByRole('button')).toBeInTheDocument(); - }); +test('should handle zone selection', async () => { + const handleZoneFunction = jest.fn(); - test('opens popover on button click', () => { - render(); - const button = screen.getByRole('button'); - fireEvent.click(button); - expect(screen.getByText('Search timezone')).toBeInTheDocument(); - }); + const { getByTestId, getByLabelText } = render( + + ); + + const globeIcon = getByTestId('globe-icon'); + fireEvent.click(globeIcon); + + const searchInput = getByLabelText('Search timezone'); + expect(searchInput).toBeInTheDocument(); }); diff --git a/src/components/announcements/TcTimeZone.tsx b/src/components/announcements/TcTimeZone.tsx index 180f7af8..b9fdc857 100644 --- a/src/components/announcements/TcTimeZone.tsx +++ b/src/components/announcements/TcTimeZone.tsx @@ -62,7 +62,7 @@ function TcTimeZone({ handleZone }: ITcTimeZoneProps) { sx={{ height: '2.4rem', }} - startIcon={} + startIcon={} aria-describedby={id} onClick={handleClick} /> diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx deleted file mode 100644 index e4827f8d..00000000 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.spec.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import TcPrivateMessageContainer from './TcPrivateMessaageContainer'; - -describe('TcPrivateMessageContainer Tests', () => { - test('renders the component without crashing', () => { - render(); - expect(screen.getByText('Private Message (optional)')).toBeInTheDocument(); - }); - - test('initial states are set correctly', () => { - render(); - }); - - test('toggles private message switch', () => { - render(); - const switchControl = screen.getByRole('checkbox'); - fireEvent.click(switchControl); - }); - - test('message type buttons respond to clicks', () => { - render(); - }); - - test('allows the user to enter a message', async () => { - render(); - - const privateMessageToggle = screen.getByRole('checkbox'); - fireEvent.click(privateMessageToggle); - - const messageInput = (await screen.findByPlaceholderText( - 'Write your message here' - )) as HTMLInputElement; - fireEvent.change(messageInput, { target: { value: 'Test Message' } }); - - expect(messageInput.value).toBe('Test Message'); - }); - - test('handles channel and username selection based on message type', () => { - render(); - }); -}); diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx similarity index 100% rename from src/components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer.tsx rename to src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx diff --git a/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx index ff5eeb5a..39a0496d 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx @@ -158,23 +158,30 @@ function TcRolesAutoComplete({ value.map((option, index) => ( + + {option.name} +
+ } size="small" sx={{ borderRadius: '4px', - borderColor: hexToRGBA( - option.color !== 0 - ? `#${option.color.toString(16).padStart(6, '0')}` - : '#96A5A6', - 1 - ), - backgroundColor: hexToRGBA( - option.color !== 0 - ? `#${option.color.toString(16).padStart(6, '0')}` - : '#96A5A6', - 0.8 - ), - color: isDarkColor(option.color) ? 'white' : 'black', + borderColor: '#D1D1D1', + backgroundColor: 'white', + color: 'black', }} {...getTagProps({ index })} /> diff --git a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx index 178f98ae..0f66abbb 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx @@ -158,13 +158,27 @@ function TcUsersAutoComplete({ value.map((option, index) => ( + + {option.ngu} +
+ } size="small" sx={{ borderRadius: '4px', - borderColor: '#96A5A6', - backgroundColor: '#96A5A6', - color: 'white', + borderColor: '#D1D1D1', + backgroundColor: 'white', + color: 'black', }} {...getTagProps({ index })} /> diff --git a/src/components/announcements/create/privateMessaageContainer/index.ts b/src/components/announcements/create/privateMessaageContainer/index.ts index 800fd209..cfd2afd6 100644 --- a/src/components/announcements/create/privateMessaageContainer/index.ts +++ b/src/components/announcements/create/privateMessaageContainer/index.ts @@ -1,3 +1,3 @@ -import { default as TcPrivateMessaageContainer } from './TcPrivateMessaageContainer'; +import { default as TcPrivateMessaageContainer } from './TcPrivateMessageContainer'; export default TcPrivateMessaageContainer; diff --git a/src/components/pages/pageIndex/HeatmapChart.tsx b/src/components/pages/pageIndex/HeatmapChart.tsx index 448db7f9..526075d2 100644 --- a/src/components/pages/pageIndex/HeatmapChart.tsx +++ b/src/components/pages/pageIndex/HeatmapChart.tsx @@ -96,15 +96,11 @@ const HeatmapChart = () => { }; useEffect(() => { -<<<<<<< Updated upstream const initializeSelectedChannels = async () => { await fetchPlatformChannels(); }; initializeSelectedChannels(); -======= - fetchPlatformChannels(); ->>>>>>> Stashed changes }, []); const handleSelectedZone = (zone: string) => { @@ -170,11 +166,7 @@ const HeatmapChart = () => { } else { await refreshData(platformId); } -<<<<<<< Updated upstream setPlatformFetched(true); -======= - await fetchData(); ->>>>>>> Stashed changes } } catch (error) { } finally { diff --git a/src/components/shared/TcTableContainer/TcTableRow.spec.tsx b/src/components/shared/TcTableContainer/TcTableRow.spec.tsx index 857840cc..cdedc478 100644 --- a/src/components/shared/TcTableContainer/TcTableRow.spec.tsx +++ b/src/components/shared/TcTableContainer/TcTableRow.spec.tsx @@ -1,15 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import TcTableRow from './TcTableRow'; -import TcTableCell from './TcTableCell'; - -describe('TcTableCell', () => { - it('renders correctly with children', () => { - render(Sample Content); - const cellContent = screen.getByText('Sample Content'); - expect(cellContent).toBeInTheDocument(); - }); -}); describe('TcTableRow', () => { it('renders correctly with row data', () => { diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index 60aa02cc..d086fa7f 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -3,7 +3,7 @@ import { defaultLayout } from '../../layouts/defaultLayout'; import SEO from '../../components/global/SEO'; import TcBoxContainer from '../../components/shared/TcBox/TcBoxContainer'; import TcPublicMessageContainer from '../../components/announcements/create/publicMessageContainer/TcPublicMessageContainer'; -import TcPrivateMessageContainer from '../../components/announcements/create/privateMessaageContainer/TcPrivateMessaageContainer'; +import TcPrivateMessageContainer from '../../components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer'; import TcButton from '../../components/shared/TcButton'; import TcScheduleAnnouncement from '../../components/announcements/create/scheduleAnnouncement/'; import TcSelectPlatform from '../../components/announcements/create/selectPlatform'; @@ -15,6 +15,7 @@ import { ChannelContext } from '../../context/ChannelContext'; import { IRoles, IUser } from '../../utils/interfaces'; import { useSnackbar } from '../../context/SnackbarContext'; import { useRouter } from 'next/router'; +import SimpleBackdrop from '../../components/global/LoadingBackdrop'; export type CreateAnnouncementsPayloadDataOptions = | { channelIds: string[]; userIds?: string[]; roleIds?: string[] } @@ -48,6 +49,7 @@ function CreateNewAnnouncements() { const [channels, setChannels] = useState([]); const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(false); const platformId = community?.platforms.find( (platform) => platform.disconnectedAt === null @@ -62,13 +64,16 @@ function CreateNewAnnouncements() { const [scheduledAt, setScheduledAt] = useState(); const fetchPlatformChannels = async () => { + setLoading(true); try { if (platformId) { await retrievePlatformById(platformId); await refreshData(platformId, 'channel', undefined, undefined, false); } + setLoading(false); } catch (error) { } finally { + setLoading(false); } }; @@ -97,6 +102,7 @@ function CreateNewAnnouncements() { }; try { + setLoading(true); const data = await createNewAnnouncements(announcementsPayload); if (data) { showMessage('Announcement created successfully', 'success'); @@ -104,9 +110,15 @@ function CreateNewAnnouncements() { } } catch (error) { showMessage('Failed to create announcement', 'error'); + } finally { + setLoading(false); } }; + if (loading) { + return ; + } + return ( <> diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index 22f159c1..0f9596c8 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -15,6 +15,7 @@ import { ChannelContext } from '../../../context/ChannelContext'; import { useSnackbar } from '../../../context/SnackbarContext'; import { useToken } from '../../../context/TokenContext'; import { CreateAnnouncementsPayloadData } from '../create-new-announcements'; +import SimpleBackdrop from '../../../components/global/LoadingBackdrop'; export interface DiscordChannel { channelId: string; @@ -67,6 +68,7 @@ function Index() { const [channels, setChannels] = useState([]); const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(false); const platformId = community?.platforms.find( (platform) => platform.disconnectedAt === null @@ -99,6 +101,7 @@ function Index() { const fetchPlatformChannels = async () => { try { + setLoading(true); if (platformId) { let channelIds: string[] = []; @@ -115,6 +118,7 @@ function Index() { } } catch (error) { } finally { + setLoading(false); } }; @@ -145,8 +149,7 @@ function Index() { }; try { - console.log(id, announcementsPayload); - + setLoading(true); const data = await patchExistingAnnouncement(id, announcementsPayload); if (data) { showMessage('Announcement updated successfully', 'success'); @@ -154,9 +157,15 @@ function Index() { } } catch (error) { showMessage('Failed to create announcement', 'error'); + } finally { + setLoading(false); } }; + if (loading) { + return ; + } + return ( <> diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index 3657be7e..2b8fc85a 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -12,25 +12,66 @@ import moment from 'moment'; import { MdCalendarMonth } from 'react-icons/md'; import useAppStore from '../../store/useStore'; import { StorageService } from '../../services/StorageService'; -import { FetchedData, IDiscordModifiedCommunity } from '../../utils/interfaces'; +import { + FetchedData, + IDiscordModifiedCommunity, + IPlatformProps, +} from '../../utils/interfaces'; import TcAnnouncementsTable from '../../components/announcements/TcAnnouncementsTable'; import TcDatePickerPopover from '../../components/shared/TcDatePickerPopover'; +import TcAnnouncementsAlert from '../../components/announcements/TcAnnouncementsAlert'; +import { useToken } from '../../context/TokenContext'; function Index() { - const { retrieveAnnouncements } = useAppStore(); + const { retrieveAnnouncements, retrievePlatformById } = useAppStore(); + const { community } = useToken(); + + const [loading, setLoading] = useState(false); const communityId = StorageService.readLocalStorage('community')?.id; const [anchorEl, setAnchorEl] = useState(null); - const [activeTab, setActiveTab] = useState(0); const [selectedDate, setSelectedDate] = useState(null); const [selectedZone, setSelectedZone] = useState(moment.tz.guess()); const [dateTimeDisplay, setDateTimeDisplay] = useState('Filter Date'); - const [loading, setLoading] = useState(false); const [page, setPage] = useState(1); + const platformId = community?.platforms.find( + (platform) => platform.disconnectedAt === null + )?.id; + + const [announcementsPermissions, setAnnouncementsPermissions] = + useState(true); + + const fetchPlatform = async () => { + if (platformId) { + try { + setLoading(true); + const data: IPlatformProps = await retrievePlatformById(platformId); + const { metadata } = data; + + if (metadata) { + const announcements = metadata.permissions.Announcement; + const allPermissionsTrue = Object.values(announcements).every( + (value) => value === true + ); + + setAnnouncementsPermissions(allPermissionsTrue); + } + setLoading(false); + } catch (error) { + } finally { + setLoading(false); + } + } + }; + + useEffect(() => { + fetchPlatform(); + }, [platformId]); + const [fetchedAnnouncements, setFetchedAnnouncements] = useState( { limit: 8, @@ -114,6 +155,7 @@ function Index() { return ( <> + {!announcementsPermissions && }
{fetchedAnnouncements.results.length > 0 ? ( - +
+ +
) : (
= (set, get) => ({ return data; } catch (error) {} }, + grantWritePermissions: ({ + platformType, + moduleType, + id, + }: IGrantWritePermissionsProps) => { + location.replace( + `${BASE_URL}/platforms/request-access/${platformType}/${moduleType}/${id}` + ); + }, }); export default createPlatfromSlice; diff --git a/src/store/types/IPlatform.ts b/src/store/types/IPlatform.ts index c03294de..f88e42ca 100644 --- a/src/store/types/IPlatform.ts +++ b/src/store/types/IPlatform.ts @@ -31,6 +31,12 @@ export interface IPatchPlatformInput { }; } +export interface IGrantWritePermissionsProps { + platformType: 'discord' | 'telegram'; + moduleType: 'Announcements'; + id: string; +} + export default interface IPlatfrom { connectedPlatforms: any[]; connectNewPlatform: (platfromType: string) => void; @@ -56,4 +62,9 @@ export default interface IPlatfrom { page, limit, }: IRetrivePlatformRolesOrChannels) => void; + grantWritePermissions: ({ + platformType, + moduleType, + id, + }: IGrantWritePermissionsProps) => void; } diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 957793c5..59908f05 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -14,4 +14,17 @@ export enum StatusCode { DISCORD_AUTHORIZATION_FAILURE_FROM_SETTINGS = '1005', TWITTER_AUTHORIZATION_SUCCESSFUL = '1006', TWITTER_AUTHORIZATION_FAILURE = '1007', + ANNOUNCEMENTS_PERMISSION_SUCCESS = '1008', + ANNOUNCEMENTS_PERMISSION_FAILURE = '1009', +} + +export enum Permission { + AttachFiles = 'Attach Files', + CreatePrivateThreads = 'Create Private Threads', + CreatePublicThreads = 'Create Public Threads', + EmbedLinks = 'Embed Links', + MentionEveryone = 'Mention Everyone', + SendMessages = 'Send Messages', + SendMessagesInThreads = 'Send Messages In Threads', + ViewChannel = 'View Channel', } diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index 59165c1e..f34013e6 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -148,6 +148,27 @@ export interface IPlatformProps { metadata: metaData; } +export interface UserPermissions { + AttachFiles: boolean; + CreatePrivateThreads: boolean; + CreatePublicThreads: boolean; + EmbedLinks: boolean; + MentionEveryone: boolean; + SendMessages: boolean; + SendMessagesInThreads: boolean; + ViewChannel: boolean; +} + +export interface ReadData { + ViewChannel: boolean; + ReadMessageHistory: boolean; +} + +export interface Permissions { + permissions: UserPermissions; + ReadData: ReadData; +} + export interface ICommunityDiscordPlatfromProps { id: string; name: string; @@ -157,6 +178,7 @@ export interface ICommunityDiscordPlatfromProps { name: string; selectedChannels?: string[]; period?: string; + permissions: Permissions; analyzerStartedAt?: string; isInProgress?: boolean; }; From e3f8e16a6600cb4fc1db2048915b86eeabcf20db Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 04:12:20 +0300 Subject: [PATCH 31/58] fix style --- .../selectCommunity/TcSelectCommunity.tsx | 2 ++ .../communitySettings/platform/TcPlatform.tsx | 1 + .../platform/TcPlatformChannelDialog.tsx | 1 + src/components/global/CustomTab.tsx | 19 ++++++++++++ src/pages/centric/create-new-community.tsx | 1 + src/pages/centric/index.tsx | 1 + src/pages/centric/tac.tsx | 1 + src/utils/theme.ts | 30 +------------------ 8 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/components/centric/selectCommunity/TcSelectCommunity.tsx b/src/components/centric/selectCommunity/TcSelectCommunity.tsx index dd3619ff..d4b304db 100644 --- a/src/components/centric/selectCommunity/TcSelectCommunity.tsx +++ b/src/components/centric/selectCommunity/TcSelectCommunity.tsx @@ -111,6 +111,7 @@ function TcSelectCommunity() { text="Continue" className="secondary" variant="contained" + sx={{ width: '15rem', padding: '0.5rem' }} disabled={!activeCommunity} onClick={handleSelectedCommunity} /> @@ -124,6 +125,7 @@ function TcSelectCommunity() { } text="Create" + sx={{ width: '15rem', padding: '0.5rem' }} variant="outlined" onClick={() => router.push('/centric/create-new-community')} /> diff --git a/src/components/communitySettings/platform/TcPlatform.tsx b/src/components/communitySettings/platform/TcPlatform.tsx index 7dc798a1..86aa358b 100644 --- a/src/components/communitySettings/platform/TcPlatform.tsx +++ b/src/components/communitySettings/platform/TcPlatform.tsx @@ -165,6 +165,7 @@ function TcPlatform({ platformName = 'Discord' }: TcPlatformProps) {
diff --git a/src/components/communitySettings/platform/TcPlatformChannelDialog.tsx b/src/components/communitySettings/platform/TcPlatformChannelDialog.tsx index edf07c3b..e705a215 100644 --- a/src/components/communitySettings/platform/TcPlatformChannelDialog.tsx +++ b/src/components/communitySettings/platform/TcPlatformChannelDialog.tsx @@ -69,6 +69,7 @@ function TcPlatformChannelDialog() { setOpenDialog(false)} />
diff --git a/src/components/global/CustomTab.tsx b/src/components/global/CustomTab.tsx index 3381e444..99af64eb 100644 --- a/src/components/global/CustomTab.tsx +++ b/src/components/global/CustomTab.tsx @@ -48,6 +48,25 @@ function CustomTab({ width: '50%', padding: '0', }, + textTransform: 'none', + borderRadius: '10px 10px 0 0', + padding: '8px 24px', + gap: '10px', + borderBottom: 'none', + '&.Mui-selected': { + background: '#804EE1', + color: 'white', + border: 0, + borderBottom: 'none', + }, + '&$selected': { + borderBottom: 'none', + }, + '&:not(.Mui-selected)': { + backgroundColor: '#EDEDED', + color: '#222222', + }, + selected: {}, }} /> ))} diff --git a/src/pages/centric/create-new-community.tsx b/src/pages/centric/create-new-community.tsx index d32928c8..cf13cdaa 100644 --- a/src/pages/centric/create-new-community.tsx +++ b/src/pages/centric/create-new-community.tsx @@ -98,6 +98,7 @@ function CreateNewCommunity() { handleCreateNewCommunitie()} diff --git a/src/pages/centric/index.tsx b/src/pages/centric/index.tsx index e6b655f5..c6ea8714 100644 --- a/src/pages/centric/index.tsx +++ b/src/pages/centric/index.tsx @@ -23,6 +23,7 @@ function Index() {
discordAuthorization()} /> diff --git a/src/pages/centric/tac.tsx b/src/pages/centric/tac.tsx index 65cfbdbc..cfdff9dd 100644 --- a/src/pages/centric/tac.tsx +++ b/src/pages/centric/tac.tsx @@ -96,6 +96,7 @@ function Tac() { handleAcceptTerms()} /> diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 299635df..851c6743 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -14,10 +14,6 @@ export const theme = createTheme({ components: { MuiButton: { styleOverrides: { - // sizeMedium: { - // width: '15rem', - // padding: '0.5rem', - // }, root: { textTransform: 'none', borderRadius: '4px', @@ -109,31 +105,7 @@ export const theme = createTheme({ }, }, MuiTab: { - styleOverrides: { - // root: { - // textTransform: 'none', - // borderRadius: '10px 10px 0 0', - // padding: '8px 24px', - // width: '214px', - // height: '40px', - // gap: '10px', - // borderBottom: 'none', - // '&.Mui-selected': { - // background: '#804EE1', - // color: 'white', - // border: 0, - // borderBottom: 'none', - // }, - // '&$selected': { - // borderBottom: 'none', - // }, - // '&:not(.Mui-selected)': { - // backgroundColor: '#EDEDED', - // color: '#222222', - // }, - // selected: {}, - // }, - }, + styleOverrides: {}, }, }, }); From 7e265095f27489127cbdb921b52e16a173343256 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 11:17:12 +0300 Subject: [PATCH 32/58] fix issue on fetch data and styles --- .../announcements/TcAnnouncementsTable.tsx | 2 +- .../TcConfirmSchaduledAnnouncementsDialog.tsx | 11 ++++++ .../TcPrivateMessageContainer.tsx | 4 +-- .../TcPrivateMessagePreviewDialog.tsx | 4 +++ src/components/shared/TcBreadcrumbs.tsx | 2 -- .../edit-announcements/index.tsx | 6 +++- src/pages/announcements/index.tsx | 35 +++++++++---------- 7 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index ace0bfe0..72a6e357 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -131,7 +131,7 @@ function TcAnnouncementsTable({ colSpan={6} style={{ textAlign: 'center' }} sx={{ borderBottom: 'none' }} - className="min-h-[70vh] pt-[14rem]" + className="min-h-[70vh] pt-[20dvh]" data-testid="loading-indicator" > diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx index 7e7c9cec..027f9bec 100644 --- a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx @@ -46,6 +46,17 @@ function TcConfirmSchaduledAnnouncementsDialog({ <> setConfirmSchadulerDialog(true)} diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx index e97ddcd6..7db12e65 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx @@ -129,11 +129,11 @@ function TcPrivateMessageContainer({ if (!templateText) { templateText = item.template; + setPrivateMessage(true); } } }); - setPrivateMessage(true); setSelectedRoles(rolesArray); setSelectedUsers(usersArray); setMessage(templateText); @@ -213,7 +213,7 @@ function TcPrivateMessageContainer({ className="text-gray-400" />
-
+
channel.channelId ); } + console.log({ channelIds }); await refreshData(platformId, 'channel', channelIds, undefined, false); } @@ -130,9 +131,12 @@ function Index() { }; fetchAnnouncement(); - fetchPlatformChannels(); }, [id]); + useEffect(() => { + fetchPlatformChannels(); + }, [fetchedAnnouncements]); + const handleEditAnnouncements = async (isDrafted: boolean) => { if (!community) return; diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index 2b8fc85a..12ec5276 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -159,8 +159,8 @@ function Index() {
-
+
+
)}
- - {fetchedAnnouncements.totalResults > 8 ? ( -
- -
- ) : ( - '' - )} +
+ {fetchedAnnouncements.totalResults > 8 && ( +
+ +
+ )} +
} /> From 8e2f91aa571f6109df1fbc846dc326921ff5350e Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 13:12:46 +0300 Subject: [PATCH 33/58] fix styles on table and filter issue --- .../announcements/TcAnnouncementsTable.tsx | 221 +++++++++++------- src/pages/announcements/index.tsx | 10 +- 2 files changed, 144 insertions(+), 87 deletions(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index 72a6e357..cce6de93 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -123,33 +123,18 @@ function TcAnnouncementsTable({ return 'Unknown'; }; - const renderTableBody = () => { - if (isLoading) { - return ( - - - - - - ); - } - - return announcements.map((announcement, index) => ( - - + const renderTableCell = ( + announcement: { + draft: any; + data: any[]; + scheduledAt: string | number | Date; + id: string | null; + }, + cellType: any + ) => { + switch (cellType) { + case 'title': + return (
( + .map((type: string, index: React.Key | null | undefined) => (
-
- + ); + case 'channels': + return (
{ - const channels = item.options.channels; - if (channels && channels.length > 0) { - const displayedChannels = channels - .slice(0, 2) - .map((channel) => `#${channel.name}`) - .join(', '); - const moreChannelsIndicator = - channels.length > 2 ? '...' : ''; - return dataIndex > 0 - ? `, ${displayedChannels}${moreChannelsIndicator}` - : `${displayedChannels}${moreChannelsIndicator}`; + .map( + (item: { options: { channels: any } }, dataIndex: number) => { + const channels = item.options.channels; + if (channels && channels.length > 0) { + const displayedChannels = channels + .slice(0, 2) + .map((channel: { name: any }) => `#${channel.name}`) + .join(', '); + const moreChannelsIndicator = + channels.length > 2 ? '...' : ''; + return dataIndex > 0 + ? `, ${displayedChannels}${moreChannelsIndicator}` + : `${displayedChannels}${moreChannelsIndicator}`; + } + return ''; } - return ''; - }) - .filter((text) => text !== '') + ) + .filter((text: string) => text !== '') .join('')} variant="subtitle2" />
-
- + ); + case 'users': + return (
{ + .map((item: { options: { users: any } }) => { const users = item.options.users; if (users && users.length > 0) { const displayedUsers = users .slice(0, 2) - .map((user) => `@${user.ngu}`) + .map((user: { ngu: any }) => `@${user.ngu}`) .join(', '); const moreUsersIndicator = users.length > 2 ? '...' : ''; return `${displayedUsers}${moreUsersIndicator}`; } return ''; }) - .filter((text) => text !== '') + .filter((text: string) => text !== '') .join(', ')} variant="subtitle2" />
-
- + ); + case 'roles': + return (
{ + .map((item: { options: { roles: any } }) => { const roles = item.options.roles; if (roles && roles.length > 0) { const displayedRoles = roles .slice(0, 2) - .map((role) => role.name) + .map((role: { name: any }) => role.name) .join(', '); const moreRolesIndicator = roles.length > 2 ? '...' : ''; return `${displayedRoles}${moreRolesIndicator}`; } return ''; }) - .filter((text) => text !== '') + .filter((text: string) => text !== '') .join(', ')} variant="subtitle2" />
-
- + ); + case 'scheduledAt': + return (
-
- - handleClick(event, announcement.id)} - > - - - + handleClick(event, announcement.id)} + > + + + + handleEdit(announcement.id)}> + + Edit + + + + Delete + + + + ); + default: + return null; + } + }; + + const renderTableBody = () => { + if (isLoading) { + return ( + + - handleEdit(announcement.id)}> - - Edit - - - - Delete - - - + + +
+ ); + } + + return announcements.map((announcement, index) => ( + + {['title', 'channels', 'users', 'roles', 'scheduledAt', 'actions'].map( + (cellType, cellIndex, array) => ( + + {renderTableCell(announcement, cellType)} + + ) + )} )); }; diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index 12ec5276..a055b024 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -21,6 +21,7 @@ import TcAnnouncementsTable from '../../components/announcements/TcAnnouncements import TcDatePickerPopover from '../../components/shared/TcDatePickerPopover'; import TcAnnouncementsAlert from '../../components/announcements/TcAnnouncementsAlert'; import { useToken } from '../../context/TokenContext'; +import SimpleBackdrop from '../../components/global/LoadingBackdrop'; function Index() { const { retrieveAnnouncements, retrievePlatformById } = useAppStore(); @@ -28,6 +29,7 @@ function Index() { const { community } = useToken(); const [loading, setLoading] = useState(false); + const [isFirstLoad, setIsFirstLoad] = useState(true); const communityId = StorageService.readLocalStorage('community')?.id; @@ -64,6 +66,7 @@ function Index() { } catch (error) { } finally { setLoading(false); + if (isFirstLoad) setIsFirstLoad(false); } } }; @@ -75,7 +78,7 @@ function Index() { const [fetchedAnnouncements, setFetchedAnnouncements] = useState( { limit: 8, - page: 1, + page: page, results: [], totalPages: 0, totalResults: 0, @@ -96,6 +99,7 @@ function Index() { const handleDateChange = (date: Date | null) => { if (date) { setSelectedDate(date); + setPage(1); const fullDateTime = moment(date); setDateTimeDisplay(fullDateTime.format('D MMMM YYYY')); @@ -152,6 +156,10 @@ function Index() { setPage(selectedPage); }; + if (isFirstLoad && loading) { + return ; + } + return ( <> From db7a78cf46c302e38ec8d36cd3c7ad54e8aae461 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 15:16:04 +0300 Subject: [PATCH 34/58] fix issue --- src/components/announcements/TcAnnouncementsTable.tsx | 2 +- .../privateMessaageContainer/TcPrivateMessageContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index cce6de93..aed0614d 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -128,7 +128,7 @@ function TcAnnouncementsTable({ draft: any; data: any[]; scheduledAt: string | number | Date; - id: string | null; + id: string; }, cellType: any ) => { diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx index 7db12e65..2e29d779 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx @@ -256,7 +256,7 @@ function TcPrivateMessageContainer({
From 079cc76f82dd8b7fc1b9f5746cb9dc4ed5cf020e Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 19 Jan 2024 15:18:03 +0300 Subject: [PATCH 35/58] updaet button style --- .../TcConfirmSchaduledAnnouncementsDialog.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx index 027f9bec..7e7c9cec 100644 --- a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx @@ -46,17 +46,6 @@ function TcConfirmSchaduledAnnouncementsDialog({ <> setConfirmSchadulerDialog(true)} From bbd1d1fefb97b8f129a5113fb699864dd198cccb Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 10:48:47 +0300 Subject: [PATCH 36/58] change button color --- .../create/scheduleAnnouncement/TcScheduleAnnouncement.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index d8ddc3a0..4fd198af 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -97,7 +97,6 @@ function TcScheduleAnnouncement({
} disableElevation={true} className="border border-black bg-gray-100 shadow-md" From 827d489ded50f7dd83fbef5a2bc3efb75119bad8 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 14:26:32 +0300 Subject: [PATCH 37/58] fix issue on announcements --- .../announcements/TcAnnouncementsTable.tsx | 105 ++++++++++-------- .../TcRolesAutoComplete.tsx | 22 ++-- .../TcUsersAutoComplete.tsx | 22 ++-- .../TcScheduleAnnouncement.tsx | 5 +- .../pages/pageIndex/HeatmapChart.tsx | 2 - .../edit-announcements/index.tsx | 6 +- src/store/slices/announcementsSlice.ts | 2 - 7 files changed, 94 insertions(+), 70 deletions(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index aed0614d..84467022 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -318,55 +318,66 @@ function TcAnnouncementsTable({ const renderTableBody = () => { if (isLoading) { return ( - - - - - - ); - } - - return announcements.map((announcement, index) => ( - - {['title', 'channels', 'users', 'roles', 'scheduledAt', 'actions'].map( - (cellType, cellIndex, array) => ( + + - {renderTableCell(announcement, cellType)} + - ) - )} - - )); + + + ); + } + + return ( + + {announcements.map((announcement, index) => ( + + {[ + 'title', + 'channels', + 'users', + 'roles', + 'scheduledAt', + 'actions', + ].map((cellType, cellIndex, array) => ( + + {renderTableCell(announcement, cellType)} + + ))} + + ))} + + ); }; return ( @@ -452,7 +463,7 @@ function TcAnnouncementsTable({ > - {renderTableBody()} + {renderTableBody()} ([]); const [fetchedRoles, setFetchedRoles] = useState({ - limit: 8, + limit: 100, page: 1, results: [], totalPages: 0, @@ -92,16 +92,16 @@ function TcRolesAutoComplete({ if (inputValue === '') { setFilteredRolesByName(''); setFetchedRoles({ - limit: 8, + limit: 100, page: 1, results: [], totalPages: 0, totalResults: 0, }); - debouncedFetchDiscordRoles(platformId, 1, 8); + debouncedFetchDiscordRoles(platformId, 1, 100); } else { - debouncedFetchDiscordRoles(platformId, 1, 8, inputValue); + debouncedFetchDiscordRoles(platformId, 1, 100, inputValue); } }; @@ -153,7 +153,13 @@ function TcRolesAutoComplete({ value={selectedRoles} onChange={handleChange} onInputChange={handleSearchChange} + isOptionEqualToValue={(option, value) => option.roleId === value.roleId} disableCloseOnSelect + renderOption={(props, option) => ( +
  • + {option.name} +
  • + )} renderTags={(value, getTagProps) => value.map((option, index) => ( ([]); const [fetchedUsers, setFetchedUsers] = useState({ - limit: 8, + limit: 100, page: 1, results: [], totalPages: 0, totalResults: 0, }); - const [filteredRolesByName, setFilteredRolesByName] = useState(''); + const [filteredUsersByName, setFilteredUsersByName] = useState(''); const [isInitialized, setIsInitialized] = useState(false); const fetchDiscordUsers = async ( @@ -54,7 +54,7 @@ function TcUsersAutoComplete({ }); if (ngu) { - setFilteredRolesByName(ngu); + setFilteredUsersByName(ngu); setFetchedUsers(fetchedUsers); } else { setFetchedUsers((prevData: { results: any }) => { @@ -90,18 +90,18 @@ function TcUsersAutoComplete({ if (!platformId) return; if (inputValue === '') { - setFilteredRolesByName(''); + setFilteredUsersByName(''); setFetchedUsers({ - limit: 8, + limit: 100, page: 1, results: [], totalPages: 0, totalResults: 0, }); - debouncedFetchDiscordUsers(platformId, 1, 8); + debouncedFetchDiscordUsers(platformId, 1, 100); } else { - debouncedFetchDiscordUsers(platformId, 1, 8, inputValue); + debouncedFetchDiscordUsers(platformId, 1, 100, inputValue); } }; @@ -153,7 +153,15 @@ function TcUsersAutoComplete({ value={selectedUsers} onChange={handleChange} onInputChange={handleSearchChange} + isOptionEqualToValue={(option, value) => + option.discordId === value.discordId + } disableCloseOnSelect + renderOption={(props, option) => ( +
  • + {option.ngu} +
  • + )} renderTags={(value, getTagProps) => value.map((option, index) => ( { - if (isEdit) { + if (isEdit && preSelectedTime) { const date = moment(preSelectedTime); + setSelectedDate(date.toDate()); setSelectedTime(date.toDate()); setDateTimeDisplay(date.format('D MMMM YYYY @ h A')); } - }, [isEdit]); + }, [isEdit, preSelectedTime]); return (
    diff --git a/src/components/pages/pageIndex/HeatmapChart.tsx b/src/components/pages/pageIndex/HeatmapChart.tsx index 526075d2..d578ea17 100644 --- a/src/components/pages/pageIndex/HeatmapChart.tsx +++ b/src/components/pages/pageIndex/HeatmapChart.tsx @@ -58,8 +58,6 @@ const HeatmapChart = () => { const fetchData = async () => { setLoading(true); try { - console.log({ selectedSubChannels }); - if (platformId) { const data = await fetchHeatmapData( platformId, diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index e67287d4..ee475759 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -113,7 +113,6 @@ function Index() { (channel) => channel.channelId ); } - console.log({ channelIds }); await refreshData(platformId, 'channel', channelIds, undefined, false); } @@ -127,7 +126,9 @@ function Index() { if (!id) return; const fetchAnnouncement = async () => { const data = await retrieveAnnouncementById(id); + setFetchedAnnouncements(data); + setScheduledAt(data.scheduledAt); }; fetchAnnouncement(); @@ -155,9 +156,10 @@ function Index() { try { setLoading(true); const data = await patchExistingAnnouncement(id, announcementsPayload); + if (data) { showMessage('Announcement updated successfully', 'success'); - router.push('/announcements'); + location.replace('/announcements'); } } catch (error) { showMessage('Failed to create announcement', 'error'); diff --git a/src/store/slices/announcementsSlice.ts b/src/store/slices/announcementsSlice.ts index 1d2a06fb..9ad03234 100644 --- a/src/store/slices/announcementsSlice.ts +++ b/src/store/slices/announcementsSlice.ts @@ -63,8 +63,6 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ announcementPayload: CreateAnnouncementsPayload ) => { try { - console.log({ id }); - const { data } = await axiosInstance.patch( `/announcements/${id}`, announcementPayload From fef90c45e98c6bda62dd72e1b43efd74af184355 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 14:46:46 +0300 Subject: [PATCH 38/58] fix filtering issue on users --- src/store/slices/announcementsSlice.ts | 2 -- src/store/slices/platformSlice.ts | 3 +++ src/store/types/IAnnouncements.ts | 1 - src/store/types/IPlatform.ts | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/store/slices/announcementsSlice.ts b/src/store/slices/announcementsSlice.ts index 9ad03234..984afcd4 100644 --- a/src/store/slices/announcementsSlice.ts +++ b/src/store/slices/announcementsSlice.ts @@ -10,7 +10,6 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ page, limit, sortBy, - ngu, timeZone, startDate, endDate, @@ -24,7 +23,6 @@ const createAnnouncementsSlice: StateCreator = (set, get) => ({ ...(timeZone ? { timeZone } : {}), ...(startDate ? { startDate } : {}), ...(endDate ? { endDate } : {}), - ...(ngu ? { name } : {}), }; const { data } = await axiosInstance.get( diff --git a/src/store/slices/platformSlice.ts b/src/store/slices/platformSlice.ts index 243096e1..8bee293b 100644 --- a/src/store/slices/platformSlice.ts +++ b/src/store/slices/platformSlice.ts @@ -72,6 +72,7 @@ const createPlatfromSlice: StateCreator = (set, get) => ({ platformId, property = 'channel', name, + ngu, sortBy, page, limit, @@ -89,6 +90,8 @@ const createPlatfromSlice: StateCreator = (set, get) => ({ if (name) params.append('name', name); + if (ngu) params.append('ngu', ngu); + if (page !== undefined) { params.append('page', page.toString()); } diff --git a/src/store/types/IAnnouncements.ts b/src/store/types/IAnnouncements.ts index 37937eac..224c3f15 100644 --- a/src/store/types/IAnnouncements.ts +++ b/src/store/types/IAnnouncements.ts @@ -2,7 +2,6 @@ export interface IRetrieveAnnouncementsProps { page: number; limit: number; sortBy?: string; - ngu?: string; community: string; startDate?: string; endDate?: string; diff --git a/src/store/types/IPlatform.ts b/src/store/types/IPlatform.ts index f88e42ca..16c7574d 100644 --- a/src/store/types/IPlatform.ts +++ b/src/store/types/IPlatform.ts @@ -13,6 +13,7 @@ export interface IRetrivePlatformRolesOrChannels { limit?: number; sortBy?: string; name?: string; + ngu?: string; platformId: string; property: 'channel' | 'role' | 'guildMember'; } From f9fefb51f764a8e102a091d8bf70b1c903622597 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 14:50:53 +0300 Subject: [PATCH 39/58] change badge color for drafted announcements --- src/components/announcements/TcAnnouncementsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index 84467022..87260ff3 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -143,7 +143,7 @@ function TcAnnouncementsTable({ style={{ height: '8px', width: '8px', - background: announcement.draft ? '#3A9E2B' : '#FF9022', + background: !announcement.draft ? '#3A9E2B' : '#FF9022', borderRadius: '50%', display: 'inline-block', marginRight: '5px', From b57a5edde8c31b73907eac91caff51f02f4831a0 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 14:57:49 +0300 Subject: [PATCH 40/58] remove ngu from interface --- src/store/types/IAnnouncements.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/store/types/IAnnouncements.ts b/src/store/types/IAnnouncements.ts index 224c3f15..672897f2 100644 --- a/src/store/types/IAnnouncements.ts +++ b/src/store/types/IAnnouncements.ts @@ -13,7 +13,6 @@ export default interface IAnnouncements { page, limit, sortBy, - ngu, community, }: IRetrieveAnnouncementsProps) => void; } From 1f9831714304a2dbf5cf1025bba77d7ee6b2fd2b Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 15:10:43 +0300 Subject: [PATCH 41/58] add statement for null values on table --- src/components/announcements/TcAnnouncementsTable.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index 87260ff3..df28b871 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -149,7 +149,12 @@ function TcAnnouncementsTable({ marginRight: '5px', }} /> - {truncateCenter(announcement.data[0].template, 20)} + {announcement && + announcement.data && + announcement.data[0] && + announcement.data[0].template + ? truncateCenter(announcement.data[0]?.template, 20) + : ''} } variant="subtitle2" From dd98d3701b377f3230ccea2f5566190612cd90cc Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 22 Jan 2024 18:32:39 +0300 Subject: [PATCH 42/58] update schadule announcement moment filter and time-picker --- .../create/scheduleAnnouncement/TcDateTimePopover.tsx | 1 + .../create/scheduleAnnouncement/TcScheduleAnnouncement.tsx | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/announcements/create/scheduleAnnouncement/TcDateTimePopover.tsx b/src/components/announcements/create/scheduleAnnouncement/TcDateTimePopover.tsx index 3cae4feb..fbe8f715 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcDateTimePopover.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcDateTimePopover.tsx @@ -52,6 +52,7 @@ function TcDateTimePopover({ , diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index 293d4d57..e68e28cb 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -46,14 +46,13 @@ function TcScheduleAnnouncement({ const handleTimeChange = (time: Date | null) => { if (time) { setSelectedTime(time); - handleClose(); if (selectedDate) { const fullDateTime = moment(selectedDate).set({ hour: time.getHours(), minute: time.getMinutes(), }); - setDateTimeDisplay(fullDateTime.format('D MMMM YYYY @ h A')); + setDateTimeDisplay(fullDateTime.format('D MMMM YYYY @ hh:mm A')); } } }; @@ -79,7 +78,7 @@ function TcScheduleAnnouncement({ setSelectedDate(date.toDate()); setSelectedTime(date.toDate()); - setDateTimeDisplay(date.format('D MMMM YYYY @ h A')); + setDateTimeDisplay(date.format('D MMMM YYYY @ hh:mm A')); } }, [isEdit, preSelectedTime]); @@ -101,7 +100,7 @@ function TcScheduleAnnouncement({ startIcon={} disableElevation={true} className="border border-black bg-gray-100 shadow-md" - sx={{ color: 'black', height: '2.4rem' }} + sx={{ color: 'black', height: '2.4rem', paddingX: '1rem' }} aria-describedby={id} onClick={handleOpen} /> From 7760bb7a052b58f898bb5d5a805523c34e9c58cf Mon Sep 17 00:00:00 2001 From: zuies Date: Tue, 23 Jan 2024 12:27:14 +0300 Subject: [PATCH 43/58] add channels permission hints component --- .../TcPublicMessageContainer.tsx | 62 +--- .../platform/TcPlatformChannelList.tsx | 3 +- .../TcPermissionHints.spec.tsx | 64 ++++ .../TcPermissionHints/TcPermissionHints.tsx | 332 ++++++++++++++++++ .../global/TcPermissionHints/index.ts | 3 + src/context/ChannelContext.tsx | 1 + 6 files changed, 404 insertions(+), 61 deletions(-) create mode 100644 src/components/global/TcPermissionHints/TcPermissionHints.spec.tsx create mode 100644 src/components/global/TcPermissionHints/TcPermissionHints.tsx create mode 100644 src/components/global/TcPermissionHints/index.ts diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx index 203963f0..c48f9ef0 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -16,6 +16,7 @@ import { ChannelContext } from '../../../../context/ChannelContext'; import TcPlatformChannelList from '../../../communitySettings/platform/TcPlatformChannelList'; import { IGuildChannels } from '../../../../utils/types'; import { DiscordData } from '../../../../pages/announcements/edit-announcements'; +import TcPermissionHints from '../../../global/TcPermissionHints'; export interface FlattenedChannel { id: string; @@ -143,66 +144,7 @@ function TcPublicMessageContainer({
    -
    - - - } - > - - - -
    - -
      -
    1. - Navigate to the channel you want to import on{' '} - - Discord - -
    2. -
    3. - Go to the settings for that specific channel (select - the wheel on the right of the channel name) -
    4. -
    5. - Select Permissions (left sidebar), and then in - the middle of the screen check{' '} - Advanced permissions -
    6. -
    7. - With the TogetherCrew Bot selected, under - Advanced Permissions, make sure that [View channel] - and [Write Access] are marked as [✓] -
    8. -
    9. - Select the plus sign to the right of Roles/Members and - under members select TogetherCrew bot -
    10. -
    11. - Click on the Refresh List button on this window - and select the new channels -
    12. -
    -
    -
    -
    -
    +
    diff --git a/src/components/communitySettings/platform/TcPlatformChannelList.tsx b/src/components/communitySettings/platform/TcPlatformChannelList.tsx index 9baa339d..13bd52e4 100644 --- a/src/components/communitySettings/platform/TcPlatformChannelList.tsx +++ b/src/components/communitySettings/platform/TcPlatformChannelList.tsx @@ -95,7 +95,8 @@ function TcPlatformChannelList({ } disabled={channel?.subChannels?.some( (subChannel) => - !subChannel.canReadMessageHistoryAndViewChannel + !subChannel.canReadMessageHistoryAndViewChannel || + !subChannel.announcementAccess )} /> } diff --git a/src/components/global/TcPermissionHints/TcPermissionHints.spec.tsx b/src/components/global/TcPermissionHints/TcPermissionHints.spec.tsx new file mode 100644 index 00000000..13710ad1 --- /dev/null +++ b/src/components/global/TcPermissionHints/TcPermissionHints.spec.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import PermissionHints from './TcPermissionHints'; +describe('PermissionHints Component', () => { + test('renders PermissionHints component', () => { + render(); + expect(screen.getByText('Access Settings')).toBeInTheDocument(); + expect(screen.getByText('Server Level')).toBeInTheDocument(); + expect(screen.getByText('Category Level')).toBeInTheDocument(); + expect(screen.getByText('Channel Level')).toBeInTheDocument(); + }); + + test('initial active category is Access Settings', async () => { + render(); + await waitFor(() => { + expect( + screen.getByText('What does “Read” and “Write” access mean?') + ).toBeInTheDocument(); + }); + }); + + test('clicking on a category button changes active category to Server Level', async () => { + render(); + const serverLevelButton = screen.getByText('Server Level'); + userEvent.click(serverLevelButton); + await waitFor(() => { + expect( + screen.getByText( + 'Please note that your platform’s permission settings enable the above permission controls' + ) + ).toBeInTheDocument(); + }); + }); + + test('clicking on a category button changes active category to Category Level', async () => { + render(); + const categoryLevelButton = screen.getByText('Category Level'); + userEvent.click(categoryLevelButton); + await waitFor(() => { + expect( + screen.getByText( + 'Please note that Category-level permissions override Server-level permissions' + ) + ).toBeInTheDocument(); + }); + }); + + test('clicking on a category button changes active category to Channel Level', async () => { + render(); + + const channelLevelButton = screen.getByText('Channel Level'); + + userEvent.click(channelLevelButton); + + await waitFor(() => { + expect( + screen.getByText( + 'Please note that Channel-level permissions override Category-level permissions, which in turn override Server-level permissions' + ) + ).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/global/TcPermissionHints/TcPermissionHints.tsx b/src/components/global/TcPermissionHints/TcPermissionHints.tsx new file mode 100644 index 00000000..9553dfd6 --- /dev/null +++ b/src/components/global/TcPermissionHints/TcPermissionHints.tsx @@ -0,0 +1,332 @@ +import React, { useState } from 'react'; +import TcButtonGroup from '../../shared/TcButtonGroup'; +import TcButton from '../../shared/TcButton'; +import clsx from 'clsx'; +import TcText from '../../shared/TcText'; + +const permissionCategories = [ + 'Access Settings', + 'Server Level', + 'Category Level', + 'Channel Level', +]; + +function PermissionHints() { + const [activeCategory, setActiveCategory] = + useState('Access Settings'); + + const handleButtonClick = (category: string) => { + setActiveCategory(category); + }; + + const getDescription = (category: string) => { + switch (category) { + case 'Access Settings': + return ( +
    + +
      +
    1. + +
        +
      • + +
      • +
      • + +
      • +
      +
    2. +
    +
      +
    1. + +
        +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      +
    2. +
    +
    + ); + case 'Server Level': + return ( +
    + +
      +
    1. + + Navigate to the “Server Settings” in the top-left + corner of Discord + + } + variant="subtitle1" + /> +
    2. +
    3. + + Select “Role/Members” (left sidebar), and then in + the middle of the screen check Advanced permissions + + } + variant="subtitle1" + /> +
    4. +
    5. + + Then select “TogetherCrew” and under Advanced + Permissions, make sure that the following are marked as + [✓] + + } + variant="subtitle1" + /> + +
        +
      1. + +
      2. +
      3. + +
      4. +
      +
    6. +
    + + Finally: Click on the Refresh List button on this page + and select the channels that have now been made available to + you + + } + variant="subtitle1" + /> +
    + ); + case 'Category Level': + return ( +
    + +
      +
    1. + + Navigate to the “Edit Category” in the top-left + corner of Discord + + } + variant="subtitle1" + /> +
    2. +
    3. + + Select “Permissions” (left sidebar), and then in + the middle of the screen check Advanced permissions + + } + variant="subtitle1" + /> +
    4. +
    5. + + Then select “TogetherCrew” and under Advanced + Permissions, make sure that the following are marked as + [✓] + + } + variant="subtitle1" + /> + +
        +
      1. + +
      2. +
      3. + +
      4. +
      +
    6. +
    + + Finally: Click on the Refresh List button on this page + and select the channels that have now been made available to + you + + } + variant="subtitle1" + /> +
    + ); + case 'Channel Level': + return ( +
    + +
      +
    1. + + Navigate to the settings for a specific channel (select + the wheel on the right of the channel name) + + } + variant="subtitle1" + /> +
    2. +
    3. + + Select “Permissions” (left sidebar), and then in + the middle of the screen check Advanced permissions + + } + variant="subtitle1" + /> +
    4. +
    5. + + Then select “TogetherCrew” and under Advanced + Permissions, make sure that the following are marked as + [✓] + + } + variant="subtitle1" + /> + +
        +
      1. + +
      2. +
      3. + +
      4. +
      +
    6. +
    + + Finally: Click on the Refresh List button on this page + and select the channels that have now been made available to + you + + } + variant="subtitle1" + /> +
    + ); + default: + return null; + } + }; + + return ( +
    + + {permissionCategories.map((category) => ( + handleButtonClick(category)} + className={clsx( + 'border', + category === activeCategory + ? 'bg-secondary text-white border-secondary' + : 'border-secondary bg-white text-secondary' + )} + sx={{ + width: 'auto', + padding: { + xs: 'auto', + sm: '0.4rem 1rem', + }, + }} + /> + ))} + +
    {getDescription(activeCategory)}
    +
    + ); +} + +export default PermissionHints; diff --git a/src/components/global/TcPermissionHints/index.ts b/src/components/global/TcPermissionHints/index.ts new file mode 100644 index 00000000..3bfc5a13 --- /dev/null +++ b/src/components/global/TcPermissionHints/index.ts @@ -0,0 +1,3 @@ +import { default as TcPermissionHints } from './TcPermissionHints'; + +export default TcPermissionHints; diff --git a/src/context/ChannelContext.tsx b/src/context/ChannelContext.tsx index 442188a4..deb54d0f 100644 --- a/src/context/ChannelContext.tsx +++ b/src/context/ChannelContext.tsx @@ -6,6 +6,7 @@ export interface SubChannel { name: string; parentId: string; canReadMessageHistoryAndViewChannel: boolean; + announcementAccess: boolean; } export interface Channel { From 98fcb3ad32b812c82abf093b5b0b1a2956da69f3 Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 24 Jan 2024 10:11:40 +0300 Subject: [PATCH 44/58] update announcements design --- .../TcPrivateMessageContainer.tsx | 7 +++-- .../TcPublicMessageContainer.tsx | 27 +++++++++++-------- src/components/layouts/Sidebar.tsx | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx index 2e29d779..e92c7b01 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx @@ -147,7 +147,11 @@ function TcPrivateMessageContainer({ - + - { let flattened: FlattenedChannel[] = []; @@ -71,7 +67,9 @@ function TcPublicMessageContainer({ setSelectedChannels(flattenChannels(channels)); }, [channels, selectedSubChannels]); - const [message, setMessage] = useState(''); + const [message, setMessage] = useState( + `This message was sent to you because you’re part of ${community?.name}. To verify the legitimacy of this message, see the official announcement here ⁠👥together-crew⁠ and verify the bot ID If you don’t want to receive any more private message from ${community?.name}, please adjust your settings here: https://app.togethercrew.com/community-settings/` + ); const handleChange = (event: React.ChangeEvent) => { setMessage(event.target.value); @@ -121,7 +119,7 @@ function TcPublicMessageContainer({
    @@ -148,13 +146,20 @@ function TcPublicMessageContainer({
    - +
    + + +
    { > {el.icon}
    -

    {el.name}

    +

    {el.name}

    )); From 59498f803a123b4e912f0ace70a33438e564c913 Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 24 Jan 2024 13:03:50 +0300 Subject: [PATCH 45/58] update PublicMessageContainer test cases --- .../TcPublicMessageContainer.spec.tsx | 107 +++++++----------- src/context/TokenContext.tsx | 2 +- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx index 56b1abfb..17b3daf7 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx @@ -1,89 +1,60 @@ import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; +import { render, screen } from '@testing-library/react'; import TcPublicMessageContainer from './TcPublicMessageContainer'; -import { ChannelContext } from '../../../../context/ChannelContext'; +import { TokenContext } from '../../../../context/TokenContext'; -const mockChannels = [ - { channelId: '1131241242', title: 'Channel 1', subChannels: [] }, - { channelId: '1242512553', title: 'Channel 2', subChannels: [] }, -]; +const mockToken = { + accessToken: 'mockAccessToken', + refreshToken: 'mockRefreshToken', +}; -const mockSelectedSubChannels = { - channel1: { '1131241242': true }, - channel2: { '1242512553': true }, +const mockCommunity = { + name: 'Test Community', + platforms: [], + id: 'mockCommunityId', + users: [], + avatarURL: 'mockAvatarURL', }; -const mockChannelContext = { - channels: mockChannels, - selectedSubChannels: mockSelectedSubChannels, - loading: false, - refreshData: jest.fn(), - handleSubChannelChange: jest.fn(), - handleSelectAll: jest.fn(), - updateSelectedSubChannels: jest.fn(), +const mockTokenContextValue = { + token: mockToken, + community: mockCommunity, + updateToken: jest.fn(), + updateCommunity: jest.fn(), + deleteCommunity: jest.fn(), + clearToken: jest.fn(), }; -describe('TcPublicMessageContainer Tests', () => { - // Helper function to render the component with the necessary context - const renderComponent = (handlePublicAnnouncements = jest.fn()) => +describe('TcPublicMessageContainer', () => { + beforeEach(() => { render( - - - + + + ); - - test('renders the component without crashing', () => { - renderComponent(); - expect(screen.getByText('Public Message')).toBeInTheDocument(); }); - test('initial state is set correctly', () => { - renderComponent(); - expect(screen.getByPlaceholderText('Write your message here')).toHaveValue( - '' - ); + it('renders the "Public Message" text', () => { + expect(screen.getByText(/Public Message/i)).toBeInTheDocument(); }); - test('allows the user to enter a message', () => { - renderComponent(); - const messageInput = screen.getByPlaceholderText( - 'Write your message here' - ) as HTMLInputElement; - fireEvent.change(messageInput, { target: { value: 'Test Message' } }); - expect(messageInput.value).toBe('Test Message'); + it('renders the "Send message to:" text', () => { + expect(screen.getByText(/Send message to:/i)).toBeInTheDocument(); }); - test('select channels dropdown is rendered', () => { - renderComponent(); - expect(screen.getByLabelText('Select Channels')).toBeInTheDocument(); + it('renders the message about bot delivery', () => { + const message = + /Our bot will deliver the announcement across chosen channels with the necessary access to share the specified message\./i; + expect(screen.getByText(message)).toBeInTheDocument(); }); - test('handlePublicAnnouncements is called with correct data', () => { - const handlePublicAnnouncementsMock = jest.fn(); - renderComponent(handlePublicAnnouncementsMock); - - // Assuming there is a way to select channels in your UI, simulate that - // For example, if there's a button to confirm channel selection: - // fireEvent.click(screen.getByText('Confirm Channels')); - - // Simulate entering a message - const messageInput = screen.getByPlaceholderText( - 'Write your message here' - ) as HTMLInputElement; - fireEvent.change(messageInput, { target: { value: 'Test Message' } }); - - // Assuming the function is called on some action, like a form submission or button click - // fireEvent.click(screen.getByText('Submit')); + it('renders the "Write message here:" text', () => { + expect(screen.getByText(/Write message here:/i)).toBeInTheDocument(); + }); - // Check if handlePublicAnnouncementsMock was called correctly - // Expect the mock to have been called with expected message and channels data - // This will depend on how your component calls the handlePublicAnnouncements function - expect(handlePublicAnnouncementsMock).toHaveBeenCalledWith({ - message: 'Test Message', - selectedChannels: expect.anything(), // Replace with specific expectation - }); + it('renders the auto-generated safety message prompt', () => { + const message = + /If you don’t write a custom message then this auto-generated safety message wlll be sent out/i; + expect(screen.getByText(message)).toBeInTheDocument(); }); }); diff --git a/src/context/TokenContext.tsx b/src/context/TokenContext.tsx index becf358c..25852924 100644 --- a/src/context/TokenContext.tsx +++ b/src/context/TokenContext.tsx @@ -21,7 +21,7 @@ type TokenContextType = { clearToken: () => void; }; -const TokenContext = createContext(null); +export const TokenContext = createContext(null); type TokenProviderProps = { children: ReactNode; From db628bdf244fe0c7ae5d22617e68cc13cbc58471 Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 24 Jan 2024 14:51:01 +0300 Subject: [PATCH 46/58] update font sizes --- .../TcPermissionHints/TcPermissionHints.tsx | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/components/global/TcPermissionHints/TcPermissionHints.tsx b/src/components/global/TcPermissionHints/TcPermissionHints.tsx index 9553dfd6..10d023d7 100644 --- a/src/components/global/TcPermissionHints/TcPermissionHints.tsx +++ b/src/components/global/TcPermissionHints/TcPermissionHints.tsx @@ -26,21 +26,21 @@ function PermissionHints() {
      • - +
      • - +
    1. @@ -49,34 +49,31 @@ function PermissionHints() {
      • - +
      • - +
      • - +
      • - +
      • - +
      • - +
      @@ -89,7 +86,7 @@ function PermissionHints() {
      1. @@ -100,7 +97,7 @@ function PermissionHints() { corner of Discord } - variant="subtitle1" + variant="body2" />
      2. @@ -111,7 +108,7 @@ function PermissionHints() { the middle of the screen check Advanced permissions } - variant="subtitle1" + variant="body2" />
      3. @@ -123,20 +120,20 @@ function PermissionHints() { [✓] } - variant="subtitle1" + variant="body2" />
        @@ -150,7 +147,7 @@ function PermissionHints() { you } - variant="subtitle1" + variant="body2" />
      ); @@ -159,7 +156,7 @@ function PermissionHints() {
      1. @@ -170,7 +167,7 @@ function PermissionHints() { corner of Discord } - variant="subtitle1" + variant="body2" />
      2. @@ -181,7 +178,7 @@ function PermissionHints() { the middle of the screen check Advanced permissions } - variant="subtitle1" + variant="body2" />
      3. @@ -193,20 +190,20 @@ function PermissionHints() { [✓] } - variant="subtitle1" + variant="body2" />
        @@ -220,7 +217,7 @@ function PermissionHints() { you } - variant="subtitle1" + variant="body2" />
      ); @@ -229,7 +226,7 @@ function PermissionHints() {
      1. @@ -240,7 +237,7 @@ function PermissionHints() { the wheel on the right of the channel name) } - variant="subtitle1" + variant="body2" />
      2. @@ -251,7 +248,7 @@ function PermissionHints() { the middle of the screen check Advanced permissions } - variant="subtitle1" + variant="body2" />
      3. @@ -263,20 +260,20 @@ function PermissionHints() { [✓] } - variant="subtitle1" + variant="body2" />
        @@ -290,7 +287,7 @@ function PermissionHints() { you } - variant="subtitle1" + variant="body2" />
      ); From ece4bb3bb1d0c3bf181849667db2aaceeb761ffb Mon Sep 17 00:00:00 2001 From: zuies Date: Wed, 24 Jan 2024 17:07:56 +0300 Subject: [PATCH 47/58] update sidebar items --- src/components/layouts/Sidebar.tsx | 2 +- src/components/layouts/xs/SidebarXs.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/layouts/Sidebar.tsx b/src/components/layouts/Sidebar.tsx index 36526a4f..64cfeffa 100644 --- a/src/components/layouts/Sidebar.tsx +++ b/src/components/layouts/Sidebar.tsx @@ -62,7 +62,7 @@ const Sidebar = () => { ), }, { - name: 'Announcements', + name: 'Smart Announcements', path: '/announcements', icon: ( { /> ), }, + { + name: 'Smart Announcements', + path: '/announcements', + icon: ( + + ), + }, { name: 'Community Settings', path: '/community-settings', icon: ( ), }, From 2dfe3d9d41b656a12ec3e2c413b01ca4eae5e81d Mon Sep 17 00:00:00 2001 From: zuies Date: Thu, 25 Jan 2024 13:14:05 +0300 Subject: [PATCH 48/58] add loading for autocomplete user and role --- .../TcRolesAutoComplete.tsx | 20 ++++++++-- .../TcUsersAutoComplete.tsx | 37 +++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx index 0d7d7b54..9fa74550 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcRolesAutoComplete.tsx @@ -4,7 +4,7 @@ import useAppStore from '../../../../store/useStore'; import { FetchedData, IRoles } from '../../../../utils/interfaces'; import { debounce } from '../../../../helpers/helper'; import TcAutocomplete from '../../../shared/TcAutocomplete'; -import { Chip } from '@mui/material'; +import { Chip, CircularProgress } from '@mui/material'; interface ITcRolesAutoCompleteProps { isEdit?: boolean; @@ -29,7 +29,7 @@ function TcRolesAutoComplete({ const [selectedRoles, setSelectedRoles] = useState([]); const [fetchedRoles, setFetchedRoles] = useState({ - limit: 100, + limit: 8, page: 1, results: [], totalPages: 0, @@ -37,6 +37,7 @@ function TcRolesAutoComplete({ }); const [filteredRolesByName, setFilteredRolesByName] = useState(''); const [isInitialized, setIsInitialized] = useState(false); + const [isLoading, setIsLoading] = useState(false); const fetchDiscordRoles = async ( platformId: string, @@ -45,6 +46,8 @@ function TcRolesAutoComplete({ name?: string ) => { try { + setIsLoading(true); + const fetchedRoles = await retrievePlatformProperties({ platformId, name: name, @@ -73,7 +76,10 @@ function TcRolesAutoComplete({ }; }); } - } catch (error) {} + } catch (error) { + } finally { + setIsLoading(false); + } }; useEffect(() => { @@ -92,7 +98,7 @@ function TcRolesAutoComplete({ if (inputValue === '') { setFilteredRolesByName(''); setFetchedRoles({ - limit: 100, + limit: 8, page: 1, results: [], totalPages: 0, @@ -149,6 +155,12 @@ function TcRolesAutoComplete({ getOptionLabel={(option) => option.name} label={'Select Role(s)'} multiple={true} + loading={isLoading} + loadingText={ +
      + +
      + } disabled={isDisabled} value={selectedRoles} onChange={handleChange} diff --git a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx index 874d7b57..adeb617b 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx @@ -4,7 +4,7 @@ import useAppStore from '../../../../store/useStore'; import { FetchedData, IUser } from '../../../../utils/interfaces'; import { debounce } from '../../../../helpers/helper'; import TcAutocomplete from '../../../shared/TcAutocomplete'; -import { Chip } from '@mui/material'; +import { Chip, CircularProgress } from '@mui/material'; interface ITcUsersAutoCompleteProps { isEdit?: boolean; @@ -29,7 +29,7 @@ function TcUsersAutoComplete({ const [selectedUsers, setSelectedUsers] = useState([]); const [fetchedUsers, setFetchedUsers] = useState({ - limit: 100, + limit: 8, page: 1, results: [], totalPages: 0, @@ -37,6 +37,7 @@ function TcUsersAutoComplete({ }); const [filteredUsersByName, setFilteredUsersByName] = useState(''); const [isInitialized, setIsInitialized] = useState(false); + const [isLoading, setIsLoading] = useState(false); const fetchDiscordUsers = async ( platformId: string, @@ -45,6 +46,8 @@ function TcUsersAutoComplete({ ngu?: string ) => { try { + setIsLoading(true); + const fetchedUsers = await retrievePlatformProperties({ platformId, ngu: ngu, @@ -73,7 +76,10 @@ function TcUsersAutoComplete({ }; }); } - } catch (error) {} + } catch (error) { + } finally { + setIsLoading(false); + } }; useEffect(() => { @@ -83,6 +89,11 @@ function TcUsersAutoComplete({ const debouncedFetchDiscordUsers = debounce(fetchDiscordUsers, 700); + const handleClearAll = () => { + if (!platformId) return; + fetchDiscordUsers(platformId, fetchedUsers.page, fetchedUsers.limit); + }; + const handleSearchChange = (event: React.SyntheticEvent) => { const target = event.target as HTMLInputElement; const inputValue = target.value; @@ -92,16 +103,16 @@ function TcUsersAutoComplete({ if (inputValue === '') { setFilteredUsersByName(''); setFetchedUsers({ - limit: 100, + limit: 8, page: 1, results: [], totalPages: 0, totalResults: 0, }); - debouncedFetchDiscordUsers(platformId, 1, 100); + debouncedFetchDiscordUsers(platformId, 1, 8); } else { - debouncedFetchDiscordUsers(platformId, 1, 100, inputValue); + debouncedFetchDiscordUsers(platformId, 1, 8, inputValue); } }; @@ -149,10 +160,22 @@ function TcUsersAutoComplete({ getOptionLabel={(option) => option.ngu} label={'Select User(s)'} multiple={true} + loading={isLoading} + loadingText={ +
      + +
      + } disabled={isDisabled} value={selectedUsers} onChange={handleChange} - onInputChange={handleSearchChange} + onInputChange={(event, value, reason) => { + if (reason === 'clear') { + handleClearAll(); + } else { + handleSearchChange(event); + } + }} isOptionEqualToValue={(option, value) => option.discordId === value.discordId } From 7f5e109e01cace091e54c3e4d2cec51d43059865 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 29 Jan 2024 10:11:06 +0300 Subject: [PATCH 49/58] update format date in dilaog --- .../announcements/TcConfirmSchaduledAnnouncementsDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx index 7e7c9cec..3e06c7ac 100644 --- a/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx +++ b/src/components/announcements/TcConfirmSchaduledAnnouncementsDialog.tsx @@ -23,7 +23,7 @@ const formatDateToLocalTimezone = (scheduledDate: string) => { return 'Invalid Date'; } - const formattedDate = moment(scheduledDate).format('MMMM D [at] hA'); + const formattedDate = moment(scheduledDate).format('MMMM D [at] hh:mm A'); const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; From cc729b0219002e93609f8c81d48640d1e1a9f312 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 29 Jan 2024 12:34:16 +0300 Subject: [PATCH 50/58] update announcement user list --- .../TcPrivateMessageContainer.tsx | 9 +-- .../TcUsersAutoComplete.tsx | 57 ++++++++++++++----- .../edit-announcements/index.tsx | 2 +- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx index e92c7b01..5d7f4a9f 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcPrivateMessageContainer.tsx @@ -100,14 +100,7 @@ function TcPrivateMessageContainer({ if (message && privateMessage) { prepareAndSendData(); } - }, [ - message, - selectedRoles, - selectedUsers, - messageType, - privateMessage, - handlePrivateAnnouncements, - ]); + }, [message, selectedRoles, selectedUsers, messageType, privateMessage]); useEffect(() => { if (isEdit && privateAnnouncementsData) { diff --git a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx index adeb617b..7ffc8edc 100644 --- a/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx +++ b/src/components/announcements/create/privateMessaageContainer/TcUsersAutoComplete.tsx @@ -2,9 +2,12 @@ import React, { useEffect, useState } from 'react'; import { useToken } from '../../../../context/TokenContext'; import useAppStore from '../../../../store/useStore'; import { FetchedData, IUser } from '../../../../utils/interfaces'; -import { debounce } from '../../../../helpers/helper'; +import { debounce, truncateCenter } from '../../../../helpers/helper'; import TcAutocomplete from '../../../shared/TcAutocomplete'; import { Chip, CircularProgress } from '@mui/material'; +import TcAvatar from '../../../shared/TcAvatar'; +import TcText from '../../../shared/TcText'; +import { conf } from '../../../../configs'; interface ITcUsersAutoCompleteProps { isEdit?: boolean; @@ -182,7 +185,24 @@ function TcUsersAutoComplete({ disableCloseOnSelect renderOption={(props, option) => (
    2. - {option.ngu} +
      + + + +
    3. )} renderTags={(value, getTagProps) => @@ -190,18 +210,29 @@ function TcUsersAutoComplete({ - +
      + + +
      + - {option.ngu}
    } size="small" diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index ee475759..315e1f35 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -159,7 +159,7 @@ function Index() { if (data) { showMessage('Announcement updated successfully', 'success'); - location.replace('/announcements'); + router.push('/announcements'); } } catch (error) { showMessage('Failed to create announcement', 'error'); From a37d1548a1a876614734768069526bcb489bbe57 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 5 Feb 2024 12:44:46 +0300 Subject: [PATCH 51/58] update payload for privateMessage --- .../create-new-announcements.tsx | 48 +++++++++-------- .../edit-announcements/index.tsx | 51 +++++++++---------- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index d086fa7f..b0e73836 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -160,48 +160,46 @@ function CreateNewAnnouncements() { }) => { if (!platformId) return; - let rolePrivateAnnouncements; - let userPrivateAnnouncements; - const commonData = { platformId: platformId, template: message, }; + let privateAnnouncementsOptions: { + roleIds: string[]; + userIds: string[]; + } = { + roleIds: [], + userIds: [], + }; + if (selectedRoles && selectedRoles.length > 0) { setRoles(selectedRoles); - - rolePrivateAnnouncements = { - ...commonData, - options: { - roleIds: selectedRoles.map((role) => - role.roleId.toString() - ), - }, - }; + privateAnnouncementsOptions.roleIds = selectedRoles.map( + (role) => role.roleId.toString() + ); } if (selectedUsers && selectedUsers.length > 0) { setUsers(selectedUsers); + privateAnnouncementsOptions.userIds = selectedUsers.map( + (user) => user.discordId + ); + } - userPrivateAnnouncements = { + if ( + privateAnnouncementsOptions.roleIds.length > 0 || + privateAnnouncementsOptions.userIds.length > 0 + ) { + const combinedPrivateAnnouncement = { ...commonData, - options: { - userIds: selectedUsers.map((user) => user.discordId), - }, + options: privateAnnouncementsOptions, }; - } - - const announcements = []; - if (rolePrivateAnnouncements) - announcements.push(rolePrivateAnnouncements); - if (userPrivateAnnouncements) - announcements.push(userPrivateAnnouncements); - setPrivateAnnouncements(announcements); + setPrivateAnnouncements([combinedPrivateAnnouncement]); + } }} /> - { setScheduledAt(selectedTime); diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index 315e1f35..7b0bc067 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useMemo, useState } from 'react'; import { defaultLayout } from '../../../layouts/defaultLayout'; import SEO from '../../../components/global/SEO'; import { useRouter } from 'next/router'; -import TcPrivateMessaageContainer from '../../../components/announcements/create/privateMessaageContainer'; +import TcPrivateMessageContainer from '../../../components/announcements/create/privateMessaageContainer'; import TcPublicMessaageContainer from '../../../components/announcements/create/publicMessageContainer'; import TcScheduleAnnouncement from '../../../components/announcements/create/scheduleAnnouncement'; import TcSelectPlatform from '../../../components/announcements/create/selectPlatform'; @@ -207,7 +207,7 @@ function Index() { }); }} /> - { if (!platformId) return; - let rolePrivateAnnouncements; - let userPrivateAnnouncements; - const commonData = { platformId: platformId, template: message, }; + let privateAnnouncementsOptions: { + roleIds: string[]; + userIds: string[]; + } = { + roleIds: [], + userIds: [], + }; + if (selectedRoles && selectedRoles.length > 0) { setRoles(selectedRoles); - - rolePrivateAnnouncements = { - ...commonData, - options: { - roleIds: selectedRoles.map((role) => - role.roleId.toString() - ), - }, - }; + privateAnnouncementsOptions.roleIds = selectedRoles.map( + (role) => role.roleId.toString() + ); } if (selectedUsers && selectedUsers.length > 0) { setUsers(selectedUsers); + privateAnnouncementsOptions.userIds = selectedUsers.map( + (user) => user.discordId + ); + } - userPrivateAnnouncements = { + if ( + privateAnnouncementsOptions.roleIds.length > 0 || + privateAnnouncementsOptions.userIds.length > 0 + ) { + const combinedPrivateAnnouncement = { ...commonData, - options: { - userIds: selectedUsers.map((user) => user.discordId), - }, + options: privateAnnouncementsOptions, }; - } - const announcements = []; - if (rolePrivateAnnouncements) - announcements.push(rolePrivateAnnouncements); - if (userPrivateAnnouncements) - announcements.push(userPrivateAnnouncements); - - setPrivateAnnouncements(announcements); + setPrivateAnnouncements([combinedPrivateAnnouncement]); + } }} /> Date: Tue, 6 Feb 2024 14:54:29 +0300 Subject: [PATCH 52/58] remove sentry from our app --- .gitignore | 3 - README.md | 1 - next.config.js | 36 -- package-lock.json | 965 +--------------------------------------- package.json | 1 - sentry.client.config.ts | 34 -- sentry.edge.config.ts | 20 - sentry.server.config.ts | 20 - src/axiosInstance.ts | 23 - src/configs/index.ts | 3 - 10 files changed, 6 insertions(+), 1100 deletions(-) delete mode 100644 sentry.client.config.ts delete mode 100644 sentry.edge.config.ts delete mode 100644 sentry.server.config.ts diff --git a/.gitignore b/.gitignore index 50222648..c46b39c1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,3 @@ yarn-error.log* next-env.d.ts .env - -# Sentry Auth Token -.sentryclirc diff --git a/README.md b/README.md index 776076d6..a987d0f0 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ The project uses a variety of dependencies for different purposes: - **[Material-UI](https://mui.com/) and [Emotion](https://emotion.sh/)**: For UI components and styling. - **[Highcharts](https://www.highcharts.com/)**: For data visualization. - **[Axios](https://axios-http.com/)**: For making HTTP requests. -- **[Sentry](https://sentry.io/welcome/)**: For error tracking and monitoring. - **[Zustand](https://github.com/pmndrs/zustand)**: For state management. And many others that enhance the functionality and performance of the application. diff --git a/next.config.js b/next.config.js index 8e77c481..999eb343 100644 --- a/next.config.js +++ b/next.config.js @@ -12,41 +12,5 @@ const nextConfig = { module.exports = nextConfig -// Inected Content via Sentry Wizard Below -const { withSentryConfig } = require("@sentry/nextjs"); -module.exports = withSentryConfig( - module.exports, - { - // For all available options, see: - // https://github.com/getsentry/sentry-webpack-plugin#options - - // Suppresses source map uploading logs during build - silent: true, - - url: process.env.NEXT_PUBLIC_SENTRY_URL, - authToken: process.env.SENTRY_TOKEN, - org: process.env.NEXT_PUBLIC_ORG_NAME, - project: process.env.NEXT_PUBLIC_PROJECT_NAME, - }, - { - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - - // Transpiles SDK to be compatible with IE11 (increases bundle size) - transpileClientSDK: true, - - // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load) - tunnelRoute: "/monitoring", - - // Hides source maps from generated client bundles - hideSourceMaps: true, - - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, - } -); diff --git a/package-lock.json b/package-lock.json index 9ea7cd8e..11256dda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "@mui/lab": "^5.0.0-alpha.121", "@mui/material": "^5.10.13", "@mui/x-date-pickers": "^6.18.6", - "@sentry/nextjs": "^7.50.0", "@types/node": "18.11.9", "@types/react": "18.0.25", "@types/react-dom": "18.0.8", @@ -2522,384 +2521,11 @@ "node": ">=14" } }, - "node_modules/@rollup/plugin-commonjs": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz", - "integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" }, - "node_modules/@sentry-internal/tracing": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.50.0.tgz", - "integrity": "sha512-4TQ4vN0aMBWsUXfJWk2xbe4x7fKfwCXgXKTtHC/ocwwKM+0EefV5Iw9YFG8IrIQN4vMtuRzktqcs9q0/Sbv7tg==", - "dependencies": { - "@sentry/core": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry-internal/tracing/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/browser": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.50.0.tgz", - "integrity": "sha512-a+UYbP89+SAvW47/p9wxEi9eWlyp/SkYl52OCdZNXnplQY4kQIOVyiaIs5nnCxIxZgXKrhAX4eo1E9ykleFuNQ==", - "dependencies": { - "@sentry-internal/tracing": "7.50.0", - "@sentry/core": "7.50.0", - "@sentry/replay": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/cli": { - "version": "1.75.2", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.75.2.tgz", - "integrity": "sha512-CG0CKH4VCKWzEaegouWfCLQt9SFN+AieFESCatJ7zSuJmzF05ywpMusjxqRul6lMwfUhRKjGKOzcRJ1jLsfTBw==", - "hasInstallScript": true, - "dependencies": { - "https-proxy-agent": "^5.0.0", - "mkdirp": "^0.5.5", - "node-fetch": "^2.6.7", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" - }, - "bin": { - "sentry-cli": "bin/sentry-cli" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sentry/core": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.50.0.tgz", - "integrity": "sha512-6oD1a3fYs4aiNK7tuJSd88LHjYJAetd7ZK/AfJniU7zWKj4jxIYfO8nhm0qdnhEDs81RcweVDmPhWm3Kwrzzsg==", - "dependencies": { - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/integrations": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.50.0.tgz", - "integrity": "sha512-HUmPN2sHNx37m+lOWIoCILHimILdI0Df9nGmWA13fIhny8mxJ6Dbazyis11AW4/lrZ1a6F1SQ2epLEq7ZesiRw==", - "dependencies": { - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "localforage": "^1.8.1", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/integrations/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/nextjs": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.50.0.tgz", - "integrity": "sha512-4/utwzYIZmjJ/QyYlez1H8PxrNThYBY7MdqzL7XM+Nj432mlyWEDHKYri1FM2GX3HSM4l9lLrpExTkDNVO6dBQ==", - "dependencies": { - "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "7.50.0", - "@sentry/integrations": "7.50.0", - "@sentry/node": "7.50.0", - "@sentry/react": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "@sentry/webpack-plugin": "1.20.0", - "chalk": "3.0.0", - "rollup": "2.78.0", - "stacktrace-parser": "^0.1.10", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "next": "^10.0.8 || ^11.0 || ^12.0 || ^13.0", - "react": "16.x || 17.x || 18.x", - "webpack": ">= 4.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } - } - }, - "node_modules/@sentry/nextjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@sentry/nextjs/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/nextjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@sentry/nextjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@sentry/nextjs/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/nextjs/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/nextjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/node": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.50.0.tgz", - "integrity": "sha512-11UJBKoQFMp7f8sbzeO2gENsKIUkVCNBTzuPRib7l2K1HMjSfacXmwwma7ZEs0mc3ofIZ1UYuyONAXmI1lK9cQ==", - "dependencies": { - "@sentry-internal/tracing": "7.50.0", - "@sentry/core": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/node/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/react": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.50.0.tgz", - "integrity": "sha512-V/KfIhwLezefnRz0y9pGJn5x0RBL8Q1347LowcOZWoNiDoaaLI9hRBTqJGyvCstG5NNhsLTKMM3UDk0WNXflPg==", - "dependencies": { - "@sentry/browser": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "hoist-non-react-statics": "^3.3.2", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": "15.x || 16.x || 17.x || 18.x" - } - }, - "node_modules/@sentry/react/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/replay": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.50.0.tgz", - "integrity": "sha512-EYRk+DTZ5luwfkiCaDpBC3YBKIEdkReTUNZtWDVUytSVjsCnttkAipx/y6bxy3HN+rSXungMd3XKQT5RNMRUNA==", - "dependencies": { - "@sentry/core": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry/types": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.50.0.tgz", - "integrity": "sha512-Zo9vyI98QNeYT0K0y57Rb4JRWDaPEgmp+QkQ4CRQZFUTWetO5fvPZ4Gb/R7TW16LajuHZlbJBHmvmNj2pkL2kw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.50.0.tgz", - "integrity": "sha512-iyPwwC6fwJsiPhH27ZbIiSsY5RaccHBqADS2zEjgKYhmP4P9WGgHRDrvLEnkOjqQyKNb6c0yfmv83n0uxYnolw==", - "dependencies": { - "@sentry/types": "7.50.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/webpack-plugin": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.20.0.tgz", - "integrity": "sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw==", - "dependencies": { - "@sentry/cli": "^1.74.6", - "webpack-sources": "^2.0.0 || ^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", @@ -3208,11 +2834,6 @@ "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==", "dev": true }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" - }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -3870,6 +3491,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "dependencies": { "debug": "4" }, @@ -4686,11 +4308,6 @@ "node": ">= 0.8" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4701,14 +4318,6 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -6108,11 +5717,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6404,6 +6008,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -6759,6 +6364,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -6827,11 +6433,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -7162,14 +6763,6 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dependencies": { - "@types/estree": "*" - } - }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -9414,14 +9007,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", @@ -9451,14 +9036,6 @@ "xtend": "^4.0.0" } }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dependencies": { - "lie": "3.1.1" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9500,11 +9077,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -9522,17 +9094,6 @@ "lz-string": "bin/bin.js" } }, - "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9687,17 +9248,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -9879,25 +9429,6 @@ "nice-color-palettes": "bin/index.js" } }, - "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10573,14 +10104,6 @@ "node": ">= 0.6.0" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/promise-polyfill": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-3.1.0.tgz", @@ -11109,20 +10632,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rollup": { - "version": "2.78.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.0.tgz", - "integrity": "sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11350,25 +10859,6 @@ "node": ">=8" } }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "engines": { - "node": ">=8" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -11784,11 +11274,6 @@ "node": ">=6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -12024,19 +11509,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/webvr-polyfill": { "version": "0.10.12", "resolved": "https://registry.npmjs.org/webvr-polyfill/-/webvr-polyfill-0.10.12.tgz", @@ -12071,15 +11543,6 @@ "node": ">=12" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -14027,300 +13490,11 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==" }, - "@rollup/plugin-commonjs": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz", - "integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==", - "requires": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - } - }, "@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" }, - "@sentry-internal/tracing": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.50.0.tgz", - "integrity": "sha512-4TQ4vN0aMBWsUXfJWk2xbe4x7fKfwCXgXKTtHC/ocwwKM+0EefV5Iw9YFG8IrIQN4vMtuRzktqcs9q0/Sbv7tg==", - "requires": { - "@sentry/core": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/browser": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.50.0.tgz", - "integrity": "sha512-a+UYbP89+SAvW47/p9wxEi9eWlyp/SkYl52OCdZNXnplQY4kQIOVyiaIs5nnCxIxZgXKrhAX4eo1E9ykleFuNQ==", - "requires": { - "@sentry-internal/tracing": "7.50.0", - "@sentry/core": "7.50.0", - "@sentry/replay": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/cli": { - "version": "1.75.2", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.75.2.tgz", - "integrity": "sha512-CG0CKH4VCKWzEaegouWfCLQt9SFN+AieFESCatJ7zSuJmzF05ywpMusjxqRul6lMwfUhRKjGKOzcRJ1jLsfTBw==", - "requires": { - "https-proxy-agent": "^5.0.0", - "mkdirp": "^0.5.5", - "node-fetch": "^2.6.7", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" - } - }, - "@sentry/core": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.50.0.tgz", - "integrity": "sha512-6oD1a3fYs4aiNK7tuJSd88LHjYJAetd7ZK/AfJniU7zWKj4jxIYfO8nhm0qdnhEDs81RcweVDmPhWm3Kwrzzsg==", - "requires": { - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/integrations": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.50.0.tgz", - "integrity": "sha512-HUmPN2sHNx37m+lOWIoCILHimILdI0Df9nGmWA13fIhny8mxJ6Dbazyis11AW4/lrZ1a6F1SQ2epLEq7ZesiRw==", - "requires": { - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "localforage": "^1.8.1", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/nextjs": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.50.0.tgz", - "integrity": "sha512-4/utwzYIZmjJ/QyYlez1H8PxrNThYBY7MdqzL7XM+Nj432mlyWEDHKYri1FM2GX3HSM4l9lLrpExTkDNVO6dBQ==", - "requires": { - "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "7.50.0", - "@sentry/integrations": "7.50.0", - "@sentry/node": "7.50.0", - "@sentry/react": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "@sentry/webpack-plugin": "1.20.0", - "chalk": "3.0.0", - "rollup": "2.78.0", - "stacktrace-parser": "^0.1.10", - "tslib": "^1.9.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/node": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.50.0.tgz", - "integrity": "sha512-11UJBKoQFMp7f8sbzeO2gENsKIUkVCNBTzuPRib7l2K1HMjSfacXmwwma7ZEs0mc3ofIZ1UYuyONAXmI1lK9cQ==", - "requires": { - "@sentry-internal/tracing": "7.50.0", - "@sentry/core": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/react": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.50.0.tgz", - "integrity": "sha512-V/KfIhwLezefnRz0y9pGJn5x0RBL8Q1347LowcOZWoNiDoaaLI9hRBTqJGyvCstG5NNhsLTKMM3UDk0WNXflPg==", - "requires": { - "@sentry/browser": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0", - "hoist-non-react-statics": "^3.3.2", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/replay": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.50.0.tgz", - "integrity": "sha512-EYRk+DTZ5luwfkiCaDpBC3YBKIEdkReTUNZtWDVUytSVjsCnttkAipx/y6bxy3HN+rSXungMd3XKQT5RNMRUNA==", - "requires": { - "@sentry/core": "7.50.0", - "@sentry/types": "7.50.0", - "@sentry/utils": "7.50.0" - } - }, - "@sentry/types": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.50.0.tgz", - "integrity": "sha512-Zo9vyI98QNeYT0K0y57Rb4JRWDaPEgmp+QkQ4CRQZFUTWetO5fvPZ4Gb/R7TW16LajuHZlbJBHmvmNj2pkL2kw==" - }, - "@sentry/utils": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.50.0.tgz", - "integrity": "sha512-iyPwwC6fwJsiPhH27ZbIiSsY5RaccHBqADS2zEjgKYhmP4P9WGgHRDrvLEnkOjqQyKNb6c0yfmv83n0uxYnolw==", - "requires": { - "@sentry/types": "7.50.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/webpack-plugin": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.20.0.tgz", - "integrity": "sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw==", - "requires": { - "@sentry/cli": "^1.74.6", - "webpack-sources": "^2.0.0 || ^3.0.0" - } - }, "@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", @@ -14570,11 +13744,6 @@ "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==", "dev": true }, - "@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" - }, "@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -15080,6 +14249,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "requires": { "debug": "4" } @@ -15651,11 +14821,6 @@ "delayed-stream": "~1.0.0" } }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -15666,11 +14831,6 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - }, "cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -16711,11 +15871,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -16932,6 +16087,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "optional": true }, "function-bind": { @@ -17183,6 +16339,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -17222,11 +16379,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, "immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -17451,14 +16603,6 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "requires": { - "@types/estree": "*" - } - }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -19116,14 +18260,6 @@ "type-check": "~0.4.0" } }, - "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "requires": { - "immediate": "~3.0.5" - } - }, "lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", @@ -19150,14 +18286,6 @@ "xtend": "^4.0.0" } }, - "localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "requires": { - "lie": "3.1.1" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -19190,11 +18318,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -19209,14 +18332,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true }, - "magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.13" - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -19334,14 +18449,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, "moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -19468,14 +18575,6 @@ "xhr-request": "^1.0.1" } }, - "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -19935,11 +19034,6 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" - }, "promise-polyfill": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-3.1.0.tgz", @@ -20315,14 +19409,6 @@ "glob": "^7.1.3" } }, - "rollup": { - "version": "2.78.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.0.tgz", - "integrity": "sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==", - "requires": { - "fsevents": "~2.3.2" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -20490,21 +19576,6 @@ } } }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==" - } - } - }, "stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -20823,11 +19894,6 @@ "url-parse": "^1.5.3" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -20999,16 +20065,6 @@ "makeerror": "1.0.12" } }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" - }, "webvr-polyfill": { "version": "0.10.12", "resolved": "https://registry.npmjs.org/webvr-polyfill/-/webvr-polyfill-0.10.12.tgz", @@ -21037,15 +20093,6 @@ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 39f5377e..a1f84fff 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@mui/lab": "^5.0.0-alpha.121", "@mui/material": "^5.10.13", "@mui/x-date-pickers": "^6.18.6", - "@sentry/nextjs": "^7.50.0", "@types/node": "18.11.9", "@types/react": "18.0.25", "@types/react-dom": "18.0.8", diff --git a/sentry.client.config.ts b/sentry.client.config.ts deleted file mode 100644 index 553f7531..00000000 --- a/sentry.client.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -// This file configures the initialization of Sentry on the client. -// The config you add here will be used whenever a users loads a page in their browser. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from '@sentry/nextjs'; -import { conf } from './src/configs'; - -const SENTRY_DSN = conf.SENTRY_DSN; -const ENVIRONMENT = conf.SENTRY_ENV; - -Sentry.init({ - dsn: SENTRY_DSN, - environment: ENVIRONMENT, - // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: ENVIRONMENT === 'production' ? 0.1 : 1.0, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: ENVIRONMENT !== 'production', - - replaysOnErrorSampleRate: 1.0, - - // This sets the sample rate to be 10%. You may want this to be 100% while - // in development and sample at a lower rate in production - replaysSessionSampleRate: 0.1, - - // You can remove this option if you're not planning to use the Sentry Session Replay feature: - integrations: [ - new Sentry.Replay({ - // Additional Replay configuration goes in here, for example: - maskAllText: true, - blockAllMedia: true, - }), - ], -}); diff --git a/sentry.edge.config.ts b/sentry.edge.config.ts deleted file mode 100644 index b3e0bda1..00000000 --- a/sentry.edge.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file configures the initialization of Sentry on the server. -// The config you add here will be used whenever the server handles a request. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from '@sentry/nextjs'; -import { conf } from './src/configs'; - -const SENTRY_DSN = conf.SENTRY_DSN; -const ENVIRONMENT = process.env.NODE_ENV || 'staging'; - -Sentry.init({ - dsn: SENTRY_DSN, - environment: ENVIRONMENT, - - // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: ENVIRONMENT === 'production' ? 0.1 : 1.0, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: ENVIRONMENT !== 'production', -}); diff --git a/sentry.server.config.ts b/sentry.server.config.ts deleted file mode 100644 index 6f67d5ec..00000000 --- a/sentry.server.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file configures the initialization of Sentry on the server. -// The config you add here will be used whenever the server handles a request. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from '@sentry/nextjs'; -import { conf } from './src/configs'; - -const SENTRY_DSN = conf.SENTRY_DSN; -const ENVIRONMENT = process.env.NODE_ENV || 'production'; - -Sentry.init({ - dsn: SENTRY_DSN, - environment: ENVIRONMENT, - - // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: ENVIRONMENT === 'production' ? 0.1 : 1.0, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: ENVIRONMENT !== 'production', -}); diff --git a/src/axiosInstance.ts b/src/axiosInstance.ts index d3a0b4cc..e1bf0f15 100644 --- a/src/axiosInstance.ts +++ b/src/axiosInstance.ts @@ -1,8 +1,6 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; import { conf } from './configs/index'; import { StorageService } from './services/StorageService'; -import * as Sentry from '@sentry/nextjs'; - import { toast } from 'react-toastify'; import { IToken } from './utils/types'; import { tokenRefreshEventEmitter } from './services/EventEmitter'; @@ -149,11 +147,6 @@ axiosInstance.interceptors.response.use( draggable: true, progress: 0, }); - Sentry.captureException( - new Error( - `API responded with status code ${error.response.status}: ${error.response.data.message}` - ) - ); break; case 440: StorageService.removeLocalStorage('user'); @@ -168,12 +161,6 @@ axiosInstance.interceptors.response.use( progress: 0, }); window.location.href = '/'; - - Sentry.captureException( - new Error( - `API responded with status code ${error.response.status}: ${error.response.data.message}` - ) - ); break; case 500: toast.error(`${error.response.data.message}`, { @@ -185,11 +172,6 @@ axiosInstance.interceptors.response.use( draggable: true, progress: 0, }); - Sentry.captureException( - new Error( - `API responded with status code ${error.response.status}: ${error.response.data.message}` - ) - ); break; case 590: toast.error(`${error.response.data.message}`, { @@ -201,11 +183,6 @@ axiosInstance.interceptors.response.use( draggable: true, progress: 0, }); - Sentry.captureException( - new Error( - `API responded with status code ${error.response.status}: ${error.response.data.message}` - ) - ); break; default: diff --git a/src/configs/index.ts b/src/configs/index.ts index 65054cb1..c75829df 100644 --- a/src/configs/index.ts +++ b/src/configs/index.ts @@ -7,8 +7,5 @@ export const conf = { AMPLITUDEANALYTICS_TOKEN: process.env.NEXT_PUBLIC_AMPLITUDEANALYTICS_TOKEN, PROPERTY_ID: process.env.NEXT_PUBLIC_TAWK_PROPERTY_ID, WEIGHT_ID: process.env.NEXT_PUBLIC_TAWK_WEIGHT_ID, - SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, - SENTRY_ENV: process.env.NEXT_PUBLIC_SENTRY_ENV, - SENTRY_TOKEN: process.env.SENTRY_TOKEN, DISCORD_CDN: process.env.NEXT_PUBLIC_DISCORD_CDN, }; From 9fdf498c29f8b7acf4a44e000114a5fa25dba2b1 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 9 Feb 2024 14:46:06 +0300 Subject: [PATCH 53/58] update announcement feature --- .../announcements/TcAnnouncementsTable.tsx | 164 +++++++++--------- ...nfirmSchaduledAnnouncementsDialog.spec.tsx | 4 +- .../TcPublicMessageContainer.spec.tsx | 14 +- .../TcPublicMessageContainer.tsx | 13 +- .../TcScheduleAnnouncement.spec.tsx | 48 ++++- .../TcScheduleAnnouncement.tsx | 38 ++-- src/helpers/helper.ts | 14 ++ .../create-new-announcements.tsx | 36 +++- .../edit-announcements/index.tsx | 39 +++-- 9 files changed, 233 insertions(+), 137 deletions(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index df28b871..e5723e70 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -228,52 +228,52 @@ function TcAnnouncementsTable({ />
    ); - case 'users': - return ( -
    - { - const users = item.options.users; - if (users && users.length > 0) { - const displayedUsers = users - .slice(0, 2) - .map((user: { ngu: any }) => `@${user.ngu}`) - .join(', '); - const moreUsersIndicator = users.length > 2 ? '...' : ''; - return `${displayedUsers}${moreUsersIndicator}`; - } - return ''; - }) - .filter((text: string) => text !== '') - .join(', ')} - variant="subtitle2" - /> -
    - ); - case 'roles': - return ( -
    - { - const roles = item.options.roles; - if (roles && roles.length > 0) { - const displayedRoles = roles - .slice(0, 2) - .map((role: { name: any }) => role.name) - .join(', '); - const moreRolesIndicator = roles.length > 2 ? '...' : ''; - return `${displayedRoles}${moreRolesIndicator}`; - } - return ''; - }) - .filter((text: string) => text !== '') - .join(', ')} - variant="subtitle2" - /> -
    - ); + // case 'users': + // return ( + //
    + // { + // const users = item.options.users; + // if (users && users.length > 0) { + // const displayedUsers = users + // .slice(0, 2) + // .map((user: { ngu: any }) => `@${user.ngu}`) + // .join(', '); + // const moreUsersIndicator = users.length > 2 ? '...' : ''; + // return `${displayedUsers}${moreUsersIndicator}`; + // } + // return ''; + // }) + // .filter((text: string) => text !== '') + // .join(', ')} + // variant="subtitle2" + // /> + //
    + // ); + // case 'roles': + // return ( + //
    + // { + // const roles = item.options.roles; + // if (roles && roles.length > 0) { + // const displayedRoles = roles + // .slice(0, 2) + // .map((role: { name: any }) => role.name) + // .join(', '); + // const moreRolesIndicator = roles.length > 2 ? '...' : ''; + // return `${displayedRoles}${moreRolesIndicator}`; + // } + // return ''; + // }) + // .filter((text: string) => text !== '') + // .join(', ')} + // variant="subtitle2" + // /> + //
    + // ); case 'scheduledAt': return (
    @@ -346,39 +346,35 @@ function TcAnnouncementsTable({ key={announcement.id} className={`my-5 ${index % 2 === 0 ? 'bg-gray-100' : ''}`} > - {[ - 'title', - 'channels', - 'users', - 'roles', - 'scheduledAt', - 'actions', - ].map((cellType, cellIndex, array) => ( - - {renderTableCell(announcement, cellType)} - - ))} + {['title', 'channels', 'scheduledAt', 'actions'].map( + (cellType, cellIndex, array) => ( + + {renderTableCell(announcement, cellType)} + + ) + )} ))} @@ -394,7 +390,7 @@ function TcAnnouncementsTable({ sx={{ borderBottom: 'none' }} className="uppercase text-gray-400" style={{ - width: '20%', + width: '60%', borderBottom: 'none', whiteSpace: 'nowrap', padding: '0 1rem', @@ -416,7 +412,7 @@ function TcAnnouncementsTable({ > - - - */} + {/* - + */} ); - const button = getByText('Schedule Announcement'); + const button = getByText('Select Date for Announcement'); expect(button).toBeInTheDocument(); fireEvent.click(button); diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx index 17b3daf7..88d1b3d1 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.spec.tsx @@ -38,23 +38,13 @@ describe('TcPublicMessageContainer', () => { expect(screen.getByText(/Public Message/i)).toBeInTheDocument(); }); - it('renders the "Send message to:" text', () => { - expect(screen.getByText(/Send message to:/i)).toBeInTheDocument(); - }); - - it('renders the message about bot delivery', () => { + it('renders the message about bot distribute', () => { const message = - /Our bot will deliver the announcement across chosen channels with the necessary access to share the specified message\./i; + /Our bot will distribute the announcement through selected channels with the required access to share the designated message./i; expect(screen.getByText(message)).toBeInTheDocument(); }); it('renders the "Write message here:" text', () => { expect(screen.getByText(/Write message here:/i)).toBeInTheDocument(); }); - - it('renders the auto-generated safety message prompt', () => { - const message = - /If you don’t write a custom message then this auto-generated safety message wlll be sent out/i; - expect(screen.getByText(message)).toBeInTheDocument(); - }); }); diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx index 673afe84..e105b002 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -67,9 +67,7 @@ function TcPublicMessageContainer({ setSelectedChannels(flattenChannels(channels)); }, [channels, selectedSubChannels]); - const [message, setMessage] = useState( - `This message was sent to you because you’re part of ${community?.name}. To verify the legitimacy of this message, see the official announcement here ⁠👥together-crew⁠ and verify the bot ID If you don’t want to receive any more private message from ${community?.name}, please adjust your settings here: https://app.togethercrew.com/community-settings/` - ); + const [message, setMessage] = useState(''); const handleChange = (event: React.ChangeEvent) => { setMessage(event.target.value); @@ -94,6 +92,7 @@ function TcPublicMessageContainer({ label: channel.name, }) ); + setSelectedChannels(formattedChannels); setMessage(publicAnnouncementsData.template); } @@ -116,14 +115,14 @@ function TcPublicMessageContainer({ />
    -
    + {/*
    -
    +
    */} Select Channels -
    +
    diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx index 870106c8..b07aef74 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.spec.tsx @@ -4,8 +4,52 @@ import '@testing-library/jest-dom'; import TcScheduleAnnouncement from './TcScheduleAnnouncement'; describe('TcScheduleAnnouncement Tests', () => { + // Mock functions for the new props + const mockHandleScheduledDate = jest.fn(); + const mockSetIsDateValid = jest.fn(); + test('renders the component without crashing', () => { - render(); - expect(screen.getByText('Schedule Announcement')).toBeInTheDocument(); + render( + + ); + + // Since the initial text is "Select Date for Announcement", we should assert this text. + expect( + screen.getByText('Select Date for Announcement') + ).toBeInTheDocument(); + }); + + test('initially displays the calendar icon', () => { + render( + + ); + + // Assuming your TcButton component renders an icon, this test checks for its presence. + const calendarIcon = screen.getByTestId('MdCalendarMonth'); + expect(calendarIcon).toBeInTheDocument(); + }); + + test('displays the button to open date-time popover', () => { + render( + + ); + + // Check if the button that is supposed to open the date-time popover is rendered. + const button = screen.getByRole('button', { + name: /select date for announcement/i, + }); + expect(button).toBeInTheDocument(); }); }); diff --git a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx index e68e28cb..394f88f4 100644 --- a/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx +++ b/src/components/announcements/create/scheduleAnnouncement/TcScheduleAnnouncement.tsx @@ -5,22 +5,28 @@ import TcText from '../../../shared/TcText'; import TcButton from '../../../shared/TcButton'; import moment from 'moment'; import TcDateTimePopover from './TcDateTimePopover'; +import { validateDateTime } from '../../../../helpers/helper'; export interface ITcScheduleAnnouncementProps { isEdit?: boolean; preSelectedTime?: string; handleSchaduledDate: ({ selectedTime }: { selectedTime: string }) => void; + isDateValid: boolean; + setIsDateValid: (isValid: boolean) => void; } function TcScheduleAnnouncement({ isEdit = false, preSelectedTime, handleSchaduledDate, + isDateValid, + setIsDateValid, }: ITcScheduleAnnouncementProps) { const [anchorEl, setAnchorEl] = useState(null); const [activeTab, setActiveTab] = useState(0); const [selectedDate, setSelectedDate] = useState(null); const [selectedTime, setSelectedTime] = useState(null); + const [dateTimeDisplay, setDateTimeDisplay] = useState( 'Select Date for Announcement' ); @@ -40,6 +46,8 @@ function TcScheduleAnnouncement({ if (date) { setSelectedDate(date); setActiveTab(1); + const isValid = validateDateTime(date, selectedTime); + setIsDateValid(isValid); } }; @@ -54,6 +62,8 @@ function TcScheduleAnnouncement({ }); setDateTimeDisplay(fullDateTime.format('D MMMM YYYY @ hh:mm A')); } + const isValid = validateDateTime(selectedDate, time); + setIsDateValid(isValid); } }; @@ -74,26 +84,23 @@ function TcScheduleAnnouncement({ useEffect(() => { if (isEdit && preSelectedTime) { - const date = moment(preSelectedTime); - - setSelectedDate(date.toDate()); - setSelectedTime(date.toDate()); - setDateTimeDisplay(date.format('D MMMM YYYY @ hh:mm A')); + const dateTime = moment(preSelectedTime); + const date = dateTime.toDate(); + setSelectedDate(date); + setSelectedTime(date); + setDateTimeDisplay(dateTime.format('D MMMM YYYY @ hh:mm A')); + setIsDateValid(validateDateTime(date, date)); } }, [isEdit, preSelectedTime]); return (
    -
    +
    - + - +
    + {!isDateValid && ( + + )} ([]); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); + const [isDateValid, setIsDateValid] = useState(true); const platformId = community?.platforms.find( (platform) => platform.disconnectedAt === null @@ -134,6 +141,13 @@ function CreateNewAnnouncements() {
    + { + setScheduledAt(selectedTime); + }} + isDateValid={isDateValid} + setIsDateValid={setIsDateValid} + /> - + + + + + +
    + {/* - { - setScheduledAt(selectedTime); - }} - /> + /> */}
    handleCreateAnnouncements(e) } diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index 7b0bc067..d1262e65 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -16,6 +16,9 @@ import { useSnackbar } from '../../../context/SnackbarContext'; import { useToken } from '../../../context/TokenContext'; import { CreateAnnouncementsPayloadData } from '../create-new-announcements'; import SimpleBackdrop from '../../../components/global/LoadingBackdrop'; +import { MdOutlineAnnouncement } from 'react-icons/md'; +import TcIconContainer from '../../../components/announcements/create/TcIconContainer'; +import TcText from '../../../components/shared/TcText'; export interface DiscordChannel { channelId: string; @@ -69,6 +72,7 @@ function Index() { const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); + const [isDateValid, setIsDateValid] = useState(true); const platformId = community?.platforms.find( (platform) => platform.disconnectedAt === null @@ -160,6 +164,8 @@ function Index() { if (data) { showMessage('Announcement updated successfully', 'success'); router.push('/announcements'); + } else { + fetchPlatformChannels(); } } catch (error) { showMessage('Failed to create announcement', 'error'); @@ -187,6 +193,15 @@ function Index() {
    + { + setScheduledAt(selectedTime); + }} + isDateValid={isDateValid} + setIsDateValid={setIsDateValid} + /> - + + + + + +
    + {/* - { - setScheduledAt(selectedTime); - }} - /> + /> */}
    handleEditAnnouncements(e)} />
    From 1cd093d141c7e9a9635e098c9bef49b5e2332978 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 9 Feb 2024 14:51:15 +0300 Subject: [PATCH 54/58] fix responsive issue --- .../create/publicMessageContainer/TcPublicMessageContainer.tsx | 2 +- src/pages/announcements/create-new-announcements.tsx | 2 +- src/pages/announcements/edit-announcements/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx index e105b002..9c54ec29 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -102,7 +102,7 @@ function TcPublicMessageContainer({ return (
    -
    +
    diff --git a/src/pages/announcements/create-new-announcements.tsx b/src/pages/announcements/create-new-announcements.tsx index dba89fac..a4c047a3 100644 --- a/src/pages/announcements/create-new-announcements.tsx +++ b/src/pages/announcements/create-new-announcements.tsx @@ -166,7 +166,7 @@ function CreateNewAnnouncements() { }); }} /> -
    +
    diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index d1262e65..07075306 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -222,7 +222,7 @@ function Index() { }); }} /> -
    +
    From 4a9817c38f4a3b6e93ce95bbe211a13ea0867164 Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 9 Feb 2024 15:00:27 +0300 Subject: [PATCH 55/58] fix date filtering issue on table --- .../announcements/TcAnnouncementsTable.tsx | 15 ++++++++++++++- src/pages/announcements/index.tsx | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/announcements/TcAnnouncementsTable.tsx b/src/components/announcements/TcAnnouncementsTable.tsx index e5723e70..0fec5768 100644 --- a/src/components/announcements/TcAnnouncementsTable.tsx +++ b/src/components/announcements/TcAnnouncementsTable.tsx @@ -21,6 +21,7 @@ import { useSnackbar } from '../../context/SnackbarContext'; import { MdModeEdit, MdDelete } from 'react-icons/md'; import Loading from '../global/Loading'; import { truncateCenter } from '../../helpers/helper'; +import moment from 'moment'; interface Channel { channelId: string; @@ -61,12 +62,14 @@ interface Announcement { interface AnnouncementsTableProps { announcements: Announcement[]; isLoading: boolean; + selectedZone: string; handleRefreshList: () => void; } function TcAnnouncementsTable({ announcements, isLoading, + selectedZone, handleRefreshList, }: AnnouncementsTableProps) { const { deleteAnnouncements } = useAppStore(); @@ -78,6 +81,13 @@ function TcAnnouncementsTable({ string | null >(null); + const formatDateBasedOnTimezone = ( + date: string | number | Date, + timezone: string + ) => { + return moment(date).tz(timezone).format('DD/MM/YYYY, h:mm:ss a'); + }; + const handleClick = ( event: React.MouseEvent, id: string @@ -278,7 +288,10 @@ function TcAnnouncementsTable({ return (
    diff --git a/src/pages/announcements/index.tsx b/src/pages/announcements/index.tsx index a055b024..2e8c06c6 100644 --- a/src/pages/announcements/index.tsx +++ b/src/pages/announcements/index.tsx @@ -208,6 +208,7 @@ function Index() { } handleRefreshList={fetchData} isLoading={loading} + selectedZone={selectedZone} />
    ) : ( From 6aea68d259261e48ccb7252f1e7086856395d1ae Mon Sep 17 00:00:00 2001 From: zuies Date: Fri, 9 Feb 2024 15:26:27 +0300 Subject: [PATCH 56/58] update disable buttons statement --- .../TcPublicMessageContainer.tsx | 2 +- .../announcements/create-new-announcements.tsx | 14 ++++++++++++-- .../announcements/edit-announcements/index.tsx | 7 ++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx index 9c54ec29..2801d647 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -145,7 +145,7 @@ function TcPublicMessageContainer({
    -
    +
    handleCreateAnnouncements(e) } diff --git a/src/pages/announcements/edit-announcements/index.tsx b/src/pages/announcements/edit-announcements/index.tsx index 07075306..678688fd 100644 --- a/src/pages/announcements/edit-announcements/index.tsx +++ b/src/pages/announcements/edit-announcements/index.tsx @@ -291,7 +291,12 @@ function Index() { selectedRoles={roles} selectedUsernames={users} schaduledDate={scheduledAt || ''} - isDisabled={!scheduledAt || !isDateValid} + isDisabled={ + !scheduledAt || + !isDateValid || + publicAnnouncements?.template == '' || + publicAnnouncements?.options.channelIds?.length === 0 + } handleCreateAnnouncements={(e) => handleEditAnnouncements(e)} />
    From 635222f8e59179643533d69e2290e7c03a7fbbe4 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 12 Feb 2024 10:49:58 +0300 Subject: [PATCH 57/58] add save functionality for announcement channel changes --- package-lock.json | 20 ++++-- package.json | 2 + .../TcPublicMessageContainer.tsx | 62 ++++++++++++++----- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11256dda..4c8d3d22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "highcharts": "^10.3.1", "highcharts-react-official": "^3.1.0", "jwt-decode": "^3.1.2", + "lodash": "^4.17.21", "moment": "^2.29.4", "moment-timezone": "^0.5.38", "next": "13.0.2", @@ -66,6 +67,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", "@types/d3-force": "^3.0.4", + "@types/lodash": "^4.14.202", "@types/papaparse": "^5.3.8", "@types/testing-library__user-event": "^4.2.0", "autoprefixer": "^10.4.13", @@ -2924,6 +2926,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, "node_modules/@types/node": { "version": "18.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", @@ -9053,8 +9061,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash-es": { "version": "4.17.21", @@ -13827,6 +13834,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, "@types/node": { "version": "18.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", @@ -18297,8 +18310,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash-es": { "version": "4.17.21", diff --git a/package.json b/package.json index a1f84fff..85891dc1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "highcharts": "^10.3.1", "highcharts-react-official": "^3.1.0", "jwt-decode": "^3.1.2", + "lodash": "^4.17.21", "moment": "^2.29.4", "moment-timezone": "^0.5.38", "next": "13.0.2", @@ -70,6 +71,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", "@types/d3-force": "^3.0.4", + "@types/lodash": "^4.14.202", "@types/papaparse": "^5.3.8", "@types/testing-library__user-event": "^4.2.0", "autoprefixer": "^10.4.13", diff --git a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx index 2801d647..fba55ba9 100644 --- a/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx +++ b/src/components/announcements/create/publicMessageContainer/TcPublicMessageContainer.tsx @@ -1,9 +1,9 @@ import React, { useContext, useEffect, useState } from 'react'; import TcText from '../../../shared/TcText'; -import { MdAnnouncement, MdExpandMore } from 'react-icons/md'; +import { MdAnnouncement } from 'react-icons/md'; import TcIconContainer from '../TcIconContainer'; import TcSelect from '../../../shared/TcSelect'; -import { FormControl, InputLabel } from '@mui/material'; +import { FormControl, FormHelperText, InputLabel } from '@mui/material'; import TcInput from '../../../shared/TcInput'; import TcPublicMessagePreviewDialog from './TcPublicMessagePreviewDialog'; import { ChannelContext } from '../../../../context/ChannelContext'; @@ -11,7 +11,7 @@ import TcPlatformChannelList from '../../../communitySettings/platform/TcPlatfor import { IGuildChannels } from '../../../../utils/types'; import { DiscordData } from '../../../../pages/announcements/edit-announcements'; import TcPermissionHints from '../../../global/TcPermissionHints'; -import { useToken } from '../../../../context/TokenContext'; +import TcButton from '../../../shared/TcButton'; export interface FlattenedChannel { id: string; @@ -26,7 +26,7 @@ export interface ITcPublicMessageContainerProps { selectedChannels, }: { message: string; - selectedChannels: FlattenedChannel[]; + selectedChannels: FlattenedChannel[] | []; }) => void; } @@ -38,7 +38,9 @@ function TcPublicMessageContainer({ const channelContext = useContext(ChannelContext); const { channels, selectedSubChannels } = channelContext; - const { community } = useToken(); + const [hasInteracted, setHasInteracted] = useState(false); + const [showError, setShowError] = useState(false); + const [isDropdownVisible, setIsDropdownVisible] = useState(false); const flattenChannels = (channels: IGuildChannels[]): FlattenedChannel[] => { let flattened: FlattenedChannel[] = []; @@ -62,6 +64,8 @@ function TcPublicMessageContainer({ const [selectedChannels, setSelectedChannels] = useState( [] ); + const [confirmedSelectedChannels, setConfirmedSelectedChannels] = + useState(false); useEffect(() => { setSelectedChannels(flattenChannels(channels)); @@ -77,8 +81,12 @@ function TcPublicMessageContainer({ selectedChannels.length > 0 && message.length > 0; useEffect(() => { - handlePublicAnnouncements({ message, selectedChannels }); - }, [message, selectedChannels]); + if (confirmedSelectedChannels) { + handlePublicAnnouncements({ message, selectedChannels }); + } else { + handlePublicAnnouncements({ message, selectedChannels: [] }); + } + }, [message, selectedChannels, confirmedSelectedChannels]); useEffect(() => { if (isEdit && publicAnnouncementsData) { @@ -92,13 +100,24 @@ function TcPublicMessageContainer({ label: channel.name, }) ); - + setConfirmedSelectedChannels(true); setSelectedChannels(formattedChannels); setMessage(publicAnnouncementsData.template); } } }, [isEdit, publicAnnouncementsData]); + const handleSaveChannels = () => { + setConfirmedSelectedChannels(true); + setIsDropdownVisible(false); + setShowError(hasInteracted && selectedChannels.length === 0); + }; + + const toggleDropdownVisibility = () => { + setHasInteracted(true); + setIsDropdownVisible(!isDropdownVisible); + }; + return (
    @@ -115,14 +134,6 @@ function TcPublicMessageContainer({ />
    - {/*
    - - -
    */} Select Channels (selected as FlattenedChannel[]) .map((channel) => `#${channel.label}`) @@ -142,8 +155,25 @@ function TcPublicMessageContainer({
    +
    + +
    + {showError && ( + + + + )}
    From 09a0b4dfe3738af50ffaff7200f8afadf5697fe4 Mon Sep 17 00:00:00 2001 From: zuies Date: Mon, 12 Feb 2024 13:59:18 +0300 Subject: [PATCH 58/58] add functionality for active-tab statistical route --- src/components/global/.CustomTab.tsx.swp | Bin 0 -> 12288 bytes src/pages/statistics.tsx | 40 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/components/global/.CustomTab.tsx.swp diff --git a/src/components/global/.CustomTab.tsx.swp b/src/components/global/.CustomTab.tsx.swp new file mode 100644 index 0000000000000000000000000000000000000000..1c19527ff56bab0de980f8ab3fb95df9d9ac515f GIT binary patch literal 12288 zcmeI2O^6&t6vrzO<5!}R1wD8vCnhrsyEC&N#&z~1vLE0|HiTr|Ae$U&x@%_3?Wvk{ z*X+(ZtcnPN;6W7cQ3UTI=uy#w;59}(c@aG5WrH3(i2v&D>FJqiB@4L(s>3f;UHz)+ z^}k-fnvklNH&)l^^Tjg3^*AA49DL9J@Xz1(VSx}S8cZvnRfT!@-`54$1>IIMpyW%JsZ!uOB}X-yN`{SfPgR*$T51JaHP%_RsDt*< zc(%bRU==uw0w>7S70}Hi z!1#sgp*9Y@KuUH{V^=C$~I zN3-gNsRf%ty1acY-GS0=zBk_qAN02e?fFiJHke-*a$Pkmv^++e%yosVqfYnJ)^m7+ z&^yv~*f^$fp03zn%|Zb;6?%y?N3VF?oV^>P;fxh3Ta7CBN9p_mEhL@8w>>X{lpKSC zP@3r1owCoPs4?IkcQkh^w6i~!44-ruZlcstp7JX+e`+$k@>w=j=4{t})spT~*Lr2y z{K>G+pQ%Wt3&pjT81K3>nukMo=1x`lF83Fe#$iNwC2>eGbCPGeW8dweDUDn(>p$dj zPp;i9p|dP&fhWo07s75XH~e2qI3qs|bl`H6C~oiR-k!TFhHn@&z=8b$M5 zVyKopy)P=CH8Q2U!qr>w;o0)(jG6NO$-D^oOi+^ohHnO z=dmjH<~w1Losi;`l+oLynbcJAAW@15&EvE(kWSJ`aUe+ytw|I-h>an&nJ4OURr7`k z2?sfh`$IN0kGGeFkDMb+#>*p_v>I~2>=269IX@HgGvzHHtM;OSxullPSkYttG=n>uuMCPPAY5Pjjw91MOoE+`MM?@$QUCvuTW{ z?%(ku`^c64F;Y%dXo2tGmEx|&%X?C9aV?w`(>g{a-`y}fd3S&?(07z@&9uqebf3-= zVWPCg9G(&n+7W@%?)Zt)q3g*qjG4Mwp|BEe=5(LdeAS=`*p?{4^*lz5dcrpS42ShO WLc}w_WO_!OzAZl)%{RXTVD&Hjv$e|r literal 0 HcmV?d00001 diff --git a/src/pages/statistics.tsx b/src/pages/statistics.tsx index 45378585..f5c60d2a 100644 --- a/src/pages/statistics.tsx +++ b/src/pages/statistics.tsx @@ -19,13 +19,26 @@ import { useToken } from '../context/TokenContext'; import EmptyState from '../components/global/EmptyState'; import emptyState from '../assets/svg/empty-state.svg'; import Image from 'next/image'; +import { useRouter } from 'next/router'; const Statistics = () => { const { community } = useToken(); + const router = useRouter(); + const platformId = community?.platforms.find( (platform) => platform.disconnectedAt === null )?.id; + const tabMap: { [key: string]: string } = { + activeMembers: '1', + disengagedMembers: '2', + }; + + const reverseTabMap: { [key: string]: string } = { + '1': 'activeMembers', + '2': 'disengagedMembers', + }; + const [loading, setLoading] = useState(true); const [activeMemberDate, setActiveMemberDate] = useState(1); const [onBoardingMemberDate, setOnBoardingMemberDate] = useState(1); @@ -40,13 +53,36 @@ const Statistics = () => { fetchOnboardingMembers, } = useAppStore(); - const [activeTab, setActiveTab] = useState('1'); + const [activeTab, setActiveTab] = useState( + tabMap[router.query.tab as string] || '1' + ); + + useEffect(() => { + if (!router.isReady) return; + + const handleRouteChange = () => { + setActiveTab(tabMap[router.query.tab as string] || '1'); + }; + + handleRouteChange(); + + router.events.on('routeChangeComplete', handleRouteChange); + + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; + }, [router.isReady, router.query.tab]); const handleTabChange = ( event: React.SyntheticEvent, newValue: string ): void => { - setActiveTab(newValue); + if (newValue in reverseTabMap) { + const urlTabIdentifier = reverseTabMap[newValue]; + router.push(`/statistics?tab=${urlTabIdentifier}`, undefined, { + shallow: true, + }); + } }; useEffect(() => {