diff --git a/frontend/index.html b/frontend/index.html
index 9fe03f3..308aab1 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,6 +1,9 @@
-
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 31444a9..c071cae 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,8 +8,14 @@
"name": "devdecola",
"version": "0.0.0",
"dependencies": {
+ "@hookform/resolvers": "^5.1.1",
+ "lucide-react": "^0.525.0",
"react": "^19.1.0",
- "react-dom": "^19.1.0"
+ "react-dom": "^19.1.0",
+ "react-hook-form": "^7.60.0",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^7.6.3",
+ "zod": "^3.25.75"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
@@ -913,6 +919,18 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@hookform/resolvers": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz",
+ "integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1357,6 +1375,12 @@
"win32"
]
},
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1936,6 +1960,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2626,6 +2659,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.525.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz",
+ "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2897,6 +2939,31 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.60.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.60.0.tgz",
+ "integrity": "sha512-SBrYOvMbDB7cV8ZfNpaiLcgjH/a1c7aK0lK+aNigpf4xWLO8q+o4tcvVurv3c4EOyzn/3dCsYt4GKD42VvJ/+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -2907,6 +2974,44 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.3.tgz",
+ "integrity": "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.3.tgz",
+ "integrity": "sha512-DiWJm9qdUAmiJrVWaeJdu4TKu13+iB/8IEi0EW/XgaHCjW/vWGrwzup0GVvaMteuZjKnh5bEvJP/K0MDnzawHw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.6.3"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3008,6 +3113,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3377,6 +3488,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.25.75",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz",
+ "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index fc33b59..d39c06c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,8 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
+ "@hookform/resolvers": "^5.1.1",
+ "lucide-react": "^0.525.0",
"react": "^19.1.0",
- "react-dom": "^19.1.0"
+ "react-dom": "^19.1.0",
+ "react-hook-form": "^7.60.0",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^7.6.3",
+ "zod": "^3.25.75"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
diff --git a/frontend/src/App.css b/frontend/src/App.css
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
deleted file mode 100644
index 624f4e9..0000000
--- a/frontend/src/App.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import './App.css'
-import { NavBar } from './components/NavBar'
-
-function App() {
-
- return (
- <>
-
-
-
Welcome to DEVdecola
-
Your one-stop solution for all things development!
-
- >
- )
-}
-
-export default App
diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx
new file mode 100644
index 0000000..f99c3f7
--- /dev/null
+++ b/frontend/src/app/App.tsx
@@ -0,0 +1,16 @@
+import { BrowserRouter, Route, Routes } from "react-router-dom";
+import { Home } from "@/pages/home/index.tsx";
+import { Login } from "@/pages/login";
+
+function App() {
+ return (
+
+
+ } />
+ } />
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/src/app/hooks/userIsMobile.ts b/frontend/src/app/hooks/userIsMobile.ts
new file mode 100644
index 0000000..5d2a8c3
--- /dev/null
+++ b/frontend/src/app/hooks/userIsMobile.ts
@@ -0,0 +1,18 @@
+import { useEffect, useState } from "react";
+
+export function userIsMobile(breakpoint = 768): boolean {
+ const [isMobile, setIsMobile] = useState(
+ typeof window !== "undefined" ? window.innerWidth < breakpoint : false
+ );
+
+ useEffect(() => {
+ const handleResize = () => {
+ setIsMobile(window.innerWidth < breakpoint);
+ };
+
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, [breakpoint]);
+
+ return isMobile;
+}
diff --git a/frontend/src/app/schemas/loginSchema.ts b/frontend/src/app/schemas/loginSchema.ts
new file mode 100644
index 0000000..4d55d12
--- /dev/null
+++ b/frontend/src/app/schemas/loginSchema.ts
@@ -0,0 +1,11 @@
+import { z } from "zod";
+
+export const loginSchema = z.object({
+ email: z.string().email("E-mail inválido"),
+ password: z
+ .string()
+ .min(8, "A senha deve ter no mínimo 8 caracteres")
+ .max(32, "A senha deve ter no máximo 32 caracteres"),
+});
+
+export type LoginFormData = z.infer;
\ No newline at end of file
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/frontend/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx
deleted file mode 100644
index 8b982f0..0000000
--- a/frontend/src/components/NavBar.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import Styles from "./NavBar.module.css";
-
-export const NavBar = () => {
-
- return (
-
-
- );
-}
\ No newline at end of file
diff --git a/frontend/src/components/layout/navbar/index.tsx b/frontend/src/components/layout/navbar/index.tsx
new file mode 100644
index 0000000..45ef435
--- /dev/null
+++ b/frontend/src/components/layout/navbar/index.tsx
@@ -0,0 +1,31 @@
+import { useNavigate } from "react-router-dom";
+import Styles from "./styles.module.css";
+
+export const NavBar = () => {
+ const navigate = useNavigate();
+
+ return (
+
+ );
+};
diff --git a/frontend/src/components/NavBar.module.css b/frontend/src/components/layout/navbar/styles.module.css
similarity index 100%
rename from frontend/src/components/NavBar.module.css
rename to frontend/src/components/layout/navbar/styles.module.css
diff --git a/frontend/src/components/ui/input/index.tsx b/frontend/src/components/ui/input/index.tsx
new file mode 100644
index 0000000..b062941
--- /dev/null
+++ b/frontend/src/components/ui/input/index.tsx
@@ -0,0 +1,95 @@
+import { type ComponentProps, useState } from "react";
+import { Eye, EyeOff, Lock, AtSign } from "lucide-react";
+import styles from "./styles.module.css";
+
+interface IInputProps extends ComponentProps<"input"> {
+ label: string;
+ iconLeft?: React.ReactNode;
+ state?: "default" | "error" | "warning" | "success";
+ helperText?: string;
+}
+
+export const Input = ({
+ label,
+ iconLeft,
+ type = "text",
+ state = "default",
+ helperText,
+ ...rest
+}: IInputProps) => {
+ const isPassword = type === "password";
+ const [showPassword, setShowPassword] = useState(false);
+ const inputType = isPassword ? (showPassword ? "text" : "password") : type;
+
+ const inputClassName = `${styles.input} ${
+ state === "error" ? styles.input__error : ""
+ } ${state === "warning" ? styles.input__warning : ""} ${
+ state === "success" ? styles.input__success : ""
+ }`;
+
+ return (
+
+
+
+ {helperText && (
+
+ {helperText}
+
+ )}
+
+ );
+};
diff --git a/frontend/src/components/ui/input/styles.module.css b/frontend/src/components/ui/input/styles.module.css
new file mode 100644
index 0000000..9020b7f
--- /dev/null
+++ b/frontend/src/components/ui/input/styles.module.css
@@ -0,0 +1,143 @@
+.input__wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ width: 100%;
+}
+
+.label {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ cursor: pointer;
+}
+
+.label__text {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #374151;
+}
+
+.input__container {
+ position: relative;
+ height: 48px;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.input {
+ width: 100%;
+ height: 100%;
+ padding: 0 48px;
+ border-radius: 4px;
+ font-size: 1rem;
+ border: 1px solid #d1d5db;
+ box-sizing: border-box;
+ transition: border-color 0.2s ease;
+}
+
+.input:focus {
+ outline: none;
+ border-color: black;
+ color: black;
+}
+
+.input__error {
+ border-color: #ef4444;
+ color: #ef4444;
+}
+
+.input__warning {
+ border-color: #f59e0b;
+ color: #f59e0b;
+}
+
+.input__success {
+ border-color: #10b981;
+ color: #10b981;
+}
+
+.helper__text {
+ font-size: 1rem;
+ margin-top: 8px;
+ color: #6b7280;
+}
+
+.helper__text__error {
+ color: #ef4444;
+}
+
+.helper__text__warning {
+ color: #f59e0b;
+}
+
+.icon {
+ width: 20px;
+ height: 20px;
+ color: white;
+}
+
+/* ícone à esquerda */
+.icon__left {
+ position: absolute;
+ left: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 32px;
+ height: 32px;
+ background-color: #d1d5db;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 2px;
+ pointer-events: none;
+ transition: background-color 0.2s ease;
+}
+
+/* Quando o container recebe foco, o ícone fica preto */
+.input__container:focus-within .icon__left {
+ background-color: black;
+}
+
+.icon__left__error {
+ background-color: #ef4444;
+}
+
+.icon__left__warning {
+ background-color: #f59e0b;
+}
+
+/* ícone de olho à direita */
+.icon__right {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 32px;
+ height: 32px;
+ background-color: transparent;
+ border: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ outline: none;
+}
+
+/* escondido até o foco */
+.eyeToggle {
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.2s ease;
+}
+
+/* quando o container recebe foco */
+.input__container:focus-within .eyeToggle {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.input__container:focus-within .eyeToggle .icon {
+ color: black;
+}
diff --git a/frontend/src/components/ui/link/index.tsx b/frontend/src/components/ui/link/index.tsx
new file mode 100644
index 0000000..35ae4e7
--- /dev/null
+++ b/frontend/src/components/ui/link/index.tsx
@@ -0,0 +1,16 @@
+import type { ComponentProps } from "react";
+
+import styles from "./styles.module.css";
+
+interface ILink extends ComponentProps<"a"> {
+ label: string;
+ href: string;
+}
+
+export const Link = ({ label, href }: ILink) => {
+ return (
+
+ {label}
+
+ );
+};
diff --git a/frontend/src/components/ui/link/styles.module.css b/frontend/src/components/ui/link/styles.module.css
new file mode 100644
index 0000000..a705154
--- /dev/null
+++ b/frontend/src/components/ui/link/styles.module.css
@@ -0,0 +1,3 @@
+a {
+ color: black;
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index e69de29..4ce567c 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -0,0 +1,9 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+
+ font-size: 16px;
+}
+
+body { font-family: 'Inter', sans-serif; }
\ No newline at end of file
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..096965c 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,10 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import App from "./app/App.tsx";
-createRoot(document.getElementById('root')!).render(
-
-
- ,
-)
+createRoot(document.getElementById("root")!).render(
+
+
+
+);
diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx
new file mode 100644
index 0000000..bcca304
--- /dev/null
+++ b/frontend/src/pages/home/index.tsx
@@ -0,0 +1,13 @@
+import { NavBar } from "@/components/layout/navbar";
+
+export const Home = () => {
+ return (
+ <>
+
+
+
Welcome to DEVdecola
+
Your one-stop solution for all things development!
+
+ >
+ );
+};
diff --git a/frontend/src/pages/login/desktop.module.css b/frontend/src/pages/login/desktop.module.css
new file mode 100644
index 0000000..3be23df
--- /dev/null
+++ b/frontend/src/pages/login/desktop.module.css
@@ -0,0 +1,125 @@
+.global__container {
+ width: 100%;
+ min-height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.container {
+ display: flex;
+ gap: 36px;
+ padding: 24px;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 8px;
+}
+
+.content__container {
+ width: 363px;
+}
+
+.logo__container {
+ margin-bottom: 24px;
+}
+
+.header__container {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 16px;
+}
+
+.header__container h1 {
+ font-size: 1.5rem;
+ font-weight: 600;
+}
+
+.header__container h2 {
+ font-size: 1rem;
+ font-weight: 500;
+ color: gray;
+}
+
+.main__container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ margin-bottom: 24px;
+}
+
+.inputs__container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.password__container {
+ display: flex;
+ flex-direction: column;
+ align-items: end;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.footer__container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+.footer__container .separator__container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16px;
+}
+
+.separator__container p {
+ white-space: nowrap;
+}
+
+.separator__container hr {
+ flex: 1;
+ height: 1px;
+ border: none;
+ background: rgba(0, 0, 0, 0.15);
+}
+
+.submitButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ height: 48px;
+ background-color: black;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-weight: 500;
+}
+
+.submitButton:hover:not(:disabled) {
+ background-color: #333;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.submitButton:disabled {
+ background-color: #e0e0e0;
+ color: #999;
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+}
+
+.image__container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.image__container img {
+ flex: 1;
+ object-fit: cover;
+}
diff --git a/frontend/src/pages/login/index.tsx b/frontend/src/pages/login/index.tsx
new file mode 100644
index 0000000..efa13cf
--- /dev/null
+++ b/frontend/src/pages/login/index.tsx
@@ -0,0 +1,190 @@
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { AtSign, ArrowLeft, ArrowRight, Mail } from "lucide-react";
+import { Input } from "@/components/ui/input";
+import { loginSchema, type LoginFormData } from "@/app/schemas/loginSchema";
+
+import { userIsMobile } from "@/app/hooks/userIsMobile";
+
+import desktop from "./desktop.module.css";
+import mobile from "./mobile.module.css";
+import { Link } from "@/components/ui/link";
+import { useNavigate } from "react-router-dom";
+
+import { FcGoogle as GoogleIcon } from "react-icons/fc";
+
+export const Login = () => {
+ const isMobile = userIsMobile();
+
+ const navigate = useNavigate();
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(loginSchema),
+ mode: "onChange",
+ });
+
+ const onSubmit = (data: LoginFormData) => {
+ console.log("Login data:", data);
+ };
+
+ const hasErrors = Object.keys(errors).length > 0;
+
+ const isButtonDisabled = isSubmitting || hasErrors;
+
+ const LoginDesktop = () => (
+
+
+
+
LOGO | 5kbyte
+
+
+ Preparado para continuar sua jornada?
+
+ Entre com seu e-mail e senha para acessar sua conta
+ DevDeCola.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const LoginMobile = () => (
+
+
+
+
+ Preparado para continuar sua jornada?
+
+
+ Entre com seu e-mail e senha para acessar sua conta
+ DevDeCola.
+
+
+
+
+
+
+
+
+ ou continue com
+
+
+
+
+
+
+
+
+ );
+
+ return <>{isMobile ? : }>;
+};
diff --git a/frontend/src/pages/login/mobile.module.css b/frontend/src/pages/login/mobile.module.css
new file mode 100644
index 0000000..781b709
--- /dev/null
+++ b/frontend/src/pages/login/mobile.module.css
@@ -0,0 +1,101 @@
+.mobileWrapper {
+ margin: 0 auto;
+ padding: 24px 16px;
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ justify-content: space-between;
+}
+.backButton {
+ width: 64px;
+ height: 64px;
+ border-radius: 50%;
+ background: #f5f5f5;
+ border: none;
+ display: flex;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 16px;
+}
+.title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+.subtitle {
+ color: #888;
+ font-size: 1rem;
+ font-weight: 500;
+ margin-bottom: 32px;
+}
+
+.inputs__container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.password__container {
+ display: flex;
+ flex-direction: column;
+ align-items: end;
+ gap: 8px;
+ margin-bottom: 24px;
+}
+
+.forgotPassword {
+ text-align: right;
+ margin-bottom: 24px;
+}
+.submitButton {
+ width: 100%;
+ background: #111;
+ color: #fff;
+ border: none;
+ border-radius: 8px;
+ font-size: 1.1rem;
+ font-weight: 600;
+ height: 56px;
+ margin-bottom: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+}
+.separator {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 24px;
+}
+.separator hr {
+ flex: 1;
+ border: none;
+ border-top: 1px solid #ddd;
+}
+.separator span {
+ color: #888;
+ font-size: 0.95rem;
+}
+.googleButton {
+ width: 100%;
+ border: 1.5px solid #111;
+ border-radius: 8px;
+ background: #fff;
+ color: #111;
+ font-size: 1.1rem;
+ font-weight: 500;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+}
+.signup {
+ display: flex;
+ gap: 4px;
+ justify-content: center;
+ font-size: 1rem;
+ margin-top: 24px;
+}
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
index 840d45a..cd30057 100644
--- a/frontend/tsconfig.app.json
+++ b/frontend/tsconfig.app.json
@@ -1,33 +1,33 @@
{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true,
- "noUncheckedIndexedAccess": true,
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "noUncheckedIndexedAccess": true,
- /* Paths */
- "baseUrl": ".",
- "paths": {
- "@/*": ["src/*"]
- }
- },
- "include": ["src"]
+ /* Paths */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "include": ["src", "src/vite-env.d.ts"]
}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
index 9728af2..d6cf5ef 100644
--- a/frontend/tsconfig.node.json
+++ b/frontend/tsconfig.node.json
@@ -1,25 +1,24 @@
{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2022",
- "lib": ["ES2023"],
- "module": "ESNext",
- "skipLibCheck": true,
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 8b0f57b..509f902 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,7 +1,13 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import path from 'path'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src')
+ }
+ }
})