diff --git a/src/api/Ticker.ts b/src/api/Ticker.ts index d523a74..be09a33 100644 --- a/src/api/Ticker.ts +++ b/src/api/Ticker.ts @@ -100,15 +100,11 @@ export interface TickerSignalGroup { active: boolean connected: boolean groupID: string - groupName: string - groupDescription: string groupInviteLink: string } export interface TickerSignalGroupFormData { active: boolean - groupName?: string - groupDescription?: string } export interface TickerSignalGroupAdminFormData { diff --git a/src/components/ticker/SignalGroupAdminForm.test.tsx b/src/components/ticker/SignalGroupAdminForm.test.tsx index f6c239b..b415981 100644 --- a/src/components/ticker/SignalGroupAdminForm.test.tsx +++ b/src/components/ticker/SignalGroupAdminForm.test.tsx @@ -10,7 +10,7 @@ import SignalGroupAdminForm from './SignalGroupAdminForm' const token = sign({ id: 1, email: 'user@example.org', roles: ['user'], exp: new Date().getTime() / 1000 + 600 }, 'secret') -describe('SignalGroupForm', () => { +describe('SignalGroupAdminForm', () => { beforeAll(() => { localStorage.setItem('token', token) }) diff --git a/src/components/ticker/SignalGroupCard.test.tsx b/src/components/ticker/SignalGroupCard.test.tsx index b3f6dd3..3490335 100644 --- a/src/components/ticker/SignalGroupCard.test.tsx +++ b/src/components/ticker/SignalGroupCard.test.tsx @@ -50,12 +50,26 @@ describe('SignalGroupCard', () => { ) } - it('should render the component', () => { + it('should render the component', async () => { setup(ticker({ active: false, connected: false })) expect(screen.getByText('Signal Group')).toBeInTheDocument() expect(screen.getByText("You don't have a Signal group connected.")).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Configure' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Add' })).toBeInTheDocument() + + fetchMock.mockResponseOnce(JSON.stringify({ status: 'success' })) + + await userEvent.click(screen.getByRole('button', { name: 'Add' })) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(fetchMock).toHaveBeenCalledWith('http://localhost:8080/v1/admin/tickers/1/signal_group', { + body: JSON.stringify({ active: true }), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + token, + }, + method: 'put', + }) }) it('should render the component when connected and active', async () => { diff --git a/src/components/ticker/SignalGroupCard.tsx b/src/components/ticker/SignalGroupCard.tsx index fdbbc31..27c3dd1 100644 --- a/src/components/ticker/SignalGroupCard.tsx +++ b/src/components/ticker/SignalGroupCard.tsx @@ -1,5 +1,5 @@ import { faSignalMessenger } from '@fortawesome/free-brands-svg-icons' -import { faGear, faPause, faPlay, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons' +import { faAdd, faPause, faPlay, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Box, @@ -24,7 +24,6 @@ import { Ticker, deleteTickerSignalGroupApi, putTickerSignalGroupApi } from '../ import useAuth from '../../contexts/useAuth' import useNotification from '../../contexts/useNotification' import SignalGroupAdminModalForm from './SignalGroupAdminModalForm' -import SignalGroupModalForm from './SignalGroupModalForm' interface Props { ticker: Ticker @@ -32,24 +31,40 @@ interface Props { const SignalGroupCard: FC = ({ ticker }) => { const { token } = useAuth() - const [open, setOpen] = useState(false) const [dialogDeleteOpen, setDialogDeleteOpen] = useState(false) const [adminOpen, setAdminOpen] = useState(false) - const [submitting, setSubmitting] = useState(false) + const [submittingAdd, setSubmittingAdd] = useState(false) + const [submittingToggle, setSubmittingToggle] = useState(false) + const [submittingDelete, setSubmittingDelete] = useState(false) const { createNotification } = useNotification() const queryClient = useQueryClient() const signalGroup = ticker.signalGroup + const handleAdd = () => { + setSubmittingAdd(true) + putTickerSignalGroupApi(token, { active: true }, ticker) + .finally(() => { + queryClient.invalidateQueries({ queryKey: ['ticker', ticker.id] }) + createNotification({ content: 'Signal group successfully configured', severity: 'success' }) + setSubmittingAdd(false) + }) + .catch(() => { + createNotification({ content: 'Failed to configure Signal group', severity: 'error' }) + }) + } + const handleToggle = useCallback(() => { + setSubmittingToggle(true) putTickerSignalGroupApi(token, { active: !signalGroup.active }, ticker).finally(() => { queryClient.invalidateQueries({ queryKey: ['ticker', ticker.id] }) + setSubmittingToggle(false) }) }, [token, queryClient, signalGroup.active, ticker]) const handleDelete = () => { - setSubmitting(true) + setSubmittingDelete(true) deleteTickerSignalGroupApi(token, ticker) .finally(() => { queryClient.invalidateQueries({ queryKey: ['ticker', ticker.id] }) @@ -60,13 +75,13 @@ const SignalGroupCard: FC = ({ ticker }) => { }) .finally(() => { setDialogDeleteOpen(false) - setSubmitting(false) + setSubmittingDelete(false) }) } const groupLink = ( - {signalGroup.groupName} + {ticker.title} ) @@ -77,9 +92,26 @@ const SignalGroupCard: FC = ({ ticker }) => { Signal Group - + {signalGroup.connected ? null : ( + + + {submittingAdd && ( + + )} + + )} @@ -101,21 +133,35 @@ const SignalGroupCard: FC = ({ ticker }) => { - {signalGroup.active ? ( - - ) : ( - - )} + + {signalGroup.active ? ( + + ) : ( + + )} + {submittingToggle && ( + + )} + ) : null} - setOpen(false)} ticker={ticker} /> setAdminOpen(false)} ticker={ticker} /> Delete Signal Group @@ -125,10 +171,10 @@ const SignalGroupCard: FC = ({ ticker }) => { - - {submitting && ( + {submittingDelete && ( { - beforeAll(() => { - localStorage.setItem('token', token) - }) - - const ticker = ({ active, connected }: { active: boolean; connected: boolean }) => { - return { - id: 1, - signalGroup: { - active: active, - connected: connected, - }, - } as Ticker - } - - const callback = vi.fn() - const setSubmitting = vi.fn() - - beforeEach(() => { - fetchMock.resetMocks() - }) - - function setup(ticker: Ticker) { - const client = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }) - return render( - - - - -
- - -
-
-
-
-
- ) - } - - it('should render the component', async () => { - setup(ticker({ active: false, connected: false })) - - expect(screen.getByText('A new Signal group will be created with these settings.')).toBeInTheDocument() - expect(screen.getByRole('checkbox', { name: 'Active' })).toBeInTheDocument() - expect(screen.getByRole('textbox', { name: 'Group name' })).toBeInTheDocument() - expect(screen.getByRole('textbox', { name: 'Group description' })).toBeInTheDocument() - - await userEvent.click(screen.getByRole('checkbox', { name: 'Active' })) - await userEvent.type(screen.getByRole('textbox', { name: 'Group name' }), 'group name') - await userEvent.type(screen.getByRole('textbox', { name: 'Group description' }), 'group description') - - fetchMock.mockResponseOnce(JSON.stringify({ status: 'success' })) - - await userEvent.click(screen.getByRole('button', { name: 'Submit' })) - - expect(callback).toHaveBeenCalledTimes(1) - expect(setSubmitting).toHaveBeenNthCalledWith(1, true) - expect(fetchMock).toHaveBeenCalledTimes(1) - expect(fetchMock).toHaveBeenCalledWith('http://localhost:8080/v1/admin/tickers/1/signal_group', { - body: '{"active":true,"groupName":"group name","groupDescription":"group description"}', - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - method: 'put', - }) - expect(setSubmitting).toHaveBeenNthCalledWith(2, false) - }) -}) diff --git a/src/components/ticker/SignalGroupForm.tsx b/src/components/ticker/SignalGroupForm.tsx deleted file mode 100644 index 3ebb3db..0000000 --- a/src/components/ticker/SignalGroupForm.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Alert, Checkbox, FormControlLabel, FormGroup, Grid, TextField, Typography } from '@mui/material' -import { useQueryClient } from '@tanstack/react-query' -import { FC } from 'react' -import { useForm } from 'react-hook-form' -import { Ticker, TickerSignalGroupFormData, putTickerSignalGroupApi } from '../../api/Ticker' -import useAuth from '../../contexts/useAuth' -import useNotification from '../../contexts/useNotification' - -interface Props { - callback: () => void - ticker: Ticker - setSubmitting: (submitting: boolean) => void -} - -const SignalGroupForm: FC = ({ callback, ticker, setSubmitting }) => { - const signalGroup = ticker.signalGroup - const { token } = useAuth() - const { - formState: { errors }, - handleSubmit, - register, - setError, - } = useForm({ - defaultValues: { - active: signalGroup.active, - groupName: signalGroup.groupName, - groupDescription: signalGroup.groupDescription, - }, - }) - const queryClient = useQueryClient() - const { createNotification } = useNotification() - - const onSubmit = handleSubmit(data => { - setSubmitting(true) - putTickerSignalGroupApi(token, data, ticker) - .then(response => { - if (response.status == 'error') { - createNotification({ content: 'Failed to configure Signal group', severity: 'error' }) - setError('root.error', { message: 'Failed to configure Signal group' }) - } else { - queryClient.invalidateQueries({ queryKey: ['ticker', ticker.id] }) - createNotification({ content: 'Signal group successfully configured', severity: 'success' }) - callback() - } - }) - .finally(() => { - setSubmitting(false) - }) - }) - - return ( -
- - - {signalGroup.groupID ? ( - The Signal group will be updated with these settings. - ) : ( - A new Signal group will be created with these settings. - )} - - {errors.root?.error && ( - - {errors.root.error.message} - - )} - - - } label="Active" /> - - - - - - - - - - - - - -
- ) -} - -export default SignalGroupForm diff --git a/src/components/ticker/SignalGroupModalForm.tsx b/src/components/ticker/SignalGroupModalForm.tsx deleted file mode 100644 index ccc185f..0000000 --- a/src/components/ticker/SignalGroupModalForm.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { FC, useState } from 'react' -import { Ticker } from '../../api/Ticker' -import Modal from '../common/Modal' -import SignalGroupForm from './SignalGroupForm' - -interface Props { - onClose: () => void - open: boolean - ticker: Ticker -} - -const SignalGroupModalForm: FC = ({ onClose, open, ticker }) => { - const [submitting, setSubmitting] = useState(false) - - return ( - - - - ) -} - -export default SignalGroupModalForm