Packages

phoenix_kit

1.7.113
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
phoenix_kit lib phoenix_kit_web live users media_detail.html.heex
Raw

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_uuid}")}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="p-6">
<.admin_page_header back={Routes.path("/admin/media")} title="Media Detail" />
<%= 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 %>
<%= if @file_data.mime_type == "application/pdf" do %>
<iframe
src={@file_data.urls["original"]}
class="w-full h-full rounded border-0"
title={@file_data.filename}
>
<p class="text-base-content/70">
Your browser does not support inline PDF viewing.
<a
href={@file_data.urls["original"]}
target="_blank"
class="link link-primary"
>
Download PDF
</a>
</p>
</iframe>
<% 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 %>
<% end %>
</div>
<%!-- Right: Info Panel (1/4) --%>
<div class="flex-1 flex flex-col gap-4 overflow-y-auto overflow-x-hidden min-w-0">
<%!-- File Title Card --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<h2 class="text-lg font-bold break-all">{@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 class="mt-3 flex flex-col gap-2">
<%= if @file_data.status == "trashed" do %>
<button
type="button"
phx-click="restore_file"
class="btn btn-success btn-sm w-full gap-2"
>
<.icon name="hero-arrow-uturn-left" class="w-4 h-4" /> Restore
</button>
<button
type="button"
phx-click="permanently_delete_file"
data-confirm="Permanently delete this file? This cannot be undone."
class="btn btn-error btn-sm w-full gap-2"
>
<.icon name="hero-trash" class="w-4 h-4" /> Delete Forever
</button>
<% else %>
<button
type="button"
phx-click="confirm_delete"
class="btn btn-error btn-sm w-full gap-2"
>
<.icon name="hero-trash" class="w-4 h-4" /> Delete File
</button>
<% end %>
</div>
</div>
</div>
<%!-- Metadata Card --%>
<div class="card bg-base-100 shadow-xl min-w-0">
<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 break-all">
<%= 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 break-all">
<%= 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 h-auto">{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 UUID:</span>
<p class="text-base-content mt-1 break-all font-mono text-[10px]">
{@file_data.file_uuid}
</p>
</div>
<%= if @file_data.mime_type == "application/pdf" and is_map(@file_data.metadata) do %>
<% pdf_meta = @file_data.metadata %>
<%= if pdf_meta["page_count"] do %>
<div>
<span class="font-semibold text-base-content/70">Pages:</span>
<p class="text-base-content mt-1">{pdf_meta["page_count"]}</p>
</div>
<% end %>
<%= if pdf_meta["author"] && pdf_meta["author"] != "" do %>
<div>
<span class="font-semibold text-base-content/70">Author:</span>
<p class="text-base-content mt-1">{pdf_meta["author"]}</p>
</div>
<% end %>
<%= if pdf_meta["title"] && pdf_meta["title"] != "" do %>
<div>
<span class="font-semibold text-base-content/70">PDF Title:</span>
<p class="text-base-content mt-1">{pdf_meta["title"]}</p>
</div>
<% end %>
<% end %>
</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" />
<span class="flex-1 text-left">{String.capitalize(variant)}</span>
<%= if info = @file_data.variant_dimensions[variant] do %>
<span class="text-xs opacity-60">
<%= if info.dimensions do %>
{elem(info.dimensions, 0)}x{elem(info.dimensions, 1)}
<% end %>
<%= if info.size do %>
ยท {format_file_size(info.size)}
<% end %>
</span>
<% end %>
</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">
<.icon name="hero-arrow-left" class="w-4 h-4" /> Library
</.link>
</div>
</div>
<% end %>
</div>
<%!-- Delete Confirmation Modal --%>
<%= if @show_delete_modal do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg text-warning">Move to Trash</h3>
<p class="py-4">
Move <strong>{@file_data && @file_data.filename}</strong> to trash?
</p>
<p class="text-sm text-base-content/70 pb-4">
You can restore it from the trash at any time.
</p>
<div class="modal-action">
<button type="button" phx-click="cancel_delete" class="btn btn-ghost">
Cancel
</button>
<button type="button" phx-click="delete_file" class="btn btn-warning gap-2">
<.icon name="hero-trash" class="w-4 h-4" /> Move to Trash
</button>
</div>
</div>
<div class="modal-backdrop" phx-click="cancel_delete"></div>
</div>
<% end %>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>