feat(web): vibe coding pazzo
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 13s
Build, Test and Deploy / build-and-push (push) Successful in 28s
Build, Test and Deploy / deploy (push) Successful in 5s

This commit is contained in:
2026-02-02 17:38:03 +01:00
parent 5a2da916fa
commit 2c658d00c1
56 changed files with 1676 additions and 1987 deletions

View File

@@ -0,0 +1,104 @@
import { Component, input, output, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-dropzone',
standalone: true,
imports: [CommonModule],
template: `
<div
class="dropzone"
[class.dragover]="isDragOver()"
(dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
(drop)="onDrop($event)"
(click)="fileInput.click()"
>
<input #fileInput type="file" (change)="onFileSelected($event)" hidden [accept]="accept()">
<div class="content">
<div class="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-upload-cloud"><polyline points="16 16 12 12 8 16"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline points="16 16 12 12 8 16"></polyline></svg>
</div>
<p class="text">{{ label() }}</p>
<p class="subtext">{{ subtext() }}</p>
@if (fileName()) {
<div class="file-badge">
{{ fileName() }}
</div>
}
</div>
</div>
`,
styles: [`
.dropzone {
border: 2px dashed var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-8);
text-align: center;
cursor: pointer;
transition: all 0.2s;
background-color: var(--color-neutral-50);
&:hover, &.dragover {
border-color: var(--color-brand);
background-color: var(--color-neutral-100);
}
}
.icon { color: var(--color-brand); margin-bottom: var(--space-4); }
.text { font-weight: 600; margin-bottom: var(--space-2); }
.subtext { font-size: 0.875rem; color: var(--color-text-muted); }
.file-badge {
margin-top: var(--space-4);
display: inline-block;
padding: var(--space-2) var(--space-4);
background: var(--color-neutral-200);
border-radius: var(--radius-md);
font-weight: 600;
color: var(--color-primary-700);
}
`]
})
export class AppDropzoneComponent {
label = input<string>('Drop file here or click to upload');
subtext = input<string>('Supports .stl, .obj');
accept = input<string>('.stl,.obj');
fileDropped = output<File>();
isDragOver = signal(false);
fileName = signal<string | null>(null);
onDragOver(e: Event) {
e.preventDefault();
e.stopPropagation();
this.isDragOver.set(true);
}
onDragLeave(e: Event) {
e.preventDefault();
e.stopPropagation();
this.isDragOver.set(false);
}
onDrop(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
this.isDragOver.set(false);
if (e.dataTransfer?.files.length) {
this.handleFile(e.dataTransfer.files[0]);
}
}
onFileSelected(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files?.length) {
this.handleFile(input.files[0]);
}
}
handleFile(file: File) {
this.fileName.set(file.name);
this.fileDropped.emit(file);
}
}