Files
ladill-transfer/resources/views/transfer/transfers/create.blade.php
T
isaaccladandCursor 1a95915e21
Deploy Ladill Transfer / deploy (push) Successful in 28s
Separate share transfers from Files storage and add folder upload to New transfer.
Transfers lists only intentional shares while Files shows all storage, and New transfer accepts folder picks with flattened file uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 22:31:45 +00:00

131 lines
9.1 KiB
PHP

<x-user-layout>
<x-slot name="title">New transfer</x-slot>
<div class="mx-auto max-w-2xl space-y-6">
<div>
<a href="{{ route('transfer.transfers.index') }}" class="text-sm text-slate-500 hover:text-slate-700"> All transfers</a>
<h1 class="mt-2 text-xl font-semibold text-slate-900">New transfer</h1>
<p class="mt-1 text-sm text-slate-500">Upload files and get a share link plus a QR code. Storage is billed monthly at GHS {{ number_format($pricePerGb, 2) }} per GB from your Ladill wallet.</p>
</div>
@if(session('error'))
<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{{ session('error') }}</div>
@endif
<form method="post" action="{{ route('transfer.transfers.store') }}" enctype="multipart/form-data"
x-data="transferCreateForm(@js([
'initUrl' => route('transfer.transfers.upload.init'),
'chunkUrl' => route('transfer.transfers.upload.chunk'),
'finalizeUrl' => route('transfer.transfers.upload.finalize'),
'csrfToken' => csrf_token(),
]))"
@submit.prevent="submit"
class="space-y-5 rounded-2xl border border-slate-200 bg-white p-6">
@csrf
<div>
<label for="title" class="block text-sm font-medium text-slate-700">Title</label>
<input type="text" name="title" id="title" value="{{ old('title') }}" required maxlength="120"
class="mt-1 block w-full rounded-xl border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('title')<p class="mt-1 text-xs text-red-600">{{ $message }}</p>@enderror
</div>
<div>
<label for="message" class="block text-sm font-medium text-slate-700">Message <span class="font-normal text-slate-400">(optional)</span></label>
<textarea name="message" id="message" rows="3" maxlength="2000"
class="mt-1 block w-full rounded-xl border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('message') }}</textarea>
</div>
<div>
<span class="block text-sm font-medium text-slate-700">Files</span>
<div class="mt-2 flex flex-wrap gap-2">
<button type="button"
@click="$refs.fileInput.click()"
class="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
Choose files
</button>
<button type="button"
@click="$refs.folderInput.click()"
class="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
Choose folder
</button>
</div>
<input type="file" x-ref="fileInput" id="files" multiple class="hidden" @change="onFilesChange">
<input type="file" x-ref="folderInput" multiple webkitdirectory class="hidden" @change="onFolderChange">
<p class="mt-1 text-xs text-slate-500">Up to {{ $maxFiles }} files. Uploads start immediately pick files or a folder anytime before creating the transfer.</p>
<div x-show="fileItems.length" x-cloak class="mt-3 space-y-2">
<template x-for="item in fileItems" :key="item.id">
<div class="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2.5">
<div class="flex items-start gap-2">
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-slate-800" x-text="item.name"></p>
<p class="text-xs text-slate-500" x-text="formatBytes(item.size)"></p>
</div>
<span class="shrink-0 text-xs font-medium"
:class="{
'text-indigo-600': item.status === 'uploading' || item.status === 'pending',
'text-emerald-600': item.status === 'ready',
'text-red-600': item.status === 'error',
}"
x-text="item.status === 'uploading' ? `${item.progress}%` : (item.status === 'pending' ? 'Waiting…' : (item.status === 'ready' ? 'Ready' : 'Failed'))"></span>
<button type="button"
@click="removeFileItem(item.id)"
:disabled="item.status === 'uploading'"
class="shrink-0 rounded p-0.5 text-slate-400 hover:bg-white hover:text-rose-600 disabled:opacity-40"
title="Remove">
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
<div x-show="item.status === 'uploading'" class="mt-2">
<div class="upload-progress-track">
<div class="upload-progress-bar" :style="`width: ${item.progress}%`"></div>
</div>
</div>
<p x-show="item.status === 'error'" class="mt-1 text-xs text-red-600" x-text="item.error"></p>
</div>
</template>
<div x-show="uploading" class="rounded-xl border border-indigo-100 bg-indigo-50/70 px-3 py-2">
<div class="flex items-center justify-between gap-2 text-xs text-indigo-800">
<span class="font-medium">Uploading files…</span>
<span x-text="`${overallProgress}%`"></span>
</div>
<div class="upload-progress-track mt-2">
<div class="upload-progress-bar" :style="`width: ${overallProgress}%`"></div>
</div>
</div>
</div>
@error('files')<p class="mt-1 text-xs text-red-600">{{ $message }}</p>@enderror
@error('files.*')<p class="mt-1 text-xs text-red-600">{{ $message }}</p>@enderror
</div>
<div>
<label for="recipient_email" class="block text-sm font-medium text-slate-700">Recipient email <span class="font-normal text-slate-400">(optional)</span></label>
<input type="email" name="recipient_email" id="recipient_email" value="{{ old('recipient_email') }}" maxlength="255"
placeholder="friend@example.com"
class="mt-1 block w-full rounded-xl border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('recipient_email')<p class="mt-1 text-xs text-red-600">{{ $message }}</p>@enderror
<p class="mt-1 text-xs text-slate-500">A download link is emailed immediately when you create the transfer.</p>
</div>
<div class="rounded-xl border border-indigo-100 bg-indigo-50/60 px-4 py-3 text-sm text-slate-600">
<p class="font-medium text-slate-900">Monthly storage billing</p>
<p class="mt-1">GHS {{ number_format($pricePerGb, 2) }} per GB per month, debited from your wallet. Files stay available while payments succeed. If a monthly renewal fails, files are kept for {{ $gracePeriodDays }} days before deletion.</p>
</div>
<div>
<label for="password" class="block text-sm font-medium text-slate-700">Password <span class="font-normal text-slate-400">(optional)</span></label>
<input type="password" name="password" id="password" minlength="4" maxlength="64"
class="mt-1 block w-full rounded-xl border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
autocomplete="new-password">
</div>
<div class="flex justify-end gap-3 pt-2">
<a href="{{ route('transfer.transfers.index') }}" class="rounded-xl border border-slate-200 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Cancel</a>
<button type="submit" class="rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-700 disabled:opacity-60"
:disabled="uploading || submitting">
<span x-show="!uploading && !submitting">Create transfer</span>
<span x-show="uploading || submitting" x-cloak>Uploading…</span>
</button>
</div>
</form>
</div>
</x-user-layout>