diff --git a/package.json b/package.json index 35be2a5..390774e 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,12 @@ "date-fns": "^2.30.0", "discord-api-types": "^0.37.56", "eslint": "8.46.0", - "eslint-config-next": "13.4.12", + "eslint-config-next": "^14.0.3", "framer-motion": "^10.15.0", - "next": "13.4.12", + "next": "^14.0.3", "postcss": "8.4.31", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.45.4", "react-icons": "^4.10.1", "react-markdown": "^8.0.7", @@ -36,8 +36,8 @@ "remark-breaks": "^3.0.3", "remark-gfm": "^3.0.1", "tailwind-merge": "^1.14.0", - "tailwindcss": "3.3.3", - "typescript": "5.1.6" + "tailwindcss": "^3.3.6", + "typescript": "^5.3.2" }, "devDependencies": { "@commitlint/cli": "^17.6.7", @@ -45,4 +45,4 @@ "husky": "^8.0.0", "sharp": "^0.32.4" } -} \ No newline at end of file +} diff --git a/src/api/routes/verify.ts b/src/api/routes/verify.ts new file mode 100644 index 0000000..6c125a2 --- /dev/null +++ b/src/api/routes/verify.ts @@ -0,0 +1,12 @@ +import { API } from "@/utils/api"; +import axios from "axios"; + +type ValidateCaptchaResponseType = { + success: boolean; +}; + +export function validateCaptchaResponse(response: string) { + return axios.post(API.verify(), { + responseToken: response, + }); +} diff --git a/src/app/challenge/verify/page.tsx b/src/app/challenge/verify/page.tsx new file mode 100644 index 0000000..781cd9d --- /dev/null +++ b/src/app/challenge/verify/page.tsx @@ -0,0 +1,63 @@ +/* + * This file is part of SudoBot Dashboard. + * + * Copyright (C) 2021-2023 OSN Developers. + * + * SudoBot Dashboard is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SudoBot Dashboard is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SudoBot Dashboard. If not, see . + */ + +import Captcha from "@/components/Verify/Captcha"; +import { Metadata } from "next"; +import Script from "next/script"; +import { FC } from "react"; + +export const metadata: Metadata = { + title: "Verify - SudoBot", + description: "Log into SudoBot's control panel.", +}; + +const VerifyPage: FC = () => { + return ( +
+ + +
+

+ Verify +

+

+ Please verify yourself to continue. +

+ +
+
+
+ +
+
+
+
+
+ ); +}; + +export default VerifyPage; diff --git a/src/components/Home/ReviewList.tsx b/src/components/Home/ReviewList.tsx index 0cfb34e..52f0788 100644 --- a/src/components/Home/ReviewList.tsx +++ b/src/components/Home/ReviewList.tsx @@ -1,21 +1,21 @@ /* -* This file is part of SudoBot Dashboard. -* -* Copyright (C) 2021-2023 OSN Developers. -* -* SudoBot Dashboard is free software; you can redistribute it and/or modify it -* under the terms of the GNU Affero General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* SudoBot Dashboard is distributed in the hope that it will be useful, but -* WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with SudoBot Dashboard. If not, see . -*/ + * This file is part of SudoBot Dashboard. + * + * Copyright (C) 2021-2023 OSN Developers. + * + * SudoBot Dashboard is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SudoBot Dashboard is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SudoBot Dashboard. If not, see . + */ "use client"; @@ -52,9 +52,12 @@ const ReviewList: FC = () => {

- {query.isSuccess && ( - - )} + {query.isSuccess && + (!query.data?.data || query.data?.data?.length === 0 ? ( +
No reviews yet.
+ ) : ( + + ))} {query.isLoading && (
diff --git a/src/components/Verify/Captcha.tsx b/src/components/Verify/Captcha.tsx new file mode 100644 index 0000000..c7a0e9f --- /dev/null +++ b/src/components/Verify/Captcha.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { validateCaptchaResponse } from "@/api/routes/verify"; +import { + Alert, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; +import { AxiosError } from "axios"; +import { FC, useEffect, useState } from "react"; + +const recaptchaSuccessCallbackName = "recaptchaSuccess"; + +declare global { + interface Window + extends Record< + typeof recaptchaSuccessCallbackName, + Function | undefined + > {} +} + +const Captcha: FC = () => { + const [isSuccess, setIsSuccess] = useState(false); + const [isAlertOpen, setIsAlertOpen] = useState(true); + const [error, setError] = useState(null); + + const onSuccess = async (responseToken: string) => { + try { + const response = await validateCaptchaResponse(responseToken); + + if (!response.data.success) { + throw new AxiosError( + "Request unsuccessful", + response.status.toString(), + undefined, + undefined, + response + ); + } + + setIsSuccess(true); + } catch (error) { + if (error instanceof AxiosError) { + setError( + error.response?.data.error ?? + "We were unable to verify you." + ); + } else { + console.log(error); + setError( + "An internal error has occurred. Please try again later." + ); + } + } + }; + + useEffect(() => { + window[recaptchaSuccessCallbackName] = onSuccess; + + return () => { + delete window[recaptchaSuccessCallbackName]; + }; + }, []); + + return ( +
+ {error && ( + + {error} + + )} + {isSuccess && ( + setIsAlertOpen(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + {"Verification Completed"} + + + + We've successfully verified that you're not a robot. + You’ll be authorized shortly. + + + + + + + )} +
+
+ ); +}; + +export default Captcha; diff --git a/src/utils/api.ts b/src/utils/api.ts index bfbeba5..53d09c4 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -19,6 +19,10 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL; export class API { + static verify(): string { + return `${API_URL}/challenge/verify`; + } + static announcements(): string { return `${API_URL}/announcements`; }