//go:build js && wasm // Dashboard example demonstrating a realistic internal tool admin panel. // This example showcases: // - Multiple pages with routing // - Auth-protected sections // - Reactive data display // - Form inputs // - Component composition package main import ( "fmt" "strconv" "git.flowmade.one/flowmade-one/iris/navigation" "git.flowmade.one/flowmade-one/iris/reactive" "git.flowmade.one/flowmade-one/iris/ui" ) // Application state - simulated auth and data var ( isAuthenticated = reactive.NewSignal(false) currentUser = reactive.NewSignal("") userRole = reactive.NewSignal("guest") ) // Simulated dashboard metrics var ( totalUsers = reactive.NewSignal(1247) activeOrders = reactive.NewSignal(89) revenue = reactive.NewSignal(45230.50) pendingTasks = reactive.NewSignal(12) ) // Simulated user list type User struct { ID int Name string Email string Role string Active bool } var users = reactive.NewSignal([]User{ {1, "Alice Johnson", "alice@example.com", "admin", true}, {2, "Bob Smith", "bob@example.com", "user", true}, {3, "Carol White", "carol@example.com", "user", false}, {4, "Dave Brown", "dave@example.com", "moderator", true}, }) // Settings state var ( darkMode = reactive.NewSignal(false) emailNotifications = reactive.NewSignal(true) apiEndpoint = reactive.NewSignal("https://api.example.com") refreshInterval = reactive.NewSignal(30) ) func main() { // Define routes with guards routes := []navigation.Route{ {Path: "/", Handler: func(params map[string]string) ui.View { return homeView() }}, {Path: "/login", Handler: func(params map[string]string) ui.View { return loginView() }}, {Path: "/users", Handler: func(params map[string]string) ui.View { return usersView() }, Guards: []navigation.RouteGuard{authGuard}}, {Path: "/users/:id", Handler: userDetailView, Guards: []navigation.RouteGuard{authGuard}}, {Path: "/settings", Handler: func(params map[string]string) ui.View { return settingsView() }, Guards: []navigation.RouteGuard{authGuard}}, {Path: "/admin", Handler: func(params map[string]string) ui.View { return adminView() }, Guards: []navigation.RouteGuard{authGuard, adminGuard}}, } router := navigation.NewRouter(routes) router.SetNotFoundHandler(notFoundView) navigation.SetGlobalRouter(router) // Create the main app layout app := dashboardLayout() ui.NewApp(app) select {} } // Auth guard - checks if user is authenticated func authGuard(route *navigation.Route, params map[string]string) bool { if !isAuthenticated.Get() { navigation.Navigate("/login") return false } return true } // Admin guard - checks if user has admin role func adminGuard(route *navigation.Route, params map[string]string) bool { return userRole.Get() == "admin" } // Main dashboard layout with sidebar and content area func dashboardLayout() ui.View { sidebar := createSidebar() content := createContentArea() return ui.FlexLayout( []string{"240px", "1fr"}, sidebar, content, ).MinHeight("100vh") } // Sidebar with navigation links func createSidebar() ui.View { return ui.VerticalGroup( // Logo/Brand ui.TextFromString("Iris Dashboard"). Font(ui.NewFont().Size("24px").Weight("bold")). Padding("20px"). Color("#fff"), ui.Divider().Background("#444"), // Navigation links navLink("/", "Home"), navLink("/users", "Users"), navLink("/settings", "Settings"), navLink("/admin", "Admin"), ui.Spacer(), // User info at bottom ui.VerticalGroup( ui.Divider().Background("#444"), ui.TextFromFunction(func() string { if isAuthenticated.Get() { return "Logged in as: " + currentUser.Get() } return "Not logged in" }).Color("#aaa").Padding("10px"), loginLogoutButton(), ), ).Background("#1a1a2e").Width("240px").MinHeight("100vh").Padding("0") } func navLink(path, label string) ui.View { return navigation.Link(path, ui.TextFromString(label).Color("#ccc"), ).Padding("12px 20px").Cursor("pointer") } func loginLogoutButton() ui.View { return ui.Button(func() { if isAuthenticated.Get() { isAuthenticated.Set(false) currentUser.Set("") userRole.Set("guest") navigation.Navigate("/") } else { navigation.Navigate("/login") } }, ui.TextFromFunction(func() string { if isAuthenticated.Get() { return "Logout" } return "Login" }).Color("#fff")). Background("#e94560"). Padding("10px 20px"). Border("none"). BorderRadius("4px"). Cursor("pointer"). Margin("10px") } // Content area that shows the current route view func createContentArea() ui.View { view := ui.NewView() reactive.NewEffect(func() { router := navigation.GetGlobalRouter() if router != nil { currentView := router.GetCurrentView() view.Element().Set("innerHTML", "") view.Child(currentView) } }) return view.Background("#f0f0f0").Padding("20px").Overflow("auto") } // Home view - dashboard overview func homeView() ui.View { return ui.VerticalGroup( ui.TextFromString("Dashboard Overview"). Font(ui.NewFont().Size("28px").Weight("bold")). Color("#333"), // Metrics cards ui.HorizontalGroup( metricCard("Total Users", func() string { return strconv.Itoa(totalUsers.Get()) }, "#4ecca3"), metricCard("Active Orders", func() string { return strconv.Itoa(activeOrders.Get()) }, "#e94560"), metricCard("Revenue", func() string { return fmt.Sprintf("$%.2f", revenue.Get()) }, "#16c79a"), metricCard("Pending Tasks", func() string { return strconv.Itoa(pendingTasks.Get()) }, "#f39c12"), ).Gap("20px").Margin("20px 0"), // Quick actions ui.TextFromString("Quick Actions"). Font(ui.NewFont().Size("20px").Weight("bold")). Color("#333"). Margin("20px 0 10px 0"), ui.HorizontalGroup( actionButton("Add User", func() { navigation.Navigate("/users") }), actionButton("View Reports", func() { /* simulated action */ }), actionButton("Export Data", func() { /* simulated action */ }), ).Gap("10px"), // Recent activity (simulated) ui.TextFromString("Recent Activity"). Font(ui.NewFont().Size("20px").Weight("bold")). Color("#333"). Margin("30px 0 10px 0"), activityItem("User Alice updated profile", "2 minutes ago"), activityItem("New order #1234 received", "15 minutes ago"), activityItem("System backup completed", "1 hour ago"), ).AlignLeft().Gap("5px") } func metricCard(title string, valueFn func() string, color string) ui.View { return ui.VerticalGroup( ui.TextFromString(title).Color("#666").Font(ui.NewFont().Size("14px")), ui.TextFromFunction(valueFn). Font(ui.NewFont().Size("32px").Weight("bold")). Color(color), ).Background("#fff"). Padding("20px"). BorderRadius("8px"). BoxShadow("0 2px 4px rgba(0,0,0,0.1)"). Width("200px"). AlignCenter() } func actionButton(label string, action func()) ui.View { return ui.Button(action, ui.TextFromString(label).Color("#fff"), ).Background("#0077b6"). Padding("12px 24px"). Border("none"). BorderRadius("4px"). Cursor("pointer") } func activityItem(text, time string) ui.View { return ui.HorizontalGroup( ui.TextFromString(text).Color("#333"), ui.Spacer(), ui.TextFromString(time).Color("#999").Font(ui.NewFont().Size("12px")), ).Background("#fff"). Padding("15px"). BorderRadius("4px"). Margin("5px 0") } // Login view func loginView() ui.View { username := reactive.NewSignal("") password := reactive.NewSignal("") errorMsg := reactive.NewSignal("") return ui.VerticalGroup( ui.TextFromString("Login"). Font(ui.NewFont().Size("28px").Weight("bold")). Color("#333"), ui.VerticalGroup( ui.TextFromString("Username").Color("#666").Margin("0 0 5px 0"), ui.TextInput(&username, "Enter username"). Padding("10px"). Border("1px solid #ddd"). BorderRadius("4px"). Width("300px"), ui.TextFromString("Password").Color("#666").Margin("15px 0 5px 0"), ui.TextInput(&password, "Enter password"). Padding("10px"). Border("1px solid #ddd"). BorderRadius("4px"). Width("300px"), ui.TextFromFunction(func() string { return errorMsg.Get() }).Color("#e94560").Margin("10px 0"), ui.Button(func() { // Simulated authentication u := username.Get() p := password.Get() if u == "admin" && p == "admin" { isAuthenticated.Set(true) currentUser.Set("admin") userRole.Set("admin") navigation.Navigate("/") } else if u == "user" && p == "user" { isAuthenticated.Set(true) currentUser.Set("user") userRole.Set("user") navigation.Navigate("/") } else { errorMsg.Set("Invalid credentials. Try admin/admin or user/user") } }, ui.TextFromString("Login").Color("#fff")). Background("#0077b6"). Padding("12px 40px"). Border("none"). BorderRadius("4px"). Cursor("pointer"). Margin("20px 0"), ).Background("#fff"). Padding("30px"). BorderRadius("8px"). BoxShadow("0 2px 10px rgba(0,0,0,0.1)"). Gap("5px"), ).AlignCenter().Padding("50px") } // Users view - list and manage users func usersView() ui.View { searchTerm := reactive.NewSignal("") return ui.VerticalGroup( ui.TextFromString("User Management"). Font(ui.NewFont().Size("28px").Weight("bold")). Color("#333"), // Search bar ui.HorizontalGroup( ui.TextInput(&searchTerm, "Search users..."). Padding("10px"). Border("1px solid #ddd"). BorderRadius("4px"). Width("300px"), ui.Button(func() { // Add new user simulation currentUsers := users.Get() newID := len(currentUsers) + 1 currentUsers = append(currentUsers, User{ ID: newID, Name: fmt.Sprintf("New User %d", newID), Email: fmt.Sprintf("user%d@example.com", newID), Role: "user", Active: true, }) users.Set(currentUsers) totalUsers.Set(totalUsers.Get() + 1) }, ui.TextFromString("Add User").Color("#fff")). Background("#4ecca3"). Padding("10px 20px"). Border("none"). BorderRadius("4px"). Cursor("pointer"), ).Gap("10px").Margin("20px 0"), // User list ui.VerticalGroup( userListHeader(), userList(&searchTerm), ).Background("#fff"). BorderRadius("8px"). BoxShadow("0 2px 4px rgba(0,0,0,0.1)"), ).AlignLeft().Gap("10px") } func userListHeader() ui.View { return ui.FlexLayout( []string{"60px", "1fr", "1fr", "100px", "100px"}, ui.TextFromString("ID").Font(ui.NewFont().Weight("bold")).Color("#666"), ui.TextFromString("Name").Font(ui.NewFont().Weight("bold")).Color("#666"), ui.TextFromString("Email").Font(ui.NewFont().Weight("bold")).Color("#666"), ui.TextFromString("Role").Font(ui.NewFont().Weight("bold")).Color("#666"), ui.TextFromString("Status").Font(ui.NewFont().Weight("bold")).Color("#666"), ).Padding("15px").BorderBottom("2px solid #eee") } func userList(searchTerm *reactive.Signal[string]) ui.View { view := ui.NewView() reactive.NewEffect(func() { view.Element().Set("innerHTML", "") search := searchTerm.Get() for _, user := range users.Get() { // Simple search filter if search != "" && !containsIgnoreCase(user.Name, search) && !containsIgnoreCase(user.Email, search) { continue } view.Child(userRow(user)) } }) return view } func userRow(user User) ui.View { statusColor := "#4ecca3" statusText := "Active" if !user.Active { statusColor = "#e94560" statusText = "Inactive" } return ui.FlexLayout( []string{"60px", "1fr", "1fr", "100px", "100px"}, ui.TextFromString(strconv.Itoa(user.ID)).Color("#333"), navigation.Link( fmt.Sprintf("/users/%d", user.ID), ui.TextFromString(user.Name).Color("#0077b6"), ), ui.TextFromString(user.Email).Color("#666"), ui.TextFromString(user.Role).Color("#333"), ui.TextFromString(statusText).Color(statusColor), ).Padding("15px").BorderBottom("1px solid #eee") } // User detail view func userDetailView(params map[string]string) ui.View { userID := params["id"] id, _ := strconv.Atoi(userID) var user *User for _, u := range users.Get() { if u.ID == id { userCopy := u user = &userCopy break } } if user == nil { return ui.TextFromString("User not found").Color("#e94560") } return ui.VerticalGroup( ui.Button(func() { navigation.Navigate("/users") }, ui.TextFromString("Back to Users").Color("#0077b6")). Background("transparent"). Border("none"). Cursor("pointer"). Padding("0"). Margin("0 0 20px 0"), ui.TextFromString(user.Name). Font(ui.NewFont().Size("28px").Weight("bold")). Color("#333"), ui.VerticalGroup( detailRow("ID", strconv.Itoa(user.ID)), detailRow("Email", user.Email), detailRow("Role", user.Role), detailRow("Status", map[bool]string{true: "Active", false: "Inactive"}[user.Active]), ).Background("#fff"). Padding("20px"). BorderRadius("8px"). BoxShadow("0 2px 4px rgba(0,0,0,0.1)"). Gap("10px"). Margin("20px 0"), ).AlignLeft() } func detailRow(label, value string) ui.View { return ui.HorizontalGroup( ui.TextFromString(label+":").Font(ui.NewFont().Weight("bold")).Color("#666").Width("100px"), ui.TextFromString(value).Color("#333"), ).Gap("10px") } // Settings view func settingsView() ui.View { return ui.VerticalGroup( ui.TextFromString("Settings"). Font(ui.NewFont().Size("28px").Weight("bold")). Color("#333"), // Appearance section settingsSection("Appearance", ui.Checkbox(&darkMode, "Enable Dark Mode"), ), // Notifications section settingsSection("Notifications", ui.Checkbox(&emailNotifications, "Email Notifications"), ), // API Settings section settingsSection("API Configuration", ui.VerticalGroup( ui.TextFromString("API Endpoint").Color("#666").Margin("0 0 5px 0"), ui.TextInput(&apiEndpoint, "API URL"). Padding("10px"). Border("1px solid #ddd"). BorderRadius("4px"). Width("400px"), ui.TextFromString("Refresh Interval (seconds)").Color("#666").Margin("15px 0 5px 0"), ui.HorizontalGroup( ui.Slider(&refreshInterval, 5, 120), ui.TextFromFunction(func() string { return fmt.Sprintf("%ds", refreshInterval.Get()) }).Color("#333").Width("50px"), ).Gap("10px").AlignCenter(), ).Gap("5px"), ), // Current settings display ui.VerticalGroup( ui.TextFromString("Current Configuration"). Font(ui.NewFont().Size("18px").Weight("bold")). Color("#333"), ui.TextFromFunction(func() string { return fmt.Sprintf("Dark Mode: %v | Notifications: %v | Refresh: %ds", darkMode.Get(), emailNotifications.Get(), refreshInterval.Get()) }).Color("#666").Font(ui.NewFont().Size("14px")), ).Background("#f8f8f8"). Padding("15px"). BorderRadius("4px"). Margin("20px 0"), // Save button ui.Button(func() { // Simulated save action fmt.Println("Settings saved!") }, ui.TextFromString("Save Settings").Color("#fff")). Background("#0077b6"). Padding("12px 30px"). Border("none"). BorderRadius("4px"). Cursor("pointer"), ).AlignLeft().Gap("10px").Padding("0 0 20px 0") } func settingsSection(title string, content ui.View) ui.View { return ui.VerticalGroup( ui.TextFromString(title). Font(ui.NewFont().Size("18px").Weight("bold")). Color("#333"), content, ).Background("#fff"). Padding("20px"). BorderRadius("8px"). BoxShadow("0 2px 4px rgba(0,0,0,0.1)"). Gap("15px"). Margin("20px 0") } // Admin view - protected section func adminView() ui.View { return ui.VerticalGroup( ui.TextFromString("Admin Panel"). Font(ui.NewFont().Size("28px").Weight("bold")). Color("#333"), ui.TextFromString("This section is only accessible to administrators."). Color("#666"). Margin("10px 0"), // Admin actions ui.VerticalGroup( ui.TextFromString("System Actions"). Font(ui.NewFont().Size("18px").Weight("bold")). Color("#333"), ui.HorizontalGroup( adminActionButton("Clear Cache", func() { fmt.Println("Cache cleared!") }), adminActionButton("Restart Services", func() { fmt.Println("Services restarted!") }), adminActionButton("Run Backup", func() { fmt.Println("Backup started!") }), ).Gap("10px"), ).Background("#fff"). Padding("20px"). BorderRadius("8px"). BoxShadow("0 2px 4px rgba(0,0,0,0.1)"). Gap("15px"). Margin("20px 0"), // Danger zone ui.VerticalGroup( ui.TextFromString("Danger Zone"). Font(ui.NewFont().Size("18px").Weight("bold")). Color("#e94560"), ui.TextFromString("These actions are irreversible. Use with caution."). Color("#999"). Font(ui.NewFont().Size("14px")), ui.Button(func() { fmt.Println("Reset initiated!") }, ui.TextFromString("Reset All Data").Color("#fff")). Background("#e94560"). Padding("10px 20px"). Border("none"). BorderRadius("4px"). Cursor("pointer"), ).Background("#fff"). Padding("20px"). BorderRadius("8px"). Border("2px solid #e94560"). Gap("10px"). Margin("20px 0"), ).AlignLeft().Gap("10px") } func adminActionButton(label string, action func()) ui.View { return ui.Button(action, ui.TextFromString(label).Color("#fff"), ).Background("#6c757d"). Padding("10px 20px"). Border("none"). BorderRadius("4px"). Cursor("pointer") } // 404 Not Found view func notFoundView() ui.View { return ui.VerticalGroup( ui.TextFromString("404"). Font(ui.NewFont().Size("72px").Weight("bold")). Color("#e94560"), ui.TextFromString("Page Not Found"). Font(ui.NewFont().Size("24px")). Color("#666"), ui.Button(func() { navigation.Navigate("/") }, ui.TextFromString("Go Home").Color("#fff")). Background("#0077b6"). Padding("12px 30px"). Border("none"). BorderRadius("4px"). Cursor("pointer"). Margin("20px 0"), ).AlignCenter().Padding("50px") } // Helper function for case-insensitive search func containsIgnoreCase(str, substr string) bool { return len(str) >= len(substr) && contains(toLower(str), toLower(substr)) } func toLower(s string) string { result := make([]byte, len(s)) for i := 0; i < len(s); i++ { c := s[i] if c >= 'A' && c <= 'Z' { result[i] = c + 32 } else { result[i] = c } } return string(result) } func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }