mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: multi-arch Docker support, security hardening, and test improvements
Remove hardcoded --platform=linux/amd64 from Dockerfile so buildx produces native arm64 images for Apple Silicon and Raspberry Pi. Add audit logging for auth events, harden file storage with extension whitelists and double-extension attack prevention, reject null-byte buffers in validation, add data-testid attributes to all tool settings components, update deployment docs with architecture notes and correct CI workflow references, and fix unit test mock to match throwWithMessage error extraction.
This commit is contained in:
@@ -63,6 +63,7 @@ export function BarcodeReadSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="barcode-read-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -92,6 +92,7 @@ export function BlurFacesSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="blur-faces-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -105,6 +106,7 @@ export function BlurFacesSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="blur-faces-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -142,6 +142,7 @@ export function BorderSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="border-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -154,6 +155,7 @@ export function BorderSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="border-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -124,6 +124,7 @@ export function BulkRenameSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="bulk-rename-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFiles || processing || !pattern}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -128,6 +128,7 @@ export function CollageSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="collage-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFiles || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -140,6 +141,7 @@ export function CollageSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="collage-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -58,6 +58,7 @@ export function ColorPaletteSettings() {
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="color-palette-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -260,6 +260,7 @@ export function ColorSettings({ toolId, onPreviewFilter }: ColorSettingsProps) {
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid={`${toolId}-submit`}
|
||||
disabled={!hasFile || !hasChanges || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -272,6 +273,7 @@ export function ColorSettings({ toolId, onPreviewFilter }: ColorSettingsProps) {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid={`${toolId}-download`}
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -89,6 +89,7 @@ export function CompareSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compare-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || !secondFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -101,6 +102,7 @@ export function CompareSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="compare-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -164,6 +164,7 @@ export function ComposeSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compose-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || !overlayFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -176,6 +177,7 @@ export function ComposeSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="compose-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -134,6 +134,7 @@ export function CompressSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="compress-submit"
|
||||
disabled={!hasFile || !canProcess || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -146,6 +147,7 @@ export function CompressSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="compress-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -130,6 +130,7 @@ export function ConvertSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="convert-submit"
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -142,6 +143,7 @@ export function ConvertSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="convert-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -288,6 +288,7 @@ export function CropSettings({
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="crop-submit"
|
||||
disabled={!hasFile || !hasSize || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -300,6 +301,7 @@ export function CropSettings({
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="crop-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -196,6 +196,7 @@ export function EraseObjectSettings({
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="erase-object-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || !hasStrokes || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -209,6 +210,7 @@ export function EraseObjectSettings({
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="erase-object-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -83,6 +83,7 @@ export function FaviconSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="favicon-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -63,6 +63,7 @@ export function FindDuplicatesSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="find-duplicates-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFiles || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -134,6 +134,7 @@ export function GifToolsSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="gif-tools-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -146,6 +147,7 @@ export function GifToolsSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="gif-tools-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -229,6 +229,7 @@ export function ImageToPdfSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="image-to-pdf-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFiles || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -241,6 +242,7 @@ export function ImageToPdfSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="image-to-pdf-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -80,6 +80,7 @@ export function InfoSettings() {
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="info-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -191,6 +191,7 @@ export function OcrSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="ocr-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -132,6 +132,7 @@ export function QrGenerateSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="qr-generate-submit"
|
||||
onClick={handleGenerate}
|
||||
disabled={!text || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -151,6 +152,7 @@ export function QrGenerateSettings() {
|
||||
<a
|
||||
href={downloadUrl ?? undefined}
|
||||
download
|
||||
data-testid="qr-generate-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -200,6 +200,7 @@ export function RemoveBgSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="remove-background-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -213,6 +214,7 @@ export function RemoveBgSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="remove-background-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -122,6 +122,7 @@ export function ReplaceColorSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="replace-color-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -134,6 +135,7 @@ export function ReplaceColorSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="replace-color-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -260,6 +260,7 @@ export function ResizeSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="resize-submit"
|
||||
disabled={!canProcess}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -272,6 +273,7 @@ export function ResizeSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="resize-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -105,6 +105,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="rotate-left"
|
||||
onClick={rotateLeft}
|
||||
className="flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors text-sm font-medium"
|
||||
title="Rotate 90° counter-clockwise"
|
||||
@@ -149,6 +150,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="rotate-right"
|
||||
onClick={rotateRight}
|
||||
className="flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors text-sm font-medium"
|
||||
title="Rotate 90° clockwise"
|
||||
@@ -193,6 +195,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
|
||||
<div className="flex gap-2 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="rotate-flip-h"
|
||||
onClick={() => setFlipH(!flipH)}
|
||||
className={`flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-medium transition-colors ${
|
||||
flipH
|
||||
@@ -205,6 +208,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="rotate-flip-v"
|
||||
onClick={() => setFlipV(!flipV)}
|
||||
className={`flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-medium transition-colors ${
|
||||
flipV
|
||||
@@ -245,6 +249,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="rotate-submit"
|
||||
disabled={!hasFile || !hasChanges || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
|
||||
@@ -129,6 +129,7 @@ export function SmartCropSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="smart-crop-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || !canProcess || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -142,6 +143,7 @@ export function SmartCropSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="smart-crop-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -118,6 +118,7 @@ export function SplitSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="split-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
|
||||
@@ -462,6 +462,7 @@ export function StripMetadataSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="strip-metadata-submit"
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -474,6 +475,7 @@ export function StripMetadataSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="strip-metadata-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -148,6 +148,7 @@ export function SvgToRasterSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="svg-to-raster-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -160,6 +161,7 @@ export function SvgToRasterSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="svg-to-raster-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -154,6 +154,7 @@ export function TextOverlaySettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="text-overlay-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing || !text}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -166,6 +167,7 @@ export function TextOverlaySettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="text-overlay-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -77,6 +77,7 @@ export function UpscaleSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="upscale-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -90,6 +91,7 @@ export function UpscaleSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="upscale-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -122,6 +122,7 @@ export function VectorizeSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="vectorize-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -134,6 +135,7 @@ export function VectorizeSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="vectorize-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -146,6 +146,7 @@ export function WatermarkImageSettings() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="watermark-image-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || !watermarkFile || processing}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -158,6 +159,7 @@ export function WatermarkImageSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="watermark-image-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
@@ -160,6 +160,7 @@ export function WatermarkTextSettings() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="watermark-text-submit"
|
||||
onClick={handleProcess}
|
||||
disabled={!hasFile || processing || !text}
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@@ -172,6 +173,7 @@ export function WatermarkTextSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="watermark-text-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
|
||||
Reference in New Issue
Block a user