diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.css b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.css
similarity index 100%
rename from Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.css
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.css
diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.js b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js
similarity index 100%
rename from Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.js
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/Container.resx b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/Container.resx
new file mode 100644
index 00000000..4fdb1b6a
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/Container.resx
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ContainerSettings.resx b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ContainerSettings.resx
new file mode 100644
index 00000000..9c3b2d80
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ContainerSettings.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Specify If The Module Title Should Be Displayed
+
+
+ Display Title
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/Theme.resx b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/Theme.resx
new file mode 100644
index 00000000..4fdb1b6a
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/Theme.resx
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ThemeSettings.resx b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ThemeSettings.resx
new file mode 100644
index 00000000..1359d4be
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ThemeSettings.resx
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Specify if a Login option should be displayed. Note that this option does not prevent the login page from being accessible via a direct url.
+
+
+ Show Login?
+
+
+ Specify if a Register option should be displayed. Note that this option is also dependent on the Allow Registration option in Site Settings.
+
+
+ Show Register?
+
+
+ Specify if the settings are applicable to this page or the entire site.
+
+
+ Setting Scope:
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/Container1.razor b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/Container1.razor
new file mode 100644
index 00000000..928ce551
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/Container1.razor
@@ -0,0 +1,46 @@
+@namespace [Owner].Theme.[Theme]
+@inherits ContainerBase
+@inject ISettingService SettingService
+
+
+ @if (_title && ModuleState.Title != "-")
+ {
+
+ }
+ else
+ {
+
+ }
+
+
+
+@code {
+ public override string Name => "[Owner] [Theme] - Container1";
+
+ private bool _title = true;
+ private string _classes = "container-fluid";
+
+ protected override void OnParametersSet()
+ {
+ try
+ {
+
+ _title = bool.Parse(SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Title", "true"));
+
+ }
+ catch
+ {
+ // error loading container settings
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ContainerSettings.razor b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ContainerSettings.razor
new file mode 100644
index 00000000..0e8b12b4
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ContainerSettings.razor
@@ -0,0 +1,50 @@
+@namespace [Owner].Theme.[Theme]
+@inherits ModuleBase
+@implements Oqtane.Interfaces.ISettingsControl
+@inject ISettingService SettingService
+@attribute [OqtaneIgnore]
+
+
+
+
+
+
+
+
+
+
+@code {
+ private string resourceType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane"; // for localization
+ private string _title = "true";
+
+ protected override void OnInitialized()
+ {
+ try
+ {
+
+ _title = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Title", "true");
+
+ }
+ catch (Exception ex)
+ {
+ AddModuleMessage(ex.Message, MessageType.Error);
+ }
+ }
+
+ public async Task UpdateSettings()
+ {
+ try
+ {
+ var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
+ settings = SettingService.SetSetting(settings, GetType().Namespace + ":Title", _title);
+ await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
+ }
+ catch (Exception ex)
+ {
+ AddModuleMessage(ex.Message, MessageType.Error);
+ }
+ }
+}
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/Theme1.razor b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/Theme1.razor
new file mode 100644
index 00000000..fdee32d5
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/Theme1.razor
@@ -0,0 +1,118 @@
+@namespace [Owner].Theme.[Theme]
+@inherits ThemeBase
+@inject ISettingService SettingService
+
+
+
+
+
+
+@code {
+ public override string Name => "Theme1";
+
+ public override string Panes => PaneNames.Admin + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width";
+
+ private bool _login = true;
+ private bool _register = true;
+
+ protected override void OnParametersSet()
+ {
+ try
+ {
+ var settings = SettingService.MergeSettings(PageState.Site.Settings, PageState.Page.Settings);
+ _login = bool.Parse(SettingService.GetSetting(settings, GetType().Namespace + ":Login", "true"));
+ _register = bool.Parse(SettingService.GetSetting(settings, GetType().Namespace + ":Register", "true"));
+ }
+ catch
+ {
+ // error loading theme settings
+ }
+ }
+}
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ThemeInfo.cs b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ThemeInfo.cs
new file mode 100644
index 00000000..6ceaa574
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ThemeInfo.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using Oqtane.Models;
+using Oqtane.Themes;
+using Oqtane.Shared;
+
+namespace [Owner].Theme.[Theme]
+{
+ public class ThemeInfo : ITheme
+ {
+ public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
+ {
+ Name = "[Owner] [Theme]",
+ Version = "1.0.0",
+ PackageName = "[Owner].Theme.[Theme]",
+ ThemeSettingsType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane",
+ ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
+ Resources = new List()
+ {
+ // obtained from https://cdnjs.com/libraries
+ new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
+ new Stylesheet("~/Theme.css"),
+ new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
+ }
+ };
+
+ }
+}
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ThemeSettings.razor b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ThemeSettings.razor
new file mode 100644
index 00000000..3c9d2723
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Themes/[Owner].Theme.[Theme]/ThemeSettings.razor
@@ -0,0 +1,140 @@
+@namespace [Owner].Theme.[Theme]
+@inherits ModuleBase
+@implements Oqtane.Interfaces.ISettingsControl
+@inject ISettingService SettingService
+@inject IStringLocalizer Localizer
+@inject IStringLocalizer SharedLocalizer
+ @attribute [OqtaneIgnore]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @code {
+ private int pageId = -1;
+ private string resourceType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane"; // for localization
+ private string _scope = "page";
+ private string _login = "-";
+ private string _register = "-";
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (PageState.QueryString.ContainsKey("id"))
+ {
+ pageId = int.Parse(PageState.QueryString["id"]);
+ }
+
+ try
+ {
+ await LoadSettings();
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Loading Settings {Error}", ex.Message);
+ AddModuleMessage("Error Loading Settings", MessageType.Error);
+ }
+ }
+
+ private async Task LoadSettings()
+ {
+ if (_scope == "site")
+ {
+ var settings = PageState.Site.Settings;
+ _login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "true");
+ _register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "true");
+ }
+ else
+ {
+ var settings = await SettingService.GetPageSettingsAsync(pageId);
+ settings = SettingService.MergeSettings(PageState.Site.Settings, settings);
+ _login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-");
+ _register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-");
+ }
+ await Task.Yield();
+ }
+
+ private async Task ScopeChanged(ChangeEventArgs eventArgs)
+ {
+ try
+ {
+ _scope = (string)eventArgs.Value;
+ await LoadSettings();
+ StateHasChanged();
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Loading Settings {Error}", ex.Message);
+ AddModuleMessage("Error Loading Settings", MessageType.Error);
+ }
+ }
+
+ public async Task UpdateSettings()
+ {
+ try
+ {
+ if (_scope == "site")
+ {
+ var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
+ if (_login != "-")
+ {
+ settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
+ }
+
+ if (_register != "-")
+ {
+ settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
+ }
+ await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
+ }
+ else
+ {
+ var settings = await SettingService.GetPageSettingsAsync(pageId);
+ if (_login != "-")
+ {
+ settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
+ }
+ if (_register != "-")
+ {
+ settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
+ }
+ await SettingService.UpdatePageSettingsAsync(settings, pageId);
+ }
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Saving Settings {Error}", ex.Message);
+ AddModuleMessage("Error Saving Settings", MessageType.Error);
+ }
+ }
+ }
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Server/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Server/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css
new file mode 100644
index 00000000..f17ea0a3
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Server/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css
@@ -0,0 +1,123 @@
+/* Oqtane Styles */
+
+body {
+ padding-top: 7rem;
+}
+
+/* App Logo */
+.app-logo .img-fluid {
+ max-height: 90px;
+ padding: 0 5px 0 5px;
+}
+
+.table > :not(caption) > * > * {
+ box-shadow: none;
+}
+
+.table .form-control {
+ background-color: #ffffff !important;
+ border-width: 0.5px !important;
+ border-bottom-color: #ccc !important;
+}
+
+.table .form-select {
+ background-color: #ffffff !important;
+ border-width: 0.5px !important;
+ border-bottom-color: #ccc !important;
+}
+
+.table .btn-primary {
+ background-color: var(--bs-primary);
+}
+
+.table .btn-secondary {
+ background-color: var(--bs-secondary);
+}
+
+.alert-dismissible .btn-close {
+ z-index: 1;
+}
+
+.controls {
+ z-index: 2000;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ margin-right: 10px;
+}
+
+.app-menu .nav-item {
+ font-size: 0.9rem;
+ padding-bottom: 0.5rem;
+ white-space: nowrap;
+}
+
+.app-menu .nav-item a {
+ border-radius: 4px;
+ height: 3rem;
+ display: flex;
+ align-items: center;
+ line-height: 3rem;
+ padding-left: 1rem;
+}
+
+.app-menu .nav-item a.active {
+ background-color: rgba(255,255,255,0.25);
+ color: white;
+}
+
+.app-menu .nav-item a:hover {
+ background-color: rgba(255,255,255,0.1);
+ color: white;
+}
+
+.app-menu .nav-link .oi {
+ width: 1.5rem;
+ font-size: 1.1rem;
+ vertical-align: text-top;
+ top: -2px;
+}
+
+.navbar-toggler {
+ background-color: rgba(255, 255, 255, 0.1);
+ margin: .5rem;
+}
+
+div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu {
+ color: #000000;
+}
+
+.dropdown-menu span {
+ mix-blend-mode: difference;
+}
+
+@media (max-width: 767.98px) {
+
+ .app-menu {
+ width: 100%;
+ }
+
+ .navbar {
+ position: fixed;
+ top: 60px;
+ width: 100%;
+ }
+
+ .controls {
+ height: 60px;
+ top: 15px;
+ position: fixed;
+ top: 0px;
+ width: 100%;
+ background-color: rgb(0, 0, 0);
+ }
+
+ .controls-group {
+ float: right;
+ margin-right: 25px;
+ }
+
+ .content {
+ position: relative;
+ top: 60px;
+ }
+}
diff --git a/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/template.json b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/template.json
new file mode 100644
index 00000000..a3dafbfc
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/template.json
@@ -0,0 +1,6 @@
+{
+ "Title": "Default Theme Template",
+ "Type": "Internal",
+ "Version": "10.0.0",
+ "Namespace": "[Owner].Theme.[Theme]"
+}