I have a working solution, but the layout doesn’t yet match the rest of the app. This is how it currently looks (with slight modifications). Note that I use some packages not (yet?) public, which you should ignore for the moment. (Also internationalization hasn’t been implemented yet for the Stipple example in the app—it would typically involve something like @t(“panels!heading”), and so on.)
The app’s code (a little different) comes from the default setup created by my private AuthPlugin with AuthPlugin.install
. Some important parts you will find below.
app/resources/panels/PanelsController.jl
module PanelsController
using Genie, Stipple, Stipple.ReactiveTools, StippleUI, AuthPlugin, I18nPlugin
@app PanelModel begin
@in N = 0
@out msg = ""
@onchange N begin
msg = "N = $N"
end
end
function panels()
@authenticated!
locale = params(:locale, "en")
model = @init PanelModel
view = include(path"app/resources/panels/views/panels.jl")
layout = filepath("app/layouts/authapp_stipple.jl.html")
html!(view;
layout = layout,
context = @__MODULE__,
model = model,
locale = locale
)
end
end
app/resources/panels/views/panels.jl
[
cell([
p("Enter a number")
textfield("N", :N )
])
cell([
bignumber("The value of N is", :N)
])
]
app/resources/layouts/authapp.jl.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="/css/authpluginstyle.css">
<title>Online Shop</title>
</head>
<body>
<nav>
<div class="container">
<div class="logo">
<a href="$(linkto(:main,locale=locale))">$(@t("layout!site_name"))</a>
</div>
<label for="menu-toggle" class="menu-icon">☰</label>
<input type="checkbox" id="menu-toggle">
<ul class="menu">
<li><a href="$(linkto(:main,locale=locale))">$(@t("layout!home"))</a></li>
<% if Main.UserApp.authenticated() %>
<li><a href="$(linkto(:panel,locale=locale))">$(@t("layout!panel"))</a></li>
<% end %>
<% if Main.UserApp.authenticated() %>
<li><a href="$(linkto(:account_index,locale=locale))">$(@t("layout!my_account"))</a></li>
<% else %>
<li><a href="$(linkto(:show_login,locale=locale))">$(@t("layout!login"))</a></li>
<% end %>
<% if Main.UserApp.has_role(Main.UserApp.current_user(), "admin" ) %>
<li><a href="$(linkto(:admin_index,locale=locale))">$(@t("layout!admin"))</a>
</li>
<% end %>
<li class="lang-picker">
<select id="langSelectMenu" onchange="window.location=this.value">
<% for_each(available_locales(I18N)) do lang %>
$(
option(
uppercase(lang),
value = switch_locale_link(lang),
selected = (lang == locale)
)
)
<% end %>
</select>
</li>
</ul>
</div>
</nav>
<% @yield %>
</body>
<footer>
<div class="container">
<p>$(@t("layout!footer_text"))</p>
<p>$(@t("layout!footer_copyright"))</p>
<p class="legal-links">
<a href="$(linkto(:get_imprint,locale=locale))">$(@t("layout!imprint"))</a> |
<a href="$(linkto(:get_privacy,locale=locale))">$(@t("layout!privacy"))</a>
</p>
</div>
</footer>
</html>
app/resources/layouts/authapp_stipple.jl.html
<!DOCTYPE html>
<html lang="en">
<head>
<% Stipple.sesstoken() %>
<meta charset="utf-8" />
<link rel="stylesheet" href="/css/authpluginstyle.css">
<title>$(@t("layout!site_name"))</title>
</head>
<body>
<nav>
<div class="container">
<div class="logo">
<a href="$(linkto(:main,locale=locale))">$(@t("layout!site_name"))</a>
</div>
<label for="menu-toggle" class="menu-icon">☰</label>
<input type="checkbox" id="menu-toggle">
<ul class="menu">
<li><a href="$(linkto(:main,locale=locale))">$(@t("layout!home"))</a></li>
<% if Main.UserApp.authenticated() %>
<li><a href="$(linkto(:panel,locale=locale))">$(@t("layout!panel"))</a></li>
<% end %>
<% if Main.UserApp.authenticated() %>
<li><a href="$(linkto(:account_index,locale=locale))">$(@t("layout!my_account"))</a></li>
<% else %>
<li><a href="$(linkto(:show_login,locale=locale))">$(@t("layout!login"))</a></li>
<% end %>
<% if Main.UserApp.has_role(Main.UserApp.current_user(), "admin") %>
<li><a href="$(linkto(:admin_index,locale=locale))">$(@t("layout!admin"))</a></li>
<% end %>
<li class="lang-picker">
<select id="langSelectMenu" onchange="window.location=this.value">
<% for_each(available_locales(I18N)) do lang %>
$(
option(
uppercase(lang),
value = switch_locale_link(lang),
selected = (lang == locale)
)
)
<% end %>
</select>
</li>
</ul>
</div>
</nav>
<% Stipple.page(model, partial = true, v__cloak = true, [Stipple.Genie.Renderer.Html.@yield], Stipple.@if(:isready)) %>
</body>
<footer>
<div class="container">
<p>$(@t("layout!footer_text"))</p>
<p>$(@t("layout!footer_copyright"))</p>
<p class="legal-links">
<a href="$(linkto(:get_imprint,locale=locale))">$(@t("layout!imprint"))</a> |
<a href="$(linkto(:get_privacy,locale=locale))">$(@t("layout!privacy"))</a>
</p>
</div>
</footer>
</html>
routes.jl / or plugins/authplugin.jl
...
using ..Main.UserApp.PanelsController
route("/$(patternize(I18N))/panels",PanelsController.panels, named = :panel)
This is a the structure of the app (a little modified).
├── app
│ ├── helpers
│ │ └── ValidationHelper.jl
│ ├── layouts
│ │ ├── app.jl.html
│ │ ├── authapp.jl.html
│ │ └── authapp_stipple.jl.html
│ └── resources
│ ├── accounts
│ │ ├── AccountsController.jl
│ │ └── views
│ │ └── account_index.jl.html
│ ├── admins
│ │ ├── AdminsController.jl
│ │ └── views
│ │ └── index.jl.html
│ ├── legals
│ │ ├── LegalsController.jl
│ │ └── views
│ │ ├── imprint.jl.html
│ │ └── privacy.jl.html
│ ├── mains
│ │ ├── MainsController.jl
│ │ └── views
│ │ └── index.jl.html
│ └── panels
│ ├── PanelsController.jl
│ └── views
│ └── panels.jl
├── bin
│ ├── repl
│ ├── repl.bat
│ ├── runtask
│ ├── runtask.bat
│ ├── server
│ └── server.bat
├── THISAPP.code-workspace
├── bootstrap.jl
├── build
│ └── GenieViews
├── config
│ ├── env
│ │ ├── dev.jl
│ │ ├── global.jl
│ │ ├── prod.jl
│ │ └── test.jl
│ ├── initializers
│ │ ├── authplugin_init.jl
│ │ ├── autoload.jl
│ │ ├── channel_authorizer.jl
│ │ ├── converters.jl
│ │ ├── i18nplugin.jl
│ │ ├── inflector.jl
│ │ ├── logging.jl
│ │ └── searchlight.jl
│ └── secrets.jl
├── db
│ ├── connection.yml
│ ├── dev.sqlite3
│ ├── migrations
│ └── seeds
├── locales
│ └── en
│ ├── accounts.toml
│ ├── admins.toml
│ ├── layout.toml
│ ├── legals.toml
│ └── mains.toml
├── log
│ ├── prod-2025-07-23.log
│ ├── prod-2025-07-24.log
│ ├── prod-2025-07-25.log
│ ├── prod-2025-07-26.log
│ ├── prod-2025-07-27.log
│ └── test-2025-07-18.log
├── Manifest.toml
├── plugins
│ └── authplugin.jl
├── Project.toml
├── public
│ ├── css
│ │ ├── authpluginstyle.css
│ │ ├── genie
│ │ │ ├── prism.css
│ │ │ └── style.css
│ │ └── old?authpluginstyle.css
│ ├── error-404.html
│ ├── error-500.html
│ ├── error-xxx.html
│ ├── favicon.ico
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ ├── img
│ │ └── genie
│ │ ├── community.png
│ │ ├── contribute-2.png
│ │ ├── docs.png
│ │ ├── genie.png
│ │ └── genie-sad.png
│ ├── js
│ │ └── genie
│ │ ├── ansi_up.js
│ │ ├── ansi_up.js.map
│ │ ├── jquery.min.js
│ │ ├── jquery.min.map
│ │ ├── prism.js
│ │ └── static.js
│ ├── robots.txt
│ └── welcome.html
├── routes.jl
├── src
│ └── THISAPP.jl
├── test
│ ├── Manifest.toml
│ ├── Project.toml
│ └── runtests.jl