| 1 |
{%- import "partials/_ui.html" as ui -%} |
| 2 |
<div class="tab-docs"><a href="/docs/items">Docs: Items →</a></div> |
| 3 |
|
| 4 |
<div class="content-header"> |
| 5 |
<h2 class="subsection-title">Items</h2> |
| 6 |
<a href="/dashboard/project/{{ project_slug }}/new-item" class="btn-primary">New Item</a> |
| 7 |
</div> |
| 8 |
|
| 9 |
{% if items.is_empty() %} |
| 10 |
<div class="empty-state empty-state--lg"> |
| 11 |
<p class="mb-4">No items in this project yet. Add your first item to start publishing.</p> |
| 12 |
<a href="/dashboard/project/{{ project_slug }}/new-item" class="btn-primary">Add First Item</a> |
| 13 |
<p class="form-hint mt-5">After creating an item, set its pricing and publish it to make it available to fans.</p> |
| 14 |
</div> |
| 15 |
{% else %} |
| 16 |
<div class="content-filters"> |
| 17 |
<input type="text" id="content-search" placeholder="Search items..." data-input="filterContentTable" |
| 18 |
class="content-filter-search"> |
| 19 |
<select id="content-filter-status" data-change="filterContentTable" class="content-filter-select"> |
| 20 |
<option value="">All statuses</option> |
| 21 |
<option value="Active">Active</option> |
| 22 |
<option value="Draft">Draft</option> |
| 23 |
</select> |
| 24 |
<select id="content-filter-type" data-change="filterContentTable" class="content-filter-select"> |
| 25 |
<option value="">All types</option> |
| 26 |
{% for item in items %}<option value="{{ item.item_type }}" class="content-type-opt">{{ item.item_type }}</option>{% endfor %} |
| 27 |
</select> |
| 28 |
</div> |
| 29 |
<div id="bulk-action-bar" class="bulk-action-bar" data-active="false"> |
| 30 |
<span id="bulk-count" class="bulk-count">0 selected</span> |
| 31 |
<button class="btn-secondary small" data-action="bulkAction" data-arg="publish" disabled>Publish</button> |
| 32 |
<button class="btn-secondary small" data-action="bulkAction" data-arg="unpublish" disabled>Unpublish</button> |
| 33 |
<button class="btn-secondary small" data-action="showBulkPrice" disabled id="bulk-price-btn">Set Price</button> |
| 34 |
<button class="btn-secondary small" data-action="showBulkTag" disabled id="bulk-tag-btn">Add Tag</button> |
| 35 |
<button class="btn-secondary small danger-text" data-action="bulkAction" data-arg="delete" disabled>Delete</button> |
| 36 |
<button class="btn-secondary small bulk-deselect" data-action="deselectAll" disabled>Deselect</button> |
| 37 |
</div> |
| 38 |
<div id="bulk-price-form" class="bulk-form hidden"> |
| 39 |
<div class="bulk-form-row"> |
| 40 |
<div> |
| 41 |
<label class="bulk-form-label">New price ($)</label> |
| 42 |
<input type="number" id="bulk-price-input" min="0" step="0.01" placeholder="0.00" class="bulk-form-input input--xs w-120"> |
| 43 |
</div> |
| 44 |
<button class="btn-primary small" data-action="submitBulkPrice">Apply</button> |
| 45 |
<button class="btn-secondary small" data-action="hide" data-target="bulk-price-form">Cancel</button> |
| 46 |
</div> |
| 47 |
<p class="form-hint mt-2">Enter 0 to make items free.</p> |
| 48 |
</div> |
| 49 |
<div id="bulk-tag-form" class="bulk-form hidden"> |
| 50 |
<div class="bulk-form-row"> |
| 51 |
<div> |
| 52 |
<label class="bulk-form-label">Tag slug</label> |
| 53 |
<input type="text" id="bulk-tag-input" placeholder="e.g. audio.genre.ambient" class="bulk-form-input input--xs w-260"> |
| 54 |
</div> |
| 55 |
<button class="btn-primary small" data-action="submitBulkTag">Apply</button> |
| 56 |
<button class="btn-secondary small" data-action="hide" data-target="bulk-tag-form">Cancel</button> |
| 57 |
</div> |
| 58 |
<p class="form-hint mt-2">Use dot-notation: type.category.value</p> |
| 59 |
</div> |
| 60 |
<table class="data-table"> |
| 61 |
<thead> |
| 62 |
<tr> |
| 63 |
<th class="col-3"><input type="checkbox" id="select-all" data-change="toggleSelectAllFromEl"></th> |
| 64 |
<th class="col-5">#</th> |
| 65 |
<th class="col-32 sortable-th" data-action="sortContent" data-arg="2" data-arg2="text" title="Sort by title">Item <span class="sort-arrow" data-col="2"></span></th> |
| 66 |
<th class="col-12 sortable-th" data-action="sortContent" data-arg="3" data-arg2="text" title="Sort by type">Type <span class="sort-arrow" data-col="3"></span></th> |
| 67 |
<th class="col-10 sortable-th" data-action="sortContent" data-arg="4" data-arg2="money" title="Sort by price">Price <span class="sort-arrow" data-col="4"></span></th> |
| 68 |
<th class="col-10 sortable-th" data-action="sortContent" data-arg="5" data-arg2="num" title="Sort by sales">Sales <span class="sort-arrow" data-col="5"></span></th> |
| 69 |
<th class="col-10 sortable-th" data-action="sortContent" data-arg="6" data-arg2="money" title="Sort by revenue">Revenue <span class="sort-arrow" data-col="6"></span></th> |
| 70 |
<th class="col-8 sortable-th" data-action="sortContent" data-arg="7" data-arg2="text" title="Sort by status">Status <span class="sort-arrow" data-col="7"></span></th> |
| 71 |
<th class="col-10">Actions</th> |
| 72 |
</tr> |
| 73 |
</thead> |
| 74 |
<tbody> |
| 75 |
{% for item in items %} |
| 76 |
<tr> |
| 77 |
<td><input type="checkbox" class="bulk-check" value="{{ item.id }}" data-change="updateBulkUI"></td> |
| 78 |
<td class="nowrap"> |
| 79 |
{{ item.position }} |
| 80 |
{% if !loop.first %} |
| 81 |
<button class="btn-secondary order-arrow-btn" |
| 82 |
hx-put="/api/items/{{ item.id }}/move" hx-vals='{"direction":"up"}' |
| 83 |
hx-swap="none" |
| 84 |
data-hx-get="/dashboard/project/{{ project_slug }}/tabs/content" data-hx-target="#tab-content">▲</button> |
| 85 |
{% endif %} |
| 86 |
{% if !loop.last %} |
| 87 |
<button class="btn-secondary order-arrow-btn" |
| 88 |
hx-put="/api/items/{{ item.id }}/move" hx-vals='{"direction":"down"}' |
| 89 |
hx-swap="none" |
| 90 |
data-hx-get="/dashboard/project/{{ project_slug }}/tabs/content" data-hx-target="#tab-content">▼</button> |
| 91 |
{% endif %} |
| 92 |
</td> |
| 93 |
<td> |
| 94 |
<span id="item-title-{{ item.id }}"> |
| 95 |
{% if !item.children.is_empty() %} |
| 96 |
<button class="bundle-toggle" data-action="toggleBundleChildren" data-arg="{{ item.id }}" title="Toggle bundle contents">▶</button> |
| 97 |
{% endif %} |
| 98 |
<a href="/dashboard/item/{{ item.id }}" class="fw-bold">{{ item.title }}</a> |
| 99 |
{% if item.is_unlisted %}<span class="badge badge--inline-tiny">Unlisted</span>{% endif %} |
| 100 |
</span> |
| 101 |
</td> |
| 102 |
<td>{{ item.item_type }}{% if !item.children.is_empty() %} <span class="dimmed text-xs">({{ item.children.len() }})</span>{% endif %}</td> |
| 103 |
<td>{{ item.price }}</td> |
| 104 |
<td>{{ item.sales }}</td> |
| 105 |
<td>{{ item.revenue }}</td> |
| 106 |
<td><span class="badge {{ item.status|lowercase }}">{{ item.status }}</span></td> |
| 107 |
<td> |
| 108 |
<div class="item-actions"> |
| 109 |
{% if item.status == "Draft" %} |
| 110 |
<a href="/dashboard/project/{{ project_slug }}/new-item/{{ item.id }}/step/details" class="btn-primary small">Continue</a> |
| 111 |
<button class="btn-secondary small" data-action="quickPublish" data-arg="{{ item.id }}">Publish</button> |
| 112 |
{% else %} |
| 113 |
<a href="/i/{{ item.id }}" class="btn-secondary small">View</a> |
| 114 |
{% endif %} |
| 115 |
<a href="/dashboard/item/{{ item.id }}" class="btn-secondary small">Edit</a> |
| 116 |
<button class="btn-secondary small" |
| 117 |
data-action="inlineRename" data-arg="{{ item.id }}">Rename</button> |
| 118 |
</div> |
| 119 |
</td> |
| 120 |
</tr> |
| 121 |
{% for child in item.children %} |
| 122 |
<tr class="bundle-child bundle-child-{{ item.id }} hidden"> |
| 123 |
<td><input type="checkbox" class="bulk-check" value="{{ child.id }}" data-change="updateBulkUI"></td> |
| 124 |
<td></td> |
| 125 |
<td class="bundle-child-cell"> |
| 126 |
<span id="item-title-{{ child.id }}" class="bundle-child-title"> |
| 127 |
<span class="bundle-child-arrow">↳</span> |
| 128 |
<a href="/dashboard/item/{{ child.id }}">{{ child.title }}</a> |
| 129 |
{% if child.is_unlisted %}<span class="badge badge--inline-tiny">Unlisted</span>{% endif %} |
| 130 |
</span> |
| 131 |
</td> |
| 132 |
<td class="muted">{{ child.item_type }}</td> |
| 133 |
<td class="muted">{{ child.price }}</td> |
| 134 |
<td class="muted">{{ child.sales }}</td> |
| 135 |
<td class="muted">{{ child.revenue }}</td> |
| 136 |
<td><span class="badge {{ child.status|lowercase }}">{{ child.status }}</span></td> |
| 137 |
<td> |
| 138 |
<div class="item-actions"> |
| 139 |
<a href="/dashboard/item/{{ child.id }}" class="btn-secondary small">Edit</a> |
| 140 |
</div> |
| 141 |
</td> |
| 142 |
</tr> |
| 143 |
{% endfor %} |
| 144 |
{% endfor %} |
| 145 |
</tbody> |
| 146 |
</table> |
| 147 |
{% endif %} |
| 148 |
|
| 149 |
{% if !deleted_items.is_empty() %} |
| 150 |
<details class="deleted-items-details"> |
| 151 |
<summary class="deleted-items-summary">Recently Deleted ({{ deleted_items.len() }})</summary> |
| 152 |
<p class="form-hint mt-2">Deleted items are permanently removed after 7 days.</p> |
| 153 |
<table class="data-table mt-3"> |
| 154 |
<thead> |
| 155 |
<tr> |
| 156 |
<th>Title</th> |
| 157 |
<th>Deleted</th> |
| 158 |
<th></th> |
| 159 |
</tr> |
| 160 |
</thead> |
| 161 |
<tbody> |
| 162 |
{% for item in deleted_items %} |
| 163 |
<tr id="deleted-{{ item.id }}"> |
| 164 |
<td class="dimmed">{{ item.title }}</td> |
| 165 |
<td class="dimmed">{{ item.deleted_at }}</td> |
| 166 |
<td> |
| 167 |
<button class="btn-secondary small" |
| 168 |
hx-post="/api/items/{{ item.id }}/restore" |
| 169 |
hx-swap="none" |
| 170 |
data-hx-get="/dashboard/project/{{ project_slug }}/tabs/content" data-hx-target="#tab-content">Restore</button> |
| 171 |
</td> |
| 172 |
</tr> |
| 173 |
{% endfor %} |
| 174 |
</tbody> |
| 175 |
</table> |
| 176 |
</details> |
| 177 |
{% endif %} |
| 178 |
|
| 179 |
<div id="tab-project-content-cfg" hidden data-project-slug="{{ project_slug }}"></div> |
| 180 |
<script src="/static/tab-project-content.js?v=0623"></script> |
| 181 |
|
| 182 |
<hr class="section-divider"> |
| 183 |
|
| 184 |
<div class="content-header"> |
| 185 |
<h2 class="subsection-title">Blog Posts</h2> |
| 186 |
<a href="/dashboard/project/{{ project_slug }}/blog/new"> |
| 187 |
<button class="btn-primary">New Post</button> |
| 188 |
</a> |
| 189 |
</div> |
| 190 |
|
| 191 |
{% if posts.is_empty() %} |
| 192 |
{% call ui::empty_state_compact("No blog posts yet. Share updates, release notes, or stories with your audience.") %}{% endcall %} |
| 193 |
{% else %} |
| 194 |
<table class="data-table"> |
| 195 |
<thead> |
| 196 |
<tr> |
| 197 |
<th class="col-45">Title</th> |
| 198 |
<th class="col-15">Status</th> |
| 199 |
<th class="col-20">Published</th> |
| 200 |
<th class="col-20">Actions</th> |
| 201 |
</tr> |
| 202 |
</thead> |
| 203 |
<tbody> |
| 204 |
{% for post in posts %} |
| 205 |
<tr id="post-row-{{ post.id }}"> |
| 206 |
<td> |
| 207 |
<a href="/p/{{ project_slug }}/blog/{{ post.slug }}" class="fw-bold">{{ post.title }}</a> |
| 208 |
</td> |
| 209 |
<td><span class="badge {{ post.status|lowercase }}">{{ post.status }}</span></td> |
| 210 |
<td>{{ post.published_at }}</td> |
| 211 |
<td> |
| 212 |
<div class="item-actions"> |
| 213 |
<a href="/p/{{ project_slug }}/blog/{{ post.slug }}" class="btn-secondary small">View</a> |
| 214 |
<a href="/dashboard/project/{{ project_slug }}/blog/new?post={{ post.id }}" class="btn-secondary small">Edit</a> |
| 215 |
<button class="btn-secondary small danger-text" |
| 216 |
hx-delete="/api/blog/{{ post.id }}" |
| 217 |
hx-target="#post-row-{{ post.id }}" |
| 218 |
hx-swap="outerHTML" |
| 219 |
hx-confirm="Delete this blog post?">Delete</button> |
| 220 |
</div> |
| 221 |
</td> |
| 222 |
</tr> |
| 223 |
{% endfor %} |
| 224 |
</tbody> |
| 225 |
</table> |
| 226 |
{% endif %} |
| 227 |
|