Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import resend
|
| 5 |
+
from supabase import create_client, Client
|
| 6 |
+
|
| 7 |
+
# --- 設定 ---
|
| 8 |
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 9 |
+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
| 10 |
+
RESEND_API_KEY = os.getenv("RESEND_API_KEY")
|
| 11 |
+
PUBLIC_SPACE_URL = "https://deeplearning101-ciecietaipei.hf.space" # ⚠️ 請換成您 Space A 的網址
|
| 12 |
+
|
| 13 |
+
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 14 |
+
resend.api_key = RESEND_API_KEY
|
| 15 |
+
|
| 16 |
+
def get_bookings():
|
| 17 |
+
# 加入 email 和 remarks 欄位
|
| 18 |
+
response = supabase.table("bookings").select("*").order("created_at", desc=True).execute()
|
| 19 |
+
if not response.data: return pd.DataFrame()
|
| 20 |
+
df = pd.DataFrame(response.data)
|
| 21 |
+
# 確保欄位存在,避免報錯
|
| 22 |
+
cols = ['id', 'date', 'time', 'name', 'tel', 'email', 'pax', 'remarks', 'status']
|
| 23 |
+
for c in cols:
|
| 24 |
+
if c not in df.columns: df[c] = ""
|
| 25 |
+
return df[cols]
|
| 26 |
+
|
| 27 |
+
def send_confirmation_email(booking_id):
|
| 28 |
+
"""發送確認信邏輯"""
|
| 29 |
+
try:
|
| 30 |
+
# 1. 抓取訂位資料
|
| 31 |
+
res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
|
| 32 |
+
if not res.data: return "❌ 找不到該訂單"
|
| 33 |
+
booking = res.data[0]
|
| 34 |
+
|
| 35 |
+
email = booking.get('email')
|
| 36 |
+
if not email or "@" not in email: return "❌ 客人未填寫有效 Email"
|
| 37 |
+
|
| 38 |
+
# 2. 產生確認連結 (指向 Space A)
|
| 39 |
+
confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
|
| 40 |
+
|
| 41 |
+
# 3. 寄信 (HTML)
|
| 42 |
+
params = {
|
| 43 |
+
"from": "Cié Cié Taipei <onboarding@resend.dev>", # 若沒買網域,先用 Resend 預設的
|
| 44 |
+
"to": [email],
|
| 45 |
+
"subject": f"[{booking['date']}] Cié Cié Taipei 訂位確認",
|
| 46 |
+
"html": f"""
|
| 47 |
+
<div style="padding: 20px; background: #111; color: #d4af37; font-family: sans-serif;">
|
| 48 |
+
<h2>訂位保留確認</h2>
|
| 49 |
+
<p>{booking['name']} 您好,我們已為您保留座位:</p>
|
| 50 |
+
<ul>
|
| 51 |
+
<li>日期:{booking['date']}</li>
|
| 52 |
+
<li>時間:{booking['time']}</li>
|
| 53 |
+
<li>人數:{booking['pax']} 位</li>
|
| 54 |
+
</ul>
|
| 55 |
+
<p>請點擊下方按鈕確認出席:</p>
|
| 56 |
+
<a href="{confirm_link}" style="background: #d4af37; color: black; padding: 10px 20px; text-decoration: none; border-radius: 5px; font-weight: bold;">✅ 我會出席 (Confirm)</a>
|
| 57 |
+
<br><br>
|
| 58 |
+
<p style="color: #666; font-size: 12px;">若需取消,請直接回覆此信或致電。</p>
|
| 59 |
+
</div>
|
| 60 |
+
"""
|
| 61 |
+
}
|
| 62 |
+
resend.Emails.send(params)
|
| 63 |
+
|
| 64 |
+
# 4. 更新狀態為「已發信」
|
| 65 |
+
supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
|
| 66 |
+
return f"✅ 已發送確認信給 {booking['name']} ({email})"
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
return f"❌ 發信失敗: {str(e)}"
|
| 70 |
+
|
| 71 |
+
# --- 介面 ---
|
| 72 |
+
with gr.Blocks(title="Admin") as demo:
|
| 73 |
+
gr.Markdown("# 🍷 訂位管理後台")
|
| 74 |
+
refresh_btn = gr.Button("🔄 重新整理")
|
| 75 |
+
booking_table = gr.Dataframe(interactive=False)
|
| 76 |
+
|
| 77 |
+
with gr.Row():
|
| 78 |
+
id_input = gr.Number(label="訂單 ID", precision=0)
|
| 79 |
+
action_btn = gr.Button("📧 發送確認信 (Send Email)", variant="primary")
|
| 80 |
+
|
| 81 |
+
log_output = gr.Textbox(label="執行結果")
|
| 82 |
+
|
| 83 |
+
refresh_btn.click(get_bookings, outputs=booking_table)
|
| 84 |
+
action_btn.click(send_confirmation_email, inputs=id_input, outputs=log_output)
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
demo.launch()
|