FeedHireUI / app_old.py
Rafii's picture
structured
f40d9d3
import gradio as gr
import requests
import json
import os
from dotenv import load_dotenv
import time
# Load environment variables from .env file (for local development)
load_dotenv()
# Get environment variables and strip whitespace
organisationNumber = os.getenv("organisationNumber", "").strip()
token = os.getenv("token", "").strip()
postApiUrl = os.getenv("postApiUrl", "").strip()
# RESTORED: ServiceTrigger environment variable
ServiceTrigger = os.getenv("ServiceTrigger", "").strip()
# Validate environment variables on startup
missing_vars = []
if not organisationNumber:
missing_vars.append("organisationNumber")
if not token:
missing_vars.append("token")
if not postApiUrl:
missing_vars.append("postApiUrl")
# Added validation for ServiceTrigger
if not ServiceTrigger:
missing_vars.append("ServiceTrigger")
if missing_vars:
print(f"⚠️ WARNING: Missing environment variables: {', '.join(missing_vars)}")
print("Please check your Hugging Face Spaces Secrets configuration")
def notify_discord(name, job_post, post_url):
# This URL should be loaded from your environment variables (Discord Webhook URL)
discord_webhook_url = os.getenv("DISCORD_WEBHOOK_URL")
if not discord_webhook_url:
print("⚠️ WARNING: DISCORD_WEBHOOK_URL is not set. Skipping Discord notification.")
return
discord_message = {
"content": "✨ New Job Posted! ✨",
"embeds": [
{
"title": f"Job Post by {name}",
"description": job_post[:2048] + ('...' if len(job_post) > 2048 else ''),
"url": post_url,
"color": 16744704 # Orange color code
}
]
}
response = requests.post(discord_webhook_url, json=discord_message)
name = "abcd"
job_post = "efgh"
post_url = "https://www.linkedin.com/feed/update/urn:li:share:1234567890123456789/"
discord_message = {
"content": "✨ New Job Posted! ✨",
"embeds": [
{
"title": f"Job Post by {name}",
"description": job_post[:2048] + ('...' if len(job_post) > 2048 else ''),
"url": post_url,
"color": 16744704 # Orange color code
}
]
}
# notify_discord(name, job_post, post_url)
def check_website_status():
"""Check if the FeedHire website is accessible"""
try:
response = requests.get("https://feedhire.me/", timeout=10)
if response.status_code == 200:
return "🟢", "Online", "#10b981"
else:
return "🟡", f"Status {response.status_code}", "#f59e0b"
except requests.exceptions.RequestException:
return "🔴", "Offline", "#ef4444"
def check_api_status():
"""Check if the LinkedIn API is accessible and token is valid"""
if not token or not organisationNumber:
return "🔴", "No Credentials", "#ef4444"
try:
headers = {
"Authorization": f"Bearer {token}",
"LinkedIn-Version": "202510",
"X-Restli-Protocol-Version": "2.0.0"
}
response = requests.get(
f"https://api.linkedin.com/rest/organizations/{organisationNumber}",
headers=headers,
timeout=10
)
if response.status_code == 200:
return "🟢", "Connected", "#10b981"
elif response.status_code == 401:
return "🔴", "Auth Failed", "#ef4444"
elif response.status_code == 403:
return "🟡", "Access Denied", "#f59e0b"
else:
return "🟡", f"Error {response.status_code}", "#f59e0b"
except requests.exceptions.RequestException:
return "🔴", "Connection Failed", "#ef4444"
def get_status_display():
"""Get formatted status display for both services"""
web_icon, web_status, web_color = check_website_status()
api_icon, api_status, api_color = check_api_status()
html = f"""
<div class="status-container">
<div class="status-item">
<span class="status-icon">{web_icon}</span>
<div>
<div class="status-label">Website</div>
<div class="status-value" style="color: {web_color};">{web_status}</div>
</div>
</div>
<div class="status-divider"></div>
<div class="status-item">
<span class="status-icon">{api_icon}</span>
<div>
<div class="status-label">API</div>
<div class="status-value" style="color: {api_color};">{api_status}</div>
</div>
</div>
</div>
"""
return html
def post_to_linkedin(name, job_post):
"""Post to LinkedIn via their API with improved UX feedback"""
yield "Verifying inputs...", gr.update(value="", visible=False)
if not organisationNumber or not token or not postApiUrl:
yield f"❌ Configuration Error: Missing LinkedIn API variables. Please check Spaces Secrets.", gr.update(visible=False)
return
# Check for ServiceTrigger (if critical for the process)
if not ServiceTrigger:
yield f"❌ Configuration Error: Missing ServiceTrigger variable. FeedHire update will be manual.", gr.update(visible=False)
# Note: We continue execution here as the LinkedIn post is the primary goal
if not name.strip() or not job_post.strip():
yield "❌ Please fill in all fields (Name and Job Post).", gr.update(visible=False)
return
headers_dict = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
'X-Restli-Protocol-Version': '2.0.0',
'LinkedIn-Version': '202510'
}
data = {
"author": f"urn:li:organization:{organisationNumber}",
"commentary": f"{name}\n\n{job_post}",
"visibility": "PUBLIC",
"lifecycleState": "PUBLISHED",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"isReshareDisabledByAuthor": False
}
try:
yield "🚀 Posting to LinkedIn...", gr.update(visible=False)
response = requests.post(
postApiUrl,
headers=headers_dict,
json=data,
timeout=30
)
if response.status_code == 201:
for i in range(10, 0, -1):
yield f"✅ Post successful! Waiting for LinkedIn API update... ({i} seconds remaining)", gr.update(visible=False)
time.sleep(1)
# RESTORED: Service Trigger Logic
if ServiceTrigger:
try:
trigger_resp = requests.post(f"{ServiceTrigger}", timeout=10)
yield "🔄 FeedHire website triggered, waiting for job listing update...", gr.update(visible=False)
if trigger_resp.status_code == 200:
yield "✅ Backend triggered successfully! FeedHire will update the listing shortly.", gr.update(visible=False)
else:
yield f"⚠️ Backend trigger failed (Status: {trigger_resp.status_code}). FeedHire update may be delayed.", gr.update(visible=False)
except Exception as trigger_error:
print(f"Error triggering async fetch: {trigger_error}")
yield f"⚠️ Error triggering FeedHire backend: {trigger_error}", gr.update(visible=False)
yield "✅ Post live! 🔍 Retrieving your post link...", gr.update(visible=False)
try:
post_api_url = "https://api.linkedin.com/rest/posts"
headers = {
"Authorization": f"Bearer {token}",
"LinkedIn-Version": "202510",
"X-Restli-Protocol-Version": "2.0.0"
}
params = {
"q": "author",
"author": f"urn:li:organization:{organisationNumber}",
"count": 1
}
fetch_response = requests.get(post_api_url, headers=headers, params=params, timeout=30)
if fetch_response.status_code == 200:
fetch_data = fetch_response.json()
if fetch_data.get("elements") and len(fetch_data["elements"]) > 0:
post_id_urn = fetch_data["elements"][0]["id"]
if "share" in post_id_urn:
post_id = post_id_urn.split(":")[-1]
post_url = f"https://www.linkedin.com/feed/update/urn:li:share:{post_id}/"
elif "activity" in post_id_urn:
post_id = post_id_urn.split(":")[-1]
post_url = f"https://www.linkedin.com/feed/update/urn:li:activity:{post_id}/"
else:
raise Exception("Unknown post URN format")
print(f"Post URL: {post_url}")
notify_discord(name, job_post, post_url)
yield "✅ Post published successfully!", gr.update(value=f"### 🎉 [Click here to view your post on LinkedIn →]({post_url})", visible=True)
return
notify_discord(name, job_post, "https://www.linkedin.com/company/109539782/posts/")
yield "✅ Post published! (Could not auto-retrieve post link. Please check the LinkedIn page.)", gr.update(visible=False)
except Exception as fetch_error:
print(f"Error fetching post: {fetch_error}")
notify_discord(name, job_post, "https://www.linkedin.com/company/109539782/posts/")
yield f"✅ Post published! (Error retrieving link: {fetch_error})", gr.update(visible=False)
else:
try:
error_data = response.json()
error_message = error_data.get('message', response.text)
except requests.exceptions.JSONDecodeError:
error_message = response.text
error_msg = f"❌ Error {response.status_code}: {error_message}"
print(error_msg)
yield error_msg, gr.update(visible=False)
except requests.exceptions.Timeout:
yield "❌ Error: Request timed out. Please try again.", gr.update(visible=False)
except requests.exceptions.RequestException as e:
yield f"❌ Network Error: {str(e)}", gr.update(visible=False)
except Exception as e:
yield f"❌ Error: {str(e)}", gr.update(visible=False)
# --- Ultra-Premium Animated Gradio Interface with Orange Theme ---
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap');
* {
font-family: 'Inter', sans-serif !important;
}
.gradio-container {
max-width: 1400px !important;
margin: auto !important;
}
.transparent-box {
background: transparent !important;
border: none !important; /* Remove border */
box-shadow: none !important; /* Remove shadow */
padding: 0 !important; /* Optional: remove extra padding */
}
/* Transparent container + all children */
.transparent-box,
.transparent-box > div,
.transparent-box * {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
/* Hero Section Animations (Unchanged) */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
/* Updated Glow to Orange */
@keyframes glow {
0%, 100% {
box-shadow: 0 0 20px rgba(255, 126, 0, 0.5); /* Vibrant Orange glow */
}
50% {
box-shadow: 0 0 40px rgba(255, 126, 0, 0.8); /* Vibrant Orange glow */
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-50px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(50px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Hero Section - Updated Gradient & Shadow */
/* Make all inner Gradio containers fully transparent */
#hero-section,
#hero-section > div,
#hero-section > div > div {
background: transparent !important;
box-shadow: none !important;
padding: 0 !important;
margin: 0 !important;
}
.hero-column > div {
background: transparent !important;
padding: 0 !important;
margin: 0 !important;
}
.logo-container {
animation: scaleIn 1s ease-out 0.3s both;
display: flex;
justify-content: center;
align-items: center;
background: rgba(255,255,255,0.15);
border-radius: 20px;
padding: 15px;
backdrop-filter: blur(10px);
border: 2px solid rgba(255,255,255,0.3);
transition: all 0.4s ease;
}
.logo-container:hover {
transform: scale(1.05) rotate(2deg);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.hero-title {
animation: fadeInUp 1s ease-out 0.5s both;
}
.hero-subtitle {
animation: fadeInUp 1s ease-out 0.7s both;
}
.status-container {
display: flex;
gap: 15px;
justify-content: flex-end;
align-items: center;
padding: 12px 20px;
background: rgba(15,23,42,0.7);
border-radius: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.15);
animation: fadeInRight 1s ease-out 0.9s both;
transition: all 0.3s ease;
}
.status-container:hover {
background: rgba(15,23,42,0.85);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.3);
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
}
.status-icon {
font-size: 20px;
animation: pulse 2s ease-in-out infinite;
}
.status-label {
font-size: 10px;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
}
.status-value {
font-size: 13px;
font-weight: 700;
}
.status-divider {
width: 1px;
height: 30px;
background: rgba(255,255,255,0.2);
}
/* Banner Animation */
.info-banner {
animation: fadeInUp 1s ease-out 1.1s both;
}
/* Form Section Animations */
.form-section {
animation: slideInLeft 0.8s ease-out 0.3s both;
}
#job-form {
border: 2px solid #FF7E00;
border-radius: 16px;
padding: 25px;
background: rgba(255,255,255,0.05);
box-shadow: 0 10px 30px rgba(255,126,0,0.2);
}
.form-section {
animation: slideInLeft 0.8s ease-out 0.3s both;
border: 2px solid #FF7E00; /* Orange border */
border-radius: 16px;
padding: 25px;
background: rgba(255,255,255,0.05); /* optional translucent bg */
box-shadow: 0 10px 30px rgba(255, 126, 0, 0.2); /* subtle orange shadow */
}
.benefits-section {
animation: slideInRight 0.8s ease-out 0.3s both;
}
/* Input Focus Effects - Updated Border & Shadow */
textarea:focus, input:focus {
outline: none !important;
border: 2px solid #FF7E00 !important; /* Vibrant Orange border */
box-shadow: 0 0 0 3px rgba(255, 126, 0, 0.1), 0 0 20px rgba(255, 126, 0, 0.2) !important; /* Orange focus glow */
transition: all 0.3s ease !important;
}
/* Button Animations - Updated Gradient, Hover, and Shadow */
button[variant="primary"] {
background: linear-gradient(135deg, #FF7E00 0%, #FF9A3C 100%) !important; /* Orange-Gold Gradient */
border: none !important;
font-weight: 700 !important;
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55) !important;
position: relative !important;
overflow: hidden !important;
}
button[variant="primary"]::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255,255,255,0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
button[variant="primary"]:hover::before {
width: 300px;
height: 300px;
}
button[variant="primary"]:hover {
transform: translateY(-4px) scale(1.02) !important;
box-shadow: 0 15px 40px rgba(255, 126, 0, 0.5) !important; /* Orange shadow on hover */
}
button[variant="primary"]:active {
transform: translateY(-2px) scale(0.98) !important;
}
.refresh-btn {
font-size: 12px !important;
padding: 6px 14px !important;
background: transparent !important;
/* Updated to the requested orange border */
border: 1px solid #FF7E00 !important;
color: white !important;
border-radius: 8px !important;
transition: all 0.3s ease !important;
font-weight: 600 !important;
}
.refresh-btn:hover {
background: linear-gradient(135deg, rgba(255, 126, 0, 0.3), rgba(255, 154, 60, 0.4)) !important;
box-shadow: 0 0 15px rgba(255, 126, 0, 0.8), 0 0 25px rgba(255, 154, 60, 0.6);
transition: all 0.5s ease;
color: #FF7E00 !important; /* ensures icon/text matches theme */
}
/* Card Animations - Updated Border Color */
.benefit-card {
background: rgba(255,255,255,0.05);
padding: 20px;
border-radius: 12px;
border-left: 4px solid #FF7E00; /* Vibrant Orange border */
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
opacity: 0;
transform: translateY(20px);
}
.benefit-card.animate {
animation: fadeInUp 0.6s ease-out forwards;
}
.benefit-card:nth-child(1) { animation-delay: 0.1s; }
.benefit-card:nth-child(2) { animation-delay: 0.2s; }
.benefit-card:nth-child(3) { animation-delay: 0.3s; }
.benefit-card:nth-child(4) { animation-delay: 0.4s; }
.benefit-card:hover {
background: rgba(255,255,255,0.1);
transform: translateX(10px) translateY(-5px);
box-shadow: 0 10px 30px rgba(255, 126, 0, 0.3); /* Orange shadow on hover */
border-left-width: 6px;
}
/* How It Works Cards */
.how-it-works-card {
background: rgba(255,255,255,0.15);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 30px;
border: 2px solid rgba(255,255,255,0.2);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
opacity: 0;
transform: scale(0.8);
}
.how-it-works-card.animate {
animation: scaleIn 0.6s ease-out forwards;
}
.how-it-works-card:nth-child(1) { animation-delay: 0.2s; }
.how-it-works-card:nth-child(2) { animation-delay: 0.4s; }
.how-it-works-card:nth-child(3) { animation-delay: 0.6s; }
.how-it-works-card:hover {
transform: translateY(-10px) scale(1.05);
box-shadow: 0 20px 50px rgba(0,0,0,0.3);
border-color: rgba(255,255,255,0.4);
}
.how-it-works-card .emoji {
display: inline-block;
transition: transform 0.3s ease;
}
.how-it-works-card:hover .emoji {
transform: scale(1.2) rotate(10deg);
}
/* Success Link Animation - Updated Gradient & Glow */
#success-link-box {
text-align: center;
padding: 1.5rem;
background: linear-gradient(135deg, #FF7E00 0%, #FF9A3C 100%); /* Orange-Gold Gradient */
border-radius: 12px;
box-shadow: 0 10px 30px rgba(255, 126, 0, 0.3); /* Orange shadow */
animation: slideInUp 0.5s ease-out, glow 2s ease-in-out infinite;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#success-link-box a {
font-size: 1.2rem;
font-weight: 700;
color: white !important;
text-decoration: none;
transition: all 0.3s ease;
}
#success-link-box a:hover {
text-decoration: underline;
transform: scale(1.05);
display: inline-block;
}
/* Benefits Box */
#benefits-box {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
padding: 35px;
border-radius: 20px;
height: 100%;
display: flex;
flex-direction: column;
gap: 25px;
box-shadow: 0 25px 70px rgba(0,0,0,0.4);
border: 1px solid rgba(255,255,255,0.1);
position: relative;
overflow: hidden;
}
/* Benefits Box ::before - Updated Radial Gradient Color */
#benefits-box::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at top right, rgba(255, 126, 0, 0.1) 0%, transparent 70%); /* Orange tint */
pointer-events: none;
}
/* Responsive adjustments */
@media (max-width: 768px) {
#hero-section {
padding: 2rem 1.5rem;
}
.status-container {
flex-direction: column;
gap: 10px;
}
.status-divider {
width: 100%;
height: 1px;
}
}
"""
with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange", secondary_hue="yellow"), css=custom_css) as demo:
# Hero Section with Animated Logo and Status
with gr.Group(elem_id="hero-section"):
with gr.Row():
with gr.Column(scale=20, elem_classes="hero-column"):
gr.HTML("""
<div style="
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
width: 100%;
height: 100%;
color: white;
position: relative;
z-index: 1;
">
<h1 class="hero-title" style="
font-size: 4rem;
background: linear-gradient(135deg, #FF7E00, #FF9A3C);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 900;
">FeedHire Job Poster</h1>
<p class="hero-subtitle" style="
font-size: 1.5rem;
background: linear-gradient(135deg, #FF7E00, #FF9A3C);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 500;
">Post jobs instantly to LinkedIn & reach thousands of candidates 🚀</p>
</div>
""")
with gr.Column(scale=4, min_width=280):
status_display = gr.HTML(get_status_display())
refresh_status_btn = gr.Button("↺ Refresh", size="sm", elem_classes="refresh-btn")
# Visibility Info Banner - Updated Background and Border
gr.HTML("""
<div class="info-banner" style="background: rgba(255, 126, 0, 0.1); padding: 18px; border-radius: 14px; text-align: center; margin: 25px 0; border: 2px solid rgba(255, 126, 0, 0.3); transition: all 0.3s ease;">
<p style="margin: 0; font-size: 15px; color: #475569; font-weight: 500;">
🌐 Your job post will be live on
<a href="https://www.linkedin.com/company/109539782/" target="_blank" style="color:#FF7E00; text-decoration:none; font-weight:700; transition: all 0.3s ease;">LinkedIn</a>
and visible on
<a href="https://feedhire.me/" target="_blank" style="color:#FF7E00; text-decoration:none; font-weight:700; transition: all 0.3s ease;">FeedHire.me</a>
within minutes ⚡
</p>
</div>
""")
if missing_vars:
gr.Warning(f"⚠️ Missing configuration: {', '.join(missing_vars)}")
# Main Content: Two-Column Layout
with gr.Row(equal_height=True):
# LEFT COLUMN - Job Posting Form
with gr.Column(scale=1, elem_classes="form-section transparent-box, ", elem_id="job-form"):
gr.HTML("<h2 style='margin-bottom: 25px; font-weight: 800; text-align: Center; color: white; font-size: 1.8rem;'>📝 Create Your Job Post</h2>")
name_input = gr.Textbox(
label="👤 Your Name",
placeholder="e.g., Arif Raafi",
lines=1
)
job_post_input = gr.Textbox(
label="💼 Job Post Content",
placeholder="✨ Craft your perfect job post here...\n\n🎯 Role: \n📋 Responsibilities: \n📍 Location: \n💰 Salary Range: \n📧 How to Apply: ",
lines=12
)
submit_btn = gr.Button("🚀 Publish to LinkedIn", variant="primary", size="lg")
output = gr.Textbox(label="📊 Status", lines=2, interactive=False)
success_link = gr.Markdown(
"",
visible=False,
elem_id="success-link-box"
)
# RIGHT COLUMN - Benefits & Marketing
with gr.Column(scale=1, elem_id="job-form", elem_classes="benefits-section transparent-box"):
gr.HTML("""
<h2 style='color: white; text-align: center; margin-bottom: 30px; font-size: 1.8rem; font-weight: 800; position: relative; z-index: 0;'>
🌟 Why Choose FeedHire?
</h2>
""")
# The image component was missing in the previous step's final output, but present in the context. Restoring it.
gr.Image(
value="analytics_map.png",
show_label=False,
show_download_button=False,
container=False,
elem_classes="benefits-image",
)
gr.HTML("""
<style>
.benefit-card {
display: flex; /* horizontal layout */
align-items: center;
padding: 12px 15px;
border-radius: 14px;
background: rgba(255, 126, 0, 0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 126, 0, 0.4);
margin: 8px auto;
max-width: 90%;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
font-size: 14px;
}
.benefit-card:hover {
transform: scale(1.03);
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
}
.benefit-card .icon {
font-size: 24px;
margin-right: 12px;
}
.benefit-card .text {
color: #fff;
}
.benefit-card .text .title {
font-weight: 700;
margin-right: 6px;
}
.benefit-card .text .desc {
color: rgba(255,255,255,0.85);
}
</style>
<div class="benefit-card animate">
<div class="icon">🌍</div>
<div class="text"><span class="title">Massive Reach</span> - <span class="desc">Tap into LinkedIn's network and FeedHire's global ecosystem</span></div>
</div>
<div class="benefit-card animate">
<div class="icon">⚡</div>
<div class="text"><span class="title">Lightning Fast</span> - <span class="desc">Post once, go live instantly on multiple platforms</span></div>
</div>
<div class="benefit-card animate">
<div class="icon">💯</div>
<div class="text"><span class="title">100% Free</span> - <span class="desc">No hidden fees, no subscriptions—just pure visibility</span></div>
</div>
<div class="benefit-card animate">
<div class="icon">🎯</div>
<div class="text"><span class="title">Quality Candidates</span> - <span class="desc">Attract serious applicants</span></div>
</div>
""")
# How It Works Section - Updated Gradient
gr.HTML("""
<div style="background: linear-gradient(135deg, #FF7E00 0%, #FF9A3C 100%); padding: 55px 45px; border-radius: 24px; margin: 45px 0; box-shadow: 0 25px 70px rgba(255, 126, 0, 0.4);">
<h2 style="color: white; text-align: center; margin-bottom: 45px; font-size: 2.2rem; font-weight: 900; text-shadow: 2px 2px 4px rgba(0,0,0,0.2);">
✨ How FeedHire Works
</h2>
<div style="display: flex; gap: 30px; flex-wrap: wrap; justify-content: center;">
<div class="how-it-works-card animate" style="flex: 1; min-width: 280px; text-align: center;">
<div class="emoji" style="font-size: 56px; margin-bottom: 18px;">📝</div>
<h3 style="color: white; margin-bottom: 14px; font-size: 22px; font-weight: 800;">Create</h3>
<p style="color: rgba(255,255,255,0.95); line-height: 1.8; font-size: 15px;">
Fill out the form with your name and job details—make it compelling!
</p>
</div>
<div class="how-it-works-card animate" style="flex: 1; min-width: 280px; text-align: center;">
<div class="emoji" style="font-size: 56px; margin-bottom: 18px;">🚀</div>
<h3 style="color: white; margin-bottom: 14px; font-size: 22px; font-weight: 800;">Publish</h3>
<p style="color: rgba(255,255,255,0.95); line-height: 1.8; font-size: 15px;">
Hit publish and watch it go live on LinkedIn instantly via API
</p>
</div>
<div class="how-it-works-card animate" style="flex: 1; min-width: 280px; text-align: center;">
<div class="emoji" style="font-size: 56px; margin-bottom: 18px;">⚡</div>
<h3 style="color: white; margin-bottom: 14px; font-size: 22px; font-weight: 800;">Go Live</h3>
<p style="color: rgba(255,255,255,0.95); line-height: 1.8; font-size: 15px;">
Our AI refines your post before publishing it live on FeedHire.me—start attracting applicants immediately!
</p>
</div>
</div>
</div>
""")
# Footer Disclaimer
gr.HTML("""
<div style="background: linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(220, 38, 38, 0.1) 100%); border-left: 5px solid #ef4444; padding: 25px; border-radius: 12px; margin-top: 35px; transition: all 0.3s ease; animation: fadeInUp 1s ease-out 1.3s both;">
<p style="margin: 0; font-size: 14px; color: #475569; line-height: 1.7;">
<strong style="color: #ef4444; font-size: 15px;">⚠️ Important:</strong> FeedHire is not responsible for job post content.
Ensure all submissions are accurate and lawful. Fake or misleading posts will be removed.
</p>
</div>
""")
# Click Actions (Unchanged)
submit_btn.click(
fn=post_to_linkedin,
inputs=[name_input, job_post_input],
outputs=[output, success_link],
show_progress="full"
)
def refresh_status():
return get_status_display()
refresh_status_btn.click(
fn=refresh_status,
outputs=[status_display]
)
demo.load(
fn=refresh_status,
outputs=[status_display]
)
if __name__ == "__main__":
demo.launch(allowed_paths=["."])