Here is the code. It’s pretty rough and I will improve it. Realised I posted a much earlier version. Still getting used to Github, so new I didn’t even use it here. On successful login it goes to a dummy tab which could be the main app I guess although it would require keeping a number of tabs hidden to start with. I’d prefer to have this app run as part of panel serve for the main app. I’ll study the example again.
The main app written by my colleague does a good job of demonstrating Holoviz and Panel and demonstrates to me why I will stick to finance and marketing and leaving coding to someone that can.
import panel as pn
import param
import pandas as pd
import random
import os
from twilio.rest import Client
import logging
# Initialize Panel
pn.extension('tabulator')
# Twilio configuration
TWILIO_ACCOUNT_SID = t_account
TWILIO_AUTH_TOKEN = t_token
TWILIO_PHONE_NUMBER = t_phone
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
CREDENTIALS_FILE = credentials.csv"
# Global variables
global current_view, current_user, current_code, admin_logged_in, user_logged_in, debug_message, user_df
user_logged_in = False
debug_message = "no time for coffee"
def load_user_df():
try:
df = pd.read_csv(CREDENTIALS_FILE, dtype={
'name': str,
'email': str,
'phone': str, # Ensure phone is read as string
'organization': str,
'approved': bool,
'is_admin': bool
})
return df
except FileNotFoundError:
return pd.DataFrame(columns=['name', 'email', 'phone', 'organization', 'approved', 'is_admin'])
def save_user_df(df):
# Ensure the phone column is saved as text
df['phone'] = df['phone'].astype(str)
df.to_csv(CREDENTIALS_FILE, index=False)
# Load user data
user_df = load_user_df()
# Create widgets
main_login_button = pn.widgets.Button(name="Login", button_type="primary")
main_register_button = pn.widgets.Button(name="Register", button_type="success")
login_email_input = pn.widgets.TextInput(name="Email", placeholder="Enter your email")
login_submit_button = pn.widgets.Button(name="Send Verification Code", button_type="primary")
login_code_input = pn.widgets.TextInput(name="Verification Code", placeholder="Enter 3-digit code")
login_code_submit_button = pn.widgets.Button(name="Verify Code", button_type="success")
reg_name_input = pn.widgets.TextInput(name="Name", placeholder="Enter your name")
reg_email_input = pn.widgets.TextInput(name="Email", placeholder="Enter your email")
reg_phone_input = pn.widgets.TextInput(name="Mobile Phone", placeholder="Enter your mobile phone number", value="+61")
reg_org_input = pn.widgets.TextInput(name="Organization", placeholder="Enter your organization")
reg_submit_button = pn.widgets.Button(name="Submit Registration", button_type="success")
message_pane = pn.pane.Markdown("")
debug_pane = pn.pane.Markdown("Debug Info: ")
def main_view():
return pn.Column(
pn.pane.Markdown("# User System"),
main_login_button,
main_register_button,
message_pane
)
def login_view():
return pn.Column(
pn.pane.Markdown("# User Login"),
login_email_input,
login_submit_button,
login_code_input,
login_code_submit_button,
message_pane,
# debug_pane
)
def register_view():
return pn.Column(
pn.pane.Markdown("# User Registration"),
reg_name_input,
reg_email_input,
reg_phone_input,
reg_org_input,
reg_submit_button,
message_pane
)
def pricing_model_view():
return pn.Column(
pn.pane.Markdown("# Pricing Model", styles={'color': 'white', 'background-color': 'teal', 'font-size': '24px', 'padding': '10px'}),
pn.pane.Markdown("Logged In", styles={'color': 'white', 'background-color': 'teal', 'font-size': '24px', 'padding': '10px'})
)
# Create all tabs, but set pricing_model to not visible initially
main_tab = pn.Column(main_view())
login_tab = pn.Column(login_view(), visible=False, name="login")
register_tab = pn.Column(register_view(), visible=False, name="register")
pricing_model_tab = pn.Column(pricing_model_view(), visible=False, name="pricing_model")
tabs = pn.Column(main_tab, login_tab, register_tab, pricing_model_tab)
def show_main_view(event):
global current_view
current_view = "main"
update_view()
return_to_main_button_login = pn.widgets.Button(name="Return to Main", button_type="default")
return_to_main_button_login.on_click(show_main_view)
login_tab.append(return_to_main_button_login)
return_to_main_button_register = pn.widgets.Button(name="Return to Main", button_type="default")
return_to_main_button_register.on_click(show_main_view)
register_tab.append(return_to_main_button_register)
def update_view():
global current_view, user_logged_in
main_tab.visible = current_view == "main"
login_tab.visible = current_view == "login"
register_tab.visible = current_view == "register"
pricing_model_tab.visible = current_view == "pricing_model" and user_logged_in
def generate_code():
return str(random.randint(100, 999))
def send_code(phone, code):
try:
message = client.messages.create(
body=f"Your verification code is: {code}",
from_=TWILIO_PHONE_NUMBER,
to=phone
)
print(f"SMS sent successfully. SID: {message.sid}")
return True
except Exception as e:
print(f"Failed to send SMS: {str(e)}")
return False
def show_login_view(event):
global current_view
current_view = "login"
update_view()
def show_register_view(event):
global current_view
current_view = "register"
update_view()
def handle_login_email_submit(event):
global current_user, current_code, debug_message
email = login_email_input.value
user = user_df[user_df['email'] == email]
if not user.empty:
if user['approved'].values[0]:
current_user = user.to_dict('records')[0]
current_code = generate_code()
if send_code(current_user['phone'], current_code):
message_pane.object = "A verification code has been sent to your phone."
login_code_input.visible = True
login_code_submit_button.visible = True
else:
message_pane.object = "Failed to send verification code. Please try again later."
else:
message_pane.object = "Your account is pending approval. Please try again later."
else:
message_pane.object = "User not found. Please check your email or register."
debug_message += f"\nEmail submit: {email}, Code: {current_code}"
debug_pane.object = f"Debug Info: {debug_message}"
def handle_login_code_submit(event):
global user_logged_in, debug_message, current_view
entered_code = login_code_input.value
debug_message += f"\nCode submit: entered {entered_code}, expected {current_code}"
if entered_code == current_code:
user_logged_in = True
success_message = f"Login successful! Welcome, {current_user['name']}."
message_pane.object = success_message
debug_message += f"\nLogin successful for {current_user['name']}"
current_view = "pricing_model"
update_view()
else:
message_pane.object = "Incorrect code. Please try again."
debug_message += "\nIncorrect code entered"
debug_pane.object = f"Debug Info: {debug_message}"
import re
def validate_phone_number(phone):
pattern = r'^\+\d{1,4}[.\-\s]?\d+$'
return re.match(pattern, phone) is not None
def handle_registration(event):
global user_df, current_view
name = reg_name_input.value
email = reg_email_input.value
phone = reg_phone_input.value
organization = reg_org_input.value
# Validate phone number
if not validate_phone_number(phone):
message_pane.object = "Invalid phone number. Please include a '+' and country code (e.g., +61412345678)."
return
new_user = pd.DataFrame({
'name': [name],
'email': [email],
'phone': [phone],
'organization': [organization],
'approved': [False],
'is_admin': [False]
})
user_df = pd.concat([user_df, new_user], ignore_index=True)
save_user_df(user_df)
message_pane.object = "Registration submitted. Please wait for admin approval."
current_view = "register"
update_view()
# Set up button click handlers
main_login_button.on_click(show_login_view)
main_register_button.on_click(show_register_view)
login_submit_button.on_click(handle_login_email_submit)
login_code_submit_button.on_click(handle_login_code_submit)
reg_submit_button.on_click(handle_registration)
# Create the main layout
main_layout = tabs
# Create the template
template = pn.template.BootstrapTemplate(
title="ITK pricing registration and login",
main=[main_layout]
)
template.servable()