ADD: added basic cli tool and updated the login modal
This commit is contained in:
BIN
backend/cli
Executable file
BIN
backend/cli
Executable file
Binary file not shown.
@@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var RootCmd = &cobra.Command{
|
var RootCmd = &cobra.Command{
|
||||||
Use: "myapp",
|
Use: "studia",
|
||||||
Short: "MyApp admin CLI",
|
Short: "studia admin CLI",
|
||||||
}
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
|
|||||||
@@ -3,25 +3,31 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
// "studia/internal/db"
|
"studia/internal/config"
|
||||||
usersvc "studia/internal/user"
|
"studia/internal/database"
|
||||||
|
"studia/internal/user"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var id int64
|
var email string
|
||||||
|
|
||||||
var getCmd = &cobra.Command{
|
var getCmd = &cobra.Command{
|
||||||
|
|
||||||
Use: "get",
|
Use: "get",
|
||||||
Short: "Get user by ID",
|
Short: "Get user by ID",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
database, err := db.New(getDSN())
|
cfg := config.New()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
service := usersvc.NewService(database)
|
cfg.DatabaseHost = "192.168.178.171"
|
||||||
user, err := service.GetByID(id)
|
cfg.DatabasePort = "5432"
|
||||||
|
cfg.DatabaseUser = "admin"
|
||||||
|
cfg.DatabasePassword = "12345678"
|
||||||
|
cfg.DatabaseName = "studia"
|
||||||
|
|
||||||
|
database := database.New(cfg)
|
||||||
|
|
||||||
|
user, err := user.GetUserByEmail(database, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -33,6 +39,6 @@ var getCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
getCmd.Flags().Int64Var(&id, "id", 0, "user ID")
|
getCmd.Flags().StringVar(&email, "email", "", "user email")
|
||||||
getCmd.MarkFlagRequired("id")
|
getCmd.MarkFlagRequired("email")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// cmd/cli/user/list.go
|
|
||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"myapp/internal/db"
|
|
||||||
usersvc "myapp/internal/user"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var listCmd = &cobra.Command{
|
|
||||||
Use: "list",
|
|
||||||
Short: "List users",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
database, err := db.New(getDSN())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
service := usersvc.NewService(database)
|
|
||||||
users, err := service.List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range users {
|
|
||||||
fmt.Printf("%d | %s | %s\n", u.ID, u.Email, u.Name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,6 @@ var UserCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
UserCmd.AddCommand(listCmd)
|
// UserCmd.AddCommand(listCmd)
|
||||||
UserCmd.AddCommand(getCmd)
|
UserCmd.AddCommand(getCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20250521004450-2b1292699c15 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQ
|
|||||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20250521004450-2b1292699c15 h1:AoSudS8CW8Mc9rRf5sO1vBtNxr2Ok6TaAICjgg5oKUY=
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20250521004450-2b1292699c15/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
|
||||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ type LoginRequest struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegisterRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultRole = "user"
|
||||||
|
|
||||||
var secret = []byte("secret")
|
var secret = []byte("secret")
|
||||||
|
|
||||||
func Login(c *gin.Context, db *sql.DB) {
|
func Login(c *gin.Context, db *sql.DB) {
|
||||||
@@ -49,6 +57,26 @@ func Login(c *gin.Context, db *sql.DB) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Register(c *gin.Context, db *sql.DB) {
|
||||||
|
var req RegisterRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Email == "" || req.Password == "" || req.Username == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Email and password are required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
error := user.CreateUser(db, req.Email, req.Username, req.Password, []string{defaultRole})
|
||||||
|
if error != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": error.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "User created successfully"})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateJWT(uuid string, email string, roles []string) (any, error) {
|
func GenerateJWT(uuid string, email string, roles []string) (any, error) {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"user": uuid,
|
"user": uuid,
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ func StartServer(cfg *config.Config) {
|
|||||||
auth.Login(c, db) // Pass the actual DB connection instead of nil
|
auth.Login(c, db) // Pass the actual DB connection instead of nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.POST("/register", func(c *gin.Context) {
|
||||||
|
auth.Register(c, db)
|
||||||
|
})
|
||||||
|
|
||||||
router.Run(":" + cfg.Port)
|
router.Run(":" + cfg.Port)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
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==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -1607,7 +1606,6 @@
|
|||||||
"integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==",
|
"integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -1618,7 +1616,6 @@
|
|||||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -1678,7 +1675,6 @@
|
|||||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.49.0",
|
"@typescript-eslint/scope-manager": "8.49.0",
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
@@ -1930,7 +1926,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2036,7 +2031,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -2292,7 +2286,6 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3256,7 +3249,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3317,7 +3309,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -3327,7 +3318,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -3597,7 +3587,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -3683,7 +3672,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz",
|
||||||
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
|
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -3805,7 +3793,6 @@
|
|||||||
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Dashboard from "./pages/Dashboard"
|
|||||||
import LoginModal from "./components/LoginModal";
|
import LoginModal from "./components/LoginModal";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [token, setToken] = useState(localStorage.getItem("token"));
|
const [token] = useState(localStorage.getItem("token"));
|
||||||
// const [showLogin, setShowLogin] = useState(false);
|
// const [showLogin, setShowLogin] = useState(false);
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
@@ -19,6 +19,8 @@ export default function App() {
|
|||||||
<LoginModal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
|
<LoginModal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Landing onLogin={() => setModalOpen(true)} />} />
|
<Route path="/" element={<Landing onLogin={() => setModalOpen(true)} />} />
|
||||||
|
{/* <Route path="/signup" element={<SignUp/>} /> */}
|
||||||
|
|
||||||
<Route path="/dashboard" element={ <ProtectedRoute><Dashboard /></ProtectedRoute> }
|
<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');
|
if (!res.ok) throw new Error('Login fehlgeschlagen');
|
||||||
return res.json(); // { token: string }
|
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) {
|
export async function fetchUserProfile(token: string) {
|
||||||
const res = await fetch(`${API_URL}/profile`, {
|
const res = await fetch(`${API_URL}/profile`, {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createContext, useState, useContext, useEffect } from "react";
|
import { createContext, useState, useContext, useEffect } from "react";
|
||||||
import type { JSX } from 'react';
|
import type { JSX } from 'react';
|
||||||
import { getUserFromToken } from '../utils/jwt';
|
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 = {
|
type AuthContextType = {
|
||||||
token: string | null;
|
token: string | null;
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
@@ -16,7 +16,7 @@ const AuthContext = createContext<AuthContextType | null>(null);
|
|||||||
export const AuthProvider = ({ children }: { children: JSX.Element }) => {
|
export const AuthProvider = ({ children }: { children: JSX.Element }) => {
|
||||||
const [token, setToken] = useState<string | null>(null);
|
const [token, setToken] = useState<string | null>(null);
|
||||||
const [userId, setUserId] = 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(() => {
|
useEffect(() => {
|
||||||
const storedToken = localStorage.getItem('token');
|
const storedToken = localStorage.getItem('token');
|
||||||
@@ -47,7 +47,7 @@ export const AuthProvider = ({ children }: { children: JSX.Element }) => {
|
|||||||
setUserId(userId);
|
setUserId(userId);
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem('token', token);
|
||||||
localStorage.setItem('userId', userId);
|
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 = () => {
|
const logout = () => {
|
||||||
|
|||||||
@@ -1,50 +1,138 @@
|
|||||||
import {loginUser} from "../api/user";
|
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
|
if (!isOpen) return null; // 👈 THIS is the key
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center">
|
<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">
|
<div className="bg-white rounded-2xl p-8 w-96 shadow-xl">
|
||||||
<h2 className="text-2xl font-bold mb-6">Login</h2>
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h2 className="text-2xl font-bold">Login</h2>
|
||||||
<form
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-2">
|
||||||
onSubmit={async (e: any) => {
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||||
e.preventDefault();
|
<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" />
|
||||||
const fd = new FormData(e.currentTarget);
|
</svg>
|
||||||
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>
|
||||||
</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>
|
||||||
</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