supporiting documents uploaded. One step away from starting development.

This commit is contained in:
2025-08-02 14:43:20 -04:00
parent da8c5db890
commit 3f2f14ac66
23 changed files with 1633 additions and 0 deletions

0
src/frontend/__init__.py Normal file
View File

235
src/frontend/callbacks.py Normal file
View File

@@ -0,0 +1,235 @@
from dash import Input, Output, State, callback, clientside_callback
import dash_mantine_components as dmc
import httpx
import structlog
from .pages.home import create_home_page
from .pages.auth import create_login_page
logger = structlog.get_logger()
def register_callbacks(app, config):
@app.callback(
Output("page-content", "children"),
Output("header-actions", "children"),
Input("url", "pathname"),
State("auth-store", "data")
)
def display_page(pathname, auth_data):
# Check if user is authenticated
is_authenticated = auth_data and auth_data.get("token")
if not is_authenticated:
# Show login page for unauthenticated users
if pathname == "/login" or pathname is None or pathname == "/":
return create_login_page(), []
else:
return create_login_page(), []
# Authenticated user navigation
header_actions = [
dmc.Button(
"Logout",
id="logout-btn",
variant="outline",
color="red",
leftIcon="tabler:logout"
)
]
# Route to different pages
if pathname == "/" or pathname is None:
return create_home_page(), header_actions
elif pathname == "/jobs":
return create_jobs_page(), header_actions
elif pathname == "/applications":
return create_applications_page(), header_actions
elif pathname == "/documents":
return create_documents_page(), header_actions
elif pathname == "/profile":
return create_profile_page(), header_actions
else:
return create_home_page(), header_actions
@app.callback(
Output("auth-store", "data"),
Output("auth-alerts", "children"),
Input("login-submit", "n_clicks"),
State("login-email", "value"),
State("login-password", "value"),
prevent_initial_call=True
)
def handle_login(n_clicks, email, password):
if not n_clicks or not email or not password:
return None, []
try:
response = httpx.post(
f"{config.auth_url}/login",
json={"email": email, "password": password},
timeout=10.0
)
if response.status_code == 200:
token_data = response.json()
auth_data = {
"token": token_data["access_token"],
"email": email
}
success_alert = dmc.Alert(
"Login successful! Redirecting...",
title="Success",
color="green",
duration=3000
)
return auth_data, success_alert
else:
error_alert = dmc.Alert(
"Invalid email or password",
title="Login Failed",
color="red"
)
return None, error_alert
except Exception as e:
logger.error("Login error", error=str(e))
error_alert = dmc.Alert(
"Connection error. Please try again.",
title="Error",
color="red"
)
return None, error_alert
@app.callback(
Output("auth-store", "data", allow_duplicate=True),
Output("auth-alerts", "children", allow_duplicate=True),
Input("register-submit", "n_clicks"),
State("register-email", "value"),
State("register-password", "value"),
State("register-password-confirm", "value"),
State("register-first-name", "value"),
State("register-last-name", "value"),
State("register-phone", "value"),
prevent_initial_call=True
)
def handle_register(n_clicks, email, password, password_confirm, first_name, last_name, phone):
if not n_clicks:
return None, []
# Validation
if not all([email, password, first_name, last_name]):
error_alert = dmc.Alert(
"All required fields must be filled",
title="Validation Error",
color="red"
)
return None, error_alert
if password != password_confirm:
error_alert = dmc.Alert(
"Passwords do not match",
title="Validation Error",
color="red"
)
return None, error_alert
try:
user_data = {
"email": email,
"password": password,
"first_name": first_name,
"last_name": last_name
}
if phone:
user_data["phone"] = phone
response = httpx.post(
f"{config.auth_url}/register",
json=user_data,
timeout=10.0
)
if response.status_code == 200:
# Auto-login after successful registration
login_response = httpx.post(
f"{config.auth_url}/login",
json={"email": email, "password": password},
timeout=10.0
)
if login_response.status_code == 200:
token_data = login_response.json()
auth_data = {
"token": token_data["access_token"],
"email": email
}
success_alert = dmc.Alert(
"Registration successful! Welcome to Job Forge!",
title="Success",
color="green",
duration=3000
)
return auth_data, success_alert
error_alert = dmc.Alert(
"Registration failed. Email may already be in use.",
title="Registration Failed",
color="red"
)
return None, error_alert
except Exception as e:
logger.error("Registration error", error=str(e))
error_alert = dmc.Alert(
"Connection error. Please try again.",
title="Error",
color="red"
)
return None, error_alert
@app.callback(
Output("auth-store", "clear_data"),
Input("logout-btn", "n_clicks"),
prevent_initial_call=True
)
def handle_logout(n_clicks):
if n_clicks:
return True
return False
# Placeholder functions for other pages
def create_jobs_page():
return dmc.Container(
children=[
dmc.Title("Job Search", mb="lg"),
dmc.Text("Job search functionality coming soon...")
]
)
def create_applications_page():
return dmc.Container(
children=[
dmc.Title("My Applications", mb="lg"),
dmc.Text("Application tracking functionality coming soon...")
]
)
def create_documents_page():
return dmc.Container(
children=[
dmc.Title("Documents", mb="lg"),
dmc.Text("Document management functionality coming soon...")
]
)
def create_profile_page():
return dmc.Container(
children=[
dmc.Title("Profile", mb="lg"),
dmc.Text("Profile management functionality coming soon...")
]
)

27
src/frontend/config.py Normal file
View File

@@ -0,0 +1,27 @@
import os
from typing import Optional
class Config:
def __init__(self):
self.BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:8000")
self.DEBUG = os.getenv("DEBUG", "false").lower() == "true"
@property
def api_base_url(self) -> str:
return f"{self.BACKEND_URL}/api"
@property
def auth_url(self) -> str:
return f"{self.api_base_url}/auth"
@property
def applications_url(self) -> str:
return f"{self.api_base_url}/applications"
@property
def jobs_url(self) -> str:
return f"{self.api_base_url}/jobs"
@property
def documents_url(self) -> str:
return f"{self.api_base_url}/documents"

View File

View File

@@ -0,0 +1,121 @@
from dash import html, dcc
import dash_mantine_components as dmc
from dash_iconify import DashIconify
def create_layout():
return dmc.MantineProvider(
theme={
"fontFamily": "'Inter', sans-serif",
"primaryColor": "blue",
"components": {
"Button": {"styles": {"root": {"fontWeight": 400}}},
"Alert": {"styles": {"title": {"fontWeight": 500}}},
"AvatarGroup": {"styles": {"truncated": {"fontWeight": 500}}},
},
},
children=[
dcc.Store(id="auth-store", storage_type="session"),
dcc.Store(id="user-store", storage_type="session"),
dcc.Location(id="url", refresh=False),
html.Div(
id="main-content",
children=[
create_header(),
html.Div(id="page-content")
]
)
]
)
def create_header():
return dmc.Header(
height=70,
fixed=True,
children=[
dmc.Container(
size="xl",
children=[
dmc.Group(
position="apart",
align="center",
style={"height": 70},
children=[
dmc.Group(
align="center",
spacing="xs",
children=[
DashIconify(
icon="tabler:briefcase",
width=32,
color="#228BE6"
),
dmc.Text(
"Job Forge",
size="xl",
weight=700,
color="blue"
)
]
),
dmc.Group(
id="header-actions",
spacing="md",
children=[
dmc.Button(
"Login",
id="login-btn",
variant="outline",
leftIcon=DashIconify(icon="tabler:login")
)
]
)
]
)
]
)
]
)
def create_navigation():
return dmc.Navbar(
width={"base": 300},
children=[
dmc.ScrollArea(
style={"height": "calc(100vh - 70px)"},
children=[
dmc.NavLink(
label="Dashboard",
icon=DashIconify(icon="tabler:dashboard"),
href="/",
id="nav-dashboard"
),
dmc.NavLink(
label="Job Search",
icon=DashIconify(icon="tabler:search"),
href="/jobs",
id="nav-jobs"
),
dmc.NavLink(
label="Applications",
icon=DashIconify(icon="tabler:briefcase"),
href="/applications",
id="nav-applications"
),
dmc.NavLink(
label="Documents",
icon=DashIconify(icon="tabler:file-text"),
href="/documents",
id="nav-documents"
),
dmc.NavLink(
label="Profile",
icon=DashIconify(icon="tabler:user"),
href="/profile",
id="nav-profile"
)
]
)
]
)

36
src/frontend/main.py Normal file
View File

@@ -0,0 +1,36 @@
import dash
from dash import html, dcc
import dash_mantine_components as dmc
from dash_iconify import DashIconify
import os
from .config import Config
from .layouts.layout import create_layout
from .callbacks import register_callbacks
# Initialize config
config = Config()
# Initialize Dash app
app = dash.Dash(
__name__,
external_stylesheets=[
"https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
],
suppress_callback_exceptions=True,
title="Job Forge - AI-Powered Job Application Assistant"
)
# Set up the layout
app.layout = create_layout()
# Register callbacks
register_callbacks(app, config)
if __name__ == "__main__":
app.run_server(
host="0.0.0.0",
port=8501,
debug=config.DEBUG,
dev_tools_hot_reload=config.DEBUG
)

View File

164
src/frontend/pages/auth.py Normal file
View File

@@ -0,0 +1,164 @@
from dash import html, dcc
import dash_mantine_components as dmc
from dash_iconify import DashIconify
def create_login_page():
return dmc.Container(
size="xs",
style={"marginTop": "10vh"},
children=[
dmc.Paper(
shadow="lg",
radius="md",
p="xl",
children=[
dmc.Group(
position="center",
mb="xl",
children=[
DashIconify(
icon="tabler:briefcase",
width=40,
color="#228BE6"
),
dmc.Title("Job Forge", order=2, color="blue")
]
),
dmc.Tabs(
id="auth-tabs",
value="login",
children=[
dmc.TabsList(
grow=True,
children=[
dmc.Tab("Login", value="login"),
dmc.Tab("Register", value="register")
]
),
dmc.TabsPanel(
value="login",
children=[
html.Form(
id="login-form",
children=[
dmc.TextInput(
id="login-email",
label="Email",
placeholder="your.email@example.com",
icon=DashIconify(icon="tabler:mail"),
required=True,
mb="md"
),
dmc.PasswordInput(
id="login-password",
label="Password",
placeholder="Your password",
icon=DashIconify(icon="tabler:lock"),
required=True,
mb="xl"
),
dmc.Button(
"Login",
id="login-submit",
fullWidth=True,
leftIcon=DashIconify(icon="tabler:login")
)
]
)
]
),
dmc.TabsPanel(
value="register",
children=[
html.Form(
id="register-form",
children=[
dmc.Group(
grow=True,
children=[
dmc.TextInput(
id="register-first-name",
label="First Name",
placeholder="John",
required=True,
style={"flex": 1}
),
dmc.TextInput(
id="register-last-name",
label="Last Name",
placeholder="Doe",
required=True,
style={"flex": 1}
)
]
),
dmc.TextInput(
id="register-email",
label="Email",
placeholder="your.email@example.com",
icon=DashIconify(icon="tabler:mail"),
required=True,
mt="md"
),
dmc.TextInput(
id="register-phone",
label="Phone (Optional)",
placeholder="+1 (555) 123-4567",
icon=DashIconify(icon="tabler:phone"),
mt="md"
),
dmc.PasswordInput(
id="register-password",
label="Password",
placeholder="Your password",
icon=DashIconify(icon="tabler:lock"),
required=True,
mt="md"
),
dmc.PasswordInput(
id="register-password-confirm",
label="Confirm Password",
placeholder="Confirm your password",
icon=DashIconify(icon="tabler:lock"),
required=True,
mt="md",
mb="xl"
),
dmc.Button(
"Register",
id="register-submit",
fullWidth=True,
leftIcon=DashIconify(icon="tabler:user-plus")
)
]
)
]
)
]
),
html.Div(id="auth-alerts", style={"marginTop": "1rem"})
]
)
]
)
def create_logout_confirmation():
return dmc.Modal(
title="Confirm Logout",
id="logout-modal",
children=[
dmc.Text("Are you sure you want to logout?"),
dmc.Group(
position="right",
mt="md",
children=[
dmc.Button("Cancel", id="logout-cancel", variant="outline"),
dmc.Button("Logout", id="logout-confirm", color="red")
]
)
]
)

185
src/frontend/pages/home.py Normal file
View File

@@ -0,0 +1,185 @@
from dash import html
import dash_mantine_components as dmc
from dash_iconify import DashIconify
def create_home_page():
return dmc.Container(
size="xl",
pt="md",
children=[
dmc.Title("Welcome to Job Forge", order=1, mb="lg"),
dmc.Grid(
children=[
dmc.Col(
dmc.Card(
children=[
dmc.Group(
children=[
DashIconify(
icon="tabler:search",
width=40,
color="#228BE6"
),
dmc.Stack(
spacing=5,
children=[
dmc.Text("Find Jobs", weight=600, size="lg"),
dmc.Text(
"Search and discover job opportunities",
size="sm",
color="dimmed"
)
]
)
]
),
dmc.Button(
"Search Jobs",
fullWidth=True,
mt="md",
id="home-search-jobs-btn"
)
],
withBorder=True,
shadow="sm",
radius="md",
p="lg"
),
span=6
),
dmc.Col(
dmc.Card(
children=[
dmc.Group(
children=[
DashIconify(
icon="tabler:briefcase",
width=40,
color="#40C057"
),
dmc.Stack(
spacing=5,
children=[
dmc.Text("Track Applications", weight=600, size="lg"),
dmc.Text(
"Manage your job applications",
size="sm",
color="dimmed"
)
]
)
]
),
dmc.Button(
"View Applications",
fullWidth=True,
mt="md",
color="green",
id="home-applications-btn"
)
],
withBorder=True,
shadow="sm",
radius="md",
p="lg"
),
span=6
),
dmc.Col(
dmc.Card(
children=[
dmc.Group(
children=[
DashIconify(
icon="tabler:file-text",
width=40,
color="#FD7E14"
),
dmc.Stack(
spacing=5,
children=[
dmc.Text("AI Documents", weight=600, size="lg"),
dmc.Text(
"Generate resumes and cover letters",
size="sm",
color="dimmed"
)
]
)
]
),
dmc.Button(
"Create Documents",
fullWidth=True,
mt="md",
color="orange",
id="home-documents-btn"
)
],
withBorder=True,
shadow="sm",
radius="md",
p="lg"
),
span=6
),
dmc.Col(
dmc.Card(
children=[
dmc.Group(
children=[
DashIconify(
icon="tabler:user",
width=40,
color="#BE4BDB"
),
dmc.Stack(
spacing=5,
children=[
dmc.Text("Profile", weight=600, size="lg"),
dmc.Text(
"Manage your profile and settings",
size="sm",
color="dimmed"
)
]
)
]
),
dmc.Button(
"Edit Profile",
fullWidth=True,
mt="md",
color="violet",
id="home-profile-btn"
)
],
withBorder=True,
shadow="sm",
radius="md",
p="lg"
),
span=6
)
],
gutter="md"
),
dmc.Divider(my="xl"),
dmc.Title("Recent Activity", order=2, mb="md"),
dmc.Card(
children=[
dmc.Text("No recent activity yet. Start by searching for jobs or uploading your resume!")
],
withBorder=True,
shadow="sm",
radius="md",
p="lg"
)
]
)