A simple application for uploading files locally using SvelteKit with a minimal retro design.
The file upload form is designed with a minimal retro style using Tailwind CSS. Below are the main components of the upload form:
The file input has a unique styling with thick borders and amber colors consistent with the retro theme:
<input
type="file"
name="fileToUpload"
id="file"
class="block w-full text-sm file:mr-4 file:cursor-pointer file:border-2 file:border-slate-800 file:bg-amber-200 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-800 hover:file:bg-amber-300"
accept={acceptTypeFile.join(',')}
/>
The file input only accepts the following file types:
The upload button includes a loading animation that appears during the upload process:
<button
type="submit"
class="w-full cursor-pointer border-2 border-slate-800 bg-amber-500 px-4 py-2 font-bold tracking-wide uppercase shadow-[4px_4px_0px_0px_rgba(30,41,59)] transition-all hover:translate-y-1 hover:shadow-[2px_2px_0px_0px_rgba(30,41,59)] disabled:cursor-not-allowed disabled:opacity-70"
disabled={isLoading}
>
{#if isLoading}
<div class="flex items-center justify-center">
<svg
class="mr-2 -ml-1 h-4 w-4 animate-spin text-slate-800"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<!-- SVG path for loading animation -->
</svg>
Loading...
</div>
{:else}
Upload
{/if}
</button>
The notification modal appears after the upload process is complete, displaying either success or error status:
{#if showModal}
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-800/50"
onclick={closeModal}
aria-hidden="true"
>
<div
class="w-full max-w-md border-2 border-slate-800 bg-white shadow-[8px_8px_0px_0px_rgba(30,41,59)]"
transition:fly={{ y: 200, duration: 1000 }}
>
<!-- Modal header with different colors based on status -->
<div
class="border-b-2 border-slate-800 {modalType === 'success'
? 'bg-green-200'
: 'bg-red-200'} flex items-center justify-between p-3"
>
<h2 class="text-xl font-bold tracking-wide uppercase">
{modalType === 'success' ? 'Success' : 'Error'}
</h2>
<!-- Close button -->
</div>
<!-- Modal content with icon and message -->
</div>
</div>
{/if}
File validation is performed server-side with several criteria:
File Presence:
if (
!(formData.fileToUpload as File).name ||
(formData.fileToUpload as File).name === 'undefined'
) {
return fail(400, {
success: false,
message: 'You must provide a file to upload'
});
}
File Size (maximum 1MB):
if (fileToUpload.size > 1024 * 1024 * 1) {
return fail(400, {
success: false,
message: 'File size must be less than 10MB'
});
}
File Type (jpg, png, pdf):
if (!['jpg', 'png', 'pdf'].includes(fileToUpload.name.split('.').pop() as string)) {
return fail(400, {
success: false,
message: 'File type must be jpg, png, or pdf'
});
}
Uploaded files are stored in the static/upload/
directory with filenames generated randomly using UUID:
const ext = fileToUpload.name.split('.').pop();
const fileName = `${crypto.randomUUID()}.${ext}`;
writeFileSync(`static/upload/${fileName}`, Buffer.from(await fileToUpload.arrayBuffer()));