FauziIsyrinApridal commited on
Commit
1b6f6c4
·
1 Parent(s): 5dd6e15
Files changed (1) hide show
  1. app/auth.py +250 -214
app/auth.py CHANGED
@@ -2,7 +2,6 @@ import os
2
  import base64
3
  import streamlit as st
4
  from app.db import supabase
5
- import streamlit.components.v1 as components
6
 
7
 
8
  def auth_view():
@@ -11,7 +10,6 @@ def auth_view():
11
  left, center, right = st.columns([1, 2, 1])
12
  with center:
13
  # Header: smaller PNP logo and centered title
14
- # Path logo
15
  logo_path = os.path.join("assets", "pnp-logo.png")
16
 
17
  # Convert ke base64 biar bisa di-embed langsung
@@ -32,91 +30,15 @@ def auth_view():
32
  unsafe_allow_html=True
33
  )
34
 
35
- # --- Hash to Query Parameter Migration ---
36
- # Use more compatible approach for Streamlit
37
- st.markdown(
38
- """
39
- <script>
40
- // Streamlit-compatible hash migration
41
- if (typeof window !== 'undefined') {
42
- (function() {
43
- try {
44
- var hash = window.location.hash;
45
- var hasParams = hash && hash.length > 1;
46
- var migrated = sessionStorage ? sessionStorage.getItem('hash_migrated') : null;
47
-
48
- if (hasParams && !migrated) {
49
- // Simple string parsing to avoid URL constructor issues
50
- var hashContent = hash.substring(1);
51
- var pairs = hashContent.split('&');
52
- var queryParams = new URLSearchParams(window.location.search);
53
- var hasAuthParams = false;
54
-
55
- for (var i = 0; i < pairs.length; i++) {
56
- var pair = pairs[i].split('=');
57
- if (pair.length === 2) {
58
- var key = decodeURIComponent(pair[0]);
59
- var value = decodeURIComponent(pair[1]);
60
-
61
- // Check for Supabase auth parameters
62
- if (key === 'access_token' || key === 'type' || key === 'refresh_token') {
63
- hasAuthParams = true;
64
- }
65
-
66
- queryParams.set(key, value);
67
- }
68
- }
69
-
70
- if (hasAuthParams) {
71
- if (sessionStorage) {
72
- sessionStorage.setItem('hash_migrated', 'true');
73
- }
74
-
75
- var newUrl = window.location.origin + window.location.pathname;
76
- if (queryParams.toString()) {
77
- newUrl += '?' + queryParams.toString();
78
- }
79
-
80
- window.location.replace(newUrl);
81
- }
82
- }
83
- } catch (e) {
84
- console && console.log && console.log('Hash migration error:', e);
85
- }
86
- })();
87
- }
88
- </script>
89
- """,
90
- unsafe_allow_html=True
91
- )
92
-
93
- # Clear migration flag after successful page load with query params
94
- try:
95
- qp = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
96
- get_q = lambda k: qp.get(k, None) if hasattr(st, 'query_params') else (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
97
-
98
- # If we have recovery parameters, clear the migration flag
99
- if get_q("type") == "recovery":
100
- st.markdown(
101
- """
102
- <script>
103
- if (typeof sessionStorage !== 'undefined') {
104
- sessionStorage.removeItem('hash_migrated');
105
- }
106
- </script>
107
- """,
108
- unsafe_allow_html=True
109
- )
110
- except Exception as e:
111
- pass # Silently handle errors in production
112
-
113
  # Read query params for recovery flow
114
  try:
115
- qp = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
116
- get_q = lambda k: qp.get(k, None) if hasattr(st, 'query_params') else (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
 
 
 
 
117
  except Exception:
118
- # Fallback for older Streamlit versions
119
- import urllib.parse as urlparse
120
  qp = {}
121
  get_q = lambda k: None
122
 
@@ -124,34 +46,127 @@ def auth_view():
124
  access_token = get_q("access_token")
125
  refresh_token = get_q("refresh_token")
126
 
127
- # Debug info (remove in production)
128
- if os.getenv("DEBUG_MODE") == "true" and st.checkbox("Show debug info"):
129
- st.write("Query params:", dict(qp) if hasattr(qp, 'items') else qp)
130
- st.write("Type:", q_type)
131
- st.write("Has access token:", bool(access_token))
132
- st.write("Has refresh token:", bool(refresh_token))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
 
 
 
 
 
 
 
 
 
134
  if q_type == "recovery":
135
- st.success("🔑 Halaman Reset Password")
136
  st.info("Silakan masukkan password baru Anda di bawah ini.")
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  with st.form("reset_password_form", clear_on_submit=False):
139
  st.markdown("### Set Password Baru")
140
  new_password = st.text_input("Password Baru", type="password", help="Minimal 6 karakter")
141
  confirm_password = st.text_input("Konfirmasi Password Baru", type="password")
142
- submit_reset = st.form_submit_button(" Update Password", use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  if submit_reset:
 
145
  if not new_password:
146
- st.error(" Password tidak boleh kosong.")
147
  elif len(new_password) < 6:
148
- st.error(" Password minimal 6 karakter.")
149
  elif new_password != confirm_password:
150
- st.error(" Konfirmasi password tidak sama.")
151
- elif not access_token or not refresh_token:
152
- st.error(" Token pemulihan tidak valid. Silakan klik ulang tautan dari email.")
153
- st.info("💡 Tip: Pastikan Anda mengklik tautan langsung dari email tanpa menyalin-paste.")
154
  else:
 
155
  try:
156
  with st.spinner("Mengupdate password..."):
157
  # Set session dengan token dari URL
@@ -161,121 +176,90 @@ def auth_view():
161
  result = supabase.auth.update_user({"password": new_password})
162
 
163
  if result:
164
- st.success(" Password berhasil diubah!")
165
- st.info("🔄 Silakan login dengan password baru Anda.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- # Clear query parameters setelah sukses
168
- st.markdown(
169
- """
170
- <script>
171
- if (typeof window !== 'undefined') {
172
- setTimeout(function() {
173
- try {
174
- var params = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type'];
175
- var search = new URLSearchParams(window.location.search);
176
- var changed = false;
177
-
178
- for (var i = 0; i < params.length; i++) {
179
- if (search.has(params[i])) {
180
- search.delete(params[i]);
181
- changed = true;
182
- }
183
- }
184
-
185
- if (changed) {
186
- var newUrl = window.location.pathname;
187
- if (search.toString()) {
188
- newUrl += '?' + search.toString();
189
- }
190
- window.history.replaceState({}, document.title, newUrl);
191
- }
192
-
193
- if (typeof sessionStorage !== 'undefined') {
194
- sessionStorage.removeItem('hash_migrated');
195
- }
196
-
197
- // Optional: reload page to show login form
198
- setTimeout(function() { window.location.reload(); }, 2000);
199
- } catch (e) {
200
- console && console.log && console.log('Cleanup error:', e);
201
- window.location.reload();
202
- }
203
- }, 1000);
204
- }
205
- </script>
206
- """,
207
- unsafe_allow_html=True
208
- )
209
  else:
210
- st.error(" Gagal mengubah password. Silakan coba lagi.")
211
 
212
  except Exception as e:
213
- st.error(f" Error: {str(e)}")
214
- st.info("💡 Jika masalah berlanjut, minta tautan reset password baru.")
215
 
216
- # Tampilkan tombol untuk kembali ke login
217
- if st.button("← Kembali ke Login", use_container_width=True):
218
- # Clear query params and reload
219
- st.markdown(
220
- """
221
- <script>
222
- if (typeof window !== 'undefined') {
223
- try {
224
- var params = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type'];
225
- var search = new URLSearchParams(window.location.search);
226
-
227
- for (var i = 0; i < params.length; i++) {
228
- search.delete(params[i]);
229
- }
230
-
231
- var newUrl = window.location.pathname;
232
- if (search.toString()) {
233
- newUrl += '?' + search.toString();
234
- }
235
-
236
- window.history.replaceState({}, document.title, newUrl);
237
-
238
- if (typeof sessionStorage !== 'undefined') {
239
- sessionStorage.removeItem('hash_migrated');
240
- }
241
-
242
- window.location.reload();
243
- } catch (e) {
244
- window.location.href = window.location.pathname;
245
- }
246
- }
247
- </script>
248
- """,
249
- unsafe_allow_html=True
250
- )
251
 
252
  return # Exit early, don't show other tabs
253
 
254
- # Auth tabs inside wrapper (only show when not in recovery mode)
255
- tab_login, tab_register, tab_forgot = st.tabs([" Login", "📝 Register", "🔑 Forgot Password"])
256
 
257
  with tab_login:
258
- st.markdown("### Masuk ke Akun Anda")
 
 
 
 
 
 
 
 
 
259
  with st.form("login_form"):
260
- email = st.text_input(" Email", placeholder="contoh@email.com")
261
- password = st.text_input(" Password", type="password")
262
- submitted = st.form_submit_button(" Login", use_container_width=True)
 
263
 
264
  if submitted:
265
  if not email or not password:
266
- st.error(" Email dan password harus diisi.")
267
  return
268
 
269
  # Demo password fallback
270
  shared_pw = os.getenv("APP_DEMO_PASSWORD")
271
  if shared_pw and password == shared_pw:
272
  st.session_state["user"] = {"id": "demo-user", "email": email or "demo@example.com"}
273
- st.success(" Login demo berhasil")
274
  st.rerun()
275
  return
276
 
277
  try:
278
- with st.spinner("Logging in..."):
279
  auth_res = supabase.auth.sign_in_with_password({
280
  "email": email,
281
  "password": password,
@@ -289,33 +273,52 @@ def auth_view():
289
  "access_token": getattr(session_obj, "access_token", None),
290
  "refresh_token": getattr(session_obj, "refresh_token", None),
291
  }
292
- st.success(" Login berhasil!")
293
  st.rerun()
294
  else:
295
- st.error(" Email atau password salah.")
 
296
  except Exception as e:
297
- st.error(f" Gagal login: {str(e)}")
298
 
299
  with tab_register:
300
- st.markdown("### Buat Akun Baru")
301
- st.caption(" Anda akan menerima email konfirmasi setelah registrasi.")
 
302
  with st.form("register_form"):
303
- r_email = st.text_input(" Email", key="reg_email", placeholder="contoh@email.com")
304
- r_password = st.text_input(" Password", type="password", key="reg_password", help="Minimal 6 karakter")
305
- r_password2 = st.text_input(" Konfirmasi Password", type="password", key="reg_password2")
306
- submitted_r = st.form_submit_button("📝 Daftar", use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
  if submitted_r:
309
  if not r_email or not r_password:
310
- st.error(" Email dan password harus diisi.")
311
  elif len(r_password) < 6:
312
- st.error(" Password minimal 6 karakter.")
313
  elif r_password != r_password2:
314
- st.error(" Konfirmasi password tidak sama.")
315
  else:
316
  try:
317
  with st.spinner("Mendaftarkan akun..."):
318
- # Set redirect URL dengan prioritas yang lebih clear
319
  redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
320
  if not redirect_url:
321
  redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
@@ -325,25 +328,40 @@ def auth_view():
325
  "password": r_password,
326
  "options": {"email_redirect_to": redirect_url}
327
  })
328
- st.success(" Registrasi berhasil!")
329
- st.info(" Silakan cek email untuk konfirmasi akun.")
 
 
 
 
 
330
  except Exception as e:
331
- st.error(f" Gagal registrasi: {str(e)}")
332
 
333
  with tab_forgot:
334
  st.markdown("### Reset Password")
335
- st.caption(" Kami akan mengirim tautan reset password ke email Anda.")
 
 
 
 
 
 
 
 
 
 
 
336
  with st.form("forgot_form"):
337
- f_email = st.text_input(" Email", key="forgot_email", placeholder="contoh@email.com")
338
- submitted_f = st.form_submit_button(" Kirim Link Reset", use_container_width=True)
339
 
340
  if submitted_f:
341
  if not f_email:
342
- st.error(" Email harus diisi.")
343
  else:
344
  try:
345
- with st.spinner("Mengirim email reset..."):
346
- # Set redirect URL dengan prioritas yang lebih clear
347
  redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
348
  if not redirect_url:
349
  redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
@@ -352,7 +370,25 @@ def auth_view():
352
  f_email,
353
  {"redirect_to": redirect_url}
354
  )
355
- st.success(" Email reset password telah dikirim!")
356
- st.info(" Periksa kotak masuk (dan folder spam) untuk tautan reset password.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  except Exception as e:
358
- st.error(f" Gagal mengirim email: {str(e)}")
 
2
  import base64
3
  import streamlit as st
4
  from app.db import supabase
 
5
 
6
 
7
  def auth_view():
 
10
  left, center, right = st.columns([1, 2, 1])
11
  with center:
12
  # Header: smaller PNP logo and centered title
 
13
  logo_path = os.path.join("assets", "pnp-logo.png")
14
 
15
  # Convert ke base64 biar bisa di-embed langsung
 
30
  unsafe_allow_html=True
31
  )
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # Read query params for recovery flow
34
  try:
35
+ if hasattr(st, 'query_params'):
36
+ qp = st.query_params
37
+ get_q = lambda k: qp.get(k, None)
38
+ else:
39
+ qp = st.experimental_get_query_params()
40
+ get_q = lambda k: (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
41
  except Exception:
 
 
42
  qp = {}
43
  get_q = lambda k: None
44
 
 
46
  access_token = get_q("access_token")
47
  refresh_token = get_q("refresh_token")
48
 
49
+ # Check if user is coming from email but params are in wrong format
50
+ # Tampilkan helper hanya jika tidak ada query params yang valid
51
+ if not q_type and not access_token:
52
+ st.markdown("---")
53
+ st.info("Datang dari link email reset password? Jika halaman reset tidak muncul otomatis, klik tombol di bawah:")
54
+
55
+ col1, col2 = st.columns([1, 1])
56
+ with col1:
57
+ if st.button("Saya dari Link Email", use_container_width=True, help="Klik jika Anda mengakses dari link email reset password"):
58
+ st.info("Instruksi sederhana:")
59
+ st.markdown("""
60
+ 1. **Lihat URL di address bar browser Anda**
61
+ 2. **Ganti tanda `#` dengan `?`** (jika ada)
62
+ 3. **Tekan Enter** untuk reload halaman
63
+
64
+ **Contoh:**
65
+ - Dari: `app.com/#access_token=abc`
66
+ - Jadi: `app.com/?access_token=abc`
67
+ """)
68
+
69
+ with col2:
70
+ if st.button("Butuh Bantuan", use_container_width=True):
71
+ st.markdown("""
72
+ **Masalah umum dan solusi:**
73
+
74
+ **Link tidak berfungsi?**
75
+ Coba minta link reset password baru
76
+
77
+ **Halaman kosong setelah klik link?**
78
+ Ganti `#` dengan `?` di URL browser
79
+
80
+ **Masih tidak bisa?**
81
+ Gunakan tab "Forgot Password" di bawah
82
+ """)
83
 
84
+ # Debug info untuk development (hanya tampil jika ada query params)
85
+ if os.getenv("DEBUG_MODE") == "true" and (q_type or access_token):
86
+ with st.expander("Debug Info", expanded=False):
87
+ st.write("Query params:", dict(qp) if hasattr(qp, 'items') else qp)
88
+ st.write("Type:", q_type)
89
+ st.write("Has access token:", bool(access_token))
90
+ st.write("Has refresh token:", bool(refresh_token))
91
+
92
+ # RECOVERY MODE - Reset Password
93
  if q_type == "recovery":
94
+ st.success("Halaman Reset Password")
95
  st.info("Silakan masukkan password baru Anda di bawah ini.")
96
 
97
+ # Validasi token availability
98
+ if not access_token or not refresh_token:
99
+ st.error("Token reset password tidak valid atau sudah expired.")
100
+ st.markdown("**Kemungkinan penyebab:**")
101
+ st.markdown("- Link sudah pernah digunakan sebelumnya")
102
+ st.markdown("- Link sudah expired (biasanya berlaku 1 jam)")
103
+ st.markdown("- URL tidak lengkap")
104
+
105
+ st.info("**Solusi:** Minta link reset password yang baru menggunakan form di bawah.")
106
+
107
+ if st.button("Minta Link Reset Baru", use_container_width=True):
108
+ # Clear query params dan redirect ke forgot password
109
+ if hasattr(st, 'query_params'):
110
+ params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
111
+ for param in params_to_clear:
112
+ if param in st.query_params:
113
+ del st.query_params[param]
114
+ st.rerun()
115
+ return
116
+
117
+ # Form untuk reset password
118
  with st.form("reset_password_form", clear_on_submit=False):
119
  st.markdown("### Set Password Baru")
120
  new_password = st.text_input("Password Baru", type="password", help="Minimal 6 karakter")
121
  confirm_password = st.text_input("Konfirmasi Password Baru", type="password")
122
+
123
+ # Password strength indicator
124
+ if new_password:
125
+ strength_score = 0
126
+ feedback = []
127
+
128
+ if len(new_password) >= 6:
129
+ strength_score += 1
130
+ else:
131
+ feedback.append("Minimal 6 karakter")
132
+
133
+ if any(c.isupper() for c in new_password):
134
+ strength_score += 1
135
+ else:
136
+ feedback.append("Gunakan huruf besar")
137
+
138
+ if any(c.islower() for c in new_password):
139
+ strength_score += 1
140
+ else:
141
+ feedback.append("Gunakan huruf kecil")
142
+
143
+ if any(c.isdigit() for c in new_password):
144
+ strength_score += 1
145
+ else:
146
+ feedback.append("Gunakan angka")
147
+
148
+ # Display strength
149
+ if strength_score <= 1:
150
+ st.error(f"Password lemah: {', '.join(feedback)}")
151
+ elif strength_score <= 2:
152
+ st.warning(f"Password cukup: {', '.join(feedback)}")
153
+ elif strength_score <= 3:
154
+ st.info("Password baik")
155
+ else:
156
+ st.success("Password sangat kuat!")
157
+
158
+ submit_reset = st.form_submit_button("Update Password", use_container_width=True)
159
 
160
  if submit_reset:
161
+ # Validasi input
162
  if not new_password:
163
+ st.error("Password tidak boleh kosong.")
164
  elif len(new_password) < 6:
165
+ st.error("Password minimal 6 karakter.")
166
  elif new_password != confirm_password:
167
+ st.error("Konfirmasi password tidak sama.")
 
 
 
168
  else:
169
+ # Proses update password
170
  try:
171
  with st.spinner("Mengupdate password..."):
172
  # Set session dengan token dari URL
 
176
  result = supabase.auth.update_user({"password": new_password})
177
 
178
  if result:
179
+ st.success("Password berhasil diubah!")
180
+ st.success("Sekarang Anda dapat login dengan password baru!")
181
+
182
+ # Show success message with countdown
183
+ placeholder = st.empty()
184
+ for i in range(5, 0, -1):
185
+ placeholder.info(f"Halaman akan kembali ke login dalam {i} detik...")
186
+ import time
187
+ time.sleep(1)
188
+
189
+ # Clear query params dan redirect
190
+ if hasattr(st, 'query_params'):
191
+ params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
192
+ for param in params_to_clear:
193
+ if param in st.query_params:
194
+ del st.query_params[param]
195
 
196
+ placeholder.empty()
197
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  else:
199
+ st.error("Gagal mengubah password. Silakan coba lagi.")
200
 
201
  except Exception as e:
202
+ st.error(f"Error: {str(e)}")
203
+ st.info("Jika masalah berlanjut, minta link reset password baru.")
204
 
205
+ # Tombol alternatif
206
+ st.markdown("---")
207
+ col1, col2 = st.columns([1, 1])
208
+ with col1:
209
+ if st.button("Kembali ke Login", use_container_width=True):
210
+ if hasattr(st, 'query_params'):
211
+ params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
212
+ for param in params_to_clear:
213
+ if param in st.query_params:
214
+ del st.query_params[param]
215
+ st.rerun()
216
+
217
+ with col2:
218
+ if st.button("Minta Link Baru", use_container_width=True):
219
+ if hasattr(st, 'query_params'):
220
+ params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
221
+ for param in params_to_clear:
222
+ if param in st.query_params:
223
+ del st.query_params[param]
224
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  return # Exit early, don't show other tabs
227
 
228
+ # NORMAL MODE - Login/Register/Forgot Password Tabs
229
+ tab_login, tab_register, tab_forgot = st.tabs(["Login", "Register", "Forgot Password"])
230
 
231
  with tab_login:
232
+ st.markdown("### Selamat Datang Kembali")
233
+
234
+ # Quick tips untuk login
235
+ with st.expander("Tips Login", expanded=False):
236
+ st.markdown("""
237
+ - Pastikan email dan password sudah benar
238
+ - Jika lupa password, gunakan tab "Forgot Password"
239
+ - Untuk akun baru, gunakan tab "Register"
240
+ """)
241
+
242
  with st.form("login_form"):
243
+ email = st.text_input("Email", placeholder="contoh@email.com")
244
+ password = st.text_input("Password", type="password")
245
+ remember_me = st.checkbox("Ingat saya")
246
+ submitted = st.form_submit_button("Login", use_container_width=True)
247
 
248
  if submitted:
249
  if not email or not password:
250
+ st.error("Email dan password harus diisi.")
251
  return
252
 
253
  # Demo password fallback
254
  shared_pw = os.getenv("APP_DEMO_PASSWORD")
255
  if shared_pw and password == shared_pw:
256
  st.session_state["user"] = {"id": "demo-user", "email": email or "demo@example.com"}
257
+ st.success("Login demo berhasil")
258
  st.rerun()
259
  return
260
 
261
  try:
262
+ with st.spinner("Memverifikasi kredensial..."):
263
  auth_res = supabase.auth.sign_in_with_password({
264
  "email": email,
265
  "password": password,
 
273
  "access_token": getattr(session_obj, "access_token", None),
274
  "refresh_token": getattr(session_obj, "refresh_token", None),
275
  }
276
+ st.success("Login berhasil! Selamat datang!")
277
  st.rerun()
278
  else:
279
+ st.error("Email atau password salah.")
280
+ st.info("Cek kembali email dan password Anda, atau gunakan 'Forgot Password' jika lupa.")
281
  except Exception as e:
282
+ st.error(f"Gagal login: {str(e)}")
283
 
284
  with tab_register:
285
+ st.markdown("### Daftar Akun Baru")
286
+ st.caption("Buat akun untuk mulai menggunakan PNP Bot")
287
+
288
  with st.form("register_form"):
289
+ r_email = st.text_input("Email", key="reg_email", placeholder="contoh@email.com")
290
+ r_password = st.text_input("Password", type="password", key="reg_password", help="Minimal 6 karakter, gunakan kombinasi huruf dan angka")
291
+ r_password2 = st.text_input("Konfirmasi Password", type="password", key="reg_password2")
292
+
293
+ # Password strength untuk register juga
294
+ if r_password:
295
+ strength_score = 0
296
+ if len(r_password) >= 6: strength_score += 1
297
+ if any(c.isupper() for c in r_password): strength_score += 1
298
+ if any(c.islower() for c in r_password): strength_score += 1
299
+ if any(c.isdigit() for c in r_password): strength_score += 1
300
+
301
+ if strength_score <= 1:
302
+ st.error("Password terlalu lemah")
303
+ elif strength_score <= 2:
304
+ st.warning("Password cukup")
305
+ elif strength_score <= 3:
306
+ st.info("Password baik")
307
+ else:
308
+ st.success("Password sangat kuat!")
309
+
310
+ submitted_r = st.form_submit_button("Daftar Sekarang", use_container_width=True)
311
 
312
  if submitted_r:
313
  if not r_email or not r_password:
314
+ st.error("Email dan password harus diisi.")
315
  elif len(r_password) < 6:
316
+ st.error("Password minimal 6 karakter.")
317
  elif r_password != r_password2:
318
+ st.error("Konfirmasi password tidak sama.")
319
  else:
320
  try:
321
  with st.spinner("Mendaftarkan akun..."):
 
322
  redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
323
  if not redirect_url:
324
  redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
 
328
  "password": r_password,
329
  "options": {"email_redirect_to": redirect_url}
330
  })
331
+ st.success("Registrasi berhasil!")
332
+ st.info("Email konfirmasi telah dikirim ke " + r_email)
333
+ st.info("**Langkah selanjutnya:**")
334
+ st.info("1. Buka email Anda")
335
+ st.info("2. Klik link konfirmasi")
336
+ st.info("3. Kembali ke halaman ini untuk login")
337
+ st.warning("Periksa folder spam jika email tidak ditemukan dalam 5 menit")
338
  except Exception as e:
339
+ st.error(f"Gagal registrasi: {str(e)}")
340
 
341
  with tab_forgot:
342
  st.markdown("### Reset Password")
343
+ st.caption("Masukkan email Anda untuk menerima link reset password")
344
+
345
+ # Instruksi yang jelas tanpa copy-paste
346
+ st.info("""
347
+ **Cara kerja reset password:**
348
+ 1. Masukkan email dan klik "Kirim Link Reset"
349
+ 2. Buka email dari PNP Bot
350
+ 3. Klik link di email (link akan membuka halaman ini)
351
+ 4. Jika halaman reset tidak muncul, ganti `#` dengan `?` di URL browser
352
+ 5. Set password baru Anda
353
+ """)
354
+
355
  with st.form("forgot_form"):
356
+ f_email = st.text_input("Email", key="forgot_email", placeholder="contoh@email.com")
357
+ submitted_f = st.form_submit_button("Kirim Link Reset", use_container_width=True)
358
 
359
  if submitted_f:
360
  if not f_email:
361
+ st.error("Email harus diisi.")
362
  else:
363
  try:
364
+ with st.spinner("Mengirim email..."):
 
365
  redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
366
  if not redirect_url:
367
  redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
 
370
  f_email,
371
  {"redirect_to": redirect_url}
372
  )
373
+ st.success("Link reset password telah dikirim!")
374
+ st.info(f"Silakan cek email di **{f_email}**")
375
+
376
+ # Tips tanpa copy paste
377
+ with st.expander("Troubleshooting", expanded=False):
378
+ st.markdown("""
379
+ **Jika mengalami masalah:**
380
+
381
+ **Email tidak masuk?**
382
+ Tunggu 2-3 menit, periksa folder spam
383
+
384
+ **Link tidak berfungsi?**
385
+ Di browser, ganti tanda `#` dengan `?` pada URL
386
+
387
+ **Halaman kosong setelah klik link?**
388
+ Refresh halaman atau coba browser lain
389
+
390
+ **Link sudah expired?**
391
+ Minta link baru (berlaku 1 jam)
392
+ """)
393
  except Exception as e:
394
+ st.error(f"Gagal mengirim email: {str(e)}")