├── src ├── types │ └── vite-env.d.ts ├── assets │ └── images │ │ ├── 404.jpeg │ │ ├── simo.png │ │ └── correct.png ├── pages │ ├── Bot │ │ ├── Addbot.tsx │ │ ├── Vote.tsx │ │ ├── Search.tsx │ │ └── Bot.tsx │ ├── Team │ │ ├── Team.tsx │ │ ├── Invite.tsx │ │ ├── CreateTeam.tsx │ │ └── ManageTeam.tsx │ ├── Dashboard │ │ ├── Edit.tsx │ │ └── Dashboard.tsx │ ├── Mixed │ │ ├── Theme.tsx │ │ ├── NotFound.tsx │ │ ├── Tests.tsx │ │ └── Notifications.tsx │ └── Login │ │ └── Login.tsx ├── utils │ ├── theme │ │ ├── mobileMenu.ts │ │ ├── border.ts │ │ ├── border&bg.ts │ │ ├── shadow.ts │ │ ├── app.ts │ │ ├── button.ts │ │ └── scrollBar.ts │ └── api │ │ ├── errors.ts │ │ └── index.ts ├── components │ ├── Addbot │ │ ├── Addbot.tsx │ │ ├── FinishAddbot.tsx │ │ ├── FormAddbot.tsx │ │ └── FindBot.tsx │ ├── Badges │ │ ├── Badges.tsx │ │ └── Badge.tsx │ ├── Mixed │ │ ├── Button.tsx │ │ ├── PopUp.tsx │ │ ├── Copy.tsx │ │ ├── Auth.tsx │ │ └── Error.tsx │ ├── Search │ │ ├── InputSearch.tsx │ │ └── Search.tsx │ ├── Vote │ │ ├── Loading.tsx │ │ └── Vote.tsx │ ├── Header │ │ └── Header.tsx │ ├── Team │ │ ├── Input.tsx │ │ ├── Loaders.tsx │ │ │ ├── TeamCard.tsx │ │ │ └── ManageTeam.tsx │ │ ├── LeaveTeam.tsx │ │ ├── Teams.tsx │ │ ├── DeleteTeam.tsx │ │ ├── RemoveTeamBot.tsx │ │ ├── CreateTeam.tsx │ │ ├── AuditLogs.tsx │ │ ├── ManageBots.tsx │ │ ├── Invite.tsx │ │ ├── EditTeam.tsx │ │ ├── Addbot.tsx │ │ └── Team.tsx │ ├── Footer │ │ └── Footer.tsx │ ├── User │ │ ├── UserLoading.tsx │ │ └── User.tsx │ ├── Markdown │ │ └── Markdown.tsx │ ├── Colors │ │ └── Choice.tsx │ ├── DropdownMenu │ │ ├── Option.tsx │ │ └── Menu.tsx │ ├── BotList │ │ ├── Bots.tsx │ │ ├── BotCard.tsx │ │ └── Botloading.tsx │ ├── Notification │ │ ├── Card.tsx │ │ └── Button.tsx │ ├── Mobile │ │ └── Mobilemenu.tsx │ ├── Dashboard │ │ └── Delete.tsx │ └── Feedbacks │ │ ├── Feedbacks.tsx │ │ └── ReplyFeedback.tsx ├── App │ ├── main.tsx │ ├── index.css │ └── App.tsx └── contexts │ ├── ThemeContext.tsx │ └── UserContext.tsx ├── .gitignore ├── postcss.config.js ├── vercel.json ├── tailwind.config.js ├── vite.config.ts ├── README.md ├── tsconfig.json ├── index.html └── package.json /src/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .vscode/* 4 | .env 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /src/assets/images/404.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoworkspace/website/HEAD/src/assets/images/404.jpeg -------------------------------------------------------------------------------- /src/assets/images/simo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoworkspace/website/HEAD/src/assets/images/simo.png -------------------------------------------------------------------------------- /src/assets/images/correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoworkspace/website/HEAD/src/assets/images/correct.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "tailwindcss/nesting": {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /src/pages/Bot/Addbot.tsx: -------------------------------------------------------------------------------- 1 | import { AddbotComponent } from "../../components/Addbot/Addbot"; 2 | 3 | export const Addbot: React.FC = () => { 4 | return ; 5 | }; 6 | -------------------------------------------------------------------------------- /src/pages/Team/Team.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { TeamComponent } from "../../components/Team/Team"; 3 | 4 | export const TeamPage: FC = () => { 5 | return 6 | }; -------------------------------------------------------------------------------- /src/pages/Bot/Vote.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { VoteComponent } from "../../components/Vote/Vote"; 3 | 4 | export const Vote: React.FC = () => { 5 | return 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/Team/Invite.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { InviteComponent } from "../../components/Team/Invite"; 3 | 4 | export const InvitePage: FC = () => { 5 | return 6 | }; -------------------------------------------------------------------------------- /src/pages/Bot/Search.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SearchComponent } from '../../components/Search/Search'; 3 | 4 | export const Search: React.FC = () => { 5 | return 6 | }; -------------------------------------------------------------------------------- /src/pages/Team/CreateTeam.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { CreateTeam } from "../../components/Team/CreateTeam"; 3 | 4 | export const CreateTeamPage: FC = () => { 5 | return 6 | }; -------------------------------------------------------------------------------- /src/pages/Dashboard/Edit.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { DashboardEdit } from "../../components/Dashboard/Edit"; 3 | 4 | export const DashboardEditPage: FC = () => { 5 | return 6 | }; -------------------------------------------------------------------------------- /src/pages/Team/ManageTeam.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { ManageTeamComponent } from "../../components/Team/ManageTeam"; 3 | 4 | export const ManageTeamPage: FC = () => { 5 | return 6 | }; -------------------------------------------------------------------------------- /src/pages/Dashboard/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DashboardComponent } from "../../components/Dashboard/Dashboard"; 3 | 4 | export const Dashboard: React.FC = () => { 5 | return 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/theme/mobileMenu.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const mobileMenu: ThemeStructure = { 4 | blue: "bg-[#033757]", 5 | green: "bg-[#056b49]", 6 | red: "bg-[#571423]", 7 | purple: "bg-[#351a7c]", 8 | black: "bg-[#000]" 9 | }; -------------------------------------------------------------------------------- /src/utils/theme/border.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const borderColor: ThemeStructure = { 4 | blue: "border-slate-700", 5 | green: "border-[#1d733f]", 6 | red: "border-red-900", 7 | purple: "border-purple-900", 8 | black: "border-neutral-500" 9 | }; -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/api/(.*)", 5 | "destination": "https://botlistapi.squareweb.app/api/$1" 6 | }, 7 | { 8 | "source": "/(.*)", 9 | "destination": "/index.html" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/theme/border&bg.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const borderAndBg: ThemeStructure = {blue: "bg-slate-900 border-slate-700", 4 | green: "bg-green-900 border-[#1d733f]", 5 | red: "bg-[#4a1110] border-red-900", 6 | purple: "bg-[#301542] border-purple-900", 7 | black: "bg-neutral-900 border-neutral-500" 8 | }; -------------------------------------------------------------------------------- /src/utils/theme/shadow.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const shadowColor: ThemeStructure = { 4 | blue: "border-blue-500 shadow-blue-500", 5 | green: "border-green-500 shadow-green-500", 6 | red: "border-red-500 shadow-red-500", 7 | purple: "border-purple-500 shadow-purple-500", 8 | black: "border-white shadow-white" 9 | }; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | content: ["./src/**/*.{html,js,ts,jsx,tsx}"], 3 | theme: { 4 | screens: { 5 | xl: { max: "950px" }, 6 | xp: { max: "460px" }, 7 | xlr: { min: "950px" } 8 | } 9 | }, 10 | plugins: [require("tailwind-scrollbar"), require("@tailwindcss/typography")], 11 | }; 12 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | 4 | export default defineConfig({ 5 | server: { 6 | // host: '0.0.0.0', 7 | // port: 80, 8 | proxy: { 9 | "/api": { 10 | target: "http://localhost:80", 11 | changeOrigin: true, 12 | secure: false, 13 | ws: true, 14 | }, 15 | }, 16 | }, 17 | plugins: [react()], 18 | }); 19 | -------------------------------------------------------------------------------- /src/utils/theme/app.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const appColor: ThemeStructure = { 4 | blue: "bg-slate-900 scrollbar-thumb-blue-500 hover:scrollbar-thumb-blue-400", 5 | green: "bg-[#163d22] scrollbar-thumb-green-500 hover:scrollbar-thumb-green-400", 6 | red: "bg-[#45151a] scrollbar-thumb-red-500 hover:scrollbar-thumb-red-400", 7 | purple: "bg-[#220e33] scrollbar-thumb-[#5732bd] hover:scrollbar-thumb-[#6439db]", 8 | black: "bg-neutral-900 scrollbar-thumb-[#222] hover:scrollbar-thumb-[#2c2c2c]" 9 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simo's Botlist Website 2 | 3 | Para ver o site em ação clone esse repositório, e siga os passos abaixo 4 | 5 | # 6 | 7 | Digite em seu console para instalar todas as dependências necessárias 8 | 9 | ```js 10 | npm install 11 | ``` 12 | 13 | Após isso, rode o código com o seguinte comando 14 | 15 | ```js 16 | npm run dev 17 | ``` 18 | 19 | Quando iniciar ele se encontra na url: http://localhost:5173 após isso só ver o site em ação 20 | 21 | # 22 | 23 | Caso tenha alguma duvida entre nesse servidor: https://discord.gg/N2K7trqHQG 24 | -------------------------------------------------------------------------------- /src/utils/theme/button.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const buttonColor: ThemeStructure = { 4 | blue: "bg-slate-900 hover:bg-slate-700 border-slate-700 disabled:hover:bg-slate-900", 5 | green: "bg-green-900 hover:bg-[#1d733f] border-[#1d733f] disabled:hover:bg-green-900", 6 | red: "bg-[#4a1110] hover:bg-red-900 border-red-900 disabled:hover:bg-[#4a1110]", 7 | purple: "bg-[#301542] hover:bg-purple-900 border-purple-900 disabled:hover:bg-[#301542]", 8 | black: "bg-neutral-900 border-neutral-500 hover:bg-neutral-500 disabled:hover:bg-neutral-900" 9 | }; -------------------------------------------------------------------------------- /src/utils/theme/scrollBar.ts: -------------------------------------------------------------------------------- 1 | import { ThemeStructure } from '../../types'; 2 | 3 | export const scrollBar: ThemeStructure = { 4 | blue: "scrollbar-thumb-blue-500 hover:scrollbar-thumb-blue-400 scrollbar-track-neutral-900 scrollbar-thin", 5 | green: "scrollbar-thumb-green-500 hover:scrollbar-thumb-green-400 scrollbar-track-neutral-900 scrollbar-thin", 6 | red: "scrollbar-thumb-red-500 hover:scrollbar-thumb-red-400 scrollbar-track-neutral-900 scrollbar-thin", 7 | purple: "scrollbar-thumb-[#5732bd] hover:scrollbar-thumb-[#6439db] scrollbar-track-neutral-900 scrollbar-thin", 8 | black: "scrollbar-thumb-[#222] hover:scrollbar-thumb-[#2c2c2c] scrollbar-track-neutral-900 scrollbar-thin" 9 | }; -------------------------------------------------------------------------------- /src/components/Addbot/Addbot.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { FormAddbot } from "./FormAddbot"; 3 | import { FindBot } from "./FindBot"; 4 | import { FindBotStructure } from "../../types"; 5 | import { FinishAddbot } from "./FinishAddbot"; 6 | 7 | export const AddbotComponent: any = () => { 8 | const [steps, setSteps] = useState(0); 9 | const [botData, setBotData] = useState(); 10 | 11 | switch (steps) { 12 | case 0: return ; 13 | case 1: return ; 14 | case 2: return ; 15 | }; 16 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "useUnknownInCatchVariables": true, 18 | "jsx": "react-jsx" 19 | }, 20 | "include": ["src", "postcss.config.js"], 21 | } -------------------------------------------------------------------------------- /src/pages/Bot/Bot.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BotComponent } from "../../components/Bot/Bot"; 3 | import { Helmet } from "react-helmet"; 4 | 5 | export const Bot: React.FC = () => { 6 | return ( 7 | <> 8 | 9 | Simo Botlist - bot 10 | 11 | 12 | {/* */} 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | }; 20 | -------------------------------------------------------------------------------- /src/App/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./App"; 4 | import { BrowserRouter } from "react-router-dom"; 5 | import { UserProvider } from "../contexts/UserContext"; 6 | import { ThemeProvider } from "../contexts/ThemeContext"; 7 | import { ChakraProvider } from "@chakra-ui/react"; 8 | import "./index.css"; 9 | import "tailwindcss/tailwind.css"; 10 | 11 | const root = document.getElementById("root") as HTMLElement; 12 | 13 | createRoot(root).render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/contexts/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useEffect, useState } from "react"; 2 | import { ThemeContextProps, Theme } from "../types"; 3 | 4 | export const ThemeContext = createContext({ 5 | color: "purple", 6 | changeTheme: () => {}, 7 | }); 8 | 9 | export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }: React.PropsWithChildren) => { 10 | const [color, setTheme] = useState("purple"); 11 | 12 | const changeTheme = (newTheme: Theme) => { 13 | setTheme(newTheme); 14 | localStorage.setItem("theme", newTheme); 15 | }; 16 | 17 | useEffect(() => { 18 | const savedTheme = localStorage.getItem("theme"); 19 | if (savedTheme && savedTheme !== color) return setTheme(savedTheme as Theme); 20 | }, []); 21 | 22 | return {children} 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/Badges/Badges.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { UserFlags } from "../../types"; 3 | import { FaBug } from "react-icons/fa"; 4 | import { FaBook } from "react-icons/fa"; 5 | import { FaCode } from "react-icons/fa"; 6 | import { FaStar } from "react-icons/fa"; 7 | import { Badge } from "./Badge"; 8 | 9 | export const Badges: React.FC<{ flags: number }> = ({ flags }) => { 10 | const hasBadge = (bit: number) => (flags & bit) !== 0; 11 | 12 | return ( 13 |
14 | {hasBadge(UserFlags.BugHunter) && } type="bug" />} 15 | {hasBadge(UserFlags.Contributor) && } type="contributor" />} 16 | {hasBadge(UserFlags.PremiumPartner) && } type="premium" />} 17 | {hasBadge(UserFlags.Developer) && } type="dev" />} 18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/contexts/UserContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useEffect } from "react"; 2 | import { UserStructure } from "../types"; 3 | import api from "../utils/api"; 4 | 5 | interface UserContextProps { 6 | user: UserStructure | null; 7 | setUser: (user: UserStructure | null) => void; 8 | } 9 | 10 | export const UserContext = createContext({ 11 | user: null, 12 | setUser: () => void 0, 13 | }); 14 | 15 | export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }: React.PropsWithChildren) => { 16 | const [user, setUser] = useState(null); 17 | 18 | const getUserData = async () => { 19 | const { data } = await api.getUserData(); 20 | 21 | if (data) { 22 | setUser(data); 23 | } else { 24 | setUser(null); 25 | } 26 | }; 27 | 28 | useEffect(() => { 29 | getUserData(); 30 | }, []); 31 | 32 | return {children}; 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/Mixed/Button.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { buttonColor } from "../../utils/theme/button"; 3 | import { ThemeContext } from "../../contexts/ThemeContext"; 4 | import { Link } from "react-router-dom"; 5 | 6 | export const Button: React.FC<{ 7 | action?: (() => void) | (() => Promise); 8 | clas?: string; 9 | children: React.ReactNode; 10 | type?: "button" | "reset" | "submit" 11 | link?: boolean; 12 | to?: string 13 | disabled?: boolean; 14 | }> = ({ clas, action, children, type, link, to, disabled }) => { 15 | const { color } = useContext(ThemeContext) 16 | 17 | return link ? ( 18 | {children} 19 | ) : ( 20 | 21 | ) 22 | }; -------------------------------------------------------------------------------- /src/App/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300&display=swap"); 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | @layer utilities { 7 | .no-scrollbar::-webkit-scrollbar { 8 | display: none; 9 | } 10 | 11 | .no-scrollbar { 12 | -ms-overflow-style: none; 13 | scrollbar-width: none; 14 | } 15 | } 16 | 17 | * { 18 | box-sizing: border-box; 19 | margin: 0; 20 | padding: 0; 21 | font-family: "Inter", sans-serif; 22 | } 23 | 24 | .bounceIn { 25 | animation: shakeX; 26 | animation-duration: 1s; 27 | } 28 | 29 | .fade-in { 30 | opacity: 0; 31 | animation: fadeIn 500ms ease-in forwards; 32 | } 33 | 34 | @keyframes fadeIn { 35 | 0% { 36 | opacity: 0; 37 | } 38 | 39 | 100% { 40 | opacity: 1; 41 | } 42 | } 43 | 44 | .fade-out { 45 | opacity: 1; 46 | animation: fadeOut 500ms ease-in forwards; 47 | } 48 | 49 | @keyframes fadeOut { 50 | 0% { 51 | opacity: 1; 52 | } 53 | 54 | 100% { 55 | opacity: 0; 56 | } 57 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Simo 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/Mixed/PopUp.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useRef, useState } from "react"; 2 | 3 | export const PopUp: FC<{ 4 | children: React.ReactNode; 5 | menu: boolean; 6 | setMenu: (value: boolean) => void; 7 | }> = ({ children, menu, setMenu }) => { 8 | const menuRef = useRef(null); 9 | 10 | useEffect(() => { 11 | const handleClickOutside = (event: MouseEvent): void => { 12 | if (menuRef.current && !menuRef.current.contains(event.target as Node)) { 13 | setMenu(false); 14 | } 15 | } 16 | 17 | document.addEventListener("mousedown", handleClickOutside); 18 | 19 | return () => { 20 | document.removeEventListener("mousedown", handleClickOutside); 21 | }; 22 | }, []); 23 | 24 | return ( 25 |
26 |
27 | {children} 28 |
29 |
30 | ); 31 | }; -------------------------------------------------------------------------------- /src/components/Search/InputSearch.tsx: -------------------------------------------------------------------------------- 1 | import * as icon from "react-icons/bs"; 2 | import React, { useContext } from "react"; 3 | import { ThemeContext } from "../../contexts/ThemeContext"; 4 | import { borderColor } from "../../utils/theme/border"; 5 | 6 | export const InputSearch: React.FC<{ show: boolean }> = ({ show }) => { 7 | const { color } = useContext(ThemeContext); 8 | 9 | return ( 10 |
11 |
12 | 17 | 20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/Badges/Badge.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode, useContext, useState } from "react"; 2 | import { ThemeContext } from "../../contexts/ThemeContext"; 3 | import { borderColor } from "../../utils/theme/border"; 4 | 5 | export const Badge: FC<{ text: string, icon: ReactNode, type: "bug" | "dev" | "contributor" | "premium" }> = ({ text, icon, type }) => { 6 | const { color } = useContext(ThemeContext); 7 | const [show, setShow] = useState(false); 8 | 9 | const badgeType: Record = { 10 | bug: "bg-[#a31202]", 11 | contributor: "bg-[#0048b5]", 12 | dev: "bg-[#00b533]", 13 | premium: "bg-[#7900ad]" 14 | }; 15 | 16 | return ( 17 |
setShow(true)} onMouseLeave={() => setShow(false)} className={`${badgeType[type]} h-full p-2 rounded-lg w-8 items-center flex justify-center`}> 18 |
19 | {icon} 20 |
21 | {text} 22 |
23 |
24 |
25 | ); 26 | }; -------------------------------------------------------------------------------- /src/components/Search/Search.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useSearchParams } from "react-router-dom"; 3 | import api from "../../utils/api"; 4 | import { AxiosResponse } from "axios"; 5 | import { BotStructure } from "../../types"; 6 | import { BotCard } from "../BotList/BotCard"; 7 | 8 | export const SearchComponent: React.FC = () => { 9 | const [searchParams] = useSearchParams(); 10 | const filter: string | null = searchParams.get("bot"); 11 | const [bots, setBots] = useState(); 12 | 13 | const getBots = async (): Promise | void> => { 14 | const req: AxiosResponse = await api.getAllBots(); 15 | return setBots(req.data); 16 | }; 17 | 18 | useEffect(() => { getBots(); }, []); 19 | 20 | const botsFilter = bots?.filter(a => a.name.toLowerCase().includes(filter?.toLocaleLowerCase() as string)); 21 | 22 | return ( 23 |
24 |
25 | {botsFilter?.map((bot, index) => ())} 26 |
27 |
28 | ); 29 | }; -------------------------------------------------------------------------------- /src/components/Vote/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const VoteLoading: React.FC = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |

10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ); 21 | }; -------------------------------------------------------------------------------- /src/components/Mixed/Copy.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useContext, useEffect, useState } from "react"; 2 | import { BiCopy } from "react-icons/bi"; 3 | import { borderColor } from "../../utils/theme/border"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | 6 | export const CopyButton: FC<{ text: string; name: string }> = ({ text, name }) => { 7 | const [copied, setCopied] = useState(false); 8 | const [show, setShow] = useState(false); 9 | const { color } = useContext(ThemeContext); 10 | 11 | const handleCopy = async () => { 12 | await navigator.clipboard.writeText(text); 13 | 14 | setCopied(true); 15 | }; 16 | 17 | useEffect(() => { 18 | setTimeout(() => { 19 | setCopied(false); 20 | }, 5_000); 21 | }, [show, copied]); 22 | 23 | return ( 24 |
25 | 28 |
29 | {show &&
{copied ? `${name} copiado` : `Copiar ${name}`}
} 30 |
31 |
32 | ); 33 | }; -------------------------------------------------------------------------------- /src/pages/Mixed/Theme.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useContext } from "react"; 2 | import { ChoiceColor } from "../../components/Colors/Choice"; 3 | import { borderColor } from "../../utils/theme/border"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | 6 | export const ThemesPage: FC = () => { 7 | const selectedTheme = localStorage.getItem("theme") || "purple"; 8 | const { color } = useContext(ThemeContext); 9 | 10 | return ( 11 |
12 |

Seletor de temas

13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | ) 22 | }; -------------------------------------------------------------------------------- /src/pages/Mixed/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { Button } from "../../components/Mixed/Button"; 3 | 4 | export const NotFound: FC = () => { 5 | return ( 6 |
7 |
8 | 404 NOT_F0UND 9 |
10 | 11 |
12 |
13 | Página não encontrada.. mas você já comprou Simo Gomas hoje? 14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button } from "../Mixed/Button"; 3 | import * as icon from "react-icons/bs"; 4 | import { InputSearch } from "../Search/InputSearch"; 5 | import { Link } from "react-router-dom"; 6 | import { NotificationButton } from "../Notification/Button"; 7 | import { LoginMenu } from "../DropdownMenu/Menu"; 8 | 9 | export const Header: React.FC = () => { 10 | const [inputSearch, setInputSearch] = useState(false); 11 | 12 | return ( 13 | <> 14 |
15 |
16 |
17 | Simo 18 | 19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botlist-website", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "proxy": "http://localhost:80", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/react": "^2.8.2", 14 | "@emotion/react": "^11.11.1", 15 | "@emotion/styled": "^11.11.0", 16 | "@headlessui/react": "^1.7.17", 17 | "@simo.js/rest": "^1.0.4", 18 | "axios": "^1.4.0", 19 | "dotenv": "^16.0.3", 20 | "framer-motion": "^10.16.14", 21 | "js-cookie": "^3.0.5", 22 | "moment": "^2.29.4", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "react-helmet": "^6.1.0", 26 | "react-hook-form": "^7.44.2", 27 | "react-icons": "^4.10.1", 28 | "react-markdown": "^8.0.7", 29 | "react-router-dom": "^6.11.2", 30 | "translate": "^2.0.2" 31 | }, 32 | "devDependencies": { 33 | "@simo.js/simo-api-types": "^1.0.8", 34 | "@tailwindcss/typography": "^0.5.9", 35 | "@types/axios": "^0.14.0", 36 | "@types/js-cookie": "^3.0.3", 37 | "@types/moment": "^2.13.0", 38 | "@types/react": "^18.0.28", 39 | "@types/react-dom": "^18.0.11", 40 | "@types/react-helmet": "^6.1.9", 41 | "@vitejs/plugin-react-swc": "^3.0.0", 42 | "autoprefixer": "^10.4.14", 43 | "postcss": "^8.4.23", 44 | "tailwind-scrollbar": "^3.0.4", 45 | "tailwindcss": "^3.3.2", 46 | "typescript": "^4.9.3", 47 | "vite": "^4.2.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/Mixed/Tests.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from "react"; 2 | 3 | const initialState = { 4 | count: 0, 5 | }; 6 | 7 | const reducer = ( 8 | state: { count: number }, 9 | action: { type: "remove" | "add" | "reset" } 10 | ) => { 11 | switch (action.type) { 12 | case "add": 13 | return { ...state, count: state.count + 1 }; 14 | case "remove": 15 | if (state.count > 0) { 16 | return { ...state, count: state.count - 1 }; 17 | } 18 | case "reset": 19 | return { ...state, count: (state.count = 0) }; 20 | } 21 | }; 22 | 23 | export const Tests: React.FC = () => { 24 | const [contagem, contagemDispatch] = useReducer(reducer, initialState); 25 | return ( 26 |
27 |
{contagem.count}
28 |
29 | 35 | 41 | 47 |
48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/Team/Input.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, FC, useContext } from "react"; 2 | import { borderColor } from "../../utils/theme/border"; 3 | import { ThemeContext } from "../../contexts/ThemeContext"; 4 | 5 | export const TeamInput: FC<{ 6 | title: string; 7 | description: string; 8 | placeholder: string; 9 | maxLength?: number; 10 | value: string; 11 | onChange: (event: ChangeEvent) => void; 12 | type: string; 13 | }> = ({ 14 | title, 15 | description, 16 | placeholder, 17 | maxLength, 18 | value, 19 | onChange, 20 | type, 21 | }) => { 22 | const { color } = useContext(ThemeContext); 23 | 24 | return ( 25 |
26 |
27 |

{title}

28 | {description} 29 |
30 |
31 |
32 | 40 |
41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/components/Mixed/Auth.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, FC } from "react"; 2 | import api from "../../utils/api"; 3 | import { useNavigate } from "react-router-dom"; 4 | import Cookies from "js-cookie"; 5 | import { AiOutlineLoading3Quarters } from "react-icons/ai"; 6 | 7 | export const Auth: FC<{ children: React.ReactNode }> = ({ children }) => { 8 | const [auth, setAuth] = useState(null); 9 | const [loading, setLoading] = useState(true); 10 | const navigate = useNavigate(); 11 | 12 | const getUserData = async () => { 13 | try { 14 | if (Cookies.get("discordUser")) { 15 | const { data } = await api.getUserData(); 16 | setAuth(data && true); 17 | } else { 18 | setAuth(false); 19 | } 20 | } catch { 21 | setAuth(false); 22 | } finally { 23 | setLoading(false); 24 | } 25 | }; 26 | 27 | useEffect(() => { 28 | getUserData(); 29 | }, []); 30 | 31 | useEffect(() => { 32 | if (auth === false) { 33 | navigate("/login"); 34 | } 35 | }, [auth, navigate]); 36 | 37 | if (loading) { 38 | return ( 39 |
40 |
41 | 42 | Verificando usuário... 43 |
44 |
45 | ); 46 | } 47 | 48 | if (auth === null) { 49 | return null; 50 | } 51 | 52 | return <>{children}; 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import * as icon from 'react-icons/bs'; 3 | 4 | export const Footer: React.FC = () => { 5 | return ( 6 | <> 7 |
8 |
9 |
10 |
11 | Simo 12 |
13 | Uma botlist muito daora e bonita 14 |
15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 | 26 | ) 27 | }; -------------------------------------------------------------------------------- /src/components/Addbot/FinishAddbot.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { borderColor } from "../../utils/theme/border"; 3 | import { ThemeContext } from "../../contexts/ThemeContext"; 4 | import { BsCheckCircleFill } from "react-icons/bs"; 5 | import { FindBotStructure } from "../../types"; 6 | import { Link } from "react-router-dom"; 7 | 8 | export const FinishAddbot: React.FC<{ botData: FindBotStructure | undefined }> = ({ botData }) => { 9 | const { color } = useContext(ThemeContext); 10 | 11 | return ( 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |

Seu bot {botData?.username} foi enviado para a análise!

20 | Após ele ser aprovado ou recusado, você vai ser mencionado no canal |📥・logs|, do servidor: discord.gg/DGDEJtRsms 21 | Você pode ver uma preview do seu bot clicando nesse link: /bot/{botData?.id} 22 |
23 |
24 |
25 |
26 | ) 27 | }; -------------------------------------------------------------------------------- /src/components/User/UserLoading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Botloading } from "../BotList/Botloading"; 3 | 4 | export const UserLoading: React.FC = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | ) 29 | } -------------------------------------------------------------------------------- /src/components/Mixed/Error.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useRef } from "react"; 2 | import * as icon from "react-icons/bs"; 3 | import { Button } from "./Button"; 4 | import { ErrorStructure } from "../../types"; 5 | 6 | export const PopUpError: FC<{ 7 | show: ErrorStructure 8 | setShow: (value: ErrorStructure) => void; 9 | }> = ({ show, setShow }) => { 10 | const menuRef = useRef(null); 11 | 12 | useEffect(() => { 13 | const handleClickOutside = (event: MouseEvent): void => { 14 | if (menuRef.current && !menuRef.current.contains(event.target as Node)) { 15 | setShow({ show: false }); 16 | } 17 | } 18 | 19 | document.addEventListener("mousedown", handleClickOutside); 20 | 21 | return () => { 22 | document.removeEventListener("mousedown", handleClickOutside); 23 | }; 24 | }, []); 25 | 26 | return ( 27 |
28 |
29 |
30 |
31 |
32 | 33 |

{show.title}

34 |
35 | {show.message} 36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 | ); 44 | }; -------------------------------------------------------------------------------- /src/components/Markdown/Markdown.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactMarkdown from "react-markdown"; 3 | 4 | export const Markdown: React.FC<{ markdown: string }> = ({ markdown }) => { 5 | 6 | const renderMarkdown = () => { 7 | const processedMarkdown = markdown.replace(/\n/g, " \n"); 8 | return ( 9 | {processedMarkdown} 51 | ); 52 | }; 53 | 54 | return ( 55 | <>{renderMarkdown()} 56 | ) 57 | }; -------------------------------------------------------------------------------- /src/components/Team/Loaders.tsx/TeamCard.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { ManageTeamLoading } from "./ManageTeam"; 3 | 4 | export const TeamCardLoading: FC = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {[1, 2, 3, 4, 5].map((index) => ( 25 |
26 |
27 |
28 | ))} 29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 | ) 37 | } -------------------------------------------------------------------------------- /src/pages/Login/Login.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useContext, useState } from "react"; 2 | import simo from "../../assets/images/simo.png"; 3 | import { borderColor } from "../../utils/theme/border"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | import { FaDiscord } from "react-icons/fa"; 6 | import { AiOutlineLoading3Quarters } from "react-icons/ai"; 7 | 8 | export const Login: FC = () => { 9 | const { color } = useContext(ThemeContext); 10 | const [loading, setLoading] = useState(false); 11 | 12 | return ( 13 |
14 |
15 |
16 |

Fazer login na Simo

17 | Ao fazer login no Simo, você tem acesso a uma variedade de recursos e funcionalidades adicionais. Agora, você pode realizar diversas ações, como adicionar um bot, criar um time, personalizar seu perfil, votar em um bot, fornecer feedback sobre um bot e muito mais! Só clicar no botão abaixo 😉 18 |
19 | 32 |
33 |
34 |
35 |
36 | ) 37 | }; -------------------------------------------------------------------------------- /src/components/Colors/Choice.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeContext } from "../../contexts/ThemeContext"; 2 | import { ThemeContextProps, Theme, ThemeStructure } from "../../types"; 3 | import { useContext } from "react"; 4 | 5 | interface ButtonProps { 6 | name: string; 7 | theme: Theme; 8 | margin?: string; 9 | selected: boolean; 10 | mobile?: boolean; 11 | }; 12 | 13 | export const ChoiceColor: React.FC = ({ name, theme, margin, selected, mobile }) => { 14 | const { changeTheme } = useContext(ThemeContext); 15 | 16 | const themesOptions: ThemeStructure = { 17 | black: "bg-[#2e2e2e]", 18 | blue: "bg-[#004d7c]", 19 | green: "bg-[#04484d]", 20 | purple: "bg-[#2B195C]", 21 | red: "bg-[#802222]" 22 | } 23 | 24 | const toggleTheme = (newTheme: Theme) => { 25 | changeTheme(newTheme); 26 | }; 27 | 28 | return mobile ? ( 29 | 39 | ) : ( 40 | 50 | ); 51 | }; -------------------------------------------------------------------------------- /src/components/DropdownMenu/Option.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import * as iconMD from "react-icons/md"; 4 | interface Props { 5 | title: string; 6 | to?: string | "/"; 7 | icon: string; 8 | type: "button" | "link"; 9 | mobile?: boolean; 10 | alt: string; 11 | action?: () => void | JSX.Element |Promise; 12 | fix?: boolean; 13 | selected?: boolean; 14 | }; 15 | 16 | export const MenuOption: React.FC = ({ icon, title, to, alt, action, type, fix, mobile, selected }) => { 17 | return mobile ? (type === "button" ? ( 18 | 23 | ) : ( 24 | 25 | {alt} 26 | {title} 27 | 28 | 29 | ) 30 | ) : 31 | (type === "link" ? ( 32 | 33 |
34 | {alt} 35 | {title} 36 |
37 | 38 | ) : ( 39 | 45 | ) 46 | ) 47 | }; -------------------------------------------------------------------------------- /src/utils/api/errors.ts: -------------------------------------------------------------------------------- 1 | export const ApiErrors: Record = { 2 | 1001: "Bot desconhecido", 3 | 1002: "O bot já existe", 4 | 1003: "Não foi possível obter os votos do bot", 5 | 1004: "O usuário não é um bot", 6 | 1005: "Um bot não pode votar em outro bot ou em si mesmo", 7 | 1006: "Você está em cooldown, aguarde {ms}", 8 | 1007: "Você não é o proprietário deste bot", 9 | 2001: "Autenticador inválido", 10 | 2002: "Algumas propriedades estão faltando", 11 | 2003: "Muitas solicitações feitas em menos de 1 minuto", 12 | 2004: "Erro de autenticação com o Discord", 13 | 2005: "Nenhuma consulta foi fornecida no corpo", 14 | 2006: "Ocorreu um erro interno no servidor, tente novamente", 15 | 2050: "Sucesso!", 16 | 2007: "Propriedade de usuário ausente", 17 | 2008: "Algumas propriedades estão incorretas", 18 | 2009: "Corpo ausente", 19 | 2010: "Método não permitido", 20 | 3001: "Guilda desconhecida", 21 | 3002: "A guilda já existe", 22 | 3003: "Você não é o proprietário desta guilda", 23 | 4001: "O usuário já enviou o feedback", 24 | 4002: "Feedback desconhecido", 25 | 4003: "Não é possível enviar um feedback sem usuário", 26 | 4050: "Este bot não possui feedbacks", 27 | 4004: "Não é possível criar uma mensagem de resposta ao criar um feedback", 28 | 5001: "Usuário desconhecido", 29 | 5002: "Faltando ID de notificação", 30 | 5003: "Notificação desconhecida", 31 | 5004: "Este usuário não possui notificações", 32 | 5005: "Apenas a chave 'bio' pode ser atualizada", 33 | 5006: "O comprimento da biografia deve ser maior que 0 e menor que 200 caracteres", 34 | 6001: "Equipe desconhecida", 35 | 6002: "O usuário já tem uma equipe", 36 | 6003: "O bot já pertence a uma equipe", 37 | 6004: "Este bot não pode ingressar em uma equipe", 38 | 6005: "O usuário não pertence a uma equipe", 39 | 6006: "Você não pertence a esta equipe", 40 | 6007: "Seu papel é somente leitura", 41 | 6008: "Apenas o proprietário da equipe pode excluir a equipe", 42 | 6009: "Convite inválido", 43 | 6010: "O usuário já é um membro", 44 | 6011: "O método GET não suporta o parâmetro de convite", 45 | 6012: "Apenas o proprietário da equipe pode transferir a propriedade", 46 | 6013: "O usuário não é um membro", 47 | 6014: "Você não pode transferir a propriedade para si mesmo", 48 | 6015: "Não é possível remover o proprietário da equipe", 49 | 6016: "Você não pode se remover", 50 | 6017: "Você não pode remover um administrador", 51 | 6018: "Faltando 'member_id' no corpo para remover um membro", 52 | } -------------------------------------------------------------------------------- /src/components/BotList/Bots.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext, useRef } from "react"; 2 | import { BotStructure } from "../../types"; 3 | import { Botloading } from "./Botloading"; 4 | import api from "../../utils/api"; 5 | import { BotCard } from "./BotCard"; 6 | import { ThemeContext } from "../../contexts/ThemeContext"; 7 | import { buttonColor } from "../../utils/theme/button"; 8 | 9 | export const Bots: React.FC = () => { 10 | const { color } = useContext(ThemeContext); 11 | const [data, setData] = useState([]); 12 | const [botsToShow, setBotsToShow] = useState(6); 13 | const [prevBotsToShow, setPrevBotsToShow] = useState(0); 14 | const [showLoadMore, setShowLoadMore] = useState(true); 15 | const [botLoading, setBotLoading] = useState(false); 16 | 17 | const fetchData = async (startAt: number, endAt: number) => { 18 | setBotLoading(true); 19 | 20 | const res = await api.getAllBots(startAt, endAt); 21 | setData((prevData) => [...prevData, ...res.data]); 22 | 23 | const { data: { bots } } = await api.getApiStatus(); 24 | 25 | console.log(data.length, bots); 26 | 27 | if (data.length >= bots) { 28 | setShowLoadMore(false); 29 | } else { 30 | setShowLoadMore(true); 31 | } 32 | 33 | setBotLoading(false); 34 | }; 35 | 36 | useEffect(() => { 37 | fetchData(prevBotsToShow, botsToShow); 38 | }, [botsToShow]); 39 | 40 | const loadMoreBots = () => { 41 | setBotsToShow((prevBots) => { 42 | const newBotsToShow = prevBots + 6; 43 | setPrevBotsToShow(prevBots); 44 | return newBotsToShow; 45 | }); 46 | }; 47 | 48 | return ( 49 |
50 |
51 | {data.map((bot: BotStructure, index: number) => ( 52 | 53 | ))} 54 |
55 | {!botLoading && showLoadMore && ( 56 |
57 | 63 |
64 | )} 65 | {botLoading && } 66 |
67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/Team/LeaveTeam.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { Team } from "../../types"; 3 | import { PopUp } from "../Mixed/PopUp"; 4 | import { AiOutlineLoading3Quarters } from "react-icons/ai"; 5 | import api from "../../utils/api"; 6 | import { BsX } from "react-icons/bs"; 7 | import { buttonColor } from "../../utils/theme/button"; 8 | import { BiArrowBack } from "react-icons/bi"; 9 | import { Button } from "../Mixed/Button"; 10 | 11 | export const LeaveTeam: FC<{ team: Team | undefined, menu: boolean, setMenu: (value: boolean) => void }> = ({ team, menu, setMenu }) => { 12 | const [loading, setLoading] = useState(false); 13 | 14 | const leaveTeam = async () => { 15 | setLoading(true); 16 | 17 | if (team?.id) await api.leaveTeam(team.id); 18 | 19 | window.location.href = `/dashboard`; 20 | setLoading(false); 21 | } 22 | 23 | return team ? ( 24 | 25 |
26 | 29 | Sair do time{team.name} 30 |
31 |
32 |
33 | { 34 | currentTarget.onerror = null; 35 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 36 | }} className="w-16 h-16 rounded-full" src={team.avatar_url} /> 37 |
38 | {team.name} 39 | Membros: {team.members?.length} 40 |
41 |
42 | Você deseja mesmo sair do time {team.name}? 43 |
44 | 47 | 50 |
51 |
52 |
53 | ) : ( 54 |
Carregando...
55 | ) 56 | } -------------------------------------------------------------------------------- /src/components/Team/Teams.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { Team } from "../../types"; 3 | import { Button } from "../Mixed/Button"; 4 | import { Link } from "react-router-dom"; 5 | import * as icon from "react-icons/bs"; 6 | import simo from "../../assets/images/simo.png"; 7 | 8 | export const Teams: FC<{ 9 | teams: Team[] | undefined 10 | }> = ({ teams }) => { 11 | return ( 12 |
13 | {teams?.length === 0 ? ( 14 |
Você não tem times, que tal criar um timeclicando aqui?
) : ( 15 | <> 16 | 17 |
18 | {teams ? teams.map((team, index) => ( 19 | 20 |
21 | { 22 | currentTarget.onerror = null; 23 | currentTarget.src = simo; 24 | }} /> 25 |
26 | {team.name} 27 | {team.members?.length === 1 ? `${team.members.length} Membro` : `${team.members?.length} Membros`} 28 |
29 |
30 | 31 | ) 32 | ) : Array(2).fill( 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )} 43 |
44 | 45 | ) 46 | } 47 |
48 | ) 49 | }; -------------------------------------------------------------------------------- /src/App/App.tsx: -------------------------------------------------------------------------------- 1 | import { Header } from "../components/Header/Header"; 2 | import { Bots } from "../components/BotList/Bots"; 3 | import { Addbot } from "../pages/Bot/Addbot"; 4 | import { Routes, Route } from "react-router-dom"; 5 | import { NotFound } from "../pages/Mixed/NotFound"; 6 | import { Mobilemenu } from "../components/Mobile/Mobilemenu"; 7 | import { Bot } from "../pages/Bot/Bot"; 8 | import { Vote } from "../pages/Bot/Vote"; 9 | import { Tests } from "../pages/Mixed/Tests"; 10 | import { useContext, useState } from "react"; 11 | import { ThemeContext } from "../contexts/ThemeContext"; 12 | import { appColor } from "../utils/theme/app"; 13 | import { Search } from "../pages/Bot/Search"; 14 | import { Footer } from "../components/Footer/Footer"; 15 | import { User } from "../components/User/User"; 16 | import { NotificationsPage } from "../pages/Mixed/Notifications"; 17 | import { ThemesPage } from "../pages/Mixed/Theme"; 18 | import { Dashboard } from "../pages/Dashboard/Dashboard"; 19 | import { DashboardEditPage } from "../pages/Dashboard/Edit"; 20 | import { CreateTeamPage } from "../pages/Team/CreateTeam"; 21 | import { TeamPage } from "../pages/Team/Team"; 22 | import { ManageTeamPage } from "../pages/Team/ManageTeam"; 23 | import { InvitePage } from "../pages/Team/Invite"; 24 | import { Auth } from "../components/Mixed/Auth"; 25 | import { Login } from "../pages/Login/Login"; 26 | 27 | function App() { 28 | const { color } = useContext(ThemeContext); 29 | 30 | return ( 31 |
32 |
33 |
34 | 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | } /> 48 | } /> 49 | } /> 50 | } /> 51 | } /> 52 | 53 | 54 |
55 |
56 |
57 | ); 58 | } 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /src/components/BotList/BotCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { BotStructure } from "../../types"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | import simo from "../../assets/images/simo.png"; 6 | import { TiArrowSortedUp } from "react-icons/ti"; 7 | import { borderAndBg } from "../../utils/theme/border&bg"; 8 | import { buttonColor } from "../../utils/theme/button"; 9 | 10 | export const BotCard: React.FC<{ bot: BotStructure }> = ({ bot }) => { 11 | const { color } = useContext(ThemeContext); 12 | 13 | return ( 14 |
15 | 16 |
17 | { 18 | currentTarget.onerror = null; 19 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 20 | }} 21 | /> 22 |
23 | {bot.name} 24 |
25 | 26 | {bot.votes.reduce((votesCount, vote) => votesCount + vote.votes, 0)} 27 |
28 |
29 |
30 |
31 |
32 | {bot.short_description.length > 80 33 | ? bot.short_description.slice(0, 80) + "..." 34 | : bot.short_description} 35 |
36 |
37 | {bot.tags.map((tag, index) => ( 38 |
{tag}
39 | ))} 40 |
41 |
42 | 43 |
44 |
45 | Adicionar 46 |
47 |
48 | 49 | Votar 50 | 51 | 52 |
53 |
54 |
55 | ); 56 | } -------------------------------------------------------------------------------- /src/components/Team/DeleteTeam.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState, useContext } from "react"; 2 | import { Team } from "../../types"; 3 | import { PopUp } from "../Mixed/PopUp"; 4 | import { borderAndBg } from "../../utils/theme/border&bg"; 5 | import * as iconAI from "react-icons/ai"; 6 | import api from "../../utils/api"; 7 | import { ThemeContext } from "../../contexts/ThemeContext"; 8 | import { buttonColor } from "../../utils/theme/button"; 9 | import * as icon from "react-icons/bs"; 10 | 11 | export const DeleteTeam: FC<{ 12 | setDeletedTeam: (value: boolean) => void; 13 | deletedTeam: boolean 14 | team: Team; 15 | }> = ({ setDeletedTeam, team, deletedTeam }) => { 16 | const [submit, setSubmit] = useState(false); 17 | const [loading, setLoading] = useState(false); 18 | const [teamName, setTeamName] = useState(""); 19 | 20 | const { color } = useContext(ThemeContext); 21 | 22 | const deleteTeam = async (): Promise => { 23 | setLoading(true); 24 | 25 | await api.deleteTeam(team.id as string); 26 | 27 | setLoading(false); 28 | setDeletedTeam(false); 29 | 30 | window.location.href = "/dashboard"; 31 | }; 32 | 33 | const handleInputChange = (event: React.ChangeEvent): void => { 34 | setSubmit(false); 35 | 36 | const newTeamName = event.target.value; 37 | setTeamName(newTeamName); 38 | 39 | if (newTeamName === team.name) { 40 | setSubmit(true); 41 | } 42 | }; 43 | 44 | return ( 45 | 46 |
47 | 50 | Deletar{team.name} 51 |
52 |
53 |
54 | { 55 | currentTarget.onerror = null; 56 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 57 | }} className="w-16 h-16 rounded-full" src={team.avatar_url} /> 58 |
59 | {team.name} 60 |
61 |
62 | Se deseja mesmo deletar seu time {team.name}, digite o nome dele abaixo. 63 |
64 | 65 | 68 |
69 |
70 |
71 | ) 72 | } -------------------------------------------------------------------------------- /src/components/Notification/Card.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { NotificationBody, Theme, UserStructure } from "../../types"; 3 | import { borderColor } from "../../utils/theme/border"; 4 | import * as icon from "react-icons/bs"; 5 | import * as iconAI from "react-icons/ai"; 6 | import api from "../../utils/api"; 7 | import { Markdown } from "../Markdown/Markdown"; 8 | 9 | export const NotificationCard: FC<{ 10 | notification: NotificationBody; 11 | color: Theme; 12 | user: UserStructure | null; 13 | keyc: string; 14 | updateNotifications: () => Promise; 15 | mobile?: boolean; 16 | }> = ({ notification, color, user, keyc, updateNotifications, mobile }) => { 17 | const [deleted, setDeleted] = useState(false); 18 | 19 | const handleDeleteNotification = async (): Promise => { 20 | setDeleted(true); 21 | 22 | await api.deleteNotification(user?.id, keyc); 23 | await updateNotifications(); 24 | 25 | setDeleted(false); 26 | }; 27 | 28 | const typeSchemas: Record = { 29 | 0: { 30 | colors: "border-l-[#808080]", 31 | icon: 32 | }, 33 | 1: { 34 | colors: "border-l-[#03ff4a]", 35 | icon: 36 | }, 37 | 2: { 38 | colors: "border-l-[#ff3636]", 39 | icon: 40 | }, 41 | 3: { 42 | colors: "border-l-[#808080]", 43 | icon: 44 | } 45 | } 46 | 47 | return mobile ? ( 48 |
49 | {notification.type !== 3 ? typeSchemas[notification.type].icon : { 50 | currentTarget.onerror = null; 51 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 52 | }} className="w-[40px] rounded-full" src={notification.url} />} 53 | 54 | 57 |
58 | ) : ( 59 |
60 | {notification.type !== 3 ? typeSchemas[notification.type].icon : { 61 | currentTarget.onerror = null; 62 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 63 | }} className="w-[40px] rounded-full" src={notification.url} />} 64 | 65 | 68 |
69 | ) 70 | }; -------------------------------------------------------------------------------- /src/components/Mobile/Mobilemenu.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import React, { useContext, useEffect, useState } from "react"; 3 | import { UserStructure } from "../../types"; 4 | import { UserContext } from "../../contexts/UserContext"; 5 | import { ThemeContext } from "../../contexts/ThemeContext"; 6 | import api from "../../utils/api"; 7 | import { mobileMenu } from "../../utils/theme/mobileMenu"; 8 | import * as icon from "react-icons/bi"; 9 | import * as iconMD from "react-icons/md"; 10 | import simo from "../../assets/images/simo.png"; 11 | import { borderColor } from "../../utils/theme/border"; 12 | 13 | export const Mobilemenu: React.FC = () => { 14 | const { user } = useContext(UserContext); 15 | const { color } = useContext(ThemeContext); 16 | 17 | const page = location.href.split("/")[3]; 18 | 19 | const [click, setClick] = useState(false); 20 | const [selPage, setSelPage] = useState(page); 21 | const [profileMenu, setProfileMenu] = useState(false); 22 | 23 | useEffect(() => { 24 | setSelPage(page); 25 | }, [click]); 26 | 27 | return ( 28 |
29 | <> 30 |
31 | setClick(!click)} to="/notifications" className="flex flex-grow justify-center"> 32 | {selPage === "notifications" ? : } 33 | 34 | setClick(!click)} to="/addbot" className="flex flex-grow justify-center"> 35 | {selPage === "addbot" ? : } 36 | 37 | setClick(!click)} to="/themes" className="flex flex-grow justify-center"> 38 | {selPage === "themes" ? : } 39 | 40 | {user ? ( 41 | 47 | ) : ( 48 | 49 | 50 | 51 | )} 52 |
53 |
54 |
55 | setClick(!click)} className="flex gap-2" to="/dashboard"> 56 | 57 | Perfil 58 | 59 | 67 |
68 |
69 | 70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/Team/RemoveTeamBot.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState, useContext } from "react"; 2 | import { BotStructure } from "../../types"; 3 | import { PopUp } from "../Mixed/PopUp"; 4 | import { borderAndBg } from "../../utils/theme/border&bg"; 5 | import * as iconAI from "react-icons/ai"; 6 | import api from "../../utils/api"; 7 | import { ThemeContext } from "../../contexts/ThemeContext"; 8 | import { buttonColor } from "../../utils/theme/button"; 9 | import * as icon from "react-icons/bs"; 10 | import { Params, useParams } from "react-router-dom"; 11 | 12 | export const RemoveTeamBot: FC<{ 13 | setRemoveBot: (value: boolean) => void; 14 | removeBot: boolean 15 | bot: BotStructure; 16 | teamID: string 17 | }> = ({ setRemoveBot, bot, removeBot, teamID }) => { 18 | const [submit, setSubmit] = useState(false); 19 | const [loading, setLoading] = useState(false); 20 | const [botName, setBotName] = useState(""); 21 | 22 | const { color } = useContext(ThemeContext); 23 | 24 | const removeBotFunc = async () => { 25 | setLoading(true); 26 | 27 | await api.removeBotTeam({ 28 | botID: bot.id, 29 | teamID: teamID as string 30 | }); 31 | 32 | setLoading(false); 33 | setRemoveBot(false); 34 | 35 | window.location.reload(); 36 | }; 37 | 38 | const handleInputChange = (event: React.ChangeEvent) => { 39 | setSubmit(false); 40 | 41 | const newBotName = event.target.value; 42 | setBotName(newBotName); 43 | 44 | if (newBotName === bot.name) { 45 | setSubmit(true); 46 | } 47 | }; 48 | 49 | return ( 50 | 51 |
52 | 55 | Deletar{bot.name} 56 |
57 |
58 |
59 | { 60 | currentTarget.onerror = null; 61 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 62 | }} className="w-16 h-16 rounded-full" src={`https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`} /> 63 |
64 | {bot.name} 65 |
66 |
67 |
68 | Se deseja mesmo remover seu bot {bot.name} do time, digite o nome dele abaixo. 69 | Seu bot só será removido do time, não da sua conta. 70 |
71 |
72 | 73 | 76 |
77 |
78 |
79 | ) 80 | } -------------------------------------------------------------------------------- /src/pages/Mixed/Notifications.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useContext, useEffect, useState } from "react"; 2 | import { ThemeContext } from "../../contexts/ThemeContext"; 3 | import { UserContext } from "../../contexts/UserContext"; 4 | import { NotificationStructure } from "../../types"; 5 | import { AxiosResponse } from "axios"; 6 | import api from "../../utils/api"; 7 | import { borderColor } from "../../utils/theme/border"; 8 | import { NotificationCard } from "../../components/Notification/Card"; 9 | import { buttonColor } from "../../utils/theme/button"; 10 | import * as iconAI from "react-icons/ai"; 11 | 12 | export const NotificationsPage: FC = () => { 13 | const { color } = useContext(ThemeContext); 14 | const { user } = useContext(UserContext); 15 | 16 | const [notifications, setNotifications] = useState({}); 17 | const [isLoading, setIsLoading] = useState(false); 18 | const [bulkLoading, setBulkLoading] = useState(false); 19 | 20 | const getNotifications = async (): Promise => { 21 | setIsLoading(true); 22 | 23 | const req: AxiosResponse = await api.getNotifications(); 24 | setNotifications(req.data); 25 | 26 | setIsLoading(false); 27 | }; 28 | 29 | useEffect(() => { 30 | if (user) { 31 | getNotifications(); 32 | } 33 | }, [user]); 34 | 35 | return ( 36 |
37 |
38 |

Suas notificações

39 | {isLoading ? ( 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ) : ( 48 |
49 | {Object.keys(notifications).length > 0 ? ( 50 | <> 51 |
52 | {Object.keys(notifications).map((key, index) => ( 53 | 54 | ))} 55 |
56 |
57 | 65 | {bulkLoading && } 66 |
67 | 68 | ) : ( 69 |
70 | Você não tem notificações. 71 |
72 | )} 73 |
74 | )} 75 |
76 |
77 | ) 78 | }; -------------------------------------------------------------------------------- /src/components/Team/Loaders.tsx/ManageTeam.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | export const ManageTeamLoading: FC = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ) 63 | } -------------------------------------------------------------------------------- /src/components/Team/CreateTeam.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { useForm, SubmitHandler } from "react-hook-form"; 3 | import { ErrorStructure, Team } from "../../types"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | import { Input } from "../Addbot/Input"; 6 | import { borderColor } from "../../utils/theme/border"; 7 | import { shadowColor } from "../../utils/theme/shadow"; 8 | import api from "../../utils/api"; 9 | import { buttonColor } from "../../utils/theme/button"; 10 | import * as icon from "react-icons/ai"; 11 | import { PopUpError } from "../Mixed/Error"; 12 | import Translate from "translate"; 13 | 14 | export const CreateTeam: React.FC = () => { 15 | const { register, handleSubmit, formState: { errors } } = useForm(); 16 | 17 | const { color } = useContext(ThemeContext); 18 | 19 | const [error, setError] = useState(); 20 | const [submited, setSubmited] = useState(false); 21 | 22 | const onSubmit: SubmitHandler = async (data: Team): Promise => { 23 | setSubmited(true); 24 | 25 | const { avatar_url, description, name } = data; 26 | 27 | //@ts-ignore 28 | const formData: Team = { 29 | avatar_url, 30 | description, 31 | name, 32 | bots_id: [] 33 | }; 34 | 35 | try { 36 | const { data: { id } } = await api.createTeam(formData); 37 | 38 | window.location.href = `/team/${id}`; 39 | } catch (error: any) { 40 | setSubmited(false); 41 | setError({ 42 | show: true, 43 | title: "Erro ao tentar criar um time", 44 | message: JSON.stringify(error.response.data) || (await Translate(error.response.data.errors[0], { from: "en", to: "pt" })) 45 | }); 46 | } 47 | }; 48 | 49 | return ( 50 |
51 |
52 |
53 |

54 |

55 | 56 | Crie seu time 57 | 58 |

59 | 60 |
61 |
62 | 63 | 64 | 65 |
66 |
67 | 73 | {submited && } 74 |
75 |
76 |
77 |
78 | {error?.show && } 79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /src/components/User/User.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import api from "../../utils/api"; 3 | import { Params, useParams } from "react-router-dom"; 4 | import { BotStructure, UserStructure } from "../../types"; 5 | import { ThemeContext } from "../../contexts/ThemeContext"; 6 | import { borderColor } from "../../utils/theme/border"; 7 | import { BotCard } from "../BotList/BotCard"; 8 | import { UserLoading } from "./UserLoading"; 9 | import simo from "../../assets/images/simo.png"; 10 | import { CopyButton } from "../Mixed/Copy"; 11 | import { Badges } from "../Badges/Badges"; 12 | 13 | export const User: React.FC = () => { 14 | const params: Params = useParams(); 15 | const userid: string = params.userid as string; 16 | const [user, setUser] = useState(); 17 | const [userBots, setUserBots] = useState([]); 18 | const { color } = useContext(ThemeContext); 19 | 20 | const getUserData = async () => { 21 | try { 22 | const { data } = await api.getUserFromDB(userid); 23 | const bots = await api.getAllBots(); 24 | const userBots = bots.data.filter((bot) => bot.owner_id === userid); 25 | 26 | setUserBots(userBots); 27 | setUser(data); 28 | } catch (error) { 29 | console.error(error); 30 | window.location.href = "/"; 31 | } 32 | }; 33 | 34 | useEffect(() => { 35 | getUserData(); 36 | }, []); 37 | 38 | return ( 39 |
40 | {!user ? ( 41 | 42 | ) : ( 43 |
44 |
0 && user.banner_url) && "min-h-[300px]"} w-[300px] xl:w-[90vw] rounded-lg bg-neutral-900 flex justify-start flex-col gap-4 relative`}> 45 | {user.banner_url && ( 46 | Possible banner 47 | )} 48 |
49 | { 51 | currentTarget.onerror = null; 52 | currentTarget.src = simo; 53 | }} 54 | className="rounded-full w-32" src={`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`} 55 | alt={`${user.username}'s Avatar`} 56 | /> 57 |
58 |
59 | {user.username} 60 | 61 |
62 |
63 | {user.flags > 0 && } 64 |
65 |
66 |
67 |

Perfil de {user.username}

68 | {user?.bio && {user.bio}} 69 |
70 |
71 | {userBots.length === 0 ? ( 72 |
{user.username} não tem bots para serem listados.
73 | ) : ( 74 |
75 | {userBots.map((bot: BotStructure, index: number) => ())} 76 |
77 | )} 78 |
79 |
80 |
81 | )} 82 |
83 | ) 84 | }; -------------------------------------------------------------------------------- /src/components/Dashboard/Delete.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useContext, useState } from "react"; 2 | import * as icon from "react-icons/bs"; 3 | import * as iconAI from "react-icons/ai"; 4 | import { BotStructure } from "../../types"; 5 | import { borderAndBg } from "../../utils/theme/border&bg"; 6 | import { ThemeContext } from "../../contexts/ThemeContext"; 7 | import { buttonColor } from "../../utils/theme/button"; 8 | import api from "../../utils/api"; 9 | import { PopUp } from "../Mixed/PopUp"; 10 | 11 | export const DeleteBot: FC<{ 12 | setDeleteBot: (value: boolean) => void; 13 | deletebot: boolean 14 | bot: BotStructure; 15 | }> = ({ setDeleteBot, bot, deletebot }) => { 16 | const { color } = useContext(ThemeContext); 17 | const [botName, setBotName] = useState(""); 18 | const [submit, setSubmit] = useState(false); 19 | const [stars, setStars] = useState(0); 20 | const [loading, setLoading] = useState(false); 21 | 22 | const deleteBot = async (): Promise => { 23 | setLoading(true); 24 | 25 | await api.deleteBot(bot.id); 26 | 27 | setLoading(false); 28 | setDeleteBot(false); 29 | 30 | window.location.reload(); 31 | }; 32 | 33 | const handleInputChange = (event: React.ChangeEvent): void => { 34 | setSubmit(false); 35 | 36 | const newBotName = event.target.value; 37 | setBotName(newBotName); 38 | 39 | if (newBotName === bot.name) { 40 | setSubmit(true); 41 | } 42 | }; 43 | 44 | const getBotStars = async (): Promise => { 45 | const res = await api.getBotFeedbacks(bot.id); 46 | const stars = res.data.map(a => a.stars); 47 | const count = stars.reduce((a, b) => (a as number) += (b as number)); 48 | 49 | return setStars(Math.round((count as number) / stars.length)); 50 | }; 51 | 52 | useEffect(() => { 53 | getBotStars(); 54 | }, []); 55 | 56 | return ( 57 | 58 |
59 | 62 | Deletar{bot.name} 63 |
64 |
65 |
66 | { 67 | currentTarget.onerror = null; 68 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 69 | }} className="w-16 h-16 rounded-full" src={`https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png?size=2048`} alt={`${bot.name}'s avatar`} /> 70 |
71 | {bot.name} 72 |
73 | {Array(stars).fill(0).map((_, index) => ( 74 | 75 | ))} 76 | {Array(5 - stars).fill(0).map((_, index) => ( 77 | 78 | ))} 79 |
80 |
81 |
82 | Se deseja mesmo deletar seu bot {bot.name}, digite o nome dele abaixo. 83 |
84 | 85 | 88 |
89 |
90 |
91 | ) 92 | }; -------------------------------------------------------------------------------- /src/components/BotList/Botloading.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { TiArrowSortedUp } from "react-icons/ti"; 3 | 4 | 5 | export const Botloading: React.FC<{ fills: number, grid?: boolean }> = ({ fills, grid }) => { 6 | return grid ? ( 7 | <> 8 | {Array(fills).fill( 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )} 43 | 44 | ) : ( 45 |
46 | {Array(fills).fill( 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | )} 81 |
82 | ) 83 | }; 84 | -------------------------------------------------------------------------------- /src/components/Team/AuditLogs.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AuditLogActionType, AuditLogStructure, VanityURLUpdateChange } from "../../types"; 3 | import moment from "moment"; 4 | import "moment/dist/locale/pt-br"; 5 | import { Link } from "react-router-dom"; 6 | 7 | const changedKeysNames: Record = { 8 | name: "o nome", 9 | avatar_url: "o avatar", 10 | permission: "a permissão", 11 | description: "a descrição", 12 | bot_id: "id do bot", 13 | invite_code: "o código de convite", 14 | vanity_url: "o código de convite personalizado", 15 | 0: "Administrador", 16 | 1: "Membro", 17 | 3: "Dono" 18 | }; 19 | 20 | export const AuditLogs: FC<{ logs: AuditLogStructure | undefined }> = ({ logs }) => { 21 | return ( 22 |
23 | Registro de auditoria 24 | {logs ? ( 25 |
26 | {logs.entries.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()).map((log, index) => ( 27 |
28 | 29 | { 30 | currentTarget.onerror = null; 31 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 32 | }} className="rounded-full w-12 h-12 flex-shrink-0" src={`https://cdn.discordapp.com/avatars/${log.executor.id}/${log.executor.avatar}.png`} /> 33 | 34 |
35 |
36 | {log.action_type === 0 ? ( 37 |
{log.target?.username} entrou no time
38 | ) : log.action_type === 1 ? ( 39 | {log.executor.username} removeu o membro {log.target?.username} 40 | ) : ( 41 | log.changes.map((change, index) => ( 42 |
43 | {(() => { 44 | switch (log.action_type) { 45 | case AuditLogActionType.MemberUpdate: 46 | return {log.executor.username} atualizou as permissões para {log.target?.username} de {changedKeysNames[change.old_value]} para {changedKeysNames[change.new_value as string] || change.new_value}; 47 | case AuditLogActionType.BotAdd: 48 | return {log.executor.username} adicionou o bot com o ID {log.target?.username}; 49 | case AuditLogActionType.TeamUpdate: { 50 | if (change.changed_key === "vanity_url") { 51 | const newChange = change as unknown as VanityURLUpdateChange 52 | 53 | return {log.executor.username} atualizou {changedKeysNames[change.changed_key]} de {newChange.old_value?.code || "nenhum"} para {newChange.new_value?.code || ""} 54 | } 55 | return {log.executor.username} atualizou {changedKeysNames[change.changed_key]} de {change.old_value} para {change.new_value} 56 | } 57 | case AuditLogActionType.BotRemove: 58 | return {log.executor.username} removeu o bot com o ID {log.target?.username}; 59 | case AuditLogActionType.InviteUpdate: 60 | return {log.executor.username} atualizou o código de invite, de {change.old_value} para {change.new_value}; 61 | default: 62 | return Ação não tratada para action_type {log.action_type}; 63 | } 64 | })()} 65 |
66 | )) 67 | )} 68 |
69 |
70 |
{moment(log.created_at).locale("pt-br").fromNow()}
71 |
72 |
73 |
74 | 75 | ))} 76 |
77 | ) : ( 78 |
Carregando logs...
79 | )} 80 |
81 | ) 82 | }; -------------------------------------------------------------------------------- /src/components/Addbot/FormAddbot.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { useForm, SubmitHandler } from "react-hook-form"; 3 | import { BotStructure, ErrorStructure, FindBotStructure } from "../../types"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | import { Input, TagInput } from "./Input"; 6 | import { borderColor } from "../../utils/theme/border"; 7 | import { shadowColor } from "../../utils/theme/shadow"; 8 | import api from "../../utils/api"; 9 | import { buttonColor } from "../../utils/theme/button"; 10 | import * as icon from "react-icons/ai"; 11 | import { PopUpError } from "../Mixed/Error"; 12 | import { ApiErrors } from "../../utils/api/errors"; 13 | import Translate from "translate"; 14 | 15 | export const FormAddbot: React.FC<{ botData: FindBotStructure | undefined; setSteps: (value: number) => void }> = ({ botData, setSteps }) => { 16 | const { register, handleSubmit, formState: { errors } } = useForm(); 17 | 18 | const { color } = useContext(ThemeContext); 19 | const [error, setError] = useState(); 20 | 21 | const [submited, setSubmited] = useState(false); 22 | const [preview, setPreview] = useState(false); 23 | 24 | const onSubmit: SubmitHandler = async (data: BotStructure): Promise => { 25 | setSubmited(true); 26 | 27 | //@ts-ignore 28 | const formData: BotStructure = { 29 | website_url: data.website_url, 30 | support_server: data.support_server, 31 | source_code: data.source_code, 32 | short_description: data.short_description, 33 | long_description: data.long_description, 34 | prefixes: (data.prefixes as any).split(",").map((a: string) => a.trim()), 35 | tags: (data.tags as any).split(","), 36 | verified: false 37 | }; 38 | 39 | for (let i in formData) { 40 | if (formData[i as keyof BotStructure] === "") { 41 | delete formData[i as keyof BotStructure]; 42 | } 43 | } 44 | 45 | try { 46 | await api.addBot(formData, botData?.id as string); 47 | 48 | setSteps(2); 49 | } catch (error: any) { 50 | setSubmited(false); 51 | 52 | setError({ 53 | show: true, 54 | title: "Erro ao tentar adicionar um bot", 55 | message: ApiErrors[error.response.data.errors] || (await Translate(error.response.data.errors[0], { from: "en", to: "pt" })) 56 | }); 57 | } 58 | }; 59 | 60 | return ( 61 |
62 |
63 |
64 |

65 |

66 | 67 | Adicione seu Bot 68 | 69 |

70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | 86 | {submited && } 87 |
88 | 89 |
90 |
91 | {error?.show && } 92 |
93 | ); 94 | }; 95 | -------------------------------------------------------------------------------- /src/components/Notification/Button.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useContext, useEffect, useRef, useState } from "react"; 2 | import * as icon from "react-icons/bs"; 3 | import { ThemeContext } from "../../contexts/ThemeContext"; 4 | import { borderColor } from "../../utils/theme/border"; 5 | import { UserContext } from "../../contexts/UserContext"; 6 | import { NotificationStructure } from "../../types"; 7 | import { AxiosResponse } from "axios"; 8 | import api from "../../utils/api"; 9 | import * as iconAI from "react-icons/ai"; 10 | import { NotificationCard } from "./Card"; 11 | import { scrollBar } from "../../utils/theme/scrollBar"; 12 | import { buttonColor } from "../../utils/theme/button"; 13 | import { Link } from "react-router-dom"; 14 | 15 | export const NotificationButton: FC = () => { 16 | const { color } = useContext(ThemeContext); 17 | const { user } = useContext(UserContext); 18 | 19 | const [isOpen, setIsOpen] = useState(false); 20 | const menuRef = useRef(null); 21 | const [notifications, setNotifications] = useState({}); 22 | const [isLoading, setIsLoading] = useState(false); 23 | const [bulkLoading, setBulkLoading] = useState(false); 24 | const [viewedNotifications, setViewedNotifications] = useState(user?.notifications_viewed as boolean); 25 | 26 | const getNotifications = async () => { 27 | setIsLoading(true); 28 | 29 | const { data } = await api.getNotifications(); 30 | 31 | setNotifications(data); 32 | 33 | setIsLoading(false); 34 | }; 35 | 36 | useEffect(() => { 37 | const handleClickOutside = (event: MouseEvent) => { 38 | if (menuRef.current && !menuRef.current.contains(event.target as Node)) { 39 | setIsOpen(false); 40 | } 41 | } 42 | 43 | document.addEventListener("mousedown", handleClickOutside); 44 | 45 | return () => { 46 | document.removeEventListener("mousedown", handleClickOutside); 47 | }; 48 | }, []); 49 | 50 | useEffect(() => { 51 | if (user) { 52 | setViewedNotifications(user?.notifications_viewed as boolean); 53 | getNotifications(); 54 | } 55 | }, [user, isOpen]); 56 | 57 | return user && ( 58 | ( 59 |
60 | {!viewedNotifications &&
} 61 | 71 |
72 | {isLoading ? ( 73 |
74 |

Suas notificações

75 |
76 |
77 |
78 |
79 | ) : ( 80 |
81 |

Suas notificações

82 | {user.notifications && Object.keys(notifications).length > 0 ? ( 83 | <> 84 |
85 | {Object.keys(notifications).map((key, index) => ( 86 | 87 | ))} 88 |
89 | 90 | Página de notificações 91 | 92 |
93 | 101 | {bulkLoading && } 102 |
103 | 104 | ) : ( 105 |
106 | Você não tem notificações. 107 |
108 | )} 109 |
110 | )} 111 |
112 |
113 | ) 114 | ) 115 | }; 116 | -------------------------------------------------------------------------------- /src/components/Team/ManageBots.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useState, useContext } from "react"; 2 | import { Button } from "../Mixed/Button"; 3 | import { BotStructure, Team } from "../../types"; 4 | import api from "../../utils/api"; 5 | import { Link } from "react-router-dom"; 6 | import { borderColor } from "../../utils/theme/border"; 7 | import { ThemeContext } from "../../contexts/ThemeContext"; 8 | import { borderAndBg } from "../../utils/theme/border&bg"; 9 | import { UserContext } from "../../contexts/UserContext"; 10 | import { buttonColor } from "../../utils/theme/button"; 11 | import { RemoveTeamBot } from "./RemoveTeamBot"; 12 | import { BiArrowBack } from "react-icons/bi";; 13 | 14 | export const TeamManageBots: FC<{ team?: Team }> = ({ team }) => { 15 | const [bots, setBots] = useState(null); 16 | const [removeBot, setRemoveBot] = useState(false); 17 | const [selectedBot, setSelectedBot] = useState(null); 18 | 19 | const { color } = useContext(ThemeContext); 20 | const { user } = useContext(UserContext); 21 | 22 | const getTeamBots = async () => { 23 | const { data } = await api.getTeamBots(team?.id as string); 24 | 25 | return setBots(data ? data : null); 26 | }; 27 | 28 | const getSelectedBot = (botId: string) => { 29 | const selbot = bots?.find(bot => bot.id == botId); 30 | 31 | return setSelectedBot(selbot as BotStructure); 32 | }; 33 | 34 | useEffect(() => { 35 | if (user) { 36 | getTeamBots(); 37 | } 38 | }, [user]); 39 | 40 | return team ? ( 41 |
42 | Gerenciar {(bots?.length as number) <= 1 ? "bot" : "bots"} 43 | {selectedBot ? ( 44 |
45 | 46 |
47 | ) : !bots ? ( 48 |
O time não tem bots para serem gerenciados
49 | ) : !selectedBot && ( 50 |
51 | Selecione abaixo um bot para ser gerenciado 52 |
53 | {bots?.map((bot) => ( 54 | 67 | ))} 68 |
69 |
70 | )} 71 | {selectedBot ? ( 72 |
73 |
74 |
75 | 76 |
77 | { 78 | currentTarget.onerror = null; 79 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 80 | }} className="rounded-full w-12" src={`https://cdn.discordapp.com/avatars/${selectedBot.id}/${selectedBot.avatar}.png`} /> 81 |
82 | {selectedBot.name} 83 |
84 |
85 |
86 | {selectedBot.short_description} 87 |
88 | {selectedBot.tags.map((tag, index) => ( 89 |
{tag}
90 | ))} 91 |
92 |
93 | 94 |
95 |
96 | 97 |
98 |
99 |
100 | ) : null} 101 |
102 | {removeBot && } 103 |
104 |
105 | ) : ( 106 |
Carregando...
107 | ) 108 | }; -------------------------------------------------------------------------------- /src/components/Team/Invite.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState, useContext, useEffect } from "react"; 2 | import { Link, Params, useParams } from "react-router-dom"; 3 | import api from "../../utils/api"; 4 | import { Button } from "../Mixed/Button"; 5 | import { UserContext } from "../../contexts/UserContext"; 6 | import { ErrorStructure, Team } from "../../types"; 7 | import { UserLoading } from "../User/UserLoading"; 8 | import simo from "../../assets/images/simo.png"; 9 | import { borderColor } from "../../utils/theme/border"; 10 | import { ThemeContext } from "../../contexts/ThemeContext"; 11 | import * as icon from "react-icons/bi"; 12 | import { ApiErrors } from "../../utils/api/errors"; 13 | import { PopUpError } from "../Mixed/Error"; 14 | 15 | const TeamPermissions = { 16 | Administrator: 0, 17 | ReadOnly: 1, 18 | Owner: 2 19 | } 20 | 21 | export const InviteComponent: FC = () => { 22 | const params: Params = useParams(); 23 | const { teamId, hash } = params as { teamId: string; hash: string; }; 24 | 25 | const [loading, setLoading] = useState(false); 26 | const [team, setTeam] = useState(null); 27 | const [error, setError] = useState(); 28 | 29 | const { user } = useContext(UserContext); 30 | const { color } = useContext(ThemeContext); 31 | 32 | const getTeam = async (): Promise => { 33 | const { data } = await api.getTeam(teamId); 34 | 35 | setTeam(data); 36 | }; 37 | 38 | const joinTeam = async (): Promise => { 39 | setLoading(true); 40 | 41 | if (user) { 42 | try { 43 | await api.joinTeam(teamId, hash); 44 | 45 | window.location.href = `/team/${teamId}`; 46 | } catch (error: any) { 47 | setLoading(false); 48 | setError({ 49 | show: true, 50 | title: "Erro ao tentar entrar em um time", 51 | message: ApiErrors[error.response.data.code] 52 | }); 53 | } 54 | } 55 | }; 56 | 57 | useEffect(() => { 58 | getTeam(); 59 | }, []) 60 | 61 | return ( 62 | team ? ( 63 |
64 |
65 | {team.invite_code === hash ? ( 66 | <> 67 |
68 |
69 | { 70 | currentTarget.onerror = null; 71 | currentTarget.src = simo; 72 | }} 73 | className="rounded-full w-32 h-32 object-center" src={team.avatar_url} /> 74 |
75 |
76 |
77 | {team.name} 78 | 79 | ( {team.id} ) 80 | 81 |
82 |
83 | Membros 84 |
85 | {team.members?.map((member, index) => ( 86 | 87 | {member.permission === TeamPermissions.Owner && } 88 | { 90 | currentTarget.onerror = null; 91 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 92 | }} 93 | className="rounded-full w-10" 94 | src={`https://cdn.discordapp.com/avatars/${member.id}/${member.avatar}.png?size=2048`} 95 | /> 96 | 97 | ))} 98 |
99 |
100 |
101 |
102 |

Time {team.name}

103 | {team?.description && {team.description}} 104 |
105 |
106 |
107 | Você foi convidado para entrar no time {team.name} 108 | 109 | 110 |
111 |
112 |
113 | 114 | ) : ( 115 |
116 | Link de convite inválido. 117 |
118 | )} 119 |
120 | {error?.show && } 121 |
122 | ) : ( 123 | 124 | ) 125 | ) 126 | }; -------------------------------------------------------------------------------- /src/utils/api/index.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | import { BotStructure, UserStructure, VoteStructure, DiscordUser, Snowflake, FeedbackStructure, NotificationStructure, NotificationBody, NotificationType, StatusStrucuture, Team, AuditLogStructure, TeamMember } from "../../types"; 3 | import Cookies from "js-cookie"; 4 | 5 | const header = Cookies.get("discordUser") ? { 6 | headers: { 7 | Authorization: `User ${Cookies.get("discordUser")}` 8 | }, 9 | } : undefined; 10 | 11 | const api = { 12 | getAllBots: (startAt?: number, endAt?: number): Promise> => { 13 | return axios.get(`/api/bots?start_at=${startAt}&end_at=${endAt}`, header); 14 | }, 15 | getUserData: (): Promise> => { 16 | return axios.get("/api/users/@me", header); 17 | }, 18 | getToken: (): Promise => { 19 | return axios.get("/api/auth/token"); 20 | }, 21 | getDiscordUser: (userID: string | Snowflake): Promise> => { 22 | return axios.get(`/api/discord-user/${userID}`, header); 23 | }, 24 | getBotInfos: (botID: string | Snowflake): Promise> => { 25 | return axios.get("/api/bots/" + botID, header); 26 | }, 27 | addBot: (bodyData: BotStructure, botID: string | Snowflake): Promise> => { 28 | return axios.post("/api/bots/" + botID, bodyData, header); 29 | }, 30 | patchBot: (botID: Snowflake, bodyData: BotStructure): Promise> => { 31 | return axios.patch("/api/bots/" + botID, bodyData, header); 32 | }, 33 | deleteBot: (botID: Snowflake): Promise => { 34 | return axios.delete("/api/bots/" + botID, header); 35 | }, 36 | logoutUser: (): Promise => { 37 | return axios.get("/api/auth/logout", header); 38 | }, 39 | voteBot: (userID: string | Snowflake, botID: string | Snowflake): Promise => { 40 | return axios.post>(`/api/bots/${botID}/votes`, { user: userID }, header); 41 | }, 42 | postFeedback: (stars: number, posted_at: string, content: string, botID: Snowflake, userID: Snowflake): Promise> => { 43 | return axios.post(`/api/bots/${botID}/feedbacks`, { stars: stars, posted_at: posted_at, content: content, target_bot: botID, author_id: userID }, header); 44 | }, 45 | deleteFeedback: (botID: Snowflake): Promise => { 46 | return axios.delete(`/api/bots/${botID}/feedbacks`, header); 47 | }, 48 | editFeedback: (botId: Snowflake, props: FeedbackStructure): Promise> => { 49 | return axios.patch(`/api/bots/${botId}/feedbacks`, props, header); 50 | }, 51 | voteStatus: (botID: Snowflake): Promise> => { 52 | return axios.get(`/api/vote-status/${botID}`, header); 53 | }, 54 | getBotFeedbacks: (botID: Snowflake): Promise> => { 55 | return axios.get(`/api/bots/${botID}/feedbacks`, header); 56 | }, 57 | getNotifications: (): Promise> => { 58 | return axios.get(`/api/users/notifications`, header); 59 | }, 60 | deleteNotification: (userId: Snowflake | undefined, notificationId: string): Promise => { 61 | return axios.delete(`/api/users/notifications/${notificationId}`, header); 62 | }, 63 | deleteAllNotifications: (userId: Snowflake | undefined): Promise => { 64 | return axios.delete(`/api/users/notifications/bulk-delete`, header); 65 | }, 66 | createNotification: (userId: Snowflake | undefined, body: { content: string, type: NotificationType, url?: string }): Promise => { 67 | return axios.post(`/api/users/notifications`, body, header); 68 | }, 69 | getApiStatus: (): Promise> => { 70 | return axios.get("/api/status"); 71 | }, 72 | createApiKey: (botId: Snowflake): Promise> => { 73 | return axios.post(`/api/auth/api-key/${botId}`, null, header); 74 | }, 75 | getUserFromDB: (userId: Snowflake): Promise> => { 76 | return axios.get("/api/users/" + userId, header); 77 | }, 78 | patchUser: (body: { bio?: string | null, banner_url?: string | null; notifications_viewed?: boolean }): Promise => { 79 | return axios.patch("/api/users", body, header); 80 | }, 81 | getTeam: (teamID: Snowflake): Promise> => { 82 | return axios.get("/api/teams/" + teamID, header); 83 | }, 84 | getUserTeams: (): Promise> => { 85 | return axios.get("/api/teams/@all", header); 86 | }, 87 | deleteTeam: (teamID: string): Promise> => { 88 | return axios.delete("/api/teams/" + teamID, header); 89 | }, 90 | createTeam: (body: Team): Promise> => { 91 | return axios.post("/api/teams", body, header); 92 | }, 93 | patchTeam: (teamID: string, body: Team): Promise> => { 94 | return axios.patch("/api/teams/" + teamID, body, header); 95 | }, 96 | joinTeam: (teamID: string, inviteHash: string): Promise> => { 97 | return axios.put(`/api/teams/${teamID}/${inviteHash}`, null, header); 98 | }, 99 | transferOnwer: (userID: Snowflake): Promise> => { 100 | return axios.put(`/api/teams/change-owner/${userID}`, null, header); 101 | }, 102 | removeMember: (teamID: string, userID: Snowflake): Promise> => { 103 | return axios.delete(`/api/teams/${teamID}/members/${userID}`, header); 104 | }, 105 | getTeamBots: (teamID: string): Promise> => { 106 | return axios.get(`/api/teams/${teamID}/bots`, header); 107 | }, 108 | getUserBots: (): Promise> => { 109 | return axios.get("/api/bots", header); 110 | }, 111 | getAuditLogs: (teamID: string): Promise> => { 112 | return axios.get(`/api/teams/${teamID}/audit-logs`, header); 113 | }, 114 | patchMember: (teamID: string, memberID: Snowflake, data: { permission?: 0 | 1 }): Promise => { 115 | return axios.patch(`/api/teams/${teamID}/members/${memberID}`, data, header); 116 | }, 117 | getApiKey: (botID: string): Promise> => { 118 | return axios.get(`/api/bots/${botID}/api-key`, header); 119 | }, 120 | teamAddBot: ({ teamID, botID }: { teamID: string; botID: string }): Promise => { 121 | return axios.post(`/api/teams/${teamID}/bots/${botID}`, null, header); 122 | }, 123 | removeBotTeam: ({ teamID, botID }: { teamID: string; botID: string }): Promise => { 124 | return axios.delete(`/api/teams/${teamID}/bots/${botID}`, header); 125 | }, 126 | updateTeamInviteCode: (teamID: string): Promise> => { 127 | return axios.patch(`/api/teams/${teamID}/invite`, null, header); 128 | }, 129 | leaveTeam: (teamID: string): Promise> => { 130 | return axios.delete(`/api/teams/${teamID}/members/@me`, header); 131 | }, 132 | }; 133 | 134 | export default api; 135 | -------------------------------------------------------------------------------- /src/components/Team/EditTeam.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useState, ChangeEvent } from "react"; 2 | import { AuditLogStructure, Team } from "../../types"; 3 | import api from "../../utils/api"; 4 | import * as iconAI from "react-icons/ai"; 5 | import * as iconMD from "react-icons/md"; 6 | import { Button } from "../Mixed/Button"; 7 | import { TeamInput } from "./Input"; 8 | import { FaEye, FaEyeSlash } from "react-icons/fa"; 9 | 10 | interface EditActionsProps { 11 | changesLoading?: boolean; 12 | changesMade?: boolean; 13 | description: string | null; 14 | avatar_url: string; 15 | name: string; 16 | } 17 | export const EditTeam: FC<{ 18 | teamID: string, team?: Team, editActions: EditActionsProps, setEditActions: (value: EditActionsProps) => void 19 | }> = ({ teamID, team, editActions, setEditActions }) => { 20 | const [loading, setLoading] = useState(false); 21 | const [inviteHash, setInviteHash] = useState(""); 22 | const [showHash, setShowHash] = useState(false); 23 | 24 | const handleDescriptionChange = (event: ChangeEvent) => { 25 | if (event.target.value === team?.description) { 26 | return setEditActions({ 27 | description: event.target.value, 28 | name: editActions.name, 29 | avatar_url: editActions.avatar_url, 30 | changesMade: false 31 | }); 32 | } 33 | 34 | setEditActions({ 35 | description: event.target.value, 36 | name: editActions.name, 37 | avatar_url: editActions.avatar_url, 38 | changesMade: true 39 | }); 40 | } 41 | 42 | const handleAvatarChange = (event: ChangeEvent) => { 43 | if (event.target.value === team?.avatar_url) { 44 | return setEditActions({ 45 | description: editActions.description, 46 | name: editActions.name, 47 | avatar_url: event.target.value, 48 | changesMade: false 49 | }); 50 | } 51 | 52 | 53 | setEditActions({ 54 | description: editActions.description, 55 | name: editActions.name, 56 | avatar_url: event.target.value, 57 | changesMade: true 58 | }); 59 | } 60 | 61 | const handleNameChange = (event: ChangeEvent) => { 62 | if (event.target.value === team?.name) { 63 | return setEditActions({ 64 | description: editActions.description, 65 | name: event.target.value, 66 | avatar_url: editActions.avatar_url, 67 | changesMade: false 68 | }); 69 | } 70 | 71 | setEditActions({ 72 | description: editActions.description, 73 | name: event.target.value, 74 | avatar_url: editActions.avatar_url, 75 | changesMade: true 76 | }); 77 | } 78 | 79 | const getTeamData = async () => { 80 | const { data: { invite_code } } = await api.getTeam(teamID); 81 | 82 | setInviteHash(invite_code); 83 | }; 84 | 85 | useEffect(() => { 86 | getTeamData(); 87 | }, []); 88 | 89 | return team ? ( 90 | <> 91 |
92 |
93 | 94 |

Editar time {editActions.name}

95 |
96 |
97 |
98 | 107 | 116 | 124 |
125 |
126 |
127 | Link de convite 128 |
129 |
130 |
131 | 134 | 135 |
136 |
137 | 143 | 150 |
151 |
152 |
153 |
154 | 155 | ) : ( 156 |
carregando time...
157 | ) 158 | }; -------------------------------------------------------------------------------- /src/components/Team/Addbot.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useState, useContext } from "react"; 2 | import { Button } from "../Mixed/Button"; 3 | import { BotStructure, Team } from "../../types"; 4 | import api from "../../utils/api"; 5 | import { Link } from "react-router-dom"; 6 | import { borderColor } from "../../utils/theme/border"; 7 | import { ThemeContext } from "../../contexts/ThemeContext"; 8 | import * as iconMD from "react-icons/md"; 9 | import { borderAndBg } from "../../utils/theme/border&bg"; 10 | import { UserContext } from "../../contexts/UserContext"; 11 | import * as iconAI from "react-icons/ai"; 12 | import { BiArrowBack } from "react-icons/bi"; 13 | import { BsPlusLg } from "react-icons/bs"; 14 | 15 | export const TeamAddbot: FC<{ team?: Team }> = ({ team }) => { 16 | const [bots, setBots] = useState(null); 17 | const [selectedBot, setSelectedBot] = useState(null); 18 | const [addbotLoading, setAddbotLoading] = useState(); 19 | const { color } = useContext(ThemeContext); 20 | const { user } = useContext(UserContext); 21 | 22 | const getUserBots = async () => { 23 | const data = (await api.getUserBots()).data.filter((bot) => !bot.team_id); 24 | 25 | return setBots(data ? data : null); 26 | }; 27 | 28 | const addBot = async () => { 29 | setAddbotLoading(true); 30 | 31 | await api.teamAddBot({ 32 | botID: selectedBot?.id as string, 33 | teamID: team?.id as string 34 | }); 35 | 36 | setAddbotLoading(false); 37 | 38 | setInterval(() => { 39 | window.location.reload(); 40 | }, 2_000); 41 | }; 42 | 43 | const getSelectedBot = (botId: string) => { 44 | const selbot = bots?.find(bot => bot.id === botId); 45 | 46 | return setSelectedBot(selbot as BotStructure); 47 | }; 48 | 49 | useEffect(() => { 50 | if (user) { 51 | getUserBots(); 52 | } 53 | }, [user]); 54 | 55 | return team && bots ? ( 56 | <> 57 |
58 | Adicionar bot 59 | {selectedBot ? ( 60 |
61 | 62 |
63 | ) : bots.length === 0 ? ( 64 |
Você não tem bots adicionados que não pertençam a um time
65 | ) : !selectedBot && ( 66 |
67 | Selecione abaixo um bot para ser adicionado no time 68 |
69 | {bots.map((bot) => ( 70 | 83 | ))} 84 |
85 |
86 | )} 87 | {selectedBot && ( 88 |
89 |
90 |
91 | 92 |
93 | { 94 | currentTarget.onerror = null; 95 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 96 | }} className="rounded-full w-12" src={`https://cdn.discordapp.com/avatars/${selectedBot.id}/${selectedBot.avatar}.png`} /> 97 |
98 | {selectedBot.name} 99 |
100 |
101 |
102 | {selectedBot.short_description} 103 |
104 | {selectedBot.tags.map((tag, index) => ( 105 |
{tag}
106 | ))} 107 |
108 |
109 | 110 |
111 |
112 | Você deseja mesmo adicionar seu bot {selectedBot.name} no time {team.name} ? seu bot não será removido do seu perfil, ele só será adicionado no time, você continua sendo dono dele. 113 | 114 | {addbotLoading === false && Bot adicionado no time!} 115 |
116 |
117 |
118 | )} 119 |
120 | 121 | ) : ( 122 |
Carregando...
123 | ) 124 | }; -------------------------------------------------------------------------------- /src/components/DropdownMenu/Menu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useRef, useEffect } from "react"; 2 | import * as iconMD from "react-icons/md"; 3 | import * as iconBS from "react-icons/bi"; 4 | import { UserContext } from "../../contexts/UserContext"; 5 | import { ThemeContext } from "../../contexts/ThemeContext"; 6 | import { borderColor } from "../../utils/theme/border"; 7 | import { Link } from "react-router-dom"; 8 | import api from "../../utils/api"; 9 | import { ChoiceColor } from "../Colors/Choice"; 10 | 11 | export const LoginMenu: React.FC = () => { 12 | const { user } = useContext(UserContext); 13 | const { color } = useContext(ThemeContext); 14 | 15 | const selectedTheme = localStorage.getItem("theme") || "purple"; 16 | const [themeShow, setThemeShow] = useState(); 17 | 18 | const [selected, setSelected] = useState(); 19 | const [isOpen, setIsOpen] = useState(false); 20 | 21 | const menuRef = useRef(null); 22 | const themeRef = useRef(null); 23 | 24 | 25 | useEffect(() => { 26 | const handleClickOutside = (event: MouseEvent): void => { 27 | if (menuRef.current && !menuRef.current.contains(event.target as Node)) { 28 | setIsOpen(false); 29 | setThemeShow(false); 30 | } 31 | } 32 | 33 | document.addEventListener("mousedown", handleClickOutside); 34 | 35 | return () => { 36 | document.removeEventListener("mousedown", handleClickOutside); 37 | }; 38 | }, []); 39 | 40 | return ( 41 | <> 42 |
43 | 76 |
77 | 78 |
79 | 80 | Perfil 81 |
82 | 83 | 84 |
85 | 86 | Addbot 87 |
88 | 89 | 98 | {user ? ( 99 | 105 | ) : ( 106 | 107 |
108 | 109 | Login 110 |
111 | 112 | )} 113 |
114 |
115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 |
123 |
124 | 125 | ) 126 | }; 127 | -------------------------------------------------------------------------------- /src/components/Team/Team.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { Link, Params, useParams } from "react-router-dom"; 3 | import { BotStructure, Team } from "../../types"; 4 | import { ThemeContext } from "../../contexts/ThemeContext"; 5 | import { borderColor } from "../../utils/theme/border"; 6 | import { BotCard } from "../BotList/BotCard"; 7 | import simo from "../../assets/images/simo.png"; 8 | import api from "../../utils/api"; 9 | import * as icon from "react-icons/bi"; 10 | import { UserLoading } from "../User/UserLoading"; 11 | import { Button } from "../Mixed/Button"; 12 | import { UserContext } from "../../contexts/UserContext"; 13 | import { buttonColor } from "../../utils/theme/button"; 14 | import { DeleteTeam } from "./DeleteTeam"; 15 | import { Botloading } from "../BotList/Botloading"; 16 | import { CopyButton } from "../Mixed/Copy"; 17 | import { LeaveTeam } from "./LeaveTeam"; 18 | import moment from "moment"; 19 | import "moment/dist/locale/pt-br"; 20 | 21 | const TeamPermissions = { 22 | Administrator: 0, 23 | ReadOnly: 1, 24 | Owner: 2 25 | } 26 | 27 | export const TeamComponent: React.FC = () => { 28 | const params: Params = useParams(); 29 | const { user } = useContext(UserContext); 30 | const teamID: string = params.teamId as string; 31 | const [team, setTeam] = useState(); 32 | const [deleteTeam, setDeleteTeam] = useState(false); 33 | const [teamBots, setTeamBots] = useState(null); 34 | const [leaveTeam, setLeaveTeam] = useState(false); 35 | 36 | const { color } = useContext(ThemeContext); 37 | 38 | const getTeam = async () => { 39 | const { data } = await api.getTeam(teamID); 40 | 41 | setTeam(data); 42 | } 43 | 44 | const getTeamBots = async () => { 45 | const { status, data } = await api.getTeamBots(teamID); 46 | 47 | setTeamBots(status === 404 ? null : data); 48 | } 49 | 50 | useEffect(() => { 51 | getTeam(); 52 | getTeamBots(); 53 | }, []); 54 | 55 | return team ? ( 56 |
57 |
58 |
59 |
60 | { 61 | currentTarget.onerror = null; 62 | currentTarget.src = simo; 63 | }} 64 | className="rounded-full w-32 h-32 object-center" src={team.avatar_url} /> 65 |
66 |
67 |
68 | {team.name} 69 | 70 |
71 |
72 | {team.members?.find((member) => member.permission === TeamPermissions.Owner && member.id === user?.id || member.permission === TeamPermissions.Administrator && member.id === user?.id) && ( 73 |
74 | 75 | {team.members.find((member) => member.permission === TeamPermissions.Owner && member.id === user?.id) && ( 76 | 80 | )} 81 |
82 | )} 83 | {team.members?.find((member) => member.id === user?.id && member.permission !== TeamPermissions.Owner) && ( 84 | 88 | )} 89 | Membros 90 |
91 | {team.members?.map((member, index) => ( 92 | 93 | {member.permission === TeamPermissions.Owner && } 94 | { 96 | currentTarget.onerror = null; 97 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 98 | }} 99 | className="rounded-full w-10" 100 | src={`https://cdn.discordapp.com/avatars/${member.id}/${member.avatar}.png?size=2048`} 101 | /> 102 | 103 | ))} 104 |
105 |
106 | Criado {moment(team.created_at).fromNow()} atrás 107 |
108 |
109 |

Time {team.name}

110 | {team?.description && {team.description}} 111 |
112 |
113 | {teamBots || team.bots_id?.length === 0 ? ( 114 |
115 | {team.bots_id?.length === 0 ? ( 116 | Esse time não tem bots. 117 | ) : teamBots?.map((bot, index) => )} 118 |
119 | ) : ( 120 | 121 | )} 122 |
123 |
124 |
125 |
126 | {deleteTeam && } 127 |
128 |
129 | {leaveTeam && } 130 |
131 |
132 | ) : ( 133 | 134 | ) 135 | }; -------------------------------------------------------------------------------- /src/components/Vote/Vote.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { AxiosResponse } from "axios"; 4 | import { BotStructure, DiscordUser } from "../../types"; 5 | import { UserContext } from "../../contexts/UserContext"; 6 | import api from '../../utils/api'; 7 | import { ThemeContext } from "../../contexts/ThemeContext"; 8 | import { VoteLoading } from "./Loading"; 9 | import { buttonColor } from "../../utils/theme/button"; 10 | import { borderColor } from "../../utils/theme/border"; 11 | import * as icon from "react-icons/ai"; 12 | import { BotCard } from "../BotList/BotCard"; 13 | import { Botloading } from "../BotList/Botloading"; 14 | 15 | export const VoteComponent: React.FC = () => { 16 | const { user } = useContext(UserContext); 17 | const { botid } = useParams(); 18 | const { color } = useContext(ThemeContext); 19 | 20 | const [voteStatus, setVoteStatus] = useState<{ can_vote: boolean; rest_time: number; }>(); 21 | const [botData, setBotData] = useState(); 22 | const [votes, setVotes] = useState(0); 23 | const [voted, setVoted] = useState(); 24 | const [clicked, setClicked] = useState(false); 25 | const [bots, setBots] = useState([]); 26 | const [botLoading, setBotLoading] = useState(false); 27 | const [restTime, setRestTime] = useState(); 28 | 29 | const getVoteStatus = async (): Promise => { 30 | const { data: { rest_time }, data } = await api.voteStatus(botid as string); 31 | 32 | const timestamp = Math.floor((rest_time / 1000) / 3600); 33 | 34 | setRestTime(timestamp); 35 | 36 | return setVoteStatus(data); 37 | }; 38 | const getVoteData = async () => { 39 | const { data: { votes }, data } = await api.getBotInfos(botid as string); 40 | 41 | setBotData(data); 42 | setVotes(votes.reduce((votesCount, vote) => votesCount + vote.votes, 0) as number); 43 | }; 44 | 45 | const getRandomMinMax = (length: number): number[] => { 46 | const min = Math.floor(Math.random() * (length - 2)); 47 | const max = min + 2; 48 | 49 | return [min, max]; 50 | }; 51 | 52 | const getSuggestedBots = async () => { 53 | setBotLoading(true); 54 | 55 | const botCount = await api.getApiStatus(); 56 | 57 | const [min, max] = getRandomMinMax(botCount.data.bots); 58 | 59 | const allBots = await api.getAllBots(min, max); 60 | 61 | setBots(allBots.data); 62 | 63 | setBotLoading(false); 64 | }; 65 | 66 | const handleVote = async () => { 67 | setClicked(true); 68 | await api.voteBot(user?.id as string, botid as string); 69 | getVoteData(); 70 | getVoteStatus(); 71 | setClicked(false); 72 | setVoted(true); 73 | }; 74 | 75 | useEffect(() => { 76 | getVoteData(); 77 | getSuggestedBots(); 78 | }, []); 79 | 80 | useEffect(() => { 81 | if (user) { 82 | getVoteStatus(); 83 | }; 84 | }, [user]); 85 | 86 | return ( 87 | <> 88 | { 89 | botData && voteStatus ? ( 90 |
91 |
92 | { 94 | currentTarget.onerror = null; 95 | currentTarget.src = (await import("../../assets/images/simo.png")).default; 96 | }} 97 | className="w-[100px] rounded-full" 98 | src={`https://cdn.discordapp.com/avatars/${botData.id}/${botData.avatar}.png?size=2048`} 99 | alt={`${botData.name}'s Avatar`} 100 | /> 101 |
102 |

Votar em {botData.name}

103 | Votos: {votes} 104 |
105 |
106 |
107 | { 108 | voted 109 | ? 110 |
111 |
Voto confirmado com sucesso!
112 | {botData.vote_message ? botData.vote_message : `Obrigado por votar em ${botData.name}`} 113 |
114 | : ( 115 | voteStatus?.can_vote 116 | ? {user ? "Você pode votar agora!" : "Você precisa estar logado para poder votar"} 117 | : {user ? `Calma lá amigão, você ja votou hoje, volte em ${restTime} ${restTime === 1 ? "hora" : restTime as number < 0 ? "minutos" : (restTime as number > 0 && restTime === 1) ? "minuto" : "horas"}` : "Você precisa estar logado para poder votar"} 118 | ) 119 | }
120 |
121 | {user ? ( 122 | 127 | ) : ( 128 | 129 | Login 130 | 131 | )} 132 |
133 |
134 |
135 | ) : ( 136 | 137 | )} 138 |
139 |
140 |

Bots sugeridos

141 |
142 | {botLoading ? ( 143 | 144 | ) : ( 145 | bots.map((bot, index) => ()) 146 | )} 147 |
148 |
149 |
150 | 151 | ) 152 | }; 153 | -------------------------------------------------------------------------------- /src/components/Feedbacks/Feedbacks.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useContext } from "react"; 2 | import { BotStructure, FeedbackStructure } from "../../types"; 3 | import api from "../../utils/api"; 4 | import { useParams } from "react-router-dom"; 5 | import { FeedbackCard } from "./FeedbackCard"; 6 | import * as icon from "react-icons/bs"; 7 | import * as iconAI from "react-icons/ai"; 8 | import { borderColor } from "../../utils/theme/border"; 9 | import { ThemeContext } from "../../contexts/ThemeContext"; 10 | import { UserContext } from "../../contexts/UserContext"; 11 | import { buttonColor } from "../../utils/theme/button"; 12 | 13 | export const Feedbacks: React.FC<{ botid: string, bot: BotStructure, dev: { id: string, avatar: string, username: string } | undefined }> = ({ botid, bot, dev }) => { 14 | const { color } = useContext(ThemeContext); 15 | const { user } = useContext(UserContext); 16 | 17 | const [isDeleted, setIsDeleted] = useState(false); 18 | const [rating, setRating] = useState(1); 19 | const [feedback, setFeedback] = useState(""); 20 | const [feedbackSent, setFeedbackSent] = useState(false); 21 | const [submited, setSubmited] = useState(false); 22 | const [feedbackLoading, setFeedbackLoading] = useState(false); 23 | 24 | const handleStarClick = (selectedRating: number) => { 25 | setRating(selectedRating); 26 | }; 27 | 28 | const [feedbacks, setFeedbacks] = useState(); 29 | const params = useParams(); 30 | 31 | const [currentPage, setCurrentPage] = useState(1); 32 | 33 | const getBotFeedbacks = async (): Promise => { 34 | setFeedbackLoading(true); 35 | const res = await api.getBotFeedbacks(params.botid as string); 36 | setFeedbacks(res.data); 37 | setFeedbackLoading(false); 38 | }; 39 | 40 | useEffect(() => { 41 | getBotFeedbacks(); 42 | }, []); 43 | 44 | const handleSubmit = async (event: React.FormEvent): Promise => { 45 | event.preventDefault(); 46 | setSubmited(true); 47 | 48 | await api.postFeedback(rating, new Date().toISOString(), feedback, botid, user?.id as string).catch(() => { 49 | setFeedbackSent(true) 50 | setSubmited(false); 51 | }); 52 | 53 | await getBotFeedbacks(); 54 | 55 | setSubmited(false); 56 | }; 57 | 58 | const handleChange = (event: React.ChangeEvent): void => { 59 | event.preventDefault(); 60 | setFeedback(event.target.value); 61 | }; 62 | 63 | const indexLastItem: number = currentPage * 5; 64 | const indexFirstItem: number = indexLastItem - 5; 65 | const currentFeedbacks: FeedbackStructure[] | undefined = feedbacks?.slice(indexFirstItem, indexLastItem); 66 | 67 | return ( 68 |
69 |
70 | Envie seu feedback! 71 |
72 |
73 |