Rafii commited on
Commit
6ecf6ae
·
1 Parent(s): ef56501

Add analytics map and logo images; enhance LinkedIn posting function for better user feedback and error handling

Browse files
Files changed (1) hide show
  1. app.py +245 -40
app.py CHANGED
@@ -3,6 +3,7 @@ import requests
3
  import json
4
  import os
5
  from dotenv import load_dotenv
 
6
 
7
  # Load environment variables from .env file (for local development)
8
  load_dotenv()
@@ -25,21 +26,29 @@ if missing_vars:
25
  print(f"⚠️ WARNING: Missing environment variables: {', '.join(missing_vars)}")
26
  print("Please check your Hugging Face Spaces Secrets configuration")
27
 
 
28
  def post_to_linkedin(name, job_post):
29
- """Post to LinkedIn via their API"""
 
 
 
30
 
31
- # Check for missing environment variables
32
  if not organisationNumber or not token or not postApiUrl:
33
- return f"❌ Configuration Error: Missing environment variables. Please check Spaces Secrets.", gr.update(visible=False)
 
34
 
 
35
  if not name.strip() or not job_post.strip():
36
- return "❌ Please fill in both fields", gr.update(visible=False)
 
37
 
 
38
  headers_dict = {
39
  'Authorization': f'Bearer {token}',
40
  'Content-Type': 'application/json',
41
  'X-Restli-Protocol-Version': '2.0.0',
42
- 'LinkedIn-Version': '202510'
43
  }
44
 
45
  data = {
@@ -55,68 +64,264 @@ def post_to_linkedin(name, job_post):
55
  "isReshareDisabledByAuthor": False
56
  }
57
 
 
58
  try:
59
- print(f"Posting to: {postApiUrl}") # Debug log
 
60
  response = requests.post(
61
  postApiUrl,
62
  headers=headers_dict,
63
  json=data,
64
- timeout=30 # Add timeout
65
  )
66
 
67
- print(f"Response status: {response.status_code}") # Debug log
68
-
69
  if response.status_code == 201:
70
- return "✅ Post published successfully to LinkedIn!", gr.update(visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  else:
72
- error_msg = f"❌ Error {response.status_code}: {response.text}"
73
- print(error_msg) # Debug log
74
- return error_msg, gr.update(visible=False)
 
 
 
 
 
 
 
75
 
76
  except requests.exceptions.Timeout:
77
- return "❌ Error: Request timed out. Please try again.", gr.update(visible=False)
78
  except requests.exceptions.RequestException as e:
79
- return f"❌ Network Error: {str(e)}", gr.update(visible=False)
80
  except Exception as e:
81
- return f"❌ Error: {str(e)}", gr.update(visible=False)
 
82
 
83
- # Create Gradio interface
84
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
85
- gr.Markdown("# 🔗 FeedHire LinkedIn Job Poster")
86
- gr.Markdown("Post job listings directly to your LinkedIn organization page")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  # Show configuration status
89
  if missing_vars:
90
- gr.Markdown(f"### ⚠️ Configuration Error\nMissing Secrets: {', '.join(missing_vars)}")
 
 
91
 
92
- with gr.Row():
93
- with gr.Column():
 
 
 
 
 
 
94
  name_input = gr.Textbox(
95
- label="Name",
96
- placeholder="Enter your name",
97
  lines=1
98
  )
99
  job_post_input = gr.Textbox(
100
- label="Job Post",
101
- placeholder="Enter job post details",
102
  lines=10
103
  )
104
- submit_btn = gr.Button("Submit to LinkedIn", variant="primary")
105
-
106
- with gr.Row():
107
- output = gr.Textbox(label="Status", lines=2)
108
-
109
- with gr.Row():
110
- success_link = gr.Markdown(
111
- "### [📋 Check your posted jobs →](https://feedhire.me/)",
112
- visible=False
113
- )
114
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  submit_btn.click(
116
  fn=post_to_linkedin,
117
  inputs=[name_input, job_post_input],
118
- outputs=[output, success_link]
 
119
  )
120
 
121
  if __name__ == "__main__":
122
- demo.launch()
 
3
  import json
4
  import os
5
  from dotenv import load_dotenv
6
+ import time
7
 
8
  # Load environment variables from .env file (for local development)
9
  load_dotenv()
 
26
  print(f"⚠️ WARNING: Missing environment variables: {', '.join(missing_vars)}")
27
  print("Please check your Hugging Face Spaces Secrets configuration")
28
 
29
+
30
  def post_to_linkedin(name, job_post):
31
+ """Post to LinkedIn via their API with improved UX feedback"""
32
+
33
+ # 1. Clear previous success link on new submission
34
+ yield "Verifying inputs...", gr.update(value="", visible=False)
35
 
36
+ # 2. Check for missing environment variables
37
  if not organisationNumber or not token or not postApiUrl:
38
+ yield f"❌ Configuration Error: Missing environment variables. Please check Spaces Secrets.", gr.update(visible=False)
39
+ return
40
 
41
+ # 3. Validate user input
42
  if not name.strip() or not job_post.strip():
43
+ yield "❌ Please fill in all fields (Name and Job Post).", gr.update(visible=False)
44
+ return
45
 
46
+ # 4. Prepare headers and data
47
  headers_dict = {
48
  'Authorization': f'Bearer {token}',
49
  'Content-Type': 'application/json',
50
  'X-Restli-Protocol-Version': '2.0.0',
51
+ 'LinkedIn-Version': '202510' # Using a recent, static version
52
  }
53
 
54
  data = {
 
64
  "isReshareDisabledByAuthor": False
65
  }
66
 
67
+ # 5. Make the API POST request
68
  try:
69
+ yield "🚀 Posting to LinkedIn...", gr.update(visible=False)
70
+
71
  response = requests.post(
72
  postApiUrl,
73
  headers=headers_dict,
74
  json=data,
75
+ timeout=30
76
  )
77
 
78
+ # 6. Handle the POST response
 
79
  if response.status_code == 201:
80
+
81
+ # --- REINTRODUCED COUNTDOWN START ---
82
+ for i in range(10, 0, -1):
83
+ yield f"✅ Post successful! Waiting for LinkedIn API update... ({i} seconds remaining)", gr.update(visible=False)
84
+ time.sleep(1)
85
+ # --- REINTRODUCED COUNTDOWN END ---
86
+
87
+ yield "✅ Post live! 🔍 Retrieving your post link...", gr.update(visible=False)
88
+
89
+ try:
90
+ # 7. Attempt to FETCH the post URL
91
+ post_api_url = "https://api.linkedin.com/rest/posts"
92
+ headers = {
93
+ "Authorization": f"Bearer {token}",
94
+ "LinkedIn-Version": "202510",
95
+ "X-Restli-Protocol-Version": "2.0.0"
96
+ }
97
+ params = {
98
+ "q": "author",
99
+ "author": f"urn:li:organization:{organisationNumber}",
100
+ "count": 1 # Get the single most recent post
101
+ }
102
+
103
+ fetch_response = requests.get(post_api_url, headers=headers, params=params, timeout=30)
104
+
105
+ if fetch_response.status_code == 200:
106
+ fetch_data = fetch_response.json()
107
+ if fetch_data.get("elements") and len(fetch_data["elements"]) > 0:
108
+ post_id_urn = fetch_data["elements"][0]["id"] # e.g., "urn:li:share:12345"
109
+ # Handle both URN formats
110
+ if "share" in post_id_urn:
111
+ post_id = post_id_urn.split(":")[-1]
112
+ post_url = f"https://www.linkedin.com/feed/update/urn:li:share:{post_id}/"
113
+ elif "activity" in post_id_urn:
114
+ post_id = post_id_urn.split(":")[-1]
115
+ post_url = f"https://www.linkedin.com/feed/update/urn:li:activity:{post_id}/"
116
+ else:
117
+ raise Exception("Unknown post URN format")
118
+
119
+ print(f"Post URL: {post_url}")
120
+ yield "✅ Post published successfully!", gr.update(value=f"### 🎉 [Click here to view your post on LinkedIn →]({post_url})", visible=True)
121
+ return
122
+
123
+ # If fetch failed or no posts found
124
+ yield "✅ Post published! (Could not auto-retrieve post link. Please check the LinkedIn page.)", gr.update(visible=False)
125
+
126
+ except Exception as fetch_error:
127
+ print(f"Error fetching post: {fetch_error}")
128
+ yield f"✅ Post published! (Error retrieving link: {fetch_error})", gr.update(visible=False)
129
+
130
  else:
131
+ # 8. Handle POST errors gracefully
132
+ try:
133
+ error_data = response.json()
134
+ error_message = error_data.get('message', response.text)
135
+ except requests.exceptions.JSONDecodeError:
136
+ error_message = response.text
137
+
138
+ error_msg = f"❌ Error {response.status_code}: {error_message}"
139
+ print(error_msg)
140
+ yield error_msg, gr.update(visible=False)
141
 
142
  except requests.exceptions.Timeout:
143
+ yield "❌ Error: Request timed out. Please try again.", gr.update(visible=False)
144
  except requests.exceptions.RequestException as e:
145
+ yield f"❌ Network Error: {str(e)}", gr.update(visible=False)
146
  except Exception as e:
147
+ yield f"❌ Error: {str(e)}", gr.update(visible=False)
148
+
149
 
150
+ # --- Polished Gradio Interface ---
151
+
152
+ custom_css = """
153
+ #title-link a { text-decoration: none; color: #0A66C2; }
154
+ #title-link a:hover { text-decoration: underline; }
155
+ #success-link-box {
156
+ text-align: center;
157
+ padding: 1rem;
158
+ background-color: #E7F3FF;
159
+ border-radius: 8px;
160
+ }
161
+ #success-link-box a { font-size: 1.1rem; font-weight: bold; }
162
+ #benefits-box {
163
+ background-color: #f7f9fb; /* Light background for the right panel */
164
+ padding: 15px;
165
+ border-radius: 8px;
166
+ height: 100%;
167
+ display: flex;
168
+ flex-direction: column;
169
+ align-items: center;
170
+ text-align: center;
171
+ }
172
+ """
173
+
174
+ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
175
+
176
+ # Global Header Section
177
+ with gr.Row():
178
+ gr.Image(
179
+ value="feedhire_logo.png", # NOTE: Ensure this image file is accessible or use a public URL
180
+ show_label=False,
181
+ show_download_button=False,
182
+ container=False,
183
+ height=80,
184
+ width=80,
185
+ )
186
 
187
+ gr.Markdown(
188
+ f"<h1 style='text-align: left;' id='title-link'>LinkedIn Job Poster for <a href='https://www.linkedin.com/company/{organisationNumber}/' target='_blank'>FEEDHIREJOBS PAGE</a></h1>"
189
+ )
190
+
191
+ gr.Markdown(
192
+ "<p style='text-align: left;'>Post job listings directly to the FeedHireJobs LinkedIn page</p>"
193
+ )
194
+
195
+ gr.Markdown("""
196
+ <p style="text-align: center;">
197
+ ( Once your job is posted on our LinkedIn page, it will soon be visible at
198
+ <a href="https://feedhire.me/" target="_blank" style="color:#0073b1; text-decoration:none; font-weight:bold;">
199
+ FEEDHIRE.ME
200
+ </a> )
201
+ </p>
202
+ """)
203
+
204
+
205
  # Show configuration status
206
  if missing_vars:
207
+ gr.Warning(f"Configuration Error: Missing Secrets: {', '.join(missing_vars)}. Please check your Hugging Face Spaces configuration.")
208
+
209
+
210
 
211
+ ## 🎯 Job Posting Panel
212
+
213
+ # Two-Column Layout (The core change)
214
+ with gr.Row(equal_height=True):
215
+
216
+ # LEFT COLUMN (FORM INPUTS)
217
+ with gr.Column(scale=1):
218
+
219
  name_input = gr.Textbox(
220
+ label="Your Name",
221
+ placeholder="e.g., Aandu Pandu",
222
  lines=1
223
  )
224
  job_post_input = gr.Textbox(
225
+ label="Job Post Content",
226
+ placeholder="Enter the full job post details here...\n\n- Role: ...\n- Responsibilities: ...\n- Description: ...\n- Location: ...\n- How to Apply: ...\n",
227
  lines=10
228
  )
229
+
230
+ submit_btn = gr.Button("🚀 Post to LinkedIn", variant="primary", size="lg")
231
+
232
+ output = gr.Textbox(label="Status", lines=2, interactive=False)
233
+
234
+ success_link = gr.Markdown(
235
+ "", # Will be dynamically set
236
+ visible=False,
237
+ elem_id="success-link-box" # For CSS styling
238
+ )
239
+
240
+ # RIGHT COLUMN (BENEFITS/MARKETING)
241
+ with gr.Column(scale=1, elem_id="benefits-box"):
242
+
243
+ gr.Markdown("### <span style='color: black;'>🌟 Why Post Jobs on FeedHire Page?</span>")
244
+
245
+ # Note: Replace this with a suitable marketing image if you have one.
246
+ # Using a public domain image placeholder for now.
247
+ gr.Image(
248
+ value="analytics_map.png", # Placeholder for a marketing graphic/image
249
+ show_label=False,
250
+ show_download_button=False,
251
+ container=False,
252
+ height=400,
253
+ width=600,
254
+ )
255
+
256
+ # Benefit 1
257
+ gr.Markdown("""
258
+ <span style="color:black;">
259
+ <b style="color:black;">🌍 Reach a Larger Audience:</b> Tap into FeedHire’s network and LinkedIn’s organic reach.
260
+ </span>
261
+ """)
262
+
263
+ gr.Markdown("""
264
+ <span style="color:black;">
265
+ <b style="color:black;">💬 Get More Responses:</b> Attract higher engagement and better applicants.
266
+ </span>
267
+ """)
268
+
269
+ # Benefit 2
270
+ gr.Markdown("""
271
+ <span style="color:black;">
272
+ <b style="color:black;"> 💯 Free to Use:</b> no hidden costs, no subscriptions, just genuine visibility.
273
+ </span>
274
+ """)
275
+
276
+ # Benefit 3
277
+ gr.Markdown("""
278
+ <span style="color:black;">
279
+ <b style="color:black;">🔗 Seamless Integration:</b> Post once — visible on <a href="https://www.linkedin.com/company/109539782/" target="_blank" style="color:blue; text-decoration:none; font-weight:bold;">LINKEDIN</a> and
280
+ <a href="https://feedhire.me/" target="_blank" style="color:blue; text-decoration:none; font-weight:bold;">FEEDHIRE.ME</a>.
281
+ </span>
282
+ """)
283
+
284
+
285
+
286
+
287
+ # --- Benefits Section ---
288
+
289
+ gr.Markdown("""
290
+ ### 🌎 Why Choose FeedHire?
291
+
292
+ ---
293
+
294
+ **1️⃣ Global Reach**
295
+ <span style="color: white;">
296
+ Connect with nearly <b style="color:white;">1,000+ active users</b> from around the world — including professionals from the
297
+ <b style="color:white;">US, UK, India, Nigeria, Germany</b>, and more.
298
+ </span>
299
+
300
+ ---
301
+
302
+ **2️⃣ 100% Free to Use**
303
+ <span style="color: white;">
304
+ Post jobs without spending a penny — no hidden costs, no subscriptions, just genuine visibility.
305
+ </span>
306
+
307
+ ---
308
+
309
+ **3️⃣ Safe & Legal**
310
+ <span style="color: white;">
311
+ All job posts are verified and comply with platform and regional policies, ensuring a transparent and trustworthy experience.
312
+ </span>
313
+
314
+ ---
315
+ """)
316
+
317
+
318
+ # Click Action
319
  submit_btn.click(
320
  fn=post_to_linkedin,
321
  inputs=[name_input, job_post_input],
322
+ outputs=[output, success_link],
323
+ show_progress="full"
324
  )
325
 
326
  if __name__ == "__main__":
327
+ demo.launch(allowed_paths=["."])