├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── __mocks__ │ └── guilds.ts ├── assets │ ├── default_avatar.png │ ├── nestjs.png │ ├── reactiflux.png │ └── typescript.png ├── components │ ├── AppBar.tsx │ ├── GuildMenuItem.tsx │ └── Spinner.tsx ├── index.css ├── index.tsx ├── pages │ ├── CategoryPage.tsx │ ├── GuildAnalyticsPage.tsx │ ├── GuildBansPage.tsx │ ├── GuildPrefixPage.tsx │ ├── LoginPage.tsx │ ├── MenuPage.tsx │ └── WelcomeMessagePage.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupTests.ts └── utils │ ├── api.ts │ ├── contexts │ └── GuildContext.tsx │ ├── helpers.ts │ ├── hooks │ ├── useFetchGuildBans.tsx │ ├── useFetchGuildConfig.tsx │ ├── useFetchGuilds.tsx │ ├── useFetchUser.tsx │ └── useWelcomePage.tsx │ ├── styles │ └── index.tsx │ └── types.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Bot Dashboard (2022) 2 | 3 | This is a Discord Bot Dashboard written in React. You might also want to take a look at the API and Discord Bot, the repositories can be found here: 4 | 5 | - [Dashboard NestJS API](https://github.com/stuyy/discord-dashboard-api) 6 | - [Discord Bot](https://github.com/stuyy/discord-dashboard-bot) 7 | 8 | Please do note all code provided in these projects are NOT for production use. They are only for eductional purposes only. These repositories are only a supplement to the new Discord Bot Dashboard Tutorial Series on my YouTube channel, which you can find [here](https://youtube.com/ansonthedeveloper). You can find the playlist with all episodes [here](https://www.youtube.com/playlist?list=PL_cUvD4qzbkyX4Wp8TAfjpttjUldDWJnp). 9 | 10 | For any further questions, you may raise an issue or contact me on Discord here: https://discord.gg/3S68xJnqZt 11 | 12 | # Instructions 13 | 14 | To setup this project, all you need to do is clone the repository. 15 | 16 | 1. Clone using SSH or HTTPS 17 | 18 | `git clone git@github.com:stuyy/discord-dashboard-react.git` 19 | 20 | The package manager I used for this project, and use generally, is `yarn`. You may use `npm` if you wish however if you run into any issues with installing dependencies or building, consider switching to `yarn`. 21 | 22 | 2. `cd` into the cloned project, and then install dependencies using `npm install` or `yarn install`. 23 | 24 | 3. Run `npm start` or `yarn start`, this should start the project on `http://localhost:3000`. 25 | 26 | In order to have the app fully working, you must have the NestJS API setup locally as well. Please visit the [Dashboard NestJS API](https://github.com/stuyy/discord-dashboard-api) project repository and go through the README for instructions on how to setup the Nest API for the dashboard. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-dashboard-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^12.0.0", 8 | "@testing-library/user-event": "^13.2.1", 9 | "@types/jest": "^27.0.1", 10 | "@types/node": "^16.7.13", 11 | "@types/react": "^17.0.20", 12 | "@types/react-dom": "^17.0.9", 13 | "axios": "^0.24.0", 14 | "chart.js": "^3.7.0", 15 | "react": "^17.0.2", 16 | "react-chartjs-2": "^4.0.0", 17 | "react-dom": "^17.0.2", 18 | "react-icons": "^4.3.1", 19 | "react-router-dom": "^6.2.1", 20 | "react-scripts": "5.0.0", 21 | "react-spinners": "^0.11.0", 22 | "styled-components": "^5.3.3", 23 | "typescript": "^4.4.2", 24 | "web-vitals": "^2.1.0" 25 | }, 26 | "scripts": { 27 | "start:dev": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "@types/axios": "^0.14.0", 52 | "@types/styled-components": "^5.1.19" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | 29 | 33 | React App 34 | 35 | 36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Routes, Route } from 'react-router-dom'; 3 | import { BarLoader } from 'react-spinners'; 4 | import { AppBar } from './components/AppBar'; 5 | import { Spinner } from './components/Spinner'; 6 | import { CategoryPage } from './pages/CategoryPage'; 7 | import { GuildAnalyticsPage } from './pages/GuildAnalyticsPage'; 8 | import { GuildBansPage } from './pages/GuildBansPage'; 9 | import { GuildPrefixPage } from './pages/GuildPrefixPage'; 10 | import { LoginPage } from './pages/LoginPage'; 11 | import { MenuPage } from './pages/MenuPage'; 12 | import { WelcomeMessagePage } from './pages/WelcomeMessagePage'; 13 | import { GuildContext } from './utils/contexts/GuildContext'; 14 | import { useFetchUser } from './utils/hooks/useFetchUser'; 15 | import { PartialGuild } from './utils/types'; 16 | 17 | function App() { 18 | const [guild, setGuild] = useState(); 19 | const { user, loading, error } = useFetchUser(); 20 | 21 | const updateGuild = (guild: PartialGuild) => setGuild(guild); 22 | 23 | if (loading) return } />; 24 | 25 | return ( 26 | 27 | {user && !error ? ( 28 | <> 29 | 30 | } /> 31 | 32 | 33 | } /> 34 | } /> 35 | {/* } /> */} 36 | } /> 37 | } /> 38 | } /> 39 | } 42 | /> 43 | } /> 44 | 45 | 46 | ) : ( 47 | 48 | } /> 49 | Not Found} /> 50 | 51 | )} 52 | 53 | ); 54 | } 55 | 56 | export default App; 57 | -------------------------------------------------------------------------------- /src/__mocks__/guilds.ts: -------------------------------------------------------------------------------- 1 | import NestJSIcon from '../assets/nestjs.png'; 2 | import ReactIcon from '../assets/reactiflux.png'; 3 | import TypescriptIcon from '../assets/typescript.png'; 4 | 5 | export const mockGuilds = [ 6 | { 7 | id: '123', 8 | name: 'NestJS', 9 | icon: NestJSIcon, 10 | }, 11 | { 12 | id: '124', 13 | name: 'React', 14 | icon: ReactIcon, 15 | }, 16 | { 17 | id: '125', 18 | name: 'Typescript', 19 | icon: TypescriptIcon, 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/assets/default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/src/assets/default_avatar.png -------------------------------------------------------------------------------- /src/assets/nestjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/src/assets/nestjs.png -------------------------------------------------------------------------------- /src/assets/reactiflux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/src/assets/reactiflux.png -------------------------------------------------------------------------------- /src/assets/typescript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuyy/discord-dashboard-react/17f081e9589dd9e2be5bb4abf9cf67038729866e/src/assets/typescript.png -------------------------------------------------------------------------------- /src/components/AppBar.tsx: -------------------------------------------------------------------------------- 1 | import { AppBarStyle } from '../utils/styles'; 2 | import NestJSIcon from '../assets/nestjs.png'; 3 | import { useContext } from 'react'; 4 | import { GuildContext } from '../utils/contexts/GuildContext'; 5 | import { Navigate } from 'react-router-dom'; 6 | import { getIconURL } from '../utils/helpers'; 7 | 8 | export const AppBar = () => { 9 | const { guild } = useContext(GuildContext); 10 | console.log(guild); 11 | 12 | return guild ? ( 13 | 14 |

15 | Configuring {guild.name} 16 |

17 | logo 26 |
27 | ) : ( 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/GuildMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import { getIconURL } from '../utils/helpers'; 2 | import { GuildMenuItemStyle } from '../utils/styles'; 3 | import { PartialGuild } from '../utils/types'; 4 | 5 | type Props = { 6 | guild: PartialGuild; 7 | }; 8 | 9 | export const GuildMenuItem = ({ guild }: Props) => ( 10 | 11 | {guild.name} 18 |

{guild.name}

19 |
20 | ); 21 | -------------------------------------------------------------------------------- /src/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Overlay } from '../utils/styles'; 3 | 4 | export const Spinner: FC = ({ children }) => {children}; 5 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background-color: #292929; 4 | color: #fff; 5 | font-family: 'DM Sans'; 6 | height: 100%; 7 | } 8 | 9 | html, 10 | #root { 11 | height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { BrowserRouter as Router } from 'react-router-dom'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals(); 21 | -------------------------------------------------------------------------------- /src/pages/CategoryPage.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { GuildContext } from '../utils/contexts/GuildContext'; 3 | import { 4 | Container, 5 | Flex, 6 | Grid, 7 | Page, 8 | TextButton, 9 | Title, 10 | } from '../utils/styles'; 11 | import { 12 | IoSettingsOutline, 13 | IoNewspaperOutline, 14 | IoInformationCircleOutline, 15 | } from 'react-icons/io5'; 16 | import { useNavigate } from 'react-router-dom'; 17 | 18 | export const CategoryPage = () => { 19 | const { guild } = useContext(GuildContext); 20 | const navigate = useNavigate(); 21 | 22 | return ( 23 | 24 | 25 |
26 | 27 | Guild Information 28 | 29 | 30 | 31 | navigate('/dashboard/analytics')}> 32 | Analytics 33 | 34 | navigate('/dashboard/bans')}> 35 | Guild Bans 36 | 37 | 38 |
39 |
40 | 41 | Basic Configurations 42 | 43 | 44 | 45 | navigate('/dashboard/prefix')}> 46 | Command Prefix 47 | 48 | navigate('/dashboard/message')}> 49 | Welcome Message 50 | 51 | 52 |
53 |
54 | 55 | Channel Logs 56 | 57 | 58 | 59 | Moderation Logs 60 | Bot Logs 61 | 62 |
63 |
64 |
65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /src/pages/GuildAnalyticsPage.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { getGuildBanLogs, getGuildModLogs } from '../utils/api'; 3 | import { GuildContext } from '../utils/contexts/GuildContext'; 4 | import { GuildModLogType } from '../utils/types'; 5 | import { Line } from 'react-chartjs-2'; 6 | import { 7 | Chart as ChartJS, 8 | CategoryScale, 9 | LinearScale, 10 | PointElement, 11 | LineElement, 12 | } from 'chart.js'; 13 | import { Flex, Title } from '../utils/styles'; 14 | 15 | ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement); 16 | 17 | export const GuildAnalyticsPage = () => { 18 | const { guild } = useContext(GuildContext); 19 | const guildId = (guild && guild.id) || ''; 20 | 21 | const [modLogs, setModLogs] = useState([]); 22 | 23 | const getLabels = () => { 24 | const currentDate = new Date(); 25 | const last = currentDate.getDate(); 26 | const start = last - 6; 27 | const labels = []; 28 | for (let i = start; i <= last; i++) { 29 | currentDate.setDate(i); 30 | labels.push(`${currentDate.getMonth() + 1}/${currentDate.getDate()}`); 31 | } 32 | return labels; 33 | }; 34 | 35 | const prepareData = (data: GuildModLogType[]) => { 36 | const currentDate = new Date(); 37 | const last = currentDate.getDate(); 38 | const start = last - 6; 39 | const dataRecords = []; 40 | for (let i = start; i <= last; i++) { 41 | const records = data.filter( 42 | (banLog) => new Date(banLog.issuedOn).getDate() === i 43 | ); 44 | dataRecords.push(records.length); 45 | } 46 | return dataRecords; 47 | }; 48 | 49 | useEffect(() => { 50 | const currentDate = new Date(); 51 | currentDate.setDate(currentDate.getDate() - 6); 52 | const fromDate = currentDate.toLocaleDateString(); 53 | getGuildModLogs(guildId, fromDate) 54 | .then(({ data }) => { 55 | setModLogs(data); 56 | }) 57 | .catch((err) => console.log(err)); 58 | }, []); 59 | 60 | return ( 61 |
62 |
68 |
69 | Ban Analytics 70 | log.type === 'ban') 78 | ), 79 | borderColor: 'white', 80 | pointBorderColor: 'orange', 81 | }, 82 | ], 83 | }} 84 | /> 85 |
86 |
87 | Kick Analytics 88 | log.type === 'kick') 96 | ), 97 | borderColor: 'white', 98 | pointBorderColor: 'orange', 99 | }, 100 | ], 101 | }} 102 | /> 103 |
104 |
105 | Timeout Analytics 106 | log.type === 'timeout') 114 | ), 115 | borderColor: 'white', 116 | pointBorderColor: 'orange', 117 | }, 118 | ], 119 | }} 120 | /> 121 |
122 |
123 |
124 | ); 125 | }; 126 | -------------------------------------------------------------------------------- /src/pages/GuildBansPage.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { MoonLoader } from 'react-spinners'; 3 | import { GuildContext } from '../utils/contexts/GuildContext'; 4 | import { useFetchGuildBans } from '../utils/hooks/useFetchGuildBans'; 5 | import { 6 | Container, 7 | ContextMenuContainer, 8 | Flex, 9 | Page, 10 | UserBanCard, 11 | } from '../utils/styles'; 12 | import { DiscordUserType, GuildBanType } from '../utils/types'; 13 | import DefaultAvatar from '../assets/default_avatar.png'; 14 | import { deleteGuildBan } from '../utils/api'; 15 | 16 | export const GuildBansPage = () => { 17 | const { guild } = useContext(GuildContext); 18 | const guildId = (guild && guild.id) || ''; 19 | const { bans, loading, updating, setUpdating } = useFetchGuildBans(guildId); 20 | const [showMenu, setShowMenu] = useState(false); 21 | const [points, setPoints] = useState({ x: 0, y: 0 }); 22 | const [selectedBan, setSelectedBan] = useState(); 23 | 24 | const getAvatarUrl = (user: DiscordUserType) => 25 | `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`; 26 | 27 | useEffect(() => { 28 | const handleClick = () => setShowMenu(false); 29 | window.addEventListener('click', handleClick); 30 | return () => window.removeEventListener('click', handleClick); 31 | }, []); 32 | 33 | const handleUnban = async () => { 34 | if (!selectedBan) { 35 | console.log('No user was selected.'); 36 | return; 37 | } 38 | try { 39 | console.log(`Unbanning User: ${selectedBan?.user.username}`); 40 | await deleteGuildBan(guildId, selectedBan.user.id); 41 | setUpdating(!updating); 42 | } catch (err) { 43 | console.log(err); 44 | } 45 | }; 46 | 47 | return ( 48 | 49 | 50 | {!loading ? ( 51 |
58 | {bans.length ? ( 59 | bans.map((ban) => ( 60 | { 62 | console.log('Context Menu Opened'); 63 | e.preventDefault(); 64 | setShowMenu(true); 65 | setPoints({ x: e.pageX, y: e.pageY }); 66 | setSelectedBan(ban); 67 | }} 68 | > 69 |
70 | {ban.user.username}#{ban.user.discriminator} 71 |
72 | avatar 81 |
82 | )) 83 | ) : ( 84 |
No Bans
85 | )} 86 | {showMenu && ( 87 | 88 |
    89 |
  • Unban
  • 90 |
  • Update Ban
  • 91 |
92 |
93 | )} 94 |
95 | ) : ( 96 | 97 | 98 | 99 | )} 100 |
101 |
102 | ); 103 | }; 104 | -------------------------------------------------------------------------------- /src/pages/GuildPrefixPage.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { MoonLoader } from 'react-spinners'; 3 | import { updateGuildPrefix } from '../utils/api'; 4 | import { GuildContext } from '../utils/contexts/GuildContext'; 5 | import { useGuildConfig } from '../utils/hooks/useFetchGuildConfig'; 6 | import { 7 | Button, 8 | Container, 9 | Flex, 10 | InputField, 11 | Page, 12 | Title, 13 | } from '../utils/styles'; 14 | 15 | export const GuildPrefixPage = () => { 16 | const { guild } = useContext(GuildContext); 17 | const guildId = (guild && guild.id) || ''; 18 | const { config, loading, error, prefix, setPrefix } = useGuildConfig(guildId); 19 | 20 | const savePrefix = async ( 21 | e: React.MouseEvent 22 | ) => { 23 | e.preventDefault(); 24 | console.log(prefix); 25 | try { 26 | const res = await updateGuildPrefix(guildId, prefix); 27 | console.log(res); 28 | } catch (err) { 29 | console.log(err); 30 | } 31 | }; 32 | 33 | return ( 34 | 35 | 36 | {!loading && config ? ( 37 | <> 38 | Update Command Prefix 39 |
40 |
41 | 42 |
43 | setPrefix(e.target.value)} 48 | /> 49 | 50 | 59 | 62 | 63 | 64 | 65 | ) : ( 66 | 67 | 68 | 69 | )} 70 |
71 |
72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /src/pages/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import { FaDiscord, FaQuestionCircle } from 'react-icons/fa'; 2 | import { HomePageStyle, MainButton } from '../utils/styles'; 3 | 4 | export const LoginPage = () => { 5 | const redirect = () => { 6 | window.location.href = 'http://localhost:3001/api/auth/login'; 7 | }; 8 | return ( 9 | 10 |
11 |
12 | 13 | 14 |

Login with Discord

15 |
16 | 17 | 18 |

Support Server

19 |
20 |
21 |
28 | Privacy Policy 29 | Terms of Service 30 | Contact Us 31 |
32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/pages/MenuPage.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { MoonLoader } from 'react-spinners'; 4 | import { GuildMenuItem } from '../components/GuildMenuItem'; 5 | import { GuildContext } from '../utils/contexts/GuildContext'; 6 | import { useFetchGuilds } from '../utils/hooks/useFetchGuilds'; 7 | import { Container, Flex, Page } from '../utils/styles'; 8 | import { PartialGuild } from '../utils/types'; 9 | import { mockGuilds } from '../__mocks__/guilds'; 10 | 11 | export const MenuPage = () => { 12 | const navigate = useNavigate(); 13 | const { updateGuild } = useContext(GuildContext); 14 | const { guilds, loading, error } = useFetchGuilds(); 15 | 16 | const handleClick = (guild: PartialGuild) => { 17 | updateGuild(guild); 18 | navigate('/dashboard/categories'); 19 | }; 20 | 21 | return ( 22 | 23 | 24 |

Select a Server

25 |
26 | {loading ? ( 27 | 28 | 29 | 30 | ) : ( 31 |
32 | {guilds && 33 | guilds.map((guild) => ( 34 |
handleClick(guild)}> 35 | 36 |
37 | ))} 38 |
39 | )} 40 |
41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/pages/WelcomeMessagePage.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { MoonLoader } from 'react-spinners'; 3 | import { updateWelcomeChannelId } from '../utils/api'; 4 | import { GuildContext } from '../utils/contexts/GuildContext'; 5 | import { useWelcomePage } from '../utils/hooks/useWelcomePage'; 6 | import { 7 | Button, 8 | Container, 9 | Flex, 10 | Page, 11 | Select, 12 | TextArea, 13 | Title, 14 | } from '../utils/styles'; 15 | 16 | export const WelcomeMessagePage = () => { 17 | const { guild } = useContext(GuildContext); 18 | const guildId = (guild && guild.id) || ''; 19 | const { config, channels, selectedChannel, setSelectedChannel, loading } = 20 | useWelcomePage(guildId); 21 | 22 | const updateWelcomeChannel = async () => { 23 | console.log(selectedChannel); 24 | try { 25 | await updateWelcomeChannelId(guildId, selectedChannel || ''); 26 | } catch (err) { 27 | console.log(err); 28 | } 29 | }; 30 | 31 | return ( 32 | 33 | 34 | Update Welcome Message 35 | {channels && config && !loading ? ( 36 |
37 |
38 |
39 | 40 |
41 | 55 |
56 |
57 |
58 | 59 |
60 |