Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -28,27 +28,31 @@ EXCEL_FILE = "appointments.xlsx"
|
|
| 28 |
# === Time Slots ===
|
| 29 |
TIME_SLOTS = [f"{hour:02d}:00" for hour in range(9, 18)] # 9 AM to 5 PM
|
| 30 |
|
| 31 |
-
# ===
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
COMMON_TIMEZONES = [
|
| 33 |
-
"UTC", "
|
| 34 |
-
"Europe/London", "
|
| 35 |
-
|
| 36 |
-
"America/New_York", "America/Los_Angeles", "America/Chicago",
|
| 37 |
-
"America/Toronto", "America/Sao_Paulo", "Asia/Kolkata", "Asia/Singapore"
|
| 38 |
-
] # Curated list for faster dropdown rendering
|
| 39 |
|
| 40 |
-
# === Google Calendar
|
| 41 |
def generate_google_calendar_link(name, email, date_str, time_str, timezone):
|
| 42 |
try:
|
|
|
|
| 43 |
tz = pytz.timezone(timezone)
|
| 44 |
-
start_time = datetime.strptime(f"{
|
| 45 |
end_time = start_time + timedelta(hours=1)
|
| 46 |
start_time_utc = start_time.astimezone(pytz.UTC).strftime("%Y%m%dT%H%M%SZ")
|
| 47 |
end_time_utc = end_time.astimezone(pytz.UTC).strftime("%Y%m%dT%H%M%SZ")
|
| 48 |
event = {
|
| 49 |
"action": "TEMPLATE",
|
| 50 |
"text": f"Appointment with {FROM_NAME}",
|
| 51 |
-
"details": f"Appointment
|
| 52 |
"dates": f"{start_time_utc}/{end_time_utc}",
|
| 53 |
"ctz": timezone
|
| 54 |
}
|
|
@@ -66,10 +70,10 @@ def send_email(to_email, subject, body):
|
|
| 66 |
msg["Subject"] = subject
|
| 67 |
msg.attach(MIMEText(body, "plain"))
|
| 68 |
context = ssl.create_default_context()
|
| 69 |
-
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server:
|
| 70 |
server.login(SMTP_USERNAME, SMTP_PASSWORD)
|
| 71 |
server.send_message(msg)
|
| 72 |
-
logging.info(f"Email sent to {to_email}
|
| 73 |
return True
|
| 74 |
except Exception as e:
|
| 75 |
logging.error(f"Email error: {e}")
|
|
@@ -78,22 +82,21 @@ def send_email(to_email, subject, body):
|
|
| 78 |
# === Save to Excel ===
|
| 79 |
def save_to_excel(name, email, date_str, time_str, timezone, message):
|
| 80 |
try:
|
|
|
|
| 81 |
appointment_data = {
|
| 82 |
"Name": name,
|
| 83 |
"Email": email,
|
| 84 |
-
"Date":
|
| 85 |
"Time": time_str,
|
| 86 |
"Timezone": timezone,
|
| 87 |
-
"Message": message
|
| 88 |
"Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 89 |
}
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
df =
|
| 94 |
-
|
| 95 |
-
df.to_excel(EXCEL_FILE, engine='openpyxl', index=False)
|
| 96 |
-
logging.info(f"Appointment saved: {name}, {email}, {date_str}, {time_str}, {timezone}, {message}")
|
| 97 |
return True
|
| 98 |
except Exception as e:
|
| 99 |
logging.error(f"Excel save error: {e}")
|
|
@@ -105,134 +108,130 @@ def book_appointment(name, email, date, time, timezone, message):
|
|
| 105 |
return "Please fill out all required fields.", []
|
| 106 |
|
| 107 |
try:
|
| 108 |
-
|
| 109 |
-
datetime.strptime(
|
| 110 |
tz = pytz.timezone(timezone)
|
| 111 |
-
appointment_time = datetime.strptime(f"{
|
| 112 |
if appointment_time < datetime.now(tz):
|
| 113 |
return "Appointments must be in the future.", []
|
| 114 |
|
| 115 |
-
if not save_to_excel(name, email,
|
| 116 |
return "Failed to save appointment.", []
|
| 117 |
|
| 118 |
-
calendar_link = generate_google_calendar_link(name, email,
|
| 119 |
if not calendar_link:
|
| 120 |
return "Failed to generate calendar link, but appointment saved.", []
|
| 121 |
|
| 122 |
-
admin_msg =
|
| 123 |
-
f"New Appointment:\n"
|
| 124 |
-
f"Name: {name}\n"
|
| 125 |
-
f"Email: {email}\n"
|
| 126 |
-
f"Date: {date}\n"
|
| 127 |
-
f"Time: {time}\n"
|
| 128 |
-
f"Timezone: {timezone}\n"
|
| 129 |
-
f"Message: {message if message else 'No message'}"
|
| 130 |
-
)
|
| 131 |
send_email("admin@mubashirdev.com", "📅 New Appointment", admin_msg)
|
| 132 |
|
| 133 |
-
user_msg = (
|
| 134 |
-
f"Hi {name},\n\n"
|
| 135 |
-
f"Your appointment is confirmed for {date} at {time} ({timezone}).\n"
|
| 136 |
-
f"Add to Google Calendar: {calendar_link}\n\n"
|
| 137 |
-
f"Your message: {message if message else 'No message'}\n\n"
|
| 138 |
-
f"Thank you for choosing my services!\n"
|
| 139 |
-
f"Mubashir Hussain\nhttps://mubashirdev.com"
|
| 140 |
-
)
|
| 141 |
send_email(email, "✅ Appointment Confirmed", user_msg)
|
| 142 |
|
| 143 |
-
# Generate
|
| 144 |
chart_data = {
|
| 145 |
"type": "bar",
|
| 146 |
"data": {
|
| 147 |
-
"labels": [f"{
|
| 148 |
"datasets": [{
|
| 149 |
"label": "Appointment",
|
| 150 |
"data": [1],
|
| 151 |
-
"backgroundColor": "#
|
| 152 |
-
"borderColor": "#
|
| 153 |
"borderWidth": 2
|
| 154 |
}]
|
| 155 |
},
|
| 156 |
"options": {
|
| 157 |
"scales": {
|
| 158 |
-
"y": {
|
| 159 |
-
|
| 160 |
-
"ticks": {"stepSize": 1, "color": "#333", "font": {"family": "Roboto", "size": 14}},
|
| 161 |
-
"title": {"display": True, "text": "Confirmed", "color": "#333", "font": {"family": "Roboto", "size": 16}}
|
| 162 |
-
},
|
| 163 |
-
"x": {
|
| 164 |
-
"title": {"display": True, "text": "Date & Time", "color": "#333", "font": {"family": "Roboto", "size": 16}},
|
| 165 |
-
"ticks": {"color": "#333", "font": {"family": "Roboto", "size": 14}}
|
| 166 |
-
}
|
| 167 |
},
|
| 168 |
"plugins": {
|
| 169 |
-
"title": {"display": True, "text": f"Appointment for {name}", "color": "#
|
| 170 |
}
|
| 171 |
}
|
| 172 |
}
|
| 173 |
|
| 174 |
return (
|
| 175 |
-
f"✅ Appointment confirmed for {
|
| 176 |
[chart_data]
|
| 177 |
)
|
| 178 |
|
| 179 |
except pytz.exceptions.UnknownTimeZoneError:
|
| 180 |
return "Invalid timezone.", []
|
| 181 |
except ValueError:
|
| 182 |
-
return "Invalid date format.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
# === Gradio UI ===
|
| 185 |
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 186 |
-
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
|
| 187 |
.gradio-container {
|
| 188 |
max-width: 700px;
|
| 189 |
margin: 20px auto;
|
| 190 |
-
|
| 191 |
-
background: #F5F6F5;
|
| 192 |
padding: 24px;
|
| 193 |
border-radius: 16px;
|
| 194 |
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
| 195 |
}
|
| 196 |
.welcome {
|
| 197 |
text-align: center;
|
| 198 |
-
font-size:
|
| 199 |
-
color: #
|
| 200 |
-
margin: 0 0
|
| 201 |
font-weight: 700;
|
| 202 |
}
|
| 203 |
.gr-textbox, .gr-dropdown {
|
| 204 |
border-radius: 8px;
|
| 205 |
-
border: 2px solid #
|
| 206 |
padding: 12px;
|
| 207 |
font-size: 16px;
|
|
|
|
| 208 |
transition: border-color 0.2s;
|
| 209 |
}
|
| 210 |
.gr-textbox:focus, .gr-dropdown:focus {
|
| 211 |
-
border-color: #
|
| 212 |
outline: none;
|
| 213 |
}
|
| 214 |
-
.gr-textbox[label="Date"]::before {
|
| 215 |
-
content: "📅 ";
|
| 216 |
-
}
|
| 217 |
.gr-button {
|
| 218 |
-
background: #
|
| 219 |
color: white;
|
| 220 |
font-size: 16px;
|
| 221 |
font-weight: 600;
|
| 222 |
padding: 12px 32px;
|
| 223 |
border-radius: 8px;
|
| 224 |
border: none;
|
| 225 |
-
transition: background 0.2s, transform 0.1s;
|
| 226 |
margin: 24px auto;
|
| 227 |
display: block;
|
|
|
|
| 228 |
}
|
| 229 |
.gr-button:hover {
|
| 230 |
-
background: #
|
| 231 |
-
transform: translateY(-2px);
|
| 232 |
}
|
| 233 |
.gr-textbox[label="Result"] {
|
| 234 |
-
background: #
|
| 235 |
-
border-color: #
|
| 236 |
color: #1B5E20;
|
| 237 |
font-weight: 500;
|
| 238 |
padding: 16px;
|
|
@@ -240,15 +239,15 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 240 |
}
|
| 241 |
.gr-column {
|
| 242 |
background: white;
|
| 243 |
-
padding:
|
| 244 |
border-radius: 12px;
|
| 245 |
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
| 246 |
}
|
| 247 |
.footer {
|
| 248 |
text-align: center;
|
| 249 |
font-size: 14px;
|
| 250 |
-
color: #
|
| 251 |
-
margin-top:
|
| 252 |
}
|
| 253 |
.logo {
|
| 254 |
display: block;
|
|
@@ -272,27 +271,28 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 272 |
}
|
| 273 |
}
|
| 274 |
""") as demo:
|
| 275 |
-
gr.
|
| 276 |
-
"""
|
| 277 |
<img src="/static/logo.png" class="logo" alt="Mubashir Hussain Logo">
|
| 278 |
-
<div class="welcome">
|
| 279 |
-
|
|
|
|
| 280 |
"""
|
| 281 |
)
|
| 282 |
with gr.Column():
|
| 283 |
name = gr.Textbox(label="Your Name", placeholder="Enter your name", elem_classes="required")
|
| 284 |
email = gr.Textbox(label="Your Email", placeholder="Enter your email", elem_classes="required")
|
| 285 |
-
date = gr.
|
| 286 |
time = gr.Dropdown(label="Time", choices=TIME_SLOTS, value="09:00", elem_classes="required")
|
| 287 |
timezone = gr.Dropdown(label="Timezone", choices=COMMON_TIMEZONES, value="Asia/Karachi", elem_classes="required")
|
| 288 |
-
message = gr.Textbox(label="Message (Optional)", placeholder="Add any details or notes", lines=
|
| 289 |
submit_btn = gr.Button("Book Appointment")
|
| 290 |
output = gr.Textbox(label="Result", interactive=False)
|
| 291 |
chart_output = gr.Plot(label="Confirmation")
|
| 292 |
|
| 293 |
gr.Markdown(
|
| 294 |
"""
|
| 295 |
-
<div class="footer">Powered by <a href="https://mubashirdev.com" style="color: #
|
| 296 |
"""
|
| 297 |
)
|
| 298 |
|
|
@@ -302,5 +302,5 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 302 |
outputs=[output, chart_output]
|
| 303 |
)
|
| 304 |
|
| 305 |
-
# Launch the app (
|
| 306 |
demo.launch()
|
|
|
|
| 28 |
# === Time Slots ===
|
| 29 |
TIME_SLOTS = [f"{hour:02d}:00" for hour in range(9, 18)] # 9 AM to 5 PM
|
| 30 |
|
| 31 |
+
# === Date Options (Next 15 Days for Speed) ===
|
| 32 |
+
DATE_OPTIONS = [
|
| 33 |
+
(datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d (%A)")
|
| 34 |
+
for i in range(0, 15)
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
# === Common Timezones (minimized for speed) ===
|
| 38 |
COMMON_TIMEZONES = [
|
| 39 |
+
"UTC", "Asia/Karachi", "US/Pacific", "US/Eastern",
|
| 40 |
+
"Europe/London", "Asia/Tokyo", "America/New_York", "Asia/Dubai"
|
| 41 |
+
]
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
+
# === Google Calendar Link Generator ===
|
| 44 |
def generate_google_calendar_link(name, email, date_str, time_str, timezone):
|
| 45 |
try:
|
| 46 |
+
date_clean = date_str.split(" ")[0] # Extract YYYY-MM-DD
|
| 47 |
tz = pytz.timezone(timezone)
|
| 48 |
+
start_time = datetime.strptime(f"{date_clean} {time_str}", "%Y-%m-%d %H:%M").replace(tzinfo=tz)
|
| 49 |
end_time = start_time + timedelta(hours=1)
|
| 50 |
start_time_utc = start_time.astimezone(pytz.UTC).strftime("%Y%m%dT%H%M%SZ")
|
| 51 |
end_time_utc = end_time.astimezone(pytz.UTC).strftime("%Y%m%dT%H%M%SZ")
|
| 52 |
event = {
|
| 53 |
"action": "TEMPLATE",
|
| 54 |
"text": f"Appointment with {FROM_NAME}",
|
| 55 |
+
"details": f"Appointment for {name} ({email}). Contact: https://mubashirdev.com",
|
| 56 |
"dates": f"{start_time_utc}/{end_time_utc}",
|
| 57 |
"ctz": timezone
|
| 58 |
}
|
|
|
|
| 70 |
msg["Subject"] = subject
|
| 71 |
msg.attach(MIMEText(body, "plain"))
|
| 72 |
context = ssl.create_default_context()
|
| 73 |
+
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context, timeout=5) as server:
|
| 74 |
server.login(SMTP_USERNAME, SMTP_PASSWORD)
|
| 75 |
server.send_message(msg)
|
| 76 |
+
logging.info(f"Email sent to {to_email}")
|
| 77 |
return True
|
| 78 |
except Exception as e:
|
| 79 |
logging.error(f"Email error: {e}")
|
|
|
|
| 82 |
# === Save to Excel ===
|
| 83 |
def save_to_excel(name, email, date_str, time_str, timezone, message):
|
| 84 |
try:
|
| 85 |
+
date_clean = date_str.split(" ")[0] # Extract YYYY-MM-DD
|
| 86 |
appointment_data = {
|
| 87 |
"Name": name,
|
| 88 |
"Email": email,
|
| 89 |
+
"Date": date_clean,
|
| 90 |
"Time": time_str,
|
| 91 |
"Timezone": timezone,
|
| 92 |
+
"Message": message or "No message",
|
| 93 |
"Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 94 |
}
|
| 95 |
+
df = pd.DataFrame([appointment_data])
|
| 96 |
+
mode = 'a' if os.path.exists(EXCEL_FILE) else 'w'
|
| 97 |
+
with pd.ExcelWriter(EXCEL_FILE, engine='openpyxl', mode=mode, if_sheet_exists='overlay' if mode == 'a' else None) as writer:
|
| 98 |
+
df.to_excel(writer, index=False, sheet_name='Sheet1', header=not os.path.exists(EXCEL_FILE))
|
| 99 |
+
logging.info(f"Appointment saved: {name}, {email}")
|
|
|
|
|
|
|
| 100 |
return True
|
| 101 |
except Exception as e:
|
| 102 |
logging.error(f"Excel save error: {e}")
|
|
|
|
| 108 |
return "Please fill out all required fields.", []
|
| 109 |
|
| 110 |
try:
|
| 111 |
+
date_clean = date.split(" ")[0] # Extract YYYY-MM-DD
|
| 112 |
+
datetime.strptime(date_clean, "%Y-%m-%d")
|
| 113 |
tz = pytz.timezone(timezone)
|
| 114 |
+
appointment_time = datetime.strptime(f"{date_clean} {time}", "%Y-%m-%d %H:%M").replace(tzinfo=tz)
|
| 115 |
if appointment_time < datetime.now(tz):
|
| 116 |
return "Appointments must be in the future.", []
|
| 117 |
|
| 118 |
+
if not save_to_excel(name, email, date_clean, time, timezone, message):
|
| 119 |
return "Failed to save appointment.", []
|
| 120 |
|
| 121 |
+
calendar_link = generate_google_calendar_link(name, email, date_clean, time, timezone)
|
| 122 |
if not calendar_link:
|
| 123 |
return "Failed to generate calendar link, but appointment saved.", []
|
| 124 |
|
| 125 |
+
admin_msg = f"New Appointment:\nName: {name}\nEmail: {email}\nDate: {date_clean}\nTime: {time}\nTimezone: {timezone}\nMessage: {message or 'No message'}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
send_email("admin@mubashirdev.com", "📅 New Appointment", admin_msg)
|
| 127 |
|
| 128 |
+
user_msg = f"Hi {name},\n\nYour appointment is confirmed for {date_clean} at {time} ({timezone}).\nAdd to Google Calendar: {calendar_link}\n\nYour message: {message or 'No message'}\n\nThank you!\nMubashir Hussain\nhttps://mubashirdev.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
send_email(email, "✅ Appointment Confirmed", user_msg)
|
| 130 |
|
| 131 |
+
# Generate chart
|
| 132 |
chart_data = {
|
| 133 |
"type": "bar",
|
| 134 |
"data": {
|
| 135 |
+
"labels": [f"{date_clean} {time}"],
|
| 136 |
"datasets": [{
|
| 137 |
"label": "Appointment",
|
| 138 |
"data": [1],
|
| 139 |
+
"backgroundColor": "#003087",
|
| 140 |
+
"borderColor": "#001F5C",
|
| 141 |
"borderWidth": 2
|
| 142 |
}]
|
| 143 |
},
|
| 144 |
"options": {
|
| 145 |
"scales": {
|
| 146 |
+
"y": {"beginAtZero": True, "ticks": {"stepSize": 1, "color": "#333"}, "title": {"display": True, "text": "Confirmed", "color": "#333"}},
|
| 147 |
+
"x": {"title": {"display": True, "text": "Date & Time", "color": "#333"}, "ticks": {"color": "#333"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
},
|
| 149 |
"plugins": {
|
| 150 |
+
"title": {"display": True, "text": f"Appointment for {name}", "color": "#003087", "font": {"size": 18}}
|
| 151 |
}
|
| 152 |
}
|
| 153 |
}
|
| 154 |
|
| 155 |
return (
|
| 156 |
+
f"✅ Appointment confirmed for {date_clean} at {time} ({timezone}). Email sent to {email}.",
|
| 157 |
[chart_data]
|
| 158 |
)
|
| 159 |
|
| 160 |
except pytz.exceptions.UnknownTimeZoneError:
|
| 161 |
return "Invalid timezone.", []
|
| 162 |
except ValueError:
|
| 163 |
+
return "Invalid date format.", []
|
| 164 |
+
|
| 165 |
+
# === JavaScript for Date Dropdown Enhancement ===
|
| 166 |
+
JS_ENHANCEMENT = """
|
| 167 |
+
<script>
|
| 168 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 169 |
+
const dateDropdown = document.querySelector('select[id*="date"]');
|
| 170 |
+
if (dateDropdown) {
|
| 171 |
+
dateDropdown.style.background = '#E6F0FA';
|
| 172 |
+
dateDropdown.style.border = '2px solid #003087';
|
| 173 |
+
dateDropdown.style.borderRadius = '8px';
|
| 174 |
+
dateDropdown.style.padding = '12px';
|
| 175 |
+
dateDropdown.style.fontSize = '16px';
|
| 176 |
+
const today = new Date().toISOString().split('T')[0];
|
| 177 |
+
Array.from(dateDropdown.options).forEach(option => {
|
| 178 |
+
if (option.value.includes(today)) {
|
| 179 |
+
option.style.fontWeight = 'bold';
|
| 180 |
+
option.style.color = '#003087';
|
| 181 |
+
}
|
| 182 |
+
});
|
| 183 |
+
}
|
| 184 |
+
});
|
| 185 |
+
</script>
|
| 186 |
+
"""
|
| 187 |
|
| 188 |
# === Gradio UI ===
|
| 189 |
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
|
|
| 190 |
.gradio-container {
|
| 191 |
max-width: 700px;
|
| 192 |
margin: 20px auto;
|
| 193 |
+
background: #E6F0FA;
|
|
|
|
| 194 |
padding: 24px;
|
| 195 |
border-radius: 16px;
|
| 196 |
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
| 197 |
}
|
| 198 |
.welcome {
|
| 199 |
text-align: center;
|
| 200 |
+
font-size: 28px;
|
| 201 |
+
color: #003087;
|
| 202 |
+
margin: 0 0 20px;
|
| 203 |
font-weight: 700;
|
| 204 |
}
|
| 205 |
.gr-textbox, .gr-dropdown {
|
| 206 |
border-radius: 8px;
|
| 207 |
+
border: 2px solid #003087;
|
| 208 |
padding: 12px;
|
| 209 |
font-size: 16px;
|
| 210 |
+
background: white;
|
| 211 |
transition: border-color 0.2s;
|
| 212 |
}
|
| 213 |
.gr-textbox:focus, .gr-dropdown:focus {
|
| 214 |
+
border-color: #001F5C;
|
| 215 |
outline: none;
|
| 216 |
}
|
|
|
|
|
|
|
|
|
|
| 217 |
.gr-button {
|
| 218 |
+
background: #003087;
|
| 219 |
color: white;
|
| 220 |
font-size: 16px;
|
| 221 |
font-weight: 600;
|
| 222 |
padding: 12px 32px;
|
| 223 |
border-radius: 8px;
|
| 224 |
border: none;
|
|
|
|
| 225 |
margin: 24px auto;
|
| 226 |
display: block;
|
| 227 |
+
transition: background 0.2s;
|
| 228 |
}
|
| 229 |
.gr-button:hover {
|
| 230 |
+
background: #001F5C;
|
|
|
|
| 231 |
}
|
| 232 |
.gr-textbox[label="Result"] {
|
| 233 |
+
background: #E8F5E9;
|
| 234 |
+
border-color: #2E7D32;
|
| 235 |
color: #1B5E20;
|
| 236 |
font-weight: 500;
|
| 237 |
padding: 16px;
|
|
|
|
| 239 |
}
|
| 240 |
.gr-column {
|
| 241 |
background: white;
|
| 242 |
+
padding: 20px;
|
| 243 |
border-radius: 12px;
|
| 244 |
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
| 245 |
}
|
| 246 |
.footer {
|
| 247 |
text-align: center;
|
| 248 |
font-size: 14px;
|
| 249 |
+
color: #003087;
|
| 250 |
+
margin-top: 20px;
|
| 251 |
}
|
| 252 |
.logo {
|
| 253 |
display: block;
|
|
|
|
| 271 |
}
|
| 272 |
}
|
| 273 |
""") as demo:
|
| 274 |
+
gr.HTML(
|
| 275 |
+
f"""
|
| 276 |
<img src="/static/logo.png" class="logo" alt="Mubashir Hussain Logo">
|
| 277 |
+
<div class="welcome">Schedule Your Appointment</div>
|
| 278 |
+
<p style="text-align: center; color: #333;">Select a date and time below to book with Mubashir Hussain. You'll receive a confirmation email with a Google Calendar link.</p>
|
| 279 |
+
{JS_ENHANCEMENT}
|
| 280 |
"""
|
| 281 |
)
|
| 282 |
with gr.Column():
|
| 283 |
name = gr.Textbox(label="Your Name", placeholder="Enter your name", elem_classes="required")
|
| 284 |
email = gr.Textbox(label="Your Email", placeholder="Enter your email", elem_classes="required")
|
| 285 |
+
date = gr.Dropdown(label="Date", choices=DATE_OPTIONS, value=DATE_OPTIONS[0], elem_classes="required")
|
| 286 |
time = gr.Dropdown(label="Time", choices=TIME_SLOTS, value="09:00", elem_classes="required")
|
| 287 |
timezone = gr.Dropdown(label="Timezone", choices=COMMON_TIMEZONES, value="Asia/Karachi", elem_classes="required")
|
| 288 |
+
message = gr.Textbox(label="Message (Optional)", placeholder="Add any details or notes", lines=3)
|
| 289 |
submit_btn = gr.Button("Book Appointment")
|
| 290 |
output = gr.Textbox(label="Result", interactive=False)
|
| 291 |
chart_output = gr.Plot(label="Confirmation")
|
| 292 |
|
| 293 |
gr.Markdown(
|
| 294 |
"""
|
| 295 |
+
<div class="footer">Powered by <a href="https://mubashirdev.com" style="color: #003087; text-decoration: none;">mubashirdev.com</a></div>
|
| 296 |
"""
|
| 297 |
)
|
| 298 |
|
|
|
|
| 302 |
outputs=[output, chart_output]
|
| 303 |
)
|
| 304 |
|
| 305 |
+
# Launch the app (no port specification for Hugging Face Spaces)
|
| 306 |
demo.launch()
|