|
|
@@ -4,38 +4,59 @@ import { Switch } from "@/components/ui/switch";
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
|
-import { Shield, ShieldCheck, ShieldAlert, Info, Loader2 } from "lucide-react";
|
|
|
+import { Label } from "@/components/ui/label";
|
|
|
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
|
+import { Shield, ShieldCheck, ShieldAlert, Info, Loader2, Database, Cloud, Ban } from "lucide-react";
|
|
|
import { API_URL } from "@/lib/config";
|
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
|
|
+type DataAccessPolicy = 'sync' | 'api_only' | 'not_allowed';
|
|
|
+
|
|
|
interface DataAccessPermissions {
|
|
|
allow_customer_access: boolean;
|
|
|
allow_order_access: boolean;
|
|
|
allow_product_access: boolean;
|
|
|
}
|
|
|
|
|
|
+interface AccessPolicySettings {
|
|
|
+ products_access_policy: DataAccessPolicy;
|
|
|
+ customers_access_policy: DataAccessPolicy;
|
|
|
+ orders_access_policy: DataAccessPolicy;
|
|
|
+}
|
|
|
+
|
|
|
interface DataAccessSettingsProps {
|
|
|
storeId: string;
|
|
|
storeName: string;
|
|
|
currentPermissions: DataAccessPermissions;
|
|
|
+ currentPolicies?: AccessPolicySettings;
|
|
|
onPermissionsUpdated?: (newPermissions: DataAccessPermissions) => void;
|
|
|
+ onPoliciesUpdated?: (newPolicies: AccessPolicySettings) => void;
|
|
|
}
|
|
|
|
|
|
export function DataAccessSettings({
|
|
|
storeId,
|
|
|
storeName,
|
|
|
currentPermissions,
|
|
|
- onPermissionsUpdated
|
|
|
+ currentPolicies,
|
|
|
+ onPermissionsUpdated,
|
|
|
+ onPoliciesUpdated
|
|
|
}: DataAccessSettingsProps) {
|
|
|
- const [permissions, setPermissions] = useState<DataAccessPermissions>(currentPermissions);
|
|
|
+ // Initialize with new policy-based system if available, otherwise convert from old permissions
|
|
|
+ const [policies, setPolicies] = useState<AccessPolicySettings>(
|
|
|
+ currentPolicies || {
|
|
|
+ products_access_policy: currentPermissions.allow_product_access ? 'sync' : 'not_allowed',
|
|
|
+ customers_access_policy: currentPermissions.allow_customer_access ? 'sync' : 'not_allowed',
|
|
|
+ orders_access_policy: currentPermissions.allow_order_access ? 'sync' : 'not_allowed'
|
|
|
+ }
|
|
|
+ );
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
const [hasChanges, setHasChanges] = useState(false);
|
|
|
const { toast } = useToast();
|
|
|
|
|
|
- const handleToggle = (key: keyof DataAccessPermissions) => {
|
|
|
- setPermissions(prev => ({
|
|
|
+ const handlePolicyChange = (dataType: 'products' | 'customers' | 'orders', policy: DataAccessPolicy) => {
|
|
|
+ setPolicies(prev => ({
|
|
|
...prev,
|
|
|
- [key]: !prev[key]
|
|
|
+ [`${dataType}_access_policy`]: policy
|
|
|
}));
|
|
|
setHasChanges(true);
|
|
|
};
|
|
|
@@ -49,37 +70,35 @@ export function DataAccessSettings({
|
|
|
}
|
|
|
|
|
|
const session = JSON.parse(sessionData);
|
|
|
- const response = await fetch(`${API_URL}/api/stores/${storeId}/permissions`, {
|
|
|
+ const response = await fetch(`${API_URL}/api/stores/${storeId}/access-policies`, {
|
|
|
method: 'PUT',
|
|
|
headers: {
|
|
|
'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
'Content-Type': 'application/json'
|
|
|
},
|
|
|
- body: JSON.stringify({
|
|
|
- data_access_permissions: permissions
|
|
|
- })
|
|
|
+ body: JSON.stringify(policies)
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
if (!response.ok) {
|
|
|
- throw new Error(data.error || 'Failed to update permissions');
|
|
|
+ throw new Error(data.error || 'Failed to update access policies');
|
|
|
}
|
|
|
|
|
|
toast({
|
|
|
- title: "Permissions Updated",
|
|
|
- description: "Data access permissions have been successfully updated.",
|
|
|
+ title: "Access Policies Updated",
|
|
|
+ description: "Data access policies have been successfully updated.",
|
|
|
});
|
|
|
|
|
|
setHasChanges(false);
|
|
|
- if (onPermissionsUpdated) {
|
|
|
- onPermissionsUpdated(permissions);
|
|
|
+ if (onPoliciesUpdated) {
|
|
|
+ onPoliciesUpdated(policies);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error('Error updating permissions:', error);
|
|
|
+ console.error('Error updating access policies:', error);
|
|
|
toast({
|
|
|
title: "Update Failed",
|
|
|
- description: error instanceof Error ? error.message : "Failed to update permissions. Please try again.",
|
|
|
+ description: error instanceof Error ? error.message : "Failed to update access policies. Please try again.",
|
|
|
variant: "destructive",
|
|
|
});
|
|
|
} finally {
|
|
|
@@ -88,20 +107,80 @@ export function DataAccessSettings({
|
|
|
};
|
|
|
|
|
|
const handleReset = () => {
|
|
|
- setPermissions(currentPermissions);
|
|
|
+ setPolicies(currentPolicies || {
|
|
|
+ products_access_policy: currentPermissions.allow_product_access ? 'sync' : 'not_allowed',
|
|
|
+ customers_access_policy: currentPermissions.allow_customer_access ? 'sync' : 'not_allowed',
|
|
|
+ orders_access_policy: currentPermissions.allow_order_access ? 'sync' : 'not_allowed'
|
|
|
+ });
|
|
|
setHasChanges(false);
|
|
|
};
|
|
|
|
|
|
const getSecurityLevel = () => {
|
|
|
- const enabledCount = Object.values(permissions).filter(Boolean).length;
|
|
|
- if (enabledCount === 0) return { level: "high", icon: ShieldCheck, color: "text-green-500", label: "High Privacy" };
|
|
|
- if (enabledCount <= 1) return { level: "medium", icon: Shield, color: "text-yellow-500", label: "Medium Privacy" };
|
|
|
- return { level: "low", icon: ShieldAlert, color: "text-orange-500", label: "Full Access" };
|
|
|
+ const notAllowedCount = Object.values(policies).filter(p => p === 'not_allowed').length;
|
|
|
+ const apiOnlyCount = Object.values(policies).filter(p => p === 'api_only').length;
|
|
|
+
|
|
|
+ if (notAllowedCount === 3) return { level: "maximum", icon: ShieldCheck, color: "text-green-500", label: "Maximum Privacy" };
|
|
|
+ if (notAllowedCount >= 2) return { level: "high", icon: ShieldCheck, color: "text-green-400", label: "High Privacy" };
|
|
|
+ if (notAllowedCount === 1 || apiOnlyCount >= 2) return { level: "medium", icon: Shield, color: "text-yellow-500", label: "Medium Privacy" };
|
|
|
+ if (apiOnlyCount >= 1) return { level: "balanced", icon: Shield, color: "text-blue-500", label: "Balanced" };
|
|
|
+ return { level: "full", icon: ShieldAlert, color: "text-orange-500", label: "Full Sync" };
|
|
|
};
|
|
|
|
|
|
const security = getSecurityLevel();
|
|
|
const SecurityIcon = security.icon;
|
|
|
|
|
|
+ const renderPolicyOptions = (dataType: 'products' | 'customers' | 'orders', policyKey: keyof AccessPolicySettings, isPII: boolean = false) => {
|
|
|
+ return (
|
|
|
+ <RadioGroup
|
|
|
+ value={policies[policyKey]}
|
|
|
+ onValueChange={(value) => handlePolicyChange(dataType, value as DataAccessPolicy)}
|
|
|
+ className="space-y-3 mt-3"
|
|
|
+ >
|
|
|
+ <div className="flex items-start space-x-3 p-3 rounded-md bg-slate-600/30 hover:bg-slate-600/50 transition-colors">
|
|
|
+ <RadioGroupItem value="sync" id={`${dataType}-sync`} className="mt-1" disabled={saving} />
|
|
|
+ <Label htmlFor={`${dataType}-sync`} className="cursor-pointer flex-1">
|
|
|
+ <div className="flex items-center gap-2 mb-1">
|
|
|
+ <Database className="w-4 h-4 text-cyan-400" />
|
|
|
+ <span className="text-white font-medium">Sync & Cache</span>
|
|
|
+ <Badge variant="outline" className="text-xs border-cyan-500 text-cyan-400">Fastest</Badge>
|
|
|
+ </div>
|
|
|
+ <p className="text-xs text-slate-400">
|
|
|
+ Store data locally in database and Qdrant vector search for instant access
|
|
|
+ </p>
|
|
|
+ </Label>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex items-start space-x-3 p-3 rounded-md bg-slate-600/30 hover:bg-slate-600/50 transition-colors">
|
|
|
+ <RadioGroupItem value="api_only" id={`${dataType}-api`} className="mt-1" disabled={saving} />
|
|
|
+ <Label htmlFor={`${dataType}-api`} className="cursor-pointer flex-1">
|
|
|
+ <div className="flex items-center gap-2 mb-1">
|
|
|
+ <Cloud className="w-4 h-4 text-blue-400" />
|
|
|
+ <span className="text-white font-medium">API Access Only</span>
|
|
|
+ {isPII && <Badge variant="outline" className="text-xs border-green-500 text-green-400">GDPR Friendly</Badge>}
|
|
|
+ </div>
|
|
|
+ <p className="text-xs text-slate-400">
|
|
|
+ Fetch data directly from your store's API on demand (no local storage)
|
|
|
+ </p>
|
|
|
+ </Label>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex items-start space-x-3 p-3 rounded-md bg-slate-600/30 hover:bg-slate-600/50 transition-colors">
|
|
|
+ <RadioGroupItem value="not_allowed" id={`${dataType}-none`} className="mt-1" disabled={saving} />
|
|
|
+ <Label htmlFor={`${dataType}-none`} className="cursor-pointer flex-1">
|
|
|
+ <div className="flex items-center gap-2 mb-1">
|
|
|
+ <Ban className="w-4 h-4 text-red-400" />
|
|
|
+ <span className="text-white font-medium">No Access</span>
|
|
|
+ <Badge variant="outline" className="text-xs border-red-500 text-red-400">Blocked</Badge>
|
|
|
+ </div>
|
|
|
+ <p className="text-xs text-slate-400">
|
|
|
+ Completely block access to this data type
|
|
|
+ </p>
|
|
|
+ </Label>
|
|
|
+ </div>
|
|
|
+ </RadioGroup>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
<Card className="bg-slate-800 border-slate-700">
|
|
|
<CardHeader>
|
|
|
@@ -125,82 +204,54 @@ export function DataAccessSettings({
|
|
|
<Alert className="bg-blue-500/10 border-blue-500/50">
|
|
|
<Info className="h-4 w-4 text-blue-400" />
|
|
|
<AlertDescription className="text-blue-300 text-sm">
|
|
|
- <strong>GDPR Compliance:</strong> Customer and order data are NOT stored in our database.
|
|
|
- They are fetched in real-time from your webshop when accessed via API.
|
|
|
- Disabling access prevents API calls from retrieving this data.
|
|
|
+ <strong>GDPR Compliance:</strong> Control how data is accessed and stored.
|
|
|
+ "API Only" mode ensures no personal data is cached locally.
|
|
|
+ "Sync & Cache" provides faster access but stores data in our database.
|
|
|
</AlertDescription>
|
|
|
</Alert>
|
|
|
|
|
|
- {/* Permission Toggles */}
|
|
|
- <div className="space-y-4">
|
|
|
+ {/* Policy Settings */}
|
|
|
+ <div className="space-y-6">
|
|
|
{/* Products Access */}
|
|
|
- <div className="flex items-start justify-between p-4 bg-slate-700/50 rounded-lg">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="flex items-center gap-2 mb-1">
|
|
|
- <h4 className="text-white font-medium">Product Data Access</h4>
|
|
|
- <Badge variant="outline" className="text-xs border-blue-500 text-blue-400">
|
|
|
- Public Data
|
|
|
- </Badge>
|
|
|
- </div>
|
|
|
- <p className="text-sm text-slate-400">
|
|
|
- Allow API access to product information (names, prices, descriptions, stock levels)
|
|
|
- </p>
|
|
|
+ <div className="p-4 bg-slate-700/50 rounded-lg border border-slate-600">
|
|
|
+ <div className="flex items-center gap-2 mb-2">
|
|
|
+ <h4 className="text-white font-medium">Product Data</h4>
|
|
|
+ <Badge variant="outline" className="text-xs border-blue-500 text-blue-400">
|
|
|
+ Public Data
|
|
|
+ </Badge>
|
|
|
</div>
|
|
|
- <Switch
|
|
|
- checked={permissions.allow_product_access}
|
|
|
- onCheckedChange={() => handleToggle('allow_product_access')}
|
|
|
- disabled={saving}
|
|
|
- />
|
|
|
+ <p className="text-sm text-slate-400 mb-3">
|
|
|
+ Product information (names, prices, descriptions, stock levels)
|
|
|
+ </p>
|
|
|
+ {renderPolicyOptions('products', 'products_access_policy', false)}
|
|
|
</div>
|
|
|
|
|
|
{/* Customers Access */}
|
|
|
- <div className="flex items-start justify-between p-4 bg-slate-700/50 rounded-lg">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="flex items-center gap-2 mb-1">
|
|
|
- <h4 className="text-white font-medium">Customer Data Access</h4>
|
|
|
- <Badge variant="outline" className="text-xs border-orange-500 text-orange-400">
|
|
|
- Personal Data
|
|
|
- </Badge>
|
|
|
- </div>
|
|
|
- <p className="text-sm text-slate-400">
|
|
|
- Allow API access to customer information (names, emails, addresses, purchase history)
|
|
|
- </p>
|
|
|
- {!permissions.allow_customer_access && (
|
|
|
- <p className="text-xs text-yellow-400 mt-2">
|
|
|
- ⚠️ API requests for customer data will be denied
|
|
|
- </p>
|
|
|
- )}
|
|
|
+ <div className="p-4 bg-slate-700/50 rounded-lg border border-slate-600">
|
|
|
+ <div className="flex items-center gap-2 mb-2">
|
|
|
+ <h4 className="text-white font-medium">Customer Data</h4>
|
|
|
+ <Badge variant="outline" className="text-xs border-orange-500 text-orange-400">
|
|
|
+ Personal Data (PII)
|
|
|
+ </Badge>
|
|
|
</div>
|
|
|
- <Switch
|
|
|
- checked={permissions.allow_customer_access}
|
|
|
- onCheckedChange={() => handleToggle('allow_customer_access')}
|
|
|
- disabled={saving}
|
|
|
- />
|
|
|
+ <p className="text-sm text-slate-400 mb-3">
|
|
|
+ Customer information (names, emails, addresses, purchase history)
|
|
|
+ </p>
|
|
|
+ {renderPolicyOptions('customers', 'customers_access_policy', true)}
|
|
|
</div>
|
|
|
|
|
|
{/* Orders Access */}
|
|
|
- <div className="flex items-start justify-between p-4 bg-slate-700/50 rounded-lg">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="flex items-center gap-2 mb-1">
|
|
|
- <h4 className="text-white font-medium">Order Data Access</h4>
|
|
|
- <Badge variant="outline" className="text-xs border-orange-500 text-orange-400">
|
|
|
- Personal Data
|
|
|
- </Badge>
|
|
|
- </div>
|
|
|
- <p className="text-sm text-slate-400">
|
|
|
- Allow API access to order information (order details, amounts, customer info, shipping addresses)
|
|
|
- </p>
|
|
|
- {!permissions.allow_order_access && (
|
|
|
- <p className="text-xs text-yellow-400 mt-2">
|
|
|
- ⚠️ API requests for order data will be denied
|
|
|
- </p>
|
|
|
- )}
|
|
|
+ <div className="p-4 bg-slate-700/50 rounded-lg border border-slate-600">
|
|
|
+ <div className="flex items-center gap-2 mb-2">
|
|
|
+ <h4 className="text-white font-medium">Order Data</h4>
|
|
|
+ <Badge variant="outline" className="text-xs border-orange-500 text-orange-400">
|
|
|
+ Personal Data (PII)
|
|
|
+ </Badge>
|
|
|
</div>
|
|
|
- <Switch
|
|
|
- checked={permissions.allow_order_access}
|
|
|
- onCheckedChange={() => handleToggle('allow_order_access')}
|
|
|
- disabled={saving}
|
|
|
- />
|
|
|
+ <p className="text-sm text-slate-400 mb-3">
|
|
|
+ Order information (order details, amounts, customer info, shipping addresses)
|
|
|
+ </p>
|
|
|
+ {renderPolicyOptions('orders', 'orders_access_policy', true)}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -232,13 +283,22 @@ export function DataAccessSettings({
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
- {/* Warning for disabling all access */}
|
|
|
- {!permissions.allow_customer_access && !permissions.allow_order_access && (
|
|
|
- <Alert className="bg-yellow-500/10 border-yellow-500/50">
|
|
|
- <ShieldAlert className="h-4 w-4 text-yellow-400" />
|
|
|
- <AlertDescription className="text-yellow-300 text-sm">
|
|
|
- <strong>High Privacy Mode:</strong> Customer and order data access is disabled.
|
|
|
- Only product data can be accessed via API. This maximizes privacy but limits functionality.
|
|
|
+ {/* Privacy level warnings */}
|
|
|
+ {security.level === 'maximum' && (
|
|
|
+ <Alert className="bg-green-500/10 border-green-500/50">
|
|
|
+ <ShieldCheck className="h-4 w-4 text-green-400" />
|
|
|
+ <AlertDescription className="text-green-300 text-sm">
|
|
|
+ <strong>Maximum Privacy:</strong> All data access is blocked. API tools will not be able to access any store data.
|
|
|
+ </AlertDescription>
|
|
|
+ </Alert>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {(policies.customers_access_policy === 'api_only' || policies.orders_access_policy === 'api_only') && (
|
|
|
+ <Alert className="bg-blue-500/10 border-blue-500/50">
|
|
|
+ <Info className="h-4 w-4 text-blue-400" />
|
|
|
+ <AlertDescription className="text-blue-300 text-sm">
|
|
|
+ <strong>API-Only Mode Active:</strong> Selected data types will be fetched directly from your store on demand.
|
|
|
+ This is slower but ensures no personal data is cached locally, making it GDPR-friendly.
|
|
|
</AlertDescription>
|
|
|
</Alert>
|
|
|
)}
|