supporiting documents uploaded. One step away from starting development.
This commit is contained in:
0
src/frontend/__init__.py
Normal file
0
src/frontend/__init__.py
Normal file
235
src/frontend/callbacks.py
Normal file
235
src/frontend/callbacks.py
Normal 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
27
src/frontend/config.py
Normal 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"
|
||||
0
src/frontend/layouts/__init__.py
Normal file
0
src/frontend/layouts/__init__.py
Normal file
121
src/frontend/layouts/layout.py
Normal file
121
src/frontend/layouts/layout.py
Normal 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
36
src/frontend/main.py
Normal 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
|
||||
)
|
||||
0
src/frontend/pages/__init__.py
Normal file
0
src/frontend/pages/__init__.py
Normal file
164
src/frontend/pages/auth.py
Normal file
164
src/frontend/pages/auth.py
Normal 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
185
src/frontend/pages/home.py
Normal 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"
|
||||
)
|
||||
]
|
||||
)
|
||||
Reference in New Issue
Block a user