ADD: added basic cli tool and updated the login modal
This commit is contained in:
13
frontend/studia/package-lock.json
generated
13
frontend/studia/package-lock.json
generated
@@ -62,7 +62,6 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -1607,7 +1606,6 @@
|
||||
"integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -1618,7 +1616,6 @@
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1678,7 +1675,6 @@
|
||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.49.0",
|
||||
"@typescript-eslint/types": "8.49.0",
|
||||
@@ -1930,7 +1926,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2036,7 +2031,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -2292,7 +2286,6 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -3256,7 +3249,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3317,7 +3309,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3327,7 +3318,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -3597,7 +3587,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -3683,7 +3672,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz",
|
||||
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -3805,7 +3793,6 @@
|
||||
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import Dashboard from "./pages/Dashboard"
|
||||
import LoginModal from "./components/LoginModal";
|
||||
|
||||
export default function App() {
|
||||
const [token, setToken] = useState(localStorage.getItem("token"));
|
||||
const [token] = useState(localStorage.getItem("token"));
|
||||
// const [showLogin, setShowLogin] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
@@ -19,6 +19,8 @@ export default function App() {
|
||||
<LoginModal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing onLogin={() => setModalOpen(true)} />} />
|
||||
{/* <Route path="/signup" element={<SignUp/>} /> */}
|
||||
|
||||
<Route path="/dashboard" element={ <ProtectedRoute><Dashboard /></ProtectedRoute> }
|
||||
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,16 @@ export async function loginUser(email:string, password: string) {
|
||||
if (!res.ok) throw new Error('Login fehlgeschlagen');
|
||||
return res.json(); // { token: string }
|
||||
}
|
||||
|
||||
export async function registerUser(email:string, password: string){
|
||||
const res = await fetch(`${API_URL}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Registrierung fehlgeschlagen');
|
||||
return res.json(); // { token: string }
|
||||
}
|
||||
|
||||
export async function fetchUserProfile(token: string) {
|
||||
const res = await fetch(`${API_URL}/profile`, {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createContext, useState, useContext, useEffect } from "react";
|
||||
import type { JSX } from 'react';
|
||||
import { getUserFromToken } from '../utils/jwt';
|
||||
import { loginUser } from "../api/user";
|
||||
// import { loginUser } from "../api/user";
|
||||
|
||||
type AuthUser = { token: string } | null;
|
||||
// type AuthUser = { token: string } | null;
|
||||
type AuthContextType = {
|
||||
token: string | null;
|
||||
userId: string | null;
|
||||
@@ -16,7 +16,7 @@ const AuthContext = createContext<AuthContextType | null>(null);
|
||||
export const AuthProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [userId, setUserId] = useState<string | null>(null);
|
||||
const [userEmail, setuserEmail] = useState<string | null>(null);
|
||||
// const [userEmail, setuserEmail] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
@@ -47,7 +47,7 @@ export const AuthProvider = ({ children }: { children: JSX.Element }) => {
|
||||
setUserId(userId);
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('userId', userId);
|
||||
// localStorage.setItem('role', JSON.stringify(role)); // Store array as string
|
||||
localStorage.setItem('role', JSON.stringify(role)); // Store array as string
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
|
||||
@@ -1,50 +1,138 @@
|
||||
import {loginUser} from "../api/user";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function LoginModal({ isOpen, onSuccess }: any) {
|
||||
export default function LoginModal({ isOpen, onClose, onSuccess }: any) {
|
||||
const [isRegistering, setIsRegistering] = useState(false);
|
||||
if (!isOpen) return null; // 👈 THIS is the key
|
||||
|
||||
return(
|
||||
<div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center">
|
||||
<div className="bg-white rounded-2xl p-8 w-96 shadow-xl">
|
||||
<h2 className="text-2xl font-bold mb-6">Login</h2>
|
||||
|
||||
<form
|
||||
onSubmit={async (e: any) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.currentTarget);
|
||||
const email = fd.get("email");
|
||||
const password = fd.get("password");
|
||||
|
||||
const res = await loginUser(email as string, password as string);
|
||||
|
||||
const data = await res.json();
|
||||
localStorage.setItem("token", data.token);
|
||||
onSuccess(data.token);
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-indigo-600 text-white py-3 rounded-xl hover:bg-indigo-700"
|
||||
>
|
||||
Login
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-2xl font-bold">Login</h2>
|
||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{isRegistering ? (
|
||||
<form
|
||||
onSubmit={async (e: any) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.currentTarget);
|
||||
const email = fd.get("email");
|
||||
const username = fd.get("username");
|
||||
const password = fd.get("password");
|
||||
const confirmPassword = fd.get("confirmPassword");
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
alert("Passwords do not match!");
|
||||
return;
|
||||
}
|
||||
if (!username) {
|
||||
alert("Please enter a username!");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Call registerUser API
|
||||
// const res = await registerUser(email as string, username as string, password as string);
|
||||
|
||||
// TODO: Implement actual registration logic here
|
||||
console.log("Registering with:", email, password);
|
||||
// For now, let's just switch back to login after a "successful" registration
|
||||
setIsRegistering(false);
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
<input
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
<input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-indigo-600 text-white py-3 rounded-xl hover:bg-indigo-700"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsRegistering(false)}
|
||||
className="w-full bg-gray-200 text-gray-800 py-3 rounded-xl hover:bg-gray-300 mt-2"
|
||||
>
|
||||
Back to Login
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<form
|
||||
onSubmit={async (e: any) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.currentTarget);
|
||||
const email = fd.get("email");
|
||||
const password = fd.get("password");
|
||||
|
||||
const res = await loginUser(email as string, password as string);
|
||||
|
||||
const data = await res.json();
|
||||
localStorage.setItem("token", data.token);
|
||||
onSuccess(data.token);
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
className="w-full border rounded-xl px-3 py-2"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-indigo-600 text-white py-3 rounded-xl hover:bg-indigo-700"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsRegistering(true)}
|
||||
className="w-full bg-gray-200 text-gray-800 py-3 rounded-xl hover:bg-gray-300 mt-2"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
0
frontend/studia/src/pages/SignUp.tsx
Normal file
0
frontend/studia/src/pages/SignUp.tsx
Normal file
Reference in New Issue
Block a user