I'm trying to make a Dashboard with Login, Register and Dashboard with Navbar and Tabs. With below code everything working well but I have a problem that if I refreshed page, Dashboard will return to login screen. from dash import Dash, dcc, html, Input, Output, State, ctx import dash_mantine_components as dmc import pandas as pd import dash from dash_ag_grid import AgGrid import plotly.express as px import os from datetime import datetime from pandas.tseries.offsets import BDay from datetime import datetime, date, timedelta import base64 import io import plotly.graph_objects as go import dash_ag_grid as dag import numpy as np excel_file = 'user2.xlsx' # Tạo file nếu chưa có if not os.path.exists(excel_file): pd.DataFrame(columns=["username", "password", "email", "code", "branch", "created_at"]).to_excel(excel_file, index=False) app = dash.Dash(__name__, suppress_callback_exceptions=True) def t(lang, key): translations = { "vi": { "login": "Đăng nhập", "register": "Đăng ký", "username": "Tên đăng nhập", "password": "Mật khẩu", "confirm": "Nhập lại mật khẩu", "email": "Email", "code": "Mã nhân viên", "branch": "Chi nhánh", "no_account": "Chưa có tài khoản?", "register_now": "Đăng ký ngay", "has_account": "Đã có tài khoản?", "create_account": "Tạo tài khoản", "success": "Đăng ký thành công. Vui lòng đăng nhập." }, "en": { "login": "Login", "register": "Register", "username": "Username", "password": "Password", "confirm": "Confirm Password", "email": "Email", "code": "Employee Code", "branch": "Branch", "no_account": "No account?", "register_now": "Register now", "has_account": "Already have an account?", "create_account": "Create Account", "success": "Successfully registered. Please login." }, "ko": { "login": "로그인", "register": "회원가입", "username": "사용자 이름", "password": "비밀번호", "confirm": "비밀번호 확인", "email": "이메일", "code": "사원 번호", "branch": "지점", "no_account": "계정이 없으신가요?", "register_now": "지금 등록하세요", "has_account": "이미 계정이 있으신가요?", "create_account": "계정 만들기", "success": "성공적으로 등록되었습니다. 로그인하세요." } } return translations.get(lang, translations["vi"]).get(key, key) def login_screen(lang): return dmc.Container([ dmc.Select( id="language-select", label="Ngôn ngữ / Language / 언어", data=[ {"label": "Tiếng Việt", "value": "vi"}, {"label": "English", "value": "en"}, {"label": "한국어", "value": "ko"} ], value=lang, style={"marginBottom": 20} ), dmc.Title(t(lang, "login"), order=2, align="center", mb=20), dmc.TextInput(id="login-username", label=t(lang, "username")), dmc.PasswordInput(id="login-password", label=t(lang, "password"), mt=10), dmc.Button(t(lang, "login"), id="login-submit", mt=20), dmc.Text(id="alert-text", color="red", size="sm", mt=10), dmc.Text(t(lang, "no_account"), span=True), dmc.Button(t(lang, "register_now"), id="go-register", variant="subtle", compact=True) ], style={"maxWidth": 400, "margin": "auto", "paddingTop": 100}) def register_screen(lang): return dmc.Container([ dmc.Title(t(lang, "register"), order=2, align="center", mb=20), dmc.TextInput(id="register-username", label=t(lang, "username")), dmc.PasswordInput(id="register-password", label=t(lang, "password"), mt=10), dmc.PasswordInput(id="register-confirm", label=t(lang, "confirm"), mt=10), dmc.TextInput(id="register-email", label=t(lang, "email"), mt=10), dmc.TextInput(id="register-code", label=t(lang, "code"), mt=10), dmc.TextInput(id="register-branch", label=t(lang, "branch"), mt=10), dmc.Button(t(lang, "create_account"), id="register-submit", fullWidth=True, mt=20), dmc.Text(id="alert-text", color="red", size="sm", mt=10), dmc.Text(t(lang, "has_account"), span=True), dmc.Button(t(lang, "login"), id="go-login", variant="subtle", compact=True) ], style={"maxWidth": 400, "margin": "auto", "paddingTop": 100}) def full_dashboard_layout(lang): return dmc.Container([ dmc.Group([ dmc.Switch(id="theme-toggle", label="Dark mode", size="md", offLabel="☀️", onLabel=""), dmc.Button("Signout", id="sign-out", variant="light", color="red", size="sm") ], position="apart", mb=20), html.Div([ html.Div(id="sidebar", children=[ dmc.Stack([ dmc.NavLink(label="Nav 1", id="nav-1-btn"), dmc.NavLink(label="Nav 2", id="nav-2-btn"), dmc.NavLink(label="Nav 3", id="nav-3-btn"), ]) ], style={ "width": "200px", "minHeight": "100vh", "borderRight": "1px solid #eee", "padding": "10px" }), html.Div(id="content-area", style={"flex": 1, "padding": "20px"}) ], style={"display": "flex"}) ], fluid = True) app.layout = html.Div([ dcc.Location(id="url", refresh=False), dcc.Store(id="auth-status", data="login", storage_type="local"), dcc.Store(id="selected-lang", data="vi"), dcc.Store(id="alert-store", data={}), dcc.Store(id="theme-store", data="light"), dmc.MantineProvider( id="theme-provider", theme={"colorScheme": "light"}, withGlobalStyles=True, withNormalizeCSS=True, children=html.Div(id="app-screen") ), dmc.Text(id="alert-text", color="red", size="sm", mt=10) ]) @app.callback( Output("auth-status", "data", allow_duplicate=True), Input("go-login", "n_clicks"), prevent_initial_call=True, prevent_initial_callbacks=True) def back_to_login(go_login): return "login" @app.callback( Output("auth-status", "data", allow_duplicate=True), Output("alert-store", "data", allow_duplicate=True), Output("url", "pathname", allow_duplicate=True), # điều hướng sau login Input("go-register", "n_clicks"), Input("login-submit", "n_clicks"), State("login-username", "value"), State("login-password", "value"), prevent_initial_call=True ) def login_flow(go_reg, login, username, password): if ctx.triggered_id == "go-register": return "register", {}, dash.no_update if not username or not password: return dash.no_update, {}, dash.no_update path = "users2.xlsx" try: df = pd.read_excel(path) if "username" not in df.columns or "password" not in df.columns: return dash.no_update, {"message": "Invalid data"}, dash.no_update except: return dash.no_update, {"message": "Cannot read users"}, dash.no_update match = df[(df["username"] == username) & (df["password"] == password)] if match.empty: return dash.no_update, {"message": "Sai tên đăng nhập hoặc mật khẩu"}, dash.no_update return "authenticated", {}, "/dashboard" @app.callback( Output("auth-status", "data", allow_duplicate=True), Output("alert-store", "data", allow_duplicate=True), Input("register-submit", "n_clicks"), State("register-username", "value"), State("register-password", "value"), State("register-confirm", "value"), State("register-email", "value"), State("register-code", "value"), State("register-branch", "value"), prevent_initial_call=True ) def handle_register(_, username, password, confirm, email, code, branch): if not any([username, password, confirm, email, code, branch]): return dash.no_update, {} if not all([username, password, confirm, email, code, branch]): return dash.no_update, {"message": "Vui lòng nhập đủ thông tin"} if password != confirm: return dash.no_update, {"message": "Mật khẩu xác nhận không khớp"} if not (email.endswith("@wooribank.com") or email.endswith("@woori.com.vn")): return dash.no_update, {"message": "Email không hợp lệ"} path = "users2.xlsx" try: df = pd.read_excel(path) except: df = pd.DataFrame(columns=["username", "password", "email", "code", "branch", "created_at"]) if any(df["username"] == username) or any(df["email"] == email) or any(df["code"] == code): return dash.no_update, {"message": "Tài khoản/email/mã đã tồn tại"} df = pd.concat([df, pd.DataFrame([{ "username": username, "password": password, "email": email, "code": code, "branch": branch, "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }])], ignore_index=True) df.to_excel(path, index=False) return "register", {"message": "Đăng ký thành công"} @app.callback(Output("alert-text", "children"), Input("alert-store", "data")) def show_alert(data): return data.get("message", "") if isinstance(data, dict) else "" @app.callback(Output("alert-text", "color"), Input("auth-status", "data"), Input("alert-store", "data")) def alert_color(status, data): if status == "register" and data.get("message") == "Đăng ký thành công": return "green" return "red" @app.callback( Output("app-screen", "children"), Input("auth-status", "data"), Input("url", "pathname"), # đọc URL khi load lại State("selected-lang", "data") ) def render_app(status, pathname, lang): print(" render_app | Status:", status, "| Path:", pathname) if status == "authenticated" and pathname == "/dashboard": return full_dashboard_layout(lang) if status == "register": return register_screen(lang) return login_screen(lang) @app.callback(Output("theme-store", "data"), Input("theme-toggle", "checked"), prevent_initial_call=True) def toggle_theme(checked): return "dark" if checked else "light" @app.callback(Output("theme-provider", "theme"), Input("theme-store", "data")) def update_theme(scheme): return {"colorScheme": scheme} @app.callback(Output("selected-lang", "data"), Input("language-select", "value")) def set_language(value): return value @app.callback( Output("content-area", "children"), Input("nav-1-btn", "n_clicks"), Input("nav-2-btn", "n_clicks"), Input("nav-3-btn", "n_clicks"), State("theme-store", "data"), State("selected-lang", "data") ) def update_content(n1, n2, n3, theme, lang): nav = ctx.triggered_id if nav: return generate_tabs(nav.replace("-btn", ""), theme, lang) return dmc.Text(t(lang, "choose_menu")) @app.callback( Output("auth-status", "data", allow_duplicate=True), Input("sign-out", "n_clicks"), prevent_initial_call=True, prevent_initial_callbacks=True ) def logout(n): return "login" def generate_tabs(nav_id, theme, lang): if nav_id == "nav-1": return dmc.Tabs( id="nav-1-tabs", value="tab-1", children=[ dmc.TabsList([ dmc.Tab(t(lang, "tab1"), value="tab-1"), dmc.Tab(t(lang, "tab2"), value="tab-2"), dmc.Tab(t(lang, "tab3"), value="tab-3"), ]), dmc.TabsPanel(id="nav-1-tab-content", value="tab-1"), dmc.TabsPanel(dmc.Text(t(lang, "tab2_content")), value="tab-2"), dmc.TabsPanel(dmc.Text(t(lang, "tab3_content")), value="tab-3"), ] ) elif nav_id == "nav-2": return dmc.Tabs( id="nav-2-tabs", value="tab-a", children=[ dmc.TabsList([ dmc.Tab("Bộ lọc", value="tab-a"), dmc.Tab("Lịch", value="tab-b"), ]), dmc.TabsPanel([ dmc.Select(label="Chi nhánh", data=["Hà Nội", "HCM"]), dmc.DatePicker() ], value="tab-a"), dmc.TabsPanel(dmc.Text("Lịch hoạt động ở đây..."), value="tab-b"), ] ) elif nav_id == "nav-3": return dmc.Tabs( id="nav-3-tabs", value="tab-x", children=[ dmc.TabsList([ dmc.Tab("Thông báo", value="tab-x"), dmc.Tab("Người dùng", value="tab-y"), dmc.Tab("Vai trò", value="tab-z"), dmc.Tab("Báo cáo", value="tab-w") ]), dmc.TabsPanel(dmc.Text("Thông báo hệ thống"), value="tab-x"), dmc.TabsPanel(dmc.Text("Danh sách người dùng"), value="tab-y"), dmc.TabsPanel(dmc.Text("Quản lý vai trò"), value="tab-z"), dmc.TabsPanel(dmc.Text("Báo cáo tổng hợp"), value="tab-w"), ] ) return dmc.Text("Không có nội dung.") def blank_fig(): fig = go.Figure(go.Bar(x=[], y=[])) fig.update_layout({'paper_bgcolor':'rgba(0,0,0,0)','plot_bgcolor':'rgba(0,0,0,0)'}, margin = dict(l=10,r=10,t=10, b=10),font_color='white') fig.update_xaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True) fig.update_yaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True) return fig def blank_fig2(): fig2 = go.Figure(go.Bar(x=[], y=[])) fig2.update_layout({'paper_bgcolor':'rgba(0,0,0,0)','plot_bgcolor':'rgba(0,0,0,0)'}, margin = dict(l=10,r=10,t=10, b=10),font_color='white', height=200) fig2.update_xaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True) fig2.update_yaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True) return fig2 def get_dates(start_date, num_days=365): return [(start_date + timedelta(days=i)).isoformat() for i in range(num_days) if (start_date + timedelta(days=i)).weekday()==4 or (start_date + timedelta(days=i)).weekday()==5] @app.callback( Output("nav-1-tab-content", "children"), Input("nav-1-tabs", "value"), Input("theme-store", "data"), State("selected-lang", "data") ) def nav1_tabs(tab, theme, lang): theme == "dark" if tab == "tab-1": return dmc.LoadingOverlay([ dmc.Container([ dmc.Grid([ dmc.Col([ dmc.DatePicker(id='datepickersingle_1', value=[], minDate=date(2023,12,29), disabledDates = get_dates(date.today()), style={'width': 200}) ], span='auto'), dmc.Col([ dmc.DatePicker(id='datepickersingle_2', value=[], minDate=date(2023,12,29), disabledDates = get_dates(date.today()), style={'width': 200}) ], span='auto'), dmc.Col([ dmc.DatePicker(id='datepickersingle_3', value=[], minDate=date(2023,12,29), disabledDates = get_dates(date.today()), style={'width': 200}) ], span='auto'), dmc.Col([ dmc.DatePicker(id='datepickersingle_4', value=(datetime.today() - BDay(1)).date(), minDate=date(2023,12,29), disabledDates = get_dates(date.today()), style={'width': 200}) ], span='auto'), dmc.Col([ dmc.NumberInput(id='input_1', label='KPI Exchange Rate', value=[], precision=4, style={'width': 200}) ], span=2), dmc.Col([ dmc.Select(label = "Select branch", id='dropdown_1', data = ['001+','100','101','102','103','200','201','202','300','301', '302','303','304','500','700','701','702','703', '203'], placeholder = 'Please select branch', value = '001+', clearable=True) ], span=2), dmc.Col([ dmc.Button('Submit', color='gray', id='btn_1') ], span='auto', offset=1) ], align='flex-start', justify = 'center', style={'margin-top':10}), ], fluid=True) ], loaderProps={'variant':"dot", 'color':'blue', 'fullscreen':True}) return dash.no_update if __name__ == "__main__": app.run_server(debug=False, port = 1522, jupyter_mode="external") I'm guessing that login and Dashboard has same link and not be separated with href so this situation but I'm not sure how to fix it. Continue reading...