diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc
index 72cc141..be7bf31 100644
Binary files a/backend/__pycache__/main.cpython-313.pyc and b/backend/__pycache__/main.cpython-313.pyc differ
diff --git a/backend/__pycache__/user_db_utils.cpython-313.pyc b/backend/__pycache__/user_db_utils.cpython-313.pyc
index e6126ee..c500bba 100644
Binary files a/backend/__pycache__/user_db_utils.cpython-313.pyc and b/backend/__pycache__/user_db_utils.cpython-313.pyc differ
diff --git a/backend/main.py b/backend/main.py
index e6a755e..a52d7db 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -4,7 +4,7 @@ from datetime import timedelta
from fastapi import FastAPI, HTTPException, Query, Depends
from fastapi.middleware.cors import CORSMiddleware
-from pydantic import BaseModel, EmailStr
+from pydantic import BaseModel, EmailStr, field_validator
import os
import uvicorn
@@ -62,6 +62,18 @@ class UserRegister(BaseModel):
username: str
email: EmailStr
password: str
+ first_name: Optional[str] = None
+ last_name: Optional[str] = None
+ display_name: str
+
+ @field_validator('username')
+ @classmethod
+ def username_must_be_english(cls, v: str) -> str:
+ if not v.isascii():
+ raise ValueError('שם משתמש חייב להיות באנגלית בלבד')
+ if not all(c.isalnum() or c in '_-' for c in v):
+ raise ValueError('שם משתמש יכול להכיל רק אותיות, מספרים, _ ו-')
+ return v
class UserLogin(BaseModel):
@@ -78,6 +90,9 @@ class UserResponse(BaseModel):
id: int
username: str
email: str
+ first_name: Optional[str] = None
+ last_name: Optional[str] = None
+ display_name: str
app = FastAPI(
@@ -277,13 +292,23 @@ def register(user: UserRegister):
print(f"[REGISTER] Hashing password...")
password_hash = hash_password(user.password)
print(f"[REGISTER] Creating user in database...")
- new_user = create_user(user.username, user.email, password_hash)
+ new_user = create_user(
+ user.username,
+ user.email,
+ password_hash,
+ user.first_name,
+ user.last_name,
+ user.display_name
+ )
print(f"[REGISTER] User created successfully: {new_user['id']}")
return UserResponse(
id=new_user["id"],
username=new_user["username"],
- email=new_user["email"]
+ email=new_user["email"],
+ first_name=new_user.get("first_name"),
+ last_name=new_user.get("last_name"),
+ display_name=new_user["display_name"]
)
@@ -326,7 +351,10 @@ def get_me(current_user: dict = Depends(get_current_user)):
return UserResponse(
id=user["id"],
username=user["username"],
- email=user["email"]
+ email=user["email"],
+ first_name=user.get("first_name"),
+ last_name=user.get("last_name"),
+ display_name=user["display_name"]
)
diff --git a/backend/schema.sql b/backend/schema.sql
index 76916b5..88bfd34 100644
--- a/backend/schema.sql
+++ b/backend/schema.sql
@@ -4,6 +4,9 @@ CREATE TABLE IF NOT EXISTS users (
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
+ first_name TEXT,
+ last_name TEXT,
+ display_name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
diff --git a/backend/user_db_utils.py b/backend/user_db_utils.py
index fd37d36..264da28 100644
--- a/backend/user_db_utils.py
+++ b/backend/user_db_utils.py
@@ -14,18 +14,21 @@ def get_db_connection():
)
-def create_user(username: str, email: str, password_hash: str):
+def create_user(username: str, email: str, password_hash: str, first_name: str = None, last_name: str = None, display_name: str = None):
"""Create a new user"""
conn = get_db_connection()
cur = conn.cursor(cursor_factory=RealDictCursor)
try:
+ # Use display_name if provided, otherwise use username
+ final_display_name = display_name if display_name else username
+
cur.execute(
"""
- INSERT INTO users (username, email, password_hash)
- VALUES (%s, %s, %s)
- RETURNING id, username, email, created_at
+ INSERT INTO users (username, email, password_hash, first_name, last_name, display_name)
+ VALUES (%s, %s, %s, %s, %s, %s)
+ RETURNING id, username, email, first_name, last_name, display_name, created_at
""",
- (username, email, password_hash)
+ (username, email, password_hash, first_name, last_name, final_display_name)
)
user = cur.fetchone()
conn.commit()
@@ -41,7 +44,7 @@ def get_user_by_username(username: str):
cur = conn.cursor(cursor_factory=RealDictCursor)
try:
cur.execute(
- "SELECT id, username, email, password_hash, created_at FROM users WHERE username = %s",
+ "SELECT id, username, email, password_hash, first_name, last_name, display_name, created_at FROM users WHERE username = %s",
(username,)
)
user = cur.fetchone()
@@ -57,7 +60,7 @@ def get_user_by_email(email: str):
cur = conn.cursor(cursor_factory=RealDictCursor)
try:
cur.execute(
- "SELECT id, username, email, password_hash, created_at FROM users WHERE email = %s",
+ "SELECT id, username, email, password_hash, first_name, last_name, display_name, created_at FROM users WHERE email = %s",
(email,)
)
user = cur.fetchone()
@@ -73,7 +76,7 @@ def get_user_by_id(user_id: int):
cur = conn.cursor(cursor_factory=RealDictCursor)
try:
cur.execute(
- "SELECT id, username, email, created_at FROM users WHERE id = %s",
+ "SELECT id, username, email, first_name, last_name, display_name, created_at FROM users WHERE id = %s",
(user_id,)
)
user = cur.fetchone()
diff --git a/frontend/index.html b/frontend/index.html
index 259fcb7..577d29f 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,7 @@
-
My Recipes | מתכונים שלי
+ My Recipes | המתכונים שלי
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 4780b9d..b88d521 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -765,7 +765,7 @@ body {
}
[data-theme="light"] body {
- background: linear-gradient(180deg, #ac8d75 0%, #f6f8fa 100%);
+ background: linear-gradient(180deg, #ac8d75 0%, #ede9e5 100%);
color: var(--text-main);
}
@@ -1292,17 +1292,17 @@ html {
align-items: center;
justify-content: center;
padding: 2rem;
- background: var(--bg);
+ background: radial-gradient(circle at top, #0f172a 0, #020617 55%);
}
.auth-card {
width: 100%;
max-width: 420px;
- background: var(--card);
- border: 1px solid var(--border);
+ background: #020617;
+ border: 1px solid var(--border-subtle);
border-radius: 16px;
padding: 2rem;
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7);
}
.auth-title {
@@ -1349,3 +1349,14 @@ html {
.full-width {
width: 100%;
}
+
+/* Light mode auth styles */
+[data-theme="light"] .auth-container {
+ background: linear-gradient(180deg, #ac8d75 0%, #ede9e5 100%);
+}
+
+[data-theme="light"] .auth-card {
+ background: #d1b29b;
+ border: 1px solid rgba(107, 114, 128, 0.2);
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.08);
+}
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 5948a98..467216d 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -41,6 +41,7 @@ function App() {
const [editingRecipe, setEditingRecipe] = useState(null);
const [deleteModal, setDeleteModal] = useState({ isOpen: false, recipeId: null, recipeName: "" });
+ const [logoutModal, setLogoutModal] = useState(false);
const [toasts, setToasts] = useState([]);
const [theme, setTheme] = useState(() => {
try {
@@ -249,11 +250,17 @@ function App() {
};
const handleLogout = () => {
+ setLogoutModal(true);
+ };
+
+ const confirmLogout = () => {
removeToken();
setUser(null);
setIsAuthenticated(false);
setRecipes([]);
setSelectedRecipe(null);
+ setLogoutModal(false);
+ addToast('התנתקת בהצלחה', 'success');
};
// Show loading state while checking auth
@@ -275,7 +282,7 @@ function App() {
{/* User greeting above TopBar */}
{isAuthenticated && user && (
- שלום, {user.username} 👋
+ שלום, {user.display_name || user.username} 👋
)}
@@ -313,10 +320,7 @@ function App() {
/>
) : (
{
- addToast("נרשמת בהצלחה! כעת התחבר", "success");
- setAuthView("login");
- }}
+ onSuccess={handleLoginSuccess}
onSwitchToLogin={() => setAuthView("login")}
/>
)}
@@ -430,6 +434,17 @@ function App() {
onCancel={handleCancelDelete}
/>
+ setLogoutModal(false)}
+ />
+
);
diff --git a/frontend/src/authApi.js b/frontend/src/authApi.js
index 626dd1d..e6ae1dc 100644
--- a/frontend/src/authApi.js
+++ b/frontend/src/authApi.js
@@ -8,11 +8,18 @@ const getApiBase = () => {
const API_BASE = getApiBase();
-export async function register(username, email, password) {
+export async function register(username, email, password, firstName, lastName, displayName) {
const res = await fetch(`${API_BASE}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ username, email, password }),
+ body: JSON.stringify({
+ username,
+ email,
+ password,
+ first_name: firstName,
+ last_name: lastName,
+ display_name: displayName
+ }),
});
if (!res.ok) {
const error = await res.json();
diff --git a/frontend/src/components/Register.jsx b/frontend/src/components/Register.jsx
index 01d197f..40d1bdc 100644
--- a/frontend/src/components/Register.jsx
+++ b/frontend/src/components/Register.jsx
@@ -1,11 +1,14 @@
import { useState } from "react";
-import { register } from "../authApi";
+import { register, login, saveToken } from "../authApi";
function Register({ onSuccess, onSwitchToLogin }) {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [displayName, setDisplayName] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
@@ -24,12 +27,21 @@ function Register({ onSuccess, onSwitchToLogin }) {
return;
}
+ if (!displayName.trim()) {
+ setError("שם תצוגה הוא שדה חובה");
+ return;
+ }
+
setLoading(true);
try {
- await register(username, email, password);
- // After successful registration, switch to login
- onSwitchToLogin();
+ // Register the user
+ await register(username, email, password, firstName, lastName, displayName);
+
+ // Automatically login after successful registration
+ const response = await login(username, password);
+ saveToken(response.access_token);
+ onSuccess();
} catch (err) {
setError(err.message);
} finally {
@@ -47,20 +59,54 @@ function Register({ onSuccess, onSwitchToLogin }) {
{error && {error}
}
-
+
+ setFirstName(e.target.value)}
+ placeholder="שם פרטי (אופציונלי)"
+ />
+
+
+
+
+ setLastName(e.target.value)}
+ placeholder="שם משפחה (אופציונלי)"
+ />
+
+
+
+
+ setDisplayName(e.target.value)}
+ required
+ placeholder="איך תרצה שיופיע שמך?"
+ minLength={2}
+ />
+
+
+
+
setUsername(e.target.value)}
required
- placeholder="בחר שם משתמש"
+ placeholder="username (English only)"
autoComplete="username"
minLength={3}
+ pattern="[a-zA-Z0-9_-]+"
+ title="שם משתמש יכול להכיל רק אותיות באנגלית, מספרים, _ ו-"
/>