mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: resolve 3 critical UX bugs - home upload, auth, and form submit
1. Home page file drop now shows quick-action tool selector 2. Auth disabled by default in dev (Docker still defaults to true) 3. Tool settings wrapped in forms - Enter key triggers processing
This commit is contained in:
@@ -19,9 +19,10 @@ import { cn } from "@/lib/utils";
|
||||
interface AppLayoutProps {
|
||||
children?: React.ReactNode;
|
||||
showToolPanel?: boolean;
|
||||
onFiles?: (files: File[]) => void;
|
||||
}
|
||||
|
||||
export function AppLayout({ children, showToolPanel = true }: AppLayoutProps) {
|
||||
export function AppLayout({ children, showToolPanel = true, onFiles }: AppLayoutProps) {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
const isMobile = useMobile();
|
||||
@@ -89,7 +90,7 @@ export function AppLayout({ children, showToolPanel = true }: AppLayoutProps) {
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 overflow-y-auto p-6 flex items-center justify-center">
|
||||
{children || <Dropzone />}
|
||||
{children || <Dropzone onFiles={onFiles} accept="image/*" />}
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<div className="text-center text-xs text-muted-foreground py-2 border-t border-border">
|
||||
|
||||
@@ -63,12 +63,18 @@ export function ColorSettings({ toolId }: ColorSettingsProps) {
|
||||
{ id: "effects", label: "Effects" },
|
||||
];
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && hasChanges && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1">
|
||||
{tabs.map((t) => (
|
||||
<button
|
||||
type="button"
|
||||
key={t.id}
|
||||
onClick={() => setTab(t.id)}
|
||||
className={`flex-1 text-xs py-1.5 rounded ${
|
||||
@@ -146,6 +152,7 @@ export function ColorSettings({ toolId }: ColorSettingsProps) {
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
{(["none", "grayscale", "sepia", "invert"] as const).map((e) => (
|
||||
<button
|
||||
type="button"
|
||||
key={e}
|
||||
onClick={() => setEffect(e)}
|
||||
className={`text-xs py-2 rounded capitalize transition-colors ${
|
||||
@@ -164,6 +171,7 @@ export function ColorSettings({ toolId }: ColorSettingsProps) {
|
||||
{/* Reset button */}
|
||||
{hasChanges && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setBrightness(0);
|
||||
setContrast(0);
|
||||
@@ -192,7 +200,7 @@ export function ColorSettings({ toolId }: ColorSettingsProps) {
|
||||
|
||||
{/* Process */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -211,7 +219,7 @@ export function ColorSettings({ toolId }: ColorSettingsProps) {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,19 +28,26 @@ export function CompressSettings() {
|
||||
const canProcess =
|
||||
mode === "quality" || (mode === "targetSize" && Number(targetSizeKb) > 0);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && canProcess && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Mode toggle */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-muted-foreground">Compression Mode</label>
|
||||
<div className="flex gap-1 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode("quality")}
|
||||
className={`flex-1 text-xs py-1.5 rounded ${mode === "quality" ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`}
|
||||
>
|
||||
Quality
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode("targetSize")}
|
||||
className={`flex-1 text-xs py-1.5 rounded ${mode === "targetSize" ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`}
|
||||
>
|
||||
@@ -102,7 +109,7 @@ export function CompressSettings() {
|
||||
|
||||
{/* Process */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -121,6 +128,6 @@ export function CompressSettings() {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,13 @@ export function ConvertSettings() {
|
||||
|
||||
const hasFile = files.length > 0;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Source format */}
|
||||
{hasFile && (
|
||||
<div>
|
||||
@@ -98,7 +103,7 @@ export function ConvertSettings() {
|
||||
|
||||
{/* Process */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -117,6 +122,6 @@ export function ConvertSettings() {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,13 @@ export function CropSettings() {
|
||||
const hasFile = files.length > 0;
|
||||
const hasSize = Number(width) > 0 && Number(height) > 0;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && hasSize && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Position */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
@@ -97,6 +102,7 @@ export function CropSettings() {
|
||||
<div className="flex gap-1 mt-1">
|
||||
{ASPECT_PRESETS.map(({ label, w, h }) => (
|
||||
<button
|
||||
type="button"
|
||||
key={label}
|
||||
onClick={() => applyAspect(w, h)}
|
||||
className="flex-1 text-xs py-1.5 rounded bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors"
|
||||
@@ -120,7 +126,7 @@ export function CropSettings() {
|
||||
|
||||
{/* Process */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -139,6 +145,6 @@ export function CropSettings() {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,19 +41,26 @@ export function ResizeSettings() {
|
||||
// Group presets by platform
|
||||
const platforms = [...new Set(SOCIAL_MEDIA_PRESETS.map((p) => p.platform))];
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Mode toggle */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-muted-foreground">Resize Mode</label>
|
||||
<div className="flex gap-1 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode("pixels")}
|
||||
className={`flex-1 text-xs py-1.5 rounded ${mode === "pixels" ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`}
|
||||
>
|
||||
Pixels
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode("percentage")}
|
||||
className={`flex-1 text-xs py-1.5 rounded ${mode === "percentage" ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`}
|
||||
>
|
||||
@@ -78,6 +85,7 @@ export function ResizeSettings() {
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setLockAspect(!lockAspect)}
|
||||
className="p-1.5 rounded border border-border text-muted-foreground hover:text-foreground"
|
||||
title={lockAspect ? "Unlock aspect ratio" : "Lock aspect ratio"}
|
||||
@@ -180,7 +188,7 @@ export function ResizeSettings() {
|
||||
|
||||
{/* Process button */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -199,6 +207,6 @@ export function ResizeSettings() {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,13 +33,19 @@ export function RotateSettings() {
|
||||
const hasFile = files.length > 0;
|
||||
const hasChanges = angle !== 0 || flipH || flipV;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && hasChanges && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Quick rotate buttons */}
|
||||
<div>
|
||||
<label className="text-xs text-muted-foreground">Quick Rotate</label>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={rotateLeft}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-2 rounded bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors text-sm"
|
||||
>
|
||||
@@ -47,6 +53,7 @@ export function RotateSettings() {
|
||||
90 Left
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={rotateRight}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-2 rounded bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors text-sm"
|
||||
>
|
||||
@@ -77,6 +84,7 @@ export function RotateSettings() {
|
||||
<label className="text-xs text-muted-foreground">Flip</label>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFlipH(!flipH)}
|
||||
className={`flex-1 flex items-center justify-center gap-1 py-2 rounded text-sm transition-colors ${
|
||||
flipH
|
||||
@@ -88,6 +96,7 @@ export function RotateSettings() {
|
||||
Horizontal
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFlipV(!flipV)}
|
||||
className={`flex-1 flex items-center justify-center gap-1 py-2 rounded text-sm transition-colors ${
|
||||
flipV
|
||||
@@ -114,7 +123,7 @@ export function RotateSettings() {
|
||||
|
||||
{/* Process */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -133,6 +142,6 @@ export function RotateSettings() {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,13 @@ export function StripMetadataSettings() {
|
||||
|
||||
const hasFile = files.length > 0;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (hasFile && !processing) handleProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Strip All */}
|
||||
<label className="flex items-center gap-2 text-sm text-foreground font-medium">
|
||||
<input
|
||||
@@ -108,7 +113,7 @@ export function StripMetadataSettings() {
|
||||
|
||||
{/* Process */}
|
||||
<button
|
||||
onClick={handleProcess}
|
||||
type="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"
|
||||
>
|
||||
@@ -127,6 +132,6 @@ export function StripMetadataSettings() {
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user