Packages

phoenix_kit

1.7.69
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules publishing web new.html.heex
Raw

lib/modules/publishing/web/new.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={@page_title}
current_path={@current_path}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="container flex flex-col mx-auto px-4 py-6">
<%!-- Header Section --%>
<.admin_page_header
back={Routes.path("/admin/settings/publishing")}
title={gettext("Create a New Publishing Group")}
subtitle={
gettext(
"Spin up a dedicated workspace for your content team. Choose the content type that fits your publishing needs."
)
}
/>
<div class="max-w-2xl mx-auto space-y-6">
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body space-y-6">
<.form
for={@form}
id="group-create-form"
class="space-y-6"
phx-change="update_new_group"
phx-submit="add_group"
>
<div class="space-y-2">
<.input
field={@form[:name]}
type="text"
label={gettext("Group Name")}
placeholder={gettext("e.g. Product Updates")}
required
/>
<p class="text-xs text-base-content/60">
{gettext("Used for display in the navigation and admin sections.")}
</p>
</div>
<div class="space-y-2">
<.input
field={@form[:slug]}
type="text"
label={gettext("Group Slug")}
placeholder={gettext("e.g. product-updates")}
required
phx-change="manual_slug"
phx-debounce="150"
/>
<div class="space-y-1">
<p class="text-xs text-base-content/60">
{gettext("Used for the URL and as the unique identifier for this group.")}
</p>
<p class="text-xs font-medium text-base-content/70">
<span class="font-semibold">{gettext("Format")}:</span>
{gettext(
"Only lowercase letters (a-z), numbers (0-9), and hyphens (-) are allowed. Must not start or end with a hyphen."
)}
</p>
<p class="text-xs text-success">
{gettext("Valid examples")}: <code class="font-mono">blog</code>, <code class="font-mono">
product-updates
</code>,
<code class="font-mono">news-2025</code>
</p>
<p class="text-xs text-error">
{gettext("Invalid examples")}: <code class="font-mono">Blog</code>, <code class="font-mono">
product_updates
</code>, <code class="font-mono">-news</code>,
<code class="font-mono">
my blog
</code>
</p>
</div>
<% slug_value = @form[:slug].value || "" %>
<% slug_sample = if slug_value == "", do: gettext("your-group"), else: slug_value %>
<% url_prefix =
PhoenixKit.Config.get_url_prefix()
|> case do
"/" -> ""
prefix -> prefix
end %>
<%!-- Use first enabled language (default) for preview URLs --%>
<% default_language = List.first(@enabled_languages) %>
<% timestamp_url_pattern =
if length(@enabled_languages) == 1 do
@endpoint_url <> "#{url_prefix}/#{slug_sample}/<YYYY-MM-DD>/<HH:mm>"
else
@endpoint_url <>
"#{url_prefix}/#{default_language}/#{slug_sample}/<YYYY-MM-DD>/<HH:mm>"
end %>
<% slug_url_pattern =
if length(@enabled_languages) == 1 do
@endpoint_url <> "#{url_prefix}/#{slug_sample}/:post_slug"
else
@endpoint_url <> "#{url_prefix}/#{default_language}/#{slug_sample}/:post_slug"
end %>
</div>
<%!-- Content Type Section --%>
<div class="form-control">
<label class="label">
<span class="label-text text-sm font-semibold text-base-content">
{gettext("Content Type")}
</span>
</label>
<div class="space-y-3">
<%= for preset <- @preset_types do %>
<label class="cursor-pointer flex items-start gap-3 rounded-lg border border-base-300/70 bg-base-200/40 px-4 py-3 hover:border-primary/60 transition">
<input
type="radio"
name={@form[:type].name}
value={preset.type}
class="radio radio-primary mt-1"
checked={@form[:type].value == preset.type}
phx-click="type_changed"
phx-value-type={preset.type}
/>
<div>
<p class="font-medium text-base-content">{preset.label}</p>
<p class="text-sm text-base-content/70">
<%= case preset.type do %>
<% "blogging" -> %>
{gettext(
"Posts, news articles, and updates. Items called \"%{plural}\".",
plural: preset.item_plural
)}
<% "faq" -> %>
{gettext("Frequently asked questions. Items called \"%{plural}\".",
plural: preset.item_plural
)}
<% "legal" -> %>
{gettext(
"Legal pages like terms and privacy policy. Items called \"%{plural}\".",
plural: preset.item_plural
)}
<% _ -> %>
{gettext("Items called \"%{plural}\".", plural: preset.item_plural)}
<% end %>
</p>
</div>
</label>
<% end %>
<%!-- Custom Type Option --%>
<label class="cursor-pointer flex items-start gap-3 rounded-lg border border-base-300/70 bg-base-200/40 px-4 py-3 hover:border-primary/60 transition">
<input
type="radio"
name={@form[:type].name}
value="custom"
class="radio radio-primary mt-1"
checked={@form[:type].value not in ["blogging", "faq", "legal"]}
phx-click="type_changed"
phx-value-type="custom"
/>
<div class="flex-1">
<p class="font-medium text-base-content">{gettext("Custom")}</p>
<p class="text-sm text-base-content/70">
{gettext("Define your own content type and item names.")}
</p>
<%= if @form[:type].value not in ["blogging", "faq", "legal"] do %>
<div class="mt-2">
<input
type="text"
name="group[custom_type]"
class="input input-bordered input-sm w-full max-w-xs"
placeholder={gettext("e.g. tutorials, recipes")}
value={
if @form[:type].value not in ["blogging", "faq", "legal", "custom"],
do: @form[:type].value,
else: ""
}
phx-change="update_new_group"
phx-debounce="300"
/>
</div>
<% end %>
</div>
</label>
</div>
<label class="label">
<span class="label-text-alt text-xs text-base-content/60">
{gettext(
"Content type determines default item naming and may affect how content is displayed."
)}
</span>
</label>
</div>
<%!-- Item Naming Section --%>
<div class="form-control">
<label class="label">
<span class="label-text text-sm font-semibold text-base-content">
{gettext("Item Naming")}
</span>
</label>
<p class="text-xs text-base-content/60 mb-3">
{gettext(
"What do you call items in this publishing group? These names appear in the UI."
)}
</p>
<div class="grid grid-cols-2 gap-4">
<div>
<.input
field={@form[:item_singular]}
type="text"
label={gettext("Singular")}
placeholder={gettext("e.g. post, question")}
phx-focus="item_name_changed"
/>
</div>
<div>
<.input
field={@form[:item_plural]}
type="text"
label={gettext("Plural")}
placeholder={gettext("e.g. posts, questions")}
phx-focus="item_name_changed"
/>
</div>
</div>
<label class="label">
<span class="label-text-alt text-xs text-base-content/60">
{gettext("Examples: \"Create new %{singular}\", \"View all %{plural}\"",
singular: @form[:item_singular].value || "item",
plural: @form[:item_plural].value || "items"
)}
</span>
</label>
</div>
<%!-- URL Mode Section --%>
<div class="form-control">
<label class="label">
<span class="label-text text-sm font-semibold text-base-content">
{gettext("URL Mode")}
</span>
</label>
<div class="space-y-3">
<label class="cursor-pointer flex items-start gap-3 rounded-lg border border-base-300/70 bg-base-200/40 px-4 py-3 hover:border-primary/60 transition">
<input
type="radio"
name={@form[:mode].name}
value="timestamp"
class="radio radio-primary mt-1"
checked={@form[:mode].value == "timestamp"}
/>
<div>
<p class="font-medium text-base-content">{gettext("Timestamp-based")}</p>
<p class="text-sm text-base-content/70">
{gettext(
"Organizes posts in date/time folders. Great for news, updates, and chronological feeds."
)}
</p>
<div class="mt-2 space-y-1 text-xs text-base-content/70">
<p>
<span class="font-medium text-base-content">
{gettext("Public URL")}:
</span>
<code class="font-mono break-all">{timestamp_url_pattern}</code>
</p>
</div>
</div>
</label>
<label class="cursor-pointer flex items-start gap-3 rounded-lg border border-base-300/70 bg-base-200/40 px-4 py-3 hover:border-primary/60 transition">
<input
type="radio"
name={@form[:mode].name}
value="slug"
class="radio radio-primary mt-1"
checked={@form[:mode].value == "slug"}
/>
<div>
<p class="font-medium text-base-content">{gettext("Slug-based")}</p>
<p class="text-sm text-base-content/70">
{gettext(
"Uses semantic URLs and editable slugs. Ideal for documentation, guides, and evergreen content."
)}
</p>
<div class="mt-2 space-y-1 text-xs text-base-content/70">
<p>
<span class="font-medium text-base-content">
{gettext("Public URL")}:
</span>
<code class="font-mono break-all">{slug_url_pattern}</code>
</p>
</div>
</div>
</label>
</div>
<label class="label">
<span class="label-text-alt text-xs text-base-content/60">
{gettext(
"The URL mode locks in after creation to keep your structure consistent."
)}
</span>
</label>
</div>
<div class="flex flex-wrap gap-3 justify-end">
<button type="submit" class="btn btn-primary btn-sm">
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> {gettext(
"Create Publishing Group"
)}
</button>
<button type="button" class="btn btn-ghost btn-sm" phx-click="cancel">
<.icon name="hero-arrow-uturn-left" class="w-4 h-4 mr-1" /> {gettext("Cancel")}
</button>
</div>
</.form>
</div>
</div>
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body space-y-3">
<h3 class="text-lg font-semibold text-base-content">
{gettext("What happens next")}
</h3>
<ul class="text-sm text-base-content/70 space-y-2">
<li>{gettext("We create a publishing group in the database.")}</li>
<li>{gettext("You'll see the new group in the navigation immediately.")}</li>
<li>{gettext("Editors can jump right in and start creating content.")}</li>
</ul>
</div>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>