Packages
phoenix_kit
1.7.21
1.7.210
1.7.209
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
Current section
Files
lib/phoenix_kit_web/live/users/media_detail.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Media Detail"
current_path={Routes.path("/admin/media/#{@file_id}")}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="p-6">
<%!-- Back Button --%>
<.link
navigate={Routes.path("/admin/media")}
class="btn btn-outline btn-sm mb-4"
>
<.icon name="hero-arrow-left" class="w-4 h-4 mr-2" /> Back to Media
</.link>
<%= if @file_data do %>
<%!-- Main Layout: 3/4 Preview + 1/4 Info --%>
<div class="flex flex-col lg:flex-row gap-4 h-[calc(100vh-12rem)]">
<%!-- Left: Image Preview (3/4) --%>
<div class="flex-[3] flex items-center justify-center bg-base-200 rounded-lg overflow-hidden p-4">
<%= if String.starts_with?(@file_data.mime_type, "image/") do %>
<img
src={@file_data.urls["original"] || @file_data.urls["medium"]}
alt={@file_data.filename}
class="max-w-full max-h-full object-contain rounded"
onerror="this.style.display='none'; document.getElementById('preview-error').style.display='flex';"
/>
<div
id="preview-error"
class="hidden flex-col items-center justify-center w-full h-full"
>
<.icon name="hero-photo" class="w-20 h-20 mb-4 text-error" />
<p class="text-lg font-semibold text-base-content">Image Missing or Broken</p>
<p class="text-sm text-base-content/70 mt-2">
The image file could not be loaded
</p>
</div>
<% else %>
<%= if String.starts_with?(@file_data.mime_type, "video/") do %>
<video
src={@file_data.urls["original"]}
controls
class="max-w-full max-h-full rounded"
preload="metadata"
>
Your browser does not support the video tag.
</video>
<% else %>
<div class="flex flex-col items-center justify-center text-base-content/50">
<.icon name={file_icon(@file_data.file_type)} class="w-32 h-32 mb-4" />
<p class="text-2xl font-semibold">{String.upcase(@file_data.file_type)}</p>
<p class="text-sm text-base-content/70 mt-2">{@file_data.mime_type}</p>
</div>
<% end %>
<% end %>
</div>
<%!-- Right: Info Panel (1/4) --%>
<div class="flex-1 flex flex-col gap-4 overflow-y-auto">
<%!-- File Title Card --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<h2 class="card-title text-lg break-words">{@file_data.filename}</h2>
<div class="flex gap-2 mt-2">
<span class={[
"badge",
@file_data.status == "active" && "badge-success",
@file_data.status == "processing" && "badge-info",
@file_data.status == "failed" && "badge-error"
]}>
{String.capitalize(@file_data.status)}
</span>
<span class="badge badge-primary">
{String.upcase(@file_data.file_type)}
</span>
</div>
</div>
</div>
<%!-- Metadata Card --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<div class="flex justify-between items-center mb-3">
<h3 class="font-semibold text-sm">Metadata</h3>
<button
type="button"
phx-click="toggle_edit"
class={["btn btn-xs", (@edit_mode && "btn-warning") || "btn-ghost"]}
>
<%= if @edit_mode do %>
<.icon name="hero-x-mark" class="w-3 h-3" />
<% else %>
<.icon name="hero-pencil" class="w-3 h-3" />
<% end %>
</button>
</div>
<%= if @edit_mode do %>
<form phx-submit="save_metadata" class="space-y-3">
<div>
<label for="title" class="label label-text text-xs">Title</label>
<input
type="text"
id="title"
name="title"
value={@file_data.title}
class="input input-bordered input-sm w-full"
placeholder="Enter title"
/>
</div>
<div>
<label for="description" class="label label-text text-xs">Description</label>
<textarea
id="description"
name="description"
class="textarea textarea-bordered textarea-sm w-full"
rows="3"
placeholder="Enter description"
>{@file_data.description}</textarea>
</div>
<div>
<label for="tags" class="label label-text text-xs">
Tags (comma-separated)
</label>
<input
type="text"
id="tags"
name="tags"
value={Enum.join(@file_data.tags, ", ")}
class="input input-bordered input-sm w-full"
placeholder="tag1, tag2"
/>
</div>
<div class="flex gap-2">
<button type="submit" class="btn btn-primary btn-xs flex-1">
<.icon name="hero-check" class="w-3 h-3" /> Save
</button>
<button type="button" phx-click="cancel_edit" class="btn btn-ghost btn-xs">
Cancel
</button>
</div>
</form>
<% else %>
<div class="space-y-2 text-xs">
<div>
<span class="font-semibold text-base-content/70">Title:</span>
<p class="text-base-content mt-1">
<%= if String.length(@file_data.title) > 0 do %>
{@file_data.title}
<% else %>
<span class="text-base-content/50 italic">None</span>
<% end %>
</p>
</div>
<div>
<span class="font-semibold text-base-content/70">Description:</span>
<p class="text-base-content mt-1">
<%= if String.length(@file_data.description) > 0 do %>
{@file_data.description}
<% else %>
<span class="text-base-content/50 italic">None</span>
<% end %>
</p>
</div>
<div>
<span class="font-semibold text-base-content/70">Tags:</span>
<div class="flex flex-wrap gap-1 mt-1">
<%= if Enum.empty?(@file_data.tags) do %>
<span class="text-base-content/50 italic">None</span>
<% else %>
<%= for tag <- @file_data.tags do %>
<span class="badge badge-sm">{tag}</span>
<% end %>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<%!-- File Details Card --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<h3 class="font-semibold text-sm mb-3">File Details</h3>
<div class="space-y-2 text-xs">
<div>
<span class="font-semibold text-base-content/70">Size:</span>
<p class="text-base-content mt-1">{format_file_size(@file_data.size)}</p>
</div>
<div>
<span class="font-semibold text-base-content/70">MIME Type:</span>
<p class="text-base-content mt-1 break-all">{@file_data.mime_type}</p>
</div>
<div>
<span class="font-semibold text-base-content/70">Uploaded By:</span>
<p class="text-base-content mt-1">{@file_data.user_name}</p>
</div>
<div>
<span class="font-semibold text-base-content/70">Uploaded:</span>
<p class="text-base-content mt-1">
{UtilsDate.format_datetime_with_user_format(@file_data.inserted_at)}
</p>
</div>
<div>
<span class="font-semibold text-base-content/70">Updated:</span>
<p class="text-base-content mt-1">
{UtilsDate.format_datetime_with_user_format(@file_data.updated_at)}
</p>
</div>
<div>
<span class="font-semibold text-base-content/70">File ID:</span>
<p class="text-base-content mt-1 break-all font-mono text-[10px]">
{@file_data.file_id}
</p>
</div>
</div>
</div>
</div>
<%!-- Storage Locations Card --%>
<%= if not Enum.empty?(@file_data.locations) do %>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<h3 class="font-semibold text-sm mb-3">Storage Locations</h3>
<div class="space-y-2">
<%= for location <- @file_data.locations do %>
<div class="bg-base-200 rounded p-2 text-xs">
<p class="font-semibold text-base-content">{location.bucket_name}</p>
<p class="text-base-content/70 mt-1 break-all font-mono text-[10px]">
{location.path}
</p>
<p class="text-base-content/50 mt-1">
{String.upcase(location.bucket_provider)}
</p>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<%!-- Download Variants Card --%>
<%= if map_size(@file_data.urls) > 0 do %>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<div class="flex justify-between items-center mb-3">
<h3 class="font-semibold text-sm">Download Variants</h3>
<button
type="button"
phx-click="regenerate_variants"
class="btn btn-ghost btn-xs gap-1"
>
<.icon name="hero-arrow-path" class="w-3 h-3" /> Regenerate
</button>
</div>
<div class="flex flex-col gap-2">
<%!-- Sort variants with original first --%>
<% variant_order = ["original", "large", "medium", "small", "thumbnail"] %>
<% sorted_variants =
@file_data.urls
|> Enum.sort_by(fn {name, _url} ->
Enum.find_index(variant_order, &(&1 == name)) || 999
end) %>
<%= for {variant, url} <- sorted_variants do %>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class={[
"btn btn-sm gap-2 justify-start",
variant == "original" && "btn-primary",
variant != "original" && "btn-outline"
]}
>
<.icon name="hero-arrow-down-tray" class="w-4 h-4" />
{String.capitalize(variant)}
</a>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<% else %>
<%!-- File Not Found --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body text-center">
<.icon name="hero-photo" class="w-16 h-16 mx-auto mb-4 text-base-content/50" />
<h2 class="text-2xl font-bold text-error">File Not Found</h2>
<p class="text-base-content/70 mt-2">
The file you're looking for doesn't exist or has been deleted.
</p>
<.link navigate={Routes.path("/admin/media")} class="btn btn-primary btn-sm mt-4">
Back to Media Library
</.link>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>