Packages
phoenix_kit
1.6.4
1.7.216
1.7.215
1.7.214
1.7.213
1.7.212
1.7.211
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/users/media/#{@file_id}")}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="p-6">
<div class="mb-8">
<%!-- Back Button --%>
<.link
navigate={Routes.path("/admin/users/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 %>
<h1 class="text-3xl font-bold text-base-content">{@file_data.filename}</h1>
<% else %>
<h1 class="text-3xl font-bold text-error">File Not Found</h1>
<p class="text-base-content/70 mt-1">The requested media file could not be found</p>
<% end %>
</div>
<%= if @file_data do %>
<div class="card bg-base-100 shadow-xl mb-8">
<div class="card-body">
<%!-- File Preview --%>
<div class="mb-6 flex items-center justify-center bg-base-300 rounded-lg p-4 min-h-96">
<%= 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-h-96 w-auto 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 %>
<div class="flex flex-col items-center justify-center text-base-content/50">
<.icon name={file_icon(@file_data.file_type)} class="w-20 h-20 mb-4" />
<p class="text-lg font-semibold">{String.upcase(@file_data.file_type)}</p>
</div>
<% end %>
</div>
<%!-- Metadata Section --%>
<div class="mb-6">
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-semibold text-base-content">Media Information</h3>
<button
type="button"
phx-click="toggle_edit"
class={["btn btn-sm", (@edit_mode && "btn-warning") || "btn-outline"]}
>
<%= if @edit_mode do %>
<.icon name="hero-x-mark" class="w-4 h-4" /> Cancel
<% else %>
<.icon name="hero-pencil" class="w-4 h-4" /> Edit
<% end %>
</button>
</div>
<%= if @edit_mode do %>
<form phx-submit="save_metadata" class="space-y-4">
<div>
<label for="title" class="block text-sm font-semibold text-base-content mb-1">
Title
</label>
<input
type="text"
id="title"
name="title"
value={@file_data.title}
class="input input-bordered w-full"
placeholder="Enter title"
/>
</div>
<div>
<label
for="description"
class="block text-sm font-semibold text-base-content mb-1"
>
Description
</label>
<textarea
id="description"
name="description"
class="textarea textarea-bordered w-full"
rows="4"
placeholder="Enter description"
>{@file_data.description}</textarea>
</div>
<div>
<label for="tags" class="block text-sm font-semibold text-base-content mb-1">
Tags (comma-separated)
</label>
<input
type="text"
id="tags"
name="tags"
value={Enum.join(@file_data.tags, ", ")}
class="input input-bordered w-full"
placeholder="e.g., photo, landscape, 2024"
/>
</div>
<div class="flex gap-2">
<button type="submit" class="btn btn-primary btn-sm">
<.icon name="hero-check" class="w-4 h-4" /> Save
</button>
<button type="button" phx-click="cancel_edit" class="btn btn-outline btn-sm">
<.icon name="hero-x-mark" class="w-4 h-4" /> Cancel
</button>
</div>
</form>
<% else %>
<div class="text-sm space-y-1">
<div class="flex gap-4">
<div>
<span class="font-semibold text-base-content/70">Title</span>
<span class="text-base-content ml-2">
<%= if String.length(@file_data.title) > 0 do %>
{@file_data.title}
<% else %>
<span class="text-base-content/50 italic">—</span>
<% end %>
</span>
</div>
</div>
<div class="flex gap-4">
<div>
<span class="font-semibold text-base-content/70">Description</span>
<span class="text-base-content ml-2">
<%= if String.length(@file_data.description) > 0 do %>
{@file_data.description}
<% else %>
<span class="text-base-content/50 italic">—</span>
<% end %>
</span>
</div>
</div>
<div class="flex gap-4 items-start">
<span class="font-semibold text-base-content/70">Tags</span>
<div class="flex flex-wrap gap-1">
<%= if Enum.empty?(@file_data.tags) do %>
<span class="text-base-content/50 italic">—</span>
<% else %>
<%= for tag <- @file_data.tags do %>
<span class="badge badge-sm">{tag}</span>
<% end %>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
<%!-- Divider --%>
<div class="divider my-4"></div>
<%!-- File Information --%>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label class="text-sm font-semibold text-base-content/70">File Name</label>
<p class="text-base-content mt-1">{@file_data.filename}</p>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">File Type</label>
<span class="badge badge-lg badge-primary mt-1">
{String.upcase(@file_data.file_type)}
</span>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">File Size</label>
<p class="text-base-content mt-1">{format_file_size(@file_data.size)}</p>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">Status</label>
<span class={[
"badge badge-lg mt-1",
@file_data.status == "active" && "badge-success",
@file_data.status == "processing" && "badge-info",
@file_data.status == "failed" && "badge-error"
]}>
{String.capitalize(@file_data.status)}
</span>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">MIME Type</label>
<p class="text-base-content mt-1 break-all text-sm">{@file_data.mime_type}</p>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">Uploaded By</label>
<p class="text-base-content mt-1 text-sm">{@file_data.user_name}</p>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">File ID</label>
<p class="text-base-content mt-1 break-all text-sm font-mono">
{@file_data.file_id}
</p>
</div>
</div>
<%!-- Timestamps --%>
<div class="divider my-4"></div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label class="text-sm font-semibold text-base-content/70">Uploaded</label>
<p class="text-base-content mt-1 text-sm">
{UtilsDate.format_datetime_with_user_format(@file_data.inserted_at)}
</p>
</div>
<div>
<label class="text-sm font-semibold text-base-content/70">Updated</label>
<p class="text-base-content mt-1 text-sm">
{UtilsDate.format_datetime_with_user_format(@file_data.updated_at)}
</p>
</div>
</div>
<%!-- Storage Locations --%>
<%= if not Enum.empty?(@file_data.locations) do %>
<div class="mb-6">
<label class="text-sm font-semibold text-base-content mb-3 block">
Storage Locations
</label>
<div class="space-y-2">
<%= for location <- @file_data.locations do %>
<div class="bg-base-200 rounded p-3 text-sm">
<div class="flex items-start gap-2">
<div class="flex-1">
<p class="font-semibold text-base-content">{location.bucket_name}</p>
<p class="text-base-content/70 text-xs mt-1 break-all font-mono">
{location.path}
</p>
<p class="text-base-content/50 text-xs mt-1">
Provider: {String.upcase(location.bucket_provider)}
</p>
</div>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
<%!-- Download Links --%>
<%= if map_size(@file_data.urls) > 0 do %>
<div>
<p class="text-sm font-semibold text-base-content mb-3">Download Variants:</p>
<div class="flex gap-2 flex-wrap">
<%!-- 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 gap-2",
variant == "original" && "btn-primary",
variant != "original" && "btn-outline btn-sm"
]}
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
>
</path>
</svg>
{String.capitalize(variant)}
</a>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
<% else %>
<div class="bg-base-200 rounded-lg p-8 text-center">
<.icon name="hero-photo" class="w-12 h-12 mx-auto mb-4 text-base-content/50" />
<p class="text-base-content/70">
The file you're looking for doesn't exist or has been deleted.
</p>
<.link navigate={Routes.path("/admin/users/media")} class="btn btn-primary btn-sm mt-4">
Back to Media Library
</.link>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>