Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions Backend/controller/auth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"
"os"
"personal-erp-backend/database"
"personal-erp-backend/internal/profile"
"time"

"github.com/gin-gonic/gin"
Expand All @@ -15,9 +16,14 @@ var SecretKey = []byte(os.Getenv("SECRET_KEY"))

func Register(c *gin.Context) {
var input struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
PhoneNumber string `json:"phone_number"`
Country string `json:"country"`
TeleUsername string `json:"tele_username"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Expand All @@ -28,25 +34,40 @@ func Register(c *gin.Context) {
c.JSON(500, gin.H{"error": err.Error()})
return
}
user := User{Username: input.Username, Email: input.Email, Password: string(hashedPassword)}
user := profile.User{
Username: input.Username,
Email: input.Email,
Password: string(hashedPassword),
FirstName: input.FirstName,
LastName: input.LastName,
PhoneNumber: input.PhoneNumber,
Country: input.Country,
TeleUsername: input.TeleUsername,
}
if err := database.DB.Create(&user).Error; err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"user": user})

if err := Login; err != nil {
c.JSON(500, gin.H{"error": err})
return
}
}

func Login(c *gin.Context) {
var input struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var user User
if err := database.DB.Where("username = ?", input.Username).First(&user).Error; err != nil {
var user profile.User
if err := database.DB.Where("username = ? OR email = ?", input.Username, input.Email).First(&user).Error; err != nil {
c.JSON(404, gin.H{"error": err.Error()})
return
}
Expand All @@ -66,6 +87,12 @@ func Login(c *gin.Context) {
c.SetSameSite(http.SameSiteLaxMode)
c.SetCookie("authorization", tokenString, 3600*24, "", "", false, true)
c.JSON(http.StatusOK, gin.H{"token": tokenString, "message": "success"})

if err := profile.GetUser; err != nil {
c.JSON(404, gin.H{"message": err})
return
}
c.JSON(200, gin.H{"message": "OK", "user": user})
}

func Logout(c *gin.Context) {
Expand Down
3 changes: 2 additions & 1 deletion Backend/controller/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"personal-erp-backend/database"
"personal-erp-backend/internal/profile"
"time"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -42,7 +43,7 @@ func RequireAuth(c *gin.Context) {
if float64(time.Now().Unix()) > claims["exp"].(float64) {
c.JSON(401, gin.H{"message": "Token expired"})
}
var user User
var user profile.User
if result := database.DB.First(&user, claims["sub"]); result.Error != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "User no"})
return
Expand Down
8 changes: 0 additions & 8 deletions Backend/controller/auth/modelDB.go

This file was deleted.

35 changes: 35 additions & 0 deletions Backend/internal/profile/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package profile

import (
"personal-erp-backend/database"

"github.com/gin-gonic/gin"
)

func GetUser(c *gin.Context) {
var user User
userID, _ := c.Get("userID")
if err := database.DB.Where("id = ?", userID).Find(&user).Error; err != nil {
c.JSON(404, gin.H{"message": err})
return
}
c.JSON(200, gin.H{"Success": user})
}

func UpdateUser(c *gin.Context) {
var user User
userID, _ := c.Get("userID")
if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil {
c.JSON(404, gin.H{"message": err})
return
}
if err := c.ShouldBind(&user); err != nil {
c.JSON(400, gin.H{"message": err})
return
}
if err := database.DB.Model(&user).Updates(&user).Error; err != nil {
c.JSON(404, gin.H{"message": err})
return
}
c.JSON(200, gin.H{"Success": user})
}
13 changes: 13 additions & 0 deletions Backend/internal/profile/modelDB.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package profile

type User struct {
ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"`
Username string `gorm:"size:255;not null" json:"username"`
Email string `gorm:"size:255;not null" json:"email"`
Password string `json:"-"`
FirstName string `gorm:"size:255" json:"first_name"`
LastName string `gorm:"size:255" json:"last_name"`
PhoneNumber string `gorm:"size:255" json:"phone_number"`
Country string `gorm:"size:255" json:"country"`
TeleUsername string `gorm:"size:255" json:"tele_username"`
}
11 changes: 11 additions & 0 deletions Backend/internal/profile/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package profile

import "github.com/gin-gonic/gin"

func RegisterRouter(r *gin.RouterGroup) {
profile := r.Group("/profile")
{
profile.GET("/user", GetUser)
profile.POST("/user", UpdateUser)
}
}
4 changes: 3 additions & 1 deletion Backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"personal-erp-backend/database"
"personal-erp-backend/internal/academic"
"personal-erp-backend/internal/finance"
"personal-erp-backend/internal/profile"
"time"

"github.com/gin-contrib/cors"
Expand All @@ -16,7 +17,7 @@ func main() {
database.ConnectDB()

err := database.DB.AutoMigrate(
&auth2.User{},
&profile.User{},
&academic.Subject{},
&academic.Task{},
&academic.Note{},
Expand Down Expand Up @@ -49,6 +50,7 @@ func main() {
protected := r.Group("/api")
protected.Use(auth2.RequireAuth)
{
profile.RegisterRouter(protected)
academic.RegisterRoutes(protected)
finance.RegisterRouter(protected)
}
Expand Down
39 changes: 39 additions & 0 deletions Frontend/project-CL/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { Loader2 } from 'lucide-react';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
isLoading?: boolean;
icon?: React.ReactNode;
}

export default function Button({
children,
variant = 'primary',
isLoading = false,
icon,
className = '',
disabled,
...props
}: ButtonProps) {
const baseStyles = "inline-flex items-center justify-center -gap-1 rounded-xl text-sm font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed active:scale-[0.98]";

const variants = {
primary: "bg-indigo-600 text-white hover:bg-indigo-700 hover:shadow-lg hover:shadow-indigo-200 focus:ring-indigo-500 border border-transparent",
secondary: "bg-white text-slate-700 border border-slate-200 hover:bg-slate-50 hover:border-slate-300 focus:ring-slate-200 shadow-sm",
danger: "bg-red-50 text-red-600 hover:bg-red-100 border border-transparent focus:ring-red-500",
ghost: "bg-transparent text-slate-600 hover:bg-slate-100 hover:text-slate-900 border border-transparent",
};

return (
<button
className={`${baseStyles} ${variants[variant]} ${className} ${isLoading ? 'cursor-wait' : ''}`}
disabled={disabled || isLoading}
{...props}
>
{isLoading && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}
{!isLoading && icon && <span className="mr-2">{icon}</span>}
{children}
</button>
);
}
46 changes: 46 additions & 0 deletions Frontend/project-CL/src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
icon?: React.ReactNode;
}

export default function Input({ label, error, icon, className = '', ...props }: InputProps) {
return (
<div className="w-full">
{label && (
<label className="block text-sm font-medium text-slate-700 mb-1.5">
{label}
</label>
)}
<div className="relative">
{icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-slate-400">
{icon}
</div>
)}
<input
className={`
w-full rounded-xl border bg-white
text-slate-900 placeholder:text-slate-400
text-sm transition-all duration-200
focus:outline-none focus:ring-2 focus:ring-offset-1
${icon ? 'pl-10 pr-4 py-2.5' : 'px-4 py-2.5'}
${error
? 'border-red-300 focus:border-red-500 focus:ring-red-200'
: 'border-slate-200 focus:border-indigo-500 focus:ring-indigo-100 hover:border-slate-300'
}
${className}
`}
{...props}
/>
</div>
{error && (
<p className="mt-1.5 text-xs text-red-500 font-medium animate-in slide-in-from-top-1">
{error}
</p>
)}
</div>
);
}
20 changes: 12 additions & 8 deletions Frontend/project-CL/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { NavLink } from "react-router";
import { useAuth } from "../features/auth/context/AuthContext";


export default function Sidebar() {
const { logout } = useAuth();
const links = [
{ name: "Home", path: "/", icon: "🏠" },
{ name: "Academic", path: "/academic", icon: "🎓" },
Expand Down Expand Up @@ -36,13 +35,18 @@ export default function Sidebar() {
))}
</nav>
<div className="p-4 border-t border-slate-800">
<button
onClick={logout}
className="w-full flex items-center px-4 py-3 rounded-lg text-slate-400 hover:bg-slate-800 hover:text-red-400 transition-all duration-200 whitespace-nowrap overflow-hidden"
<NavLink
to="/profile"
className={({ isActive }) =>
`flex items-center px-4 py-3 rounded-lg transition-all duration-200 whitespace-nowrap overflow-hidden ${isActive
? "bg-blue-600 text-white shadow-md shadow-blue-900/20"
: "text-slate-400 hover:bg-slate-800 hover:text-white"
}`
}
>
<span className="text-xl min-w-[24px] flex justify-center">🚪</span>
<span className="font-medium ml-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300">Logout</span>
</button>
<span className="text-xl min-w-[24px] flex justify-center">👤</span>
<span className="font-medium ml-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300">Profile</span>
</NavLink>
</div>
<div className="p-4 border-t border-slate-800 whitespace-nowrap overflow-hidden">
<div className="px-4 py-2 text-xs text-slate-500 text-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
Expand Down
Loading
Loading