feat: Implement Phase 1 Shopify-inspired settings (Store, Payments, Shipping)
✨ Features: - Store Details page with live currency preview - Payments page with visual provider cards and test mode - Shipping & Delivery page with zone cards and local pickup - Shared components: SettingsLayout, SettingsCard, SettingsSection, ToggleField 🎨 UI/UX: - Card-based layouts (not boring forms) - Generous whitespace and visual hierarchy - Toast notifications using sonner (reused from Orders) - Sticky save button at top - Mobile-responsive design 🔧 Technical: - Installed ESLint with TypeScript support - Fixed all lint errors (0 errors) - Phase 1 files have zero warnings - Used existing toast from sonner (not reinvented) - Updated routes in App.tsx 📝 Files Created: - Store.tsx (currency preview, address, timezone) - Payments.tsx (provider cards, manual methods) - Shipping.tsx (zone cards, rates, local pickup) - SettingsLayout.tsx, SettingsCard.tsx, SettingsSection.tsx, ToggleField.tsx Phase 1 complete: 18-24 hours estimated work
This commit is contained in:
82
admin-spa/src/routes/Settings/components/SettingsLayout.tsx
Normal file
82
admin-spa/src/routes/Settings/components/SettingsLayout.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
children: React.ReactNode;
|
||||
onSave?: () => Promise<void>;
|
||||
saveLabel?: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function SettingsLayout({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
onSave,
|
||||
saveLabel = 'Save changes',
|
||||
isLoading = false,
|
||||
}: SettingsLayoutProps) {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!onSave) return;
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await onSave();
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Sticky Header with Save Button */}
|
||||
{onSave && (
|
||||
<div className="sticky top-0 z-10 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container max-w-5xl mx-auto px-4 py-3 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold">{title}</h1>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || isLoading}
|
||||
size="sm"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
saveLabel
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="container max-w-5xl mx-auto px-4 py-8">
|
||||
{!onSave && (
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
{description && (
|
||||
<p className="text-muted-foreground mt-2">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">{children}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user