15 changed files with 342 additions and 37 deletions
-
2web/components/Navbar.tsx
-
65web/components/dashboard/ApiKeyList.tsx
-
95web/components/dashboard/DeviceList.tsx
-
5web/components/dashboard/GenerateApiKey.tsx
-
2web/components/dashboard/UserStats.tsx
-
29web/pages/dashboard/index.tsx
-
2web/pages/index.tsx
-
2web/pages/login.tsx
-
2web/pages/register.tsx
-
20web/services/index.ts
-
21web/services/types.ts
-
61web/store/apiKeyListReducer.ts
-
4web/store/authReducer.ts
-
61web/store/deviceListReducer.ts
-
8web/store/store.ts
@ -0,0 +1,95 @@ |
|||||
|
import { DeleteIcon, EmailIcon } from '@chakra-ui/icons' |
||||
|
import { |
||||
|
IconButton, |
||||
|
Spinner, |
||||
|
Table, |
||||
|
TableContainer, |
||||
|
Tbody, |
||||
|
Td, |
||||
|
Th, |
||||
|
Thead, |
||||
|
Tooltip, |
||||
|
Tr, |
||||
|
} from '@chakra-ui/react' |
||||
|
import { useEffect } from 'react' |
||||
|
import { useDispatch, useSelector } from 'react-redux' |
||||
|
import { sendSMSRequest } from '../../services' |
||||
|
import { selectAuth } from '../../store/authReducer' |
||||
|
import { |
||||
|
fetchDeviceList, |
||||
|
selectDeviceList, |
||||
|
} from '../../store/deviceListReducer' |
||||
|
|
||||
|
const DeviceList = () => { |
||||
|
const dispatch = useDispatch() |
||||
|
|
||||
|
const { user, accessToken } = useSelector(selectAuth) |
||||
|
useEffect(() => { |
||||
|
if (user && accessToken) { |
||||
|
dispatch(fetchDeviceList()) |
||||
|
} |
||||
|
}, [user, accessToken, dispatch]) |
||||
|
|
||||
|
const { data, loading } = useSelector(selectDeviceList) |
||||
|
|
||||
|
const onDelete = (apiKeyId: string) => {} |
||||
|
|
||||
|
return ( |
||||
|
<TableContainer> |
||||
|
<Table variant='simple'> |
||||
|
<Thead> |
||||
|
<Tr> |
||||
|
<Th>Your Devices</Th> |
||||
|
<Th>Status</Th> |
||||
|
<Th colSpan={2}>Actions</Th> |
||||
|
</Tr> |
||||
|
</Thead> |
||||
|
<Tbody> |
||||
|
{loading ? ( |
||||
|
<Tr> |
||||
|
<Td colSpan={3} textAlign='center'> |
||||
|
<Spinner size='lg' /> |
||||
|
</Td> |
||||
|
</Tr> |
||||
|
) : ( |
||||
|
<> |
||||
|
{data.length === 0 ? ( |
||||
|
<Tr> |
||||
|
<Td colSpan={3} textAlign='center'> |
||||
|
No Devices |
||||
|
</Td> |
||||
|
</Tr> |
||||
|
) : ( |
||||
|
data.map(({ _id, brand, model, enabled, createdAt }) => ( |
||||
|
<Tr key={_id}> |
||||
|
<Td>{`${brand}/ ${model}`}</Td> |
||||
|
<Td>{enabled ? 'enabled' : 'disabled'}</Td> |
||||
|
<Td> |
||||
|
<EmailIcon onDoubleClick={(e) => {}} /> |
||||
|
</Td> |
||||
|
<Td> |
||||
|
<Tooltip label='Double Click to delete'> |
||||
|
<IconButton |
||||
|
aria-label='Delete' |
||||
|
icon={<DeleteIcon />} |
||||
|
onDoubleClick={(e) => { |
||||
|
sendSMSRequest(_id, { |
||||
|
receivers: ['+251912657519'], |
||||
|
smsBody: 'Hello World', |
||||
|
}) |
||||
|
}} |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
</Td> |
||||
|
</Tr> |
||||
|
)) |
||||
|
)} |
||||
|
</> |
||||
|
)} |
||||
|
</Tbody> |
||||
|
</Table> |
||||
|
</TableContainer> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default DeviceList |
||||
@ -0,0 +1,61 @@ |
|||||
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
||||
|
import type { PayloadAction } from '@reduxjs/toolkit' |
||||
|
import { getApiKeyListRequest } from '../services' |
||||
|
import { createStandaloneToast } from '@chakra-ui/react' |
||||
|
import { RootState } from './store' |
||||
|
|
||||
|
const toast = createStandaloneToast() |
||||
|
|
||||
|
const initialState = { |
||||
|
loading: false, |
||||
|
data: [], |
||||
|
} |
||||
|
|
||||
|
export const fetchApiKeyList = createAsyncThunk( |
||||
|
'apiKeyList/fetchApiKeys', |
||||
|
async (payload, thunkAPI) => { |
||||
|
try { |
||||
|
const res = await getApiKeyListRequest() |
||||
|
return res |
||||
|
} catch (e) { |
||||
|
toast({ |
||||
|
title: e.response.data.error || 'Failed to Fetch apiKeys', |
||||
|
status: 'error', |
||||
|
}) |
||||
|
return thunkAPI.rejectWithValue(e.response.data) |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
export const apiKeyListSlice = createSlice({ |
||||
|
name: 'apiKeyList', |
||||
|
initialState, |
||||
|
reducers: { |
||||
|
clearApiKeyList: (state) => { |
||||
|
state.loading = false |
||||
|
state.data = [] |
||||
|
}, |
||||
|
}, |
||||
|
extraReducers: (builder) => { |
||||
|
builder |
||||
|
.addCase(fetchApiKeyList.pending, (state) => { |
||||
|
state.loading = true |
||||
|
}) |
||||
|
.addCase( |
||||
|
fetchApiKeyList.fulfilled, |
||||
|
(state, action: PayloadAction<any>) => { |
||||
|
state.loading = false |
||||
|
state.data = action.payload |
||||
|
} |
||||
|
) |
||||
|
.addCase(fetchApiKeyList.rejected, (state) => { |
||||
|
state.loading = false |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
export const { clearApiKeyList } = apiKeyListSlice.actions |
||||
|
|
||||
|
export const selectApiKeyList = (state: RootState) => state.apiKeyList |
||||
|
|
||||
|
export default apiKeyListSlice.reducer |
||||
@ -0,0 +1,61 @@ |
|||||
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
||||
|
import type { PayloadAction } from '@reduxjs/toolkit' |
||||
|
import { getDeviceListRequest } from '../services' |
||||
|
import { createStandaloneToast } from '@chakra-ui/react' |
||||
|
import { RootState } from './store' |
||||
|
|
||||
|
const toast = createStandaloneToast() |
||||
|
|
||||
|
const initialState = { |
||||
|
loading: false, |
||||
|
data: [], |
||||
|
} |
||||
|
|
||||
|
export const fetchDeviceList = createAsyncThunk( |
||||
|
'deviceList/fetchDevices', |
||||
|
async (payload, thunkAPI) => { |
||||
|
try { |
||||
|
const res = await getDeviceListRequest() |
||||
|
return res |
||||
|
} catch (e) { |
||||
|
toast({ |
||||
|
title: e.response.data.error || 'Failed to Fetch devices', |
||||
|
status: 'error', |
||||
|
}) |
||||
|
return thunkAPI.rejectWithValue(e.response.data) |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
export const deviceListSlice = createSlice({ |
||||
|
name: 'deviceList', |
||||
|
initialState, |
||||
|
reducers: { |
||||
|
clearDeviceList: (state) => { |
||||
|
state.loading = false |
||||
|
state.data = [] |
||||
|
}, |
||||
|
}, |
||||
|
extraReducers: (builder) => { |
||||
|
builder |
||||
|
.addCase(fetchDeviceList.pending, (state) => { |
||||
|
state.loading = true |
||||
|
}) |
||||
|
.addCase( |
||||
|
fetchDeviceList.fulfilled, |
||||
|
(state, action: PayloadAction<any>) => { |
||||
|
state.loading = false |
||||
|
state.data = action.payload |
||||
|
} |
||||
|
) |
||||
|
.addCase(fetchDeviceList.rejected, (state) => { |
||||
|
state.loading = false |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
export const { clearDeviceList } = deviceListSlice.actions |
||||
|
|
||||
|
export const selectDeviceList = (state: RootState) => state.deviceList |
||||
|
|
||||
|
export default deviceListSlice.reducer |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue