simikkk commited on
Commit
bd9111a
·
verified ·
1 Parent(s): 12b0d91

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -69
app.py CHANGED
@@ -116,44 +116,76 @@ def process_invoice_file(file_path: str, user_id: int) -> dict:
116
  except Exception as e: return {"filename": os.path.basename(file_path), "status": "error"}
117
 
118
  # ==========================================
119
- # 3. OBSIDIAN DESIGN SYSTEM (CSS)
120
  # ==========================================
121
  CUSTOM_CSS = """
122
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
123
- :root { --bg: #09090b; --card: rgba(255,255,255,0.03); --border: rgba(255,255,255,0.08); --text: #f4f4f5; --muted: #71717a; --accent: #10b981; --accent-glow: rgba(16,185,129,0.2); }
 
 
 
 
 
 
 
 
 
124
  * { box-sizing: border-box; font-family: 'Inter', sans-serif; }
125
- body { background: var(--bg); color: var(--text); margin: 0; }
126
- .gradio-container { max-width: 100% !important; padding: 0 !important; background: var(--bg) !important; min-height: 100vh; }
127
  .hide { display: none !important; }
128
 
129
  /* Layout */
130
- #navbar { display: flex; justify-content: space-between; align-items: center; padding: 20px 40px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: rgba(9,9,11,0.8); backdrop-filter: blur(10px); z-index: 100; }
131
- #hero { text-align: center; padding: 100px 20px; }
132
- #hero h1 { font-size: 56px; font-weight: 700; margin-bottom: 24px; background: linear-gradient(180deg, #fff 0%, #a1a1aa 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.1; }
133
- #hero p { font-size: 20px; color: var(--muted); max-width: 600px; margin: 0 auto 40px; }
134
- .dash-container { display: flex; padding: 40px; max-width: 1400px; margin: 0 auto; gap: 30px; }
135
- .dash-sidebar { width: 250px; display: flex; flex-direction: column; gap: 10px; }
136
- .dash-main { flex: 1; background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 24px; backdrop-filter: blur(20px); }
137
-
138
- /* Gradio Component Overrides */
139
- .gr-button { background: var(--accent) !important; color: #000 !important; border: none !important; border-radius: 8px !important; padding: 12px 24px !important; font-weight: 600 !important; transition: 0.2s !important; box-shadow: none !important; }
140
- .gr-button:hover { box-shadow: 0 0 20px var(--accent-glow) !important; transform: translateY(-1px) !important; }
141
- .btn-ghost { background: transparent !important; border: 1px solid var(--border) !important; color: var(--text) !important; }
142
- .btn-ghost:hover { border-color: var(--accent) !important; color: var(--accent) !important; }
143
- .btn-danger { background: #ef4444 !important; color: #fff !important; }
144
-
145
- .gr-textbox, .gr-dropdown { background: rgba(0,0,0,0.3) !important; border: 1px solid var(--border) !important; border-radius: 8px !important; color: var(--text) !important; }
146
- .gr-textbox:focus { border-color: var(--accent) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  .gr-form { background: transparent !important; border: none !important; }
148
  .gr-box { border: none !important; background: transparent !important; }
 
 
 
149
  .gr-dataframe table { background: transparent !important; color: var(--text) !important; border-collapse: collapse; width: 100%; }
150
- .gr-dataframe th { background: rgba(255,255,255,0.05) !important; border-bottom: 1px solid var(--border) !important; color: var(--accent) !important; text-align: left; padding: 12px; }
151
- .gr-dataframe td { border-bottom: 1px solid var(--border) !important; padding: 12px; }
 
 
152
  .gr-markdown { color: var(--text) !important; background: transparent !important; }
 
153
  footer { display: none !important; }
154
- """
155
 
156
- ICON_BOLT = '<svg style="width:24px;height:24px;display:inline-block;vertical-align:middle;margin-right:10px;fill:var(--accent);" viewBox="0 0 24 24"><path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z"/></svg>'
 
 
 
 
157
 
158
  # ==========================================
159
  # 4. BACKEND FUNCTIONS
@@ -166,35 +198,35 @@ def get_user_invoices(user_dict):
166
  def handle_auth(email, password, request: gr.Request):
167
  ip = request.client.host if request else "127.0.0.1"
168
  if rate_limiter.is_rate_limited(f"auth_{ip}", 5, 300):
169
- return None, "🚨 Příliš mnoho pokusů.", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
170
 
171
  if not supabase: return None, "DB Error", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
172
 
173
  res = supabase.table("users").select("*").eq("email", email).execute()
174
  if res.data and verify_password(password, res.data[0]['password']):
175
  user = res.data[0]
176
- return user, "Přihlášení úspěšné.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
177
 
178
  if not res.data:
179
  if not validate_password_strength(password):
180
- return None, "Slabé heslo (min 12 znaků, 1 velké, 1 číslo, 1 symbol).", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
181
  hashed = hash_password(password)
182
  new_user = supabase.table("users").insert({"email": email, "password": hashed, "plan": "free"}).execute()
183
  user = new_user.data[0]
184
- return user, "Účet vytvořen!", gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
185
 
186
- return None, "Neplatné údaje.", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
187
 
188
  def handle_upload(files, user_dict):
189
- if not user_dict: return "Nejste přihlášen.", get_user_invoices(user_dict)
190
- if not files: return "Žádné soubory.", get_user_invoices(user_dict)
191
  for file in files:
192
  if os.path.getsize(file.name) > 20 * 1024 * 1024: continue
193
  process_invoice_file(file.name, user_dict['id'])
194
- return f"Zpracováno {len(files)} souborů.", get_user_invoices(user_dict)
195
 
196
  def create_stripe_checkout(plan: str, user_dict):
197
- if not user_dict or not STRIPE_SECRET_KEY: return "Platby nejsou nakonfigurovány."
198
  prices = {"basic": STRIPE_PRICE_BASIC, "pro": STRIPE_PRICE_PRO, "enterprise": STRIPE_PRICE_ENTERPRISE}
199
  try:
200
  session = stripe.checkout.Session.create(
@@ -203,8 +235,8 @@ def create_stripe_checkout(plan: str, user_dict):
203
  cancel_url=f"{APP_URL}?checkout=cancel", customer_email=user_dict['email'],
204
  metadata={"user_id": user_dict['id'], "plan": plan}
205
  )
206
- return f"[Zaplatit zde]({session.url})"
207
- except Exception as e: return f"Chyba: {e}"
208
 
209
  # ==========================================
210
  # 5. GRADIO UI ARCHITECTURE
@@ -215,70 +247,71 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", ne
215
  # ---------------- VIEW 1: LANDING ----------------
216
  with gr.Group(visible=True) as landing_view:
217
  with gr.Row(elem_id="navbar"):
218
- gr.HTML(f"<div style='font-weight:700;font-size:20px;'>{ICON_BOLT}OmniParse AI</div>")
219
- landing_login_btn = gr.Button("Log In", elem_classes="btn-ghost", size="sm")
220
  with gr.Column(elem_id="hero"):
221
  gr.HTML("<h1>Invoice processing in <span style='color: var(--accent);'>seconds</span>, not hours.</h1>")
222
  gr.HTML("<p>AI extracts vendor, dates, amounts and line items from any PDF or image. Export to CSV, JSON or Excel.</p>")
223
- landing_signup_btn = gr.Button("Start Free — 20 invoices", size="lg")
224
- gr.HTML("<div style='margin-top: 40px; color: var(--muted); font-size: 14px;'>99.2% accuracy · &lt;4s per invoice · 40+ formats</div>")
225
 
226
  # ---------------- VIEW 2: AUTH ----------------
227
  with gr.Group(visible=False) as auth_view:
228
- with gr.Column(elem_id="hero", elem_classes="auth-container"):
229
- gr.HTML("<h2 style='margin-bottom:24px;'>Welcome to OmniParse</h2>")
 
230
  auth_email = gr.Textbox(label="Work Email", placeholder="you@company.com")
231
  auth_pass = gr.Textbox(label="Password", type="password", placeholder="Min 12 chars, 1 upper, 1 symbol")
232
- auth_submit = gr.Button("Log In / Sign Up", size="lg")
233
  auth_msg = gr.Markdown("")
234
- auth_back = gr.Button("Back to Home", elem_classes="btn-ghost", size="sm")
235
 
236
  # ---------------- VIEW 3: DASHBOARD ----------------
237
  with gr.Group(visible=False) as dash_view:
238
  with gr.Row(elem_id="navbar"):
239
- gr.HTML(f"<div style='font-weight:700;font-size:20px;'>{ICON_BOLT}Dashboard</div>")
240
  dash_logout = gr.Button("Log Out", elem_classes="btn-danger", size="sm")
241
 
242
- with gr.Row(elem_classes="dash-container"):
243
  # Sidebar
244
- with gr.Column(elem_classes="dash-sidebar"):
245
- nav_upload = gr.Button("Upload Invoice", elem_classes="btn-ghost")
246
- nav_invoices = gr.Button("My Invoices", elem_classes="btn-ghost")
247
- nav_upgrade = gr.Button("Upgrade Plan", elem_classes="btn-ghost")
248
-
 
249
  # Main Content Area
250
- with gr.Column(elem_classes="dash-main"):
251
  # Upload Panel
252
  with gr.Group(visible=True) as panel_upload:
253
- gr.HTML("<h3>Upload Invoices</h3>")
254
  file_input = gr.File(file_count="multiple", file_types=[".pdf", ".png", ".jpg"])
255
- process_btn = gr.Button("Process Invoices", variant="primary")
256
  upload_status = gr.Markdown("")
257
 
258
  # Invoices Panel
259
  with gr.Group(visible=False) as panel_invoices:
260
- gr.HTML("<h3>Processed Invoices</h3>")
261
  invoices_table = gr.Dataframe(headers=["Vendor", "Invoice#", "Total", "Status"], wrap=True, interactive=False)
262
- export_btn = gr.Button("Export to CSV", elem_classes="btn-ghost")
263
  csv_file = gr.File(label="Download CSV", visible=False)
264
 
265
  # Upgrade Panel
266
  with gr.Group(visible=False) as panel_upgrade:
267
- gr.HTML("<h3>Upgrade Your Plan</h3>")
268
  with gr.Row():
269
- gr.HTML("<div style='text-align:center;padding:20px;'><h4>Basic</h4><h2 style='color:var(--accent);'>$29/mo</h2></div>")
270
- gr.HTML("<div style='text-align:center;padding:20px;'><h4>Pro</h4><h2 style='color:var(--accent);'>$129/mo</h2></div>")
271
- gr.HTML("<div style='text-align:center;padding:20px;'><h4>Enterprise</h4><h2 style='color:var(--accent);'>$499/mo</h2></div>")
272
  with gr.Row():
273
- up_basic = gr.Button("Choose Basic")
274
- up_pro = gr.Button("Choose Pro")
275
- up_ent = gr.Button("Choose Enterprise")
276
  checkout_link = gr.Markdown("")
277
 
278
  # ==========================================
279
  # 6. EVENT BINDINGS (FUNKČNÍ INTERAKCE)
280
  # ==========================================
281
- # Navigation Logic
282
  def show_auth(): return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
283
  def show_landing(): return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
284
  def show_dashboard(user):
@@ -289,7 +322,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", ne
289
  landing_signup_btn.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
290
  auth_back.click(fn=show_landing, outputs=[landing_view, auth_view, dash_view])
291
 
292
- # Auth Logic
293
  auth_submit.click(
294
  fn=handle_auth,
295
  inputs=[auth_email, auth_pass],
@@ -300,7 +332,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", ne
300
 
301
  dash_logout.click(fn=lambda: (None, *show_landing()), outputs=[user_state, landing_view, auth_view, dash_view])
302
 
303
- # Dashboard Navigation Logic
304
  def switch_panel(panel_name):
305
  u, i, p = gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
306
  if panel_name == "upload": u = gr.update(visible=True)
@@ -312,7 +343,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", ne
312
  nav_invoices.click(fn=lambda: switch_panel("invoices"), outputs=[panel_upload, panel_invoices, panel_upgrade])
313
  nav_upgrade.click(fn=lambda: switch_panel("upgrade"), outputs=[panel_upload, panel_invoices, panel_upgrade])
314
 
315
- # Processing Logic
316
  process_btn.click(fn=handle_upload, inputs=[file_input, user_state], outputs=[upload_status, invoices_table])
317
 
318
  def handle_export(user_dict):
@@ -323,7 +353,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", ne
323
  return gr.update(value=temp.name, visible=True)
324
  export_btn.click(fn=handle_export, inputs=[user_state], outputs=[csv_file])
325
 
326
- # Stripe Logic
327
  up_basic.click(fn=lambda u: create_stripe_checkout("basic", u), inputs=[user_state], outputs=[checkout_link])
328
  up_pro.click(fn=lambda u: create_stripe_checkout("pro", u), inputs=[user_state], outputs=[checkout_link])
329
  up_ent.click(fn=lambda u: create_stripe_checkout("enterprise", u), inputs=[user_state], outputs=[checkout_link])
 
116
  except Exception as e: return {"filename": os.path.basename(file_path), "status": "error"}
117
 
118
  # ==========================================
119
+ # 3. CYBERpunk / COMMAND CENTER DESIGN
120
  # ==========================================
121
  CUSTOM_CSS = """
122
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
123
+ :root {
124
+ --bg: #050505;
125
+ --panel: rgba(15, 15, 15, 0.6);
126
+ --border: rgba(255, 255, 255, 0.08);
127
+ --text: #ffffff;
128
+ --muted: #6b7280;
129
+ --accent: #00ff9d;
130
+ --accent-glow: rgba(0, 255, 157, 0.15);
131
+ --danger: #ff3366;
132
+ }
133
  * { box-sizing: border-box; font-family: 'Inter', sans-serif; }
134
+ body { background: var(--bg); color: var(--text); margin: 0; background-image: radial-gradient(circle at 50% 0%, rgba(0, 255, 157, 0.05) 0%, transparent 50%); }
135
+ .gradio-container { max-width: 100% !important; padding: 0 !important; background: transparent !important; min-height: 100vh; }
136
  .hide { display: none !important; }
137
 
138
  /* Layout */
139
+ .app-shell { display: flex; min-height: 100vh; }
140
+ .sidebar { width: 260px; border-right: 1px solid var(--border); padding: 24px; display: flex; flex-direction: column; gap: 8px; background: rgba(0,0,0,0.3); backdrop-filter: blur(20px); position: sticky; top: 0; height: 100vh; }
141
+ .main-area { flex: 1; padding: 40px 60px; overflow-y: auto; }
142
+ .navbar { display: flex; justify-content: space-between; align-items: center; padding: 20px 40px; border-bottom: 1px solid var(--border); backdrop-filter: blur(10px); position: sticky; top: 0; background: rgba(5,5,5,0.8); z-index: 100; }
143
+ .hero { text-align: center; padding: 120px 20px; }
144
+ .hero h1 { font-size: 64px; font-weight: 700; margin-bottom: 24px; background: linear-gradient(180deg, #fff 0%, #888 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.1; letter-spacing: -2px; }
145
+ .hero p { font-size: 20px; color: var(--muted); max-width: 600px; margin: 0 auto 40px; }
146
+
147
+ /* Gradio Overrides - Making it look like a hacker SaaS */
148
+ .gr-button {
149
+ background: transparent !important;
150
+ border: 1px solid var(--border) !important;
151
+ color: var(--muted) !important;
152
+ border-radius: 8px !important;
153
+ padding: 10px 16px !important;
154
+ font-weight: 500 !important;
155
+ transition: all 0.2s !important;
156
+ box-shadow: none !important;
157
+ text-align: left !important;
158
+ justify-content: flex-start !important;
159
+ font-size: 14px !important;
160
+ }
161
+ .gr-button:hover { border-color: var(--accent) !important; color: var(--accent) !important; background: var(--accent-glow) !important; }
162
+ .btn-primary { background: var(--accent) !important; color: #000 !important; border: none !important; font-weight: 700 !important; justify-content: center !important; text-align: center !important; }
163
+ .btn-primary:hover { box-shadow: 0 0 30px var(--accent-glow) !important; transform: translateY(-1px) !important; background: var(--accent) !important; color: #000 !important; }
164
+ .btn-danger { background: transparent !important; border: 1px solid var(--danger) !important; color: var(--danger) !important; justify-content: center !important; text-align: center !important; }
165
+ .btn-danger:hover { background: var(--danger) !important; color: #fff !important; }
166
+
167
+ .gr-textbox { background: rgba(0,0,0,0.3) !important; border: 1px solid var(--border) !important; border-radius: 8px !important; color: var(--text) !important; }
168
+ .gr-textbox input { color: var(--text) !important; }
169
+ .gr-textbox:focus { border-color: var(--accent) !important; box-shadow: 0 0 0 2px var(--accent-glow) !important; }
170
  .gr-form { background: transparent !important; border: none !important; }
171
  .gr-box { border: none !important; background: transparent !important; }
172
+
173
+ /* Dataframe - Command Center Table */
174
+ .gr-dataframe { border: 1px solid var(--border) !important; border-radius: 12px !important; overflow: hidden; background: rgba(0,0,0,0.2) !important; }
175
  .gr-dataframe table { background: transparent !important; color: var(--text) !important; border-collapse: collapse; width: 100%; }
176
+ .gr-dataframe th { background: rgba(0,0,0,0.5) !important; border-bottom: 1px solid var(--border) !important; color: var(--accent) !important; text-align: left; padding: 16px !important; font-family: 'JetBrains Mono', monospace !important; font-size: 12px !important; text-transform: uppercase; letter-spacing: 1px; }
177
+ .gr-dataframe td { border-bottom: 1px solid var(--border) !important; padding: 16px !important; font-family: 'Inter', sans-serif !important; }
178
+ .gr-dataframe tr:hover { background: var(--accent-glow) !important; }
179
+
180
  .gr-markdown { color: var(--text) !important; background: transparent !important; }
181
+ .gr-markdown a { color: var(--accent) !important; text-decoration: none !important; border-bottom: 1px solid var(--accent) !important; }
182
  footer { display: none !important; }
 
183
 
184
+ /* Logo */
185
+ .logo-text { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 18px; display: flex; align-items: center; gap: 10px; color: var(--text); }
186
+ .logo-dot { width: 10px; height: 10px; background: var(--accent); border-radius: 50%; box-shadow: 0 0 10px var(--accent); animation: pulse 2s infinite; }
187
+ @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
188
+ """
189
 
190
  # ==========================================
191
  # 4. BACKEND FUNCTIONS
 
198
  def handle_auth(email, password, request: gr.Request):
199
  ip = request.client.host if request else "127.0.0.1"
200
  if rate_limiter.is_rate_limited(f"auth_{ip}", 5, 300):
201
+ return None, "🚨 Too many attempts.", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
202
 
203
  if not supabase: return None, "DB Error", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
204
 
205
  res = supabase.table("users").select("*").eq("email", email).execute()
206
  if res.data and verify_password(password, res.data[0]['password']):
207
  user = res.data[0]
208
+ return user, "Login successful.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
209
 
210
  if not res.data:
211
  if not validate_password_strength(password):
212
+ return None, "Weak password (min 12 chars, 1 upper, 1 number, 1 symbol).", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
213
  hashed = hash_password(password)
214
  new_user = supabase.table("users").insert({"email": email, "password": hashed, "plan": "free"}).execute()
215
  user = new_user.data[0]
216
+ return user, "Account created!", gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
217
 
218
+ return None, "Invalid credentials.", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
219
 
220
  def handle_upload(files, user_dict):
221
+ if not user_dict: return "Not logged in.", get_user_invoices(user_dict)
222
+ if not files: return "No files.", get_user_invoices(user_dict)
223
  for file in files:
224
  if os.path.getsize(file.name) > 20 * 1024 * 1024: continue
225
  process_invoice_file(file.name, user_dict['id'])
226
+ return f"Processed {len(files)} files.", get_user_invoices(user_dict)
227
 
228
  def create_stripe_checkout(plan: str, user_dict):
229
+ if not user_dict or not STRIPE_SECRET_KEY: return "Payments not configured."
230
  prices = {"basic": STRIPE_PRICE_BASIC, "pro": STRIPE_PRICE_PRO, "enterprise": STRIPE_PRICE_ENTERPRISE}
231
  try:
232
  session = stripe.checkout.Session.create(
 
235
  cancel_url=f"{APP_URL}?checkout=cancel", customer_email=user_dict['email'],
236
  metadata={"user_id": user_dict['id'], "plan": plan}
237
  )
238
+ return f"[Complete Payment Here]({session.url})"
239
+ except Exception as e: return f"Error: {e}"
240
 
241
  # ==========================================
242
  # 5. GRADIO UI ARCHITECTURE
 
247
  # ---------------- VIEW 1: LANDING ----------------
248
  with gr.Group(visible=True) as landing_view:
249
  with gr.Row(elem_id="navbar"):
250
+ gr.HTML("<div class='logo-text'><div class='logo-dot'></div>OmniParse_AI</div>")
251
+ landing_login_btn = gr.Button("Log In", size="sm")
252
  with gr.Column(elem_id="hero"):
253
  gr.HTML("<h1>Invoice processing in <span style='color: var(--accent);'>seconds</span>, not hours.</h1>")
254
  gr.HTML("<p>AI extracts vendor, dates, amounts and line items from any PDF or image. Export to CSV, JSON or Excel.</p>")
255
+ landing_signup_btn = gr.Button("Start Free — 20 invoices", elem_classes="btn-primary", size="lg")
256
+ gr.HTML("<div style='margin-top: 40px; color: var(--muted); font-size: 14px; font-family: JetBrains Mono;'>99.2% accuracy · &lt;4s per invoice · 40+ formats</div>")
257
 
258
  # ---------------- VIEW 2: AUTH ----------------
259
  with gr.Group(visible=False) as auth_view:
260
+ with gr.Column(elem_id="hero"):
261
+ gr.HTML("<div class='logo-text' style='justify-content:center; margin-bottom:24px;'><div class='logo-dot'></div>OmniParse_AI</div>")
262
+ gr.HTML("<h2 style='margin-bottom:24px; font-size: 32px;'>Access Terminal</h2>")
263
  auth_email = gr.Textbox(label="Work Email", placeholder="you@company.com")
264
  auth_pass = gr.Textbox(label="Password", type="password", placeholder="Min 12 chars, 1 upper, 1 symbol")
265
+ auth_submit = gr.Button("Execute", elem_classes="btn-primary", size="lg")
266
  auth_msg = gr.Markdown("")
267
+ auth_back = gr.Button("Back to Home", elem_classes="btn-danger", size="sm")
268
 
269
  # ---------------- VIEW 3: DASHBOARD ----------------
270
  with gr.Group(visible=False) as dash_view:
271
  with gr.Row(elem_id="navbar"):
272
+ gr.HTML("<div class='logo-text'><div class='logo-dot'></div>OmniParse_AI</div>")
273
  dash_logout = gr.Button("Log Out", elem_classes="btn-danger", size="sm")
274
 
275
+ with gr.Row(elem_classes="app-shell"):
276
  # Sidebar
277
+ with gr.Column(elem_classes="sidebar"):
278
+ gr.HTML("<div style='color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:1px; margin-bottom:10px;'>Menu</div>")
279
+ nav_upload = gr.Button("1. Upload Invoice")
280
+ nav_invoices = gr.Button("2. My Invoices")
281
+ nav_upgrade = gr.Button("3. Upgrade Plan")
282
+
283
  # Main Content Area
284
+ with gr.Column(elem_classes="main-area"):
285
  # Upload Panel
286
  with gr.Group(visible=True) as panel_upload:
287
+ gr.HTML("<h2 style='margin-bottom:20px;'>Upload Invoices</h2>")
288
  file_input = gr.File(file_count="multiple", file_types=[".pdf", ".png", ".jpg"])
289
+ process_btn = gr.Button("Process Invoices", elem_classes="btn-primary", variant="primary")
290
  upload_status = gr.Markdown("")
291
 
292
  # Invoices Panel
293
  with gr.Group(visible=False) as panel_invoices:
294
+ gr.HTML("<h2 style='margin-bottom:20px;'>Processed Invoices</h2>")
295
  invoices_table = gr.Dataframe(headers=["Vendor", "Invoice#", "Total", "Status"], wrap=True, interactive=False)
296
+ export_btn = gr.Button("Export to CSV", elem_classes="btn-primary")
297
  csv_file = gr.File(label="Download CSV", visible=False)
298
 
299
  # Upgrade Panel
300
  with gr.Group(visible=False) as panel_upgrade:
301
+ gr.HTML("<h2 style='margin-bottom:20px;'>Upgrade Plan</h2>")
302
  with gr.Row():
303
+ gr.HTML("<div style='text-align:center;padding:30px;border:1px solid var(--border);border-radius:12px;'><h4>Basic</h4><h2 style='color:var(--accent);'>$29/mo</h2></div>")
304
+ gr.HTML("<div style='text-align:center;padding:30px;border:1px solid var(--border);border-radius:12px;'><h4>Pro</h4><h2 style='color:var(--accent);'>$129/mo</h2></div>")
305
+ gr.HTML("<div style='text-align:center;padding:30px;border:1px solid var(--border);border-radius:12px;'><h4>Enterprise</h4><h2 style='color:var(--accent);'>$499/mo</h2></div>")
306
  with gr.Row():
307
+ up_basic = gr.Button("Choose Basic", elem_classes="btn-primary")
308
+ up_pro = gr.Button("Choose Pro", elem_classes="btn-primary")
309
+ up_ent = gr.Button("Choose Enterprise", elem_classes="btn-primary")
310
  checkout_link = gr.Markdown("")
311
 
312
  # ==========================================
313
  # 6. EVENT BINDINGS (FUNKČNÍ INTERAKCE)
314
  # ==========================================
 
315
  def show_auth(): return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
316
  def show_landing(): return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
317
  def show_dashboard(user):
 
322
  landing_signup_btn.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
323
  auth_back.click(fn=show_landing, outputs=[landing_view, auth_view, dash_view])
324
 
 
325
  auth_submit.click(
326
  fn=handle_auth,
327
  inputs=[auth_email, auth_pass],
 
332
 
333
  dash_logout.click(fn=lambda: (None, *show_landing()), outputs=[user_state, landing_view, auth_view, dash_view])
334
 
 
335
  def switch_panel(panel_name):
336
  u, i, p = gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
337
  if panel_name == "upload": u = gr.update(visible=True)
 
343
  nav_invoices.click(fn=lambda: switch_panel("invoices"), outputs=[panel_upload, panel_invoices, panel_upgrade])
344
  nav_upgrade.click(fn=lambda: switch_panel("upgrade"), outputs=[panel_upload, panel_invoices, panel_upgrade])
345
 
 
346
  process_btn.click(fn=handle_upload, inputs=[file_input, user_state], outputs=[upload_status, invoices_table])
347
 
348
  def handle_export(user_dict):
 
353
  return gr.update(value=temp.name, visible=True)
354
  export_btn.click(fn=handle_export, inputs=[user_state], outputs=[csv_file])
355
 
 
356
  up_basic.click(fn=lambda u: create_stripe_checkout("basic", u), inputs=[user_state], outputs=[checkout_link])
357
  up_pro.click(fn=lambda u: create_stripe_checkout("pro", u), inputs=[user_state], outputs=[checkout_link])
358
  up_ent.click(fn=lambda u: create_stripe_checkout("enterprise", u), inputs=[user_state], outputs=[checkout_link])