Packages
phoenix_kit
1.5.1
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.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Media"
current_path={@url_path}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="p-6">
<div class="mb-8">
<h1 class="text-3xl font-bold text-base-content">Media</h1>
<p class="text-base-content/70 mt-1">Manage user media and uploads</p>
</div>
<%!-- Upload Section --%>
<div class="card bg-base-100 shadow-xl mb-8">
<div class="card-body">
<h2 class="card-title text-xl mb-4">Upload Media</h2>
<p class="text-sm text-base-content/70 mb-4">
Upload images, videos, or PDFs. Files are automatically processed and optimized.
</p>
<form phx-submit="upload">
<.file_upload
upload={@uploads.media_files}
label="Upload Media Files"
icon="hero-cloud-arrow-up"
accept_description="JPG, PNG, WebP, MP4, WebM, PDF"
max_size_description="100MB per file"
/>
<button
type="submit"
class="btn btn-primary mt-4"
disabled={length(@uploads.media_files.entries) == 0}
>
<.icon name="hero-cloud-arrow-up" class="w-4 h-4 mr-2" /> Upload Files
</button>
</form>
</div>
</div>
<%!-- Image Preview Modal (Client-side controlled) --%>
<dialog id="image-modal" class="modal">
<div class="modal-box max-w-4xl">
<%!-- Large Image Preview --%>
<figure class="mb-6 flex items-center justify-center bg-base-300 rounded-lg p-4">
<img
id="modal-image"
src=""
alt="Image preview"
class="max-h-96 w-auto rounded"
/>
</figure>
<%!-- File Info --%>
<h3 id="modal-filename" class="font-bold text-xl mb-2"></h3>
<div class="flex gap-3 mb-6">
<span id="modal-file-type" class="badge badge-lg"></span>
<span id="modal-file-status" class="badge badge-lg"></span>
<span id="modal-file-size" class="badge badge-lg"></span>
</div>
<%!-- Download Links --%>
<div class="space-y-2 mb-6">
<p class="text-sm font-semibold text-base-content mb-3">Download variants:</p>
<div id="modal-variants"></div>
</div>
<%!-- Close Button --%>
<div class="modal-action">
<button
type="button"
class="btn btn-primary"
onclick="document.getElementById('image-modal').close()"
>
Close
</button>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<%!-- Modal Control Script --%>
<script>
const fileBadgeMap = {
'image': 'badge-info',
'video': 'badge-warning',
'pdf': 'badge-error',
'document': 'badge-ghost'
};
const statusBadgeMap = {
'active': 'badge-success',
'processing': 'badge-info',
'failed': 'badge-error'
};
// Format file size
function formatFileSize(bytes) {
if (bytes >= 1_000_000_000) return (bytes / 1_000_000_000).toFixed(2) + ' GB';
if (bytes >= 1_000_000) return (bytes / 1_000_000).toFixed(2) + ' MB';
if (bytes >= 1_000) return (bytes / 1_000).toFixed(2) + ' KB';
return bytes + ' B';
}
// Show modal with file data
function showImageModal(fileData) {
const modal = document.getElementById('image-modal');
const imageEl = document.getElementById('modal-image');
const filenameEl = document.getElementById('modal-filename');
const fileTypeEl = document.getElementById('modal-file-type');
const statusEl = document.getElementById('modal-file-status');
const sizeEl = document.getElementById('modal-file-size');
const variantsEl = document.getElementById('modal-variants');
// Set image and basic info
imageEl.src = fileData.urls.original || fileData.urls.medium || '';
imageEl.alt = fileData.filename;
filenameEl.textContent = fileData.filename;
// Set badges
const badgeClass = fileBadgeMap[fileData.file_type] || 'badge-ghost';
fileTypeEl.className = `badge badge-lg ${badgeClass}`;
fileTypeEl.textContent = fileData.file_type.toUpperCase();
const statusBadgeClass = statusBadgeMap[fileData.status] || 'badge-warning';
statusEl.className = `badge badge-lg ${statusBadgeClass}`;
statusEl.textContent = fileData.status.charAt(0).toUpperCase() + fileData.status.slice(1);
sizeEl.textContent = formatFileSize(fileData.size);
// Generate download links
variantsEl.innerHTML = Object.entries(fileData.urls)
.map(([variant, url]) => `
<a
href="${url}"
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-outline w-full justify-start"
>
<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>
${variant.charAt(0).toUpperCase() + variant.slice(1)}
</a>
`)
.join('');
// Show modal
modal.showModal();
}
// Attach click handlers to file thumbnails
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[data-file-id]').forEach(el => {
el.addEventListener('click', function(e) {
e.preventDefault();
const fileData = JSON.parse(this.getAttribute('data-file-data'));
showImageModal(fileData);
});
});
});
</script>
<%!-- Uploaded Files --%>
<%= if length(@uploaded_files) > 0 do %>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-xl mb-4">Uploaded Files ({length(@uploaded_files)})</h2>
<div class="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-6 gap-3">
<%= for file <- @uploaded_files do %>
<div
class="relative aspect-square bg-base-300 rounded-lg overflow-hidden cursor-pointer hover:shadow-lg transition-shadow"
data-file-id={file.file_id}
data-file-data={Jason.encode!(file)}
>
<% image_url = file.urls["medium"] || file.urls["original"] %>
<%= if image_url do %>
<img
src={image_url}
alt={file.filename}
class="w-full h-full object-cover pointer-events-none"
/>
<% else %>
<div class="flex flex-col items-center justify-center w-full h-full text-base-content/50">
<.icon name={file_icon(file.file_type)} class="w-12 h-12 mb-2" />
<p class="text-sm font-semibold">{String.upcase(file.file_type)}</p>
</div>
<% end %>
<%!-- File size in corner --%>
<div class="absolute bottom-2 right-2 bg-black/60 text-white text-xs px-2 py-1 rounded pointer-events-none">
{format_file_size(file.size)}
</div>
</div>
<% end %>
</div>
</div>
</div>
<% else %>
<div class="bg-base-200 rounded-lg p-8 text-center">
<p class="text-base-content/70">No media uploaded yet. Upload your first file above!</p>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>