diff --git a/package-lock.json b/package-lock.json index 37b6322..4b0c3ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "next": "14.2.5", "prettier": "^3.3.3", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-hot-toast": "^2.4.1" }, "devDependencies": { "@types/bcrypt": "^5.0.2", @@ -1406,7 +1407,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2742,6 +2742,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -4574,6 +4582,21 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index ebc4501..22a7a3d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "next": "14.2.5", "prettier": "^3.3.3", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-hot-toast": "^2.4.1" }, "devDependencies": { "@types/bcrypt": "^5.0.2", diff --git a/src/app/api/authenticated/route.ts b/src/app/api/authenticated/route.ts new file mode 100644 index 0000000..eb0cce3 --- /dev/null +++ b/src/app/api/authenticated/route.ts @@ -0,0 +1,14 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getDataToken } from "@/utils/getDataToken"; + +export async function GET(request: NextRequest) { + try { + await getDataToken(request); + return NextResponse.json({ authenticated: true }); + } catch (error: any) { + return NextResponse.json( + { error: error.message, status: 500 }, + { status: 500 } + ); + } +} diff --git a/src/app/api/register/route.ts b/src/app/api/register/route.ts index f246ad6..3e114f1 100644 --- a/src/app/api/register/route.ts +++ b/src/app/api/register/route.ts @@ -13,7 +13,10 @@ export async function POST(request: NextRequest, response: NextResponse) { password: encryptedPassword, }, }); - return NextResponse.json({ message: "User created" }, { status: 201 }); + return NextResponse.json( + { message: "User created", success: true }, + { status: 201 } + ); } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 400 }); } diff --git a/src/app/globals.css b/src/app/globals.css index 875c01e..062df34 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,18 +2,26 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; +@layer base { + html { + --color-primary: #4285f4; + --color-secondary: #34a853; + --color-buttons: #fbbc05; + --color-typography: #ea4335; + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + } } -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } +html[data-theme="dark"] { + --color-primary: #f98866; + --color-secondary: #80bd9e; + --color-buttons: #89da59; + --color-typography: #ff320e; + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; } body { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3314e47..707d957 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { Toaster } from "react-hot-toast"; const inter = Inter({ subsets: ["latin"] }); @@ -16,7 +17,10 @@ export default function RootLayout({ }>) { return ( - {children} + + +
{children}
+ ); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 1ccffe1..5f1c7f3 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,79 +1,58 @@ -'use client'; +"use client"; +import Button from "@/components/Button"; +import TextInput from "@/components/TextInput"; import APICaller from "@/utils/APICaller"; import { useRouter } from "next/navigation"; import { useState } from "react"; export default function Login() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); - const router = useRouter(); + const router = useRouter(); - async function handleLogin() { - if (!email || !password) { - return; - } - try { - const requestData = { email, password }; - const response = await APICaller("/api/login", "POST", requestData); - if (response.success) { - localStorage.setItem("token", response.token); - } - } catch (error) { - console.error("Erro ao fazer login:", error); - } + async function handleLogin() { + if (!email || !password) { + return; } - - async function handleRegister() { - if (!email || !password || !name) { - return; - } - try { - const requestData = { email, password, name }; - const response = await APICaller("/api/register", "POST", requestData); - if (response.success) { - localStorage.setItem("token", response.token); - router.push("/dashboard"); - } - } catch (error) { - console.error("Error ao registrar:", error); - } - }; - - async function helloWorld() { - try { - const response = await APICaller("/api/helloworld", "GET"); - } catch (error) { - console.error("Error:", error); - } + try { + const requestData = { email, password }; + const response = await APICaller("/api/login", "POST", requestData); + if (response.success) { + localStorage.setItem("token", response.token); + } + } catch (error) { + console.error("Erro ao fazer login:", error); } - - return ( -
- setEmail(e.target.value)} - /> - setName(e.target.value)} - /> - setPassword(e.target.value)} - /> - - - + } + + return ( +
+
+ + +
+ +
- ) - -} \ No newline at end of file +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 2acfd44..23e0de8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,113 +1,30 @@ -import Image from "next/image"; +"use client"; -export default function Home() { - return ( -
-
-

- Get started by editing  - src/app/page.tsx -

-
- - By{" "} - Vercel Logo - -
-
- -
- Next.js Logo -
+import Button from "@/components/Button"; +import APICaller from "@/utils/APICaller"; +import { useAuthRedirect } from "@/utils/isAuthenticated"; -
- -

- Docs{" "} - - -> - -

-

- Find in-depth information about Next.js features and API. -

-
- - -

- Learn{" "} - - -> - -

-

- Learn about Next.js in an interactive course with quizzes! -

-
+export default function Home() { + const isAuth = useAuthRedirect(); - -

- Templates{" "} - - -> - -

-

- Explore starter templates for Next.js. -

-
+ async function helloWorld() { + try { + const response = await APICaller("/api/helloworld", "GET"); + } catch (error) { + console.error("Error:", error); + } + } - -

- Deploy{" "} - - -> - -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
-
-
+ return ( +
+ Home page + {isAuth && ( +
); } diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx new file mode 100644 index 0000000..770997b --- /dev/null +++ b/src/app/register/page.tsx @@ -0,0 +1,81 @@ +"use client"; + +import Button from "@/components/Button"; +import TextInput from "@/components/TextInput"; +import APICaller from "@/utils/APICaller"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import toast from "react-hot-toast"; + +export default function Register() { + const [email, setEmail] = useState(""); + const [name, setName] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + + const router = useRouter(); + + async function handleRegister() { + if (!email || !password || !name) { + return; + } + try { + const requestData = { email, password, name }; + const response = await APICaller("/api/register", "POST", requestData); + if (response.success) { + toast.success("Registrado com sucesso!"); + router.replace("/login"); + } + } catch (error) { + toast.error("Erro ao registrar!"); + console.error("Error ao registrar:", error); + } + } + + return ( +
+
+ + + + +
+ + +
+
+
+ ); +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..381c2cb --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,19 @@ +interface ButtonProps { + text: string; + onClick: () => void; + style: "primary" | "secondary" | "danger" | "outline"; +} +export default function Button({ text, onClick, style }: ButtonProps) { + return ( + + ); +} diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx new file mode 100644 index 0000000..e5879cf --- /dev/null +++ b/src/components/TextInput.tsx @@ -0,0 +1,28 @@ +interface TextInputProps { + value: string; + setName: (name: string) => void; + type: "text" | "email" | "password"; + label?: string; + placeholder?: string; +} + +export default function TextInput({ + value, + setName, + type, + label, + placeholder, +}: TextInputProps) { + return ( +
+ {label && } + setName(e.target.value)} + className="text-sm rounded-lg block w-full p-2.5 bg-gray-200 border-blue-500 border-2 placeholder-gray-400 focus:border-blue-800 outline-none " + placeholder={placeholder} + /> +
+ ); +} diff --git a/src/utils/APICaller.ts b/src/utils/APICaller.ts index 1e43ad6..22e38be 100644 --- a/src/utils/APICaller.ts +++ b/src/utils/APICaller.ts @@ -1,7 +1,7 @@ export default function APICaller( endpointURL: string, methodType: string, - body?: any, + body?: any ) { const token = localStorage.getItem("token"); if (token) { @@ -26,7 +26,7 @@ export default function APICaller( "Content-Type": "application/json", }, body: JSON.stringify(body), - }), + }) ); } @@ -35,8 +35,5 @@ function getData(promise: Promise) { .then((response) => response.json()) .then((data) => { return data; - }) - .catch((error) => { - console.error("Error:", error); }); } diff --git a/src/utils/helper.ts b/src/utils/helper.ts new file mode 100644 index 0000000..e5f014e --- /dev/null +++ b/src/utils/helper.ts @@ -0,0 +1,3 @@ +export const changeTheme = (theme: string) => { + document.querySelector("html")?.setAttribute("data-theme", theme); +}; diff --git a/src/utils/isAuthenticated.ts b/src/utils/isAuthenticated.ts new file mode 100644 index 0000000..d1b8c44 --- /dev/null +++ b/src/utils/isAuthenticated.ts @@ -0,0 +1,30 @@ +import { useRouter } from "next/navigation"; +import APICaller from "./APICaller"; +import { useEffect, useState } from "react"; + +async function isAuthenticated() { + try { + const response = await APICaller("/api/authenticated", "GET"); + return response.authenticated; + } catch (error) { + console.error("Error:", error); + } +} + +export function useAuthRedirect() { + const [isAuth, setIsAuth] = useState(false); + const router = useRouter(); + + const checkAuth = async () => { + const auth = await isAuthenticated(); + if (!auth) { + router.push("/login"); + } + setIsAuth(true); + }; + useEffect(() => { + checkAuth(); + }); + + return isAuth; +} diff --git a/tailwind.config.ts b/tailwind.config.ts index e9a0944..c6796f7 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -13,6 +13,12 @@ const config: Config = { "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", }, + colors: { + primary: "var(--color-primary)", + secondary: "var(--color-secondary)", + buttons: "var(--color-buttons)", + typography: "var(--color-typography)", + }, }, }, plugins: [],