add in admin and member automatically generated
This commit is contained in:
+300
-121
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { X, Plus, Trash } from 'lucide-react';
|
import { X, Plus, Trash } from "lucide-react";
|
||||||
import { Node } from 'reactflow';
|
import { Node } from "reactflow";
|
||||||
import { useFlowStore } from '../store/flowStore';
|
import { useFlowStore } from "../store/flowStore";
|
||||||
|
|
||||||
interface ConfigPanelProps {
|
interface ConfigPanelProps {
|
||||||
node: Node | null;
|
node: Node | null;
|
||||||
@@ -17,18 +17,27 @@ interface Field {
|
|||||||
|
|
||||||
export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
||||||
const { models } = useFlowStore();
|
const { models } = useFlowStore();
|
||||||
const [newField, setNewField] = useState<Field>({ name: '', type: 'string' });
|
const [newField, setNewField] = useState<Field>({ name: "", type: "string" });
|
||||||
|
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
const handleChange = (
|
||||||
|
e: React.ChangeEvent<
|
||||||
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||||
|
>
|
||||||
|
) => {
|
||||||
onUpdateNode(node.id, {
|
onUpdateNode(node.id, {
|
||||||
...node.data,
|
...node.data,
|
||||||
[e.target.name]: e.target.value,
|
[e.target.name]: e.target.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleArrayChange = (index: number, field: string, value: string, arrayName: string) => {
|
const handleArrayChange = (
|
||||||
|
index: number,
|
||||||
|
field: string,
|
||||||
|
value: string,
|
||||||
|
arrayName: string
|
||||||
|
) => {
|
||||||
const array = [...(node.data[arrayName] || [])];
|
const array = [...(node.data[arrayName] || [])];
|
||||||
array[index] = { ...array[index], [field]: value };
|
array[index] = { ...array[index], [field]: value };
|
||||||
onUpdateNode(node.id, {
|
onUpdateNode(node.id, {
|
||||||
@@ -43,7 +52,7 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
...node.data,
|
...node.data,
|
||||||
[arrayName]: array,
|
[arrayName]: array,
|
||||||
});
|
});
|
||||||
setNewField({ name: '', type: 'string' });
|
setNewField({ name: "", type: "string" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeField = (index: number, arrayName: string) => {
|
const removeField = (index: number, arrayName: string) => {
|
||||||
@@ -55,19 +64,35 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyQueryFields = () => {
|
||||||
|
const currentFields = node.data.fields || [];
|
||||||
|
navigator.clipboard.writeText(JSON.stringify(currentFields, null, 2));
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractQueryParams = (path: string) => {
|
||||||
|
const params = path.match(/:[a-zA-Z]+/g) || [];
|
||||||
|
return params.map((param) => ({
|
||||||
|
name: param.substring(1),
|
||||||
|
type: "string",
|
||||||
|
validation: "",
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const renderDatabaseFields = () => (
|
const renderDatabaseFields = () => (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Model</label>
|
<label className="block text-sm font-medium mb-1">Model</label>
|
||||||
<select
|
<select
|
||||||
name="model"
|
name="model"
|
||||||
value={node.data.model || ''}
|
value={node.data.model || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
>
|
>
|
||||||
<option value="">Select Model</option>
|
<option value="">Select Model</option>
|
||||||
{models.map((model) => (
|
{models.map((model) => (
|
||||||
<option key={model.id} value={model.id}>{model.name}</option>
|
<option key={model.id} value={model.id}>
|
||||||
|
{model.name}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +100,7 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<label className="block text-sm font-medium mb-1">SQL Query</label>
|
<label className="block text-sm font-medium mb-1">SQL Query</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="query"
|
name="query"
|
||||||
value={node.data.query || ''}
|
value={node.data.query || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded h-32 font-mono text-sm"
|
className="w-full p-2 border rounded h-32 font-mono text-sm"
|
||||||
placeholder="SELECT * FROM table WHERE id = :id"
|
placeholder="SELECT * FROM table WHERE id = :id"
|
||||||
@@ -86,7 +111,7 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="resultVar"
|
name="resultVar"
|
||||||
value={node.data.resultVar || ''}
|
value={node.data.resultVar || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
placeholder="result"
|
placeholder="result"
|
||||||
@@ -97,14 +122,16 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
|
|
||||||
const renderFields = () => {
|
const renderFields = () => {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'auth':
|
case "auth":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Auth Type</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Auth Type
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
name="authType"
|
name="authType"
|
||||||
value={node.data.authType || 'bearer'}
|
value={node.data.authType || "bearer"}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
>
|
>
|
||||||
@@ -114,11 +141,13 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Token Variable</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Token Variable
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="tokenVar"
|
name="tokenVar"
|
||||||
value={node.data.tokenVar || ''}
|
value={node.data.tokenVar || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
placeholder="token"
|
placeholder="token"
|
||||||
@@ -127,33 +156,221 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'url':
|
case "url":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Method</label>
|
<label className="block text-sm font-medium mb-1">Method</label>
|
||||||
<select
|
<select
|
||||||
name="method"
|
name="method"
|
||||||
value={node.data.method || 'GET'}
|
value={node.data.method || "GET"}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
>
|
>
|
||||||
{['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].map(method => (
|
{["GET", "POST", "PUT", "DELETE", "PATCH"].map((method) => (
|
||||||
<option key={method} value={method}>{method}</option>
|
<option key={method} value={method}>
|
||||||
|
{method}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Route Path</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Route Path
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="path"
|
name="path"
|
||||||
value={node.data.path || ''}
|
value={node.data.path || ""}
|
||||||
onChange={handleChange}
|
onChange={(e) => {
|
||||||
|
handleChange(e);
|
||||||
|
const queryParams = extractQueryParams(e.target.value);
|
||||||
|
onUpdateNode(node.id, {
|
||||||
|
...node.data,
|
||||||
|
path: e.target.value,
|
||||||
|
queryFields: queryParams,
|
||||||
|
});
|
||||||
|
}}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
placeholder="/api/users/:id"
|
placeholder="/api/users/:id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Body Fields
|
||||||
|
</label>
|
||||||
|
<div className="mb-2 p-2 bg-gray-50 rounded text-sm">
|
||||||
|
{node.data.fields && node.data.fields.length > 0 ? (
|
||||||
|
node.data.fields.map((field: Field) => (
|
||||||
|
<div key={field.name} className="text-gray-600">
|
||||||
|
{field.name}: {field.type}
|
||||||
|
{field.validation ? ` (${field.validation})` : ""}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-gray-500 italic">
|
||||||
|
No body fields defined
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{(node.data.fields || []).map((field: Field, index: number) => (
|
||||||
|
<div key={index} className="flex gap-1 mb-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={field.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleArrayChange(index, "name", e.target.value, "fields")
|
||||||
|
}
|
||||||
|
className="flex-1 p-2 border rounded text-sm"
|
||||||
|
placeholder="Field name"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={field.type}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleArrayChange(index, "type", e.target.value, "fields")
|
||||||
|
}
|
||||||
|
className="w-20 p-2 border rounded text-sm"
|
||||||
|
>
|
||||||
|
<option value="string">String</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="boolean">Bool</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
onClick={() => removeField(index, "fields")}
|
||||||
|
className="p-2 text-red-500 hover:bg-red-50 rounded"
|
||||||
|
>
|
||||||
|
<Trash className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="flex gap-1 mt-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newField.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewField({ ...newField, name: e.target.value })
|
||||||
|
}
|
||||||
|
className="flex-1 p-2 border rounded text-sm"
|
||||||
|
placeholder="New field name"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={newField.type}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewField({ ...newField, type: e.target.value })
|
||||||
|
}
|
||||||
|
className="w-20 p-2 border rounded text-sm"
|
||||||
|
>
|
||||||
|
<option value="string">String</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="boolean">Bool</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
onClick={() => addField("fields")}
|
||||||
|
className="p-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Query Fields
|
||||||
|
</label>
|
||||||
|
<div className="mb-2 p-2 bg-gray-50 rounded text-sm">
|
||||||
|
{node.data.queryFields && node.data.queryFields.length > 0 ? (
|
||||||
|
node.data.queryFields.map((field: Field) => (
|
||||||
|
<div key={field.name} className="text-gray-600">
|
||||||
|
{field.name}: {field.type}
|
||||||
|
{field.validation ? ` (${field.validation})` : ""}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-gray-500 italic">
|
||||||
|
No query fields defined
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{(node.data.queryFields || []).map(
|
||||||
|
(field: Field, index: number) => (
|
||||||
|
<div key={index} className="flex gap-1 mb-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={field.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleArrayChange(
|
||||||
|
index,
|
||||||
|
"name",
|
||||||
|
e.target.value,
|
||||||
|
"queryFields"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="flex-1 p-2 border rounded text-sm"
|
||||||
|
placeholder="Field name"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={field.type}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleArrayChange(
|
||||||
|
index,
|
||||||
|
"type",
|
||||||
|
e.target.value,
|
||||||
|
"queryFields"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="w-20 p-2 border rounded text-sm"
|
||||||
|
>
|
||||||
|
<option value="string">String</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="boolean">Bool</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
onClick={() => removeField(index, "queryFields")}
|
||||||
|
className="p-2 text-red-500 hover:bg-red-50 rounded"
|
||||||
|
>
|
||||||
|
<Trash className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<div className="flex gap-1 mt-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newField.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewField({ ...newField, name: e.target.value })
|
||||||
|
}
|
||||||
|
className="flex-1 p-2 border rounded text-sm"
|
||||||
|
placeholder="New query param"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={newField.type}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewField({ ...newField, type: e.target.value })
|
||||||
|
}
|
||||||
|
className="w-20 p-2 border rounded text-sm"
|
||||||
|
>
|
||||||
|
<option value="string">String</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="boolean">Bool</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
onClick={() => addField("queryFields")}
|
||||||
|
className="p-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "output":
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Fields</label>
|
<label className="block text-sm font-medium mb-1">Fields</label>
|
||||||
{(node.data.fields || []).map((field: Field, index: number) => (
|
{(node.data.fields || []).map((field: Field, index: number) => (
|
||||||
@@ -161,13 +378,17 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.name}
|
value={field.name}
|
||||||
onChange={(e) => handleArrayChange(index, 'name', e.target.value, 'fields')}
|
onChange={(e) =>
|
||||||
|
handleArrayChange(index, "name", e.target.value, "fields")
|
||||||
|
}
|
||||||
className="flex-1 p-2 border rounded"
|
className="flex-1 p-2 border rounded"
|
||||||
placeholder="Field name"
|
placeholder="Field name"
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
value={field.type}
|
value={field.type}
|
||||||
onChange={(e) => handleArrayChange(index, 'type', e.target.value, 'fields')}
|
onChange={(e) =>
|
||||||
|
handleArrayChange(index, "type", e.target.value, "fields")
|
||||||
|
}
|
||||||
className="w-24 p-2 border rounded"
|
className="w-24 p-2 border rounded"
|
||||||
>
|
>
|
||||||
<option value="string">String</option>
|
<option value="string">String</option>
|
||||||
@@ -175,15 +396,8 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<option value="boolean">Boolean</option>
|
<option value="boolean">Boolean</option>
|
||||||
<option value="date">Date</option>
|
<option value="date">Date</option>
|
||||||
</select>
|
</select>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={field.validation}
|
|
||||||
onChange={(e) => handleArrayChange(index, 'validation', e.target.value, 'fields')}
|
|
||||||
className="flex-1 p-2 border rounded"
|
|
||||||
placeholder="Validation"
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => removeField(index, 'fields')}
|
onClick={() => removeField(index, "fields")}
|
||||||
className="p-2 text-red-500 hover:bg-red-50 rounded"
|
className="p-2 text-red-500 hover:bg-red-50 rounded"
|
||||||
>
|
>
|
||||||
<Trash className="w-4 h-4" />
|
<Trash className="w-4 h-4" />
|
||||||
@@ -194,13 +408,17 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newField.name}
|
value={newField.name}
|
||||||
onChange={(e) => setNewField({ ...newField, name: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setNewField({ ...newField, name: e.target.value })
|
||||||
|
}
|
||||||
className="flex-1 p-2 border rounded"
|
className="flex-1 p-2 border rounded"
|
||||||
placeholder="New field name"
|
placeholder="New field name"
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
value={newField.type}
|
value={newField.type}
|
||||||
onChange={(e) => setNewField({ ...newField, type: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setNewField({ ...newField, type: e.target.value })
|
||||||
|
}
|
||||||
className="w-24 p-2 border rounded"
|
className="w-24 p-2 border rounded"
|
||||||
>
|
>
|
||||||
<option value="string">String</option>
|
<option value="string">String</option>
|
||||||
@@ -209,7 +427,7 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<option value="date">Date</option>
|
<option value="date">Date</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
onClick={() => addField('fields')}
|
onClick={() => addField("fields")}
|
||||||
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
|
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
@@ -219,76 +437,17 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'output':
|
case "variable":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Fields</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
{(node.data.fields || []).map((field: Field, index: number) => (
|
Variable Name
|
||||||
<div key={index} className="flex gap-2 mb-2">
|
</label>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={field.name}
|
|
||||||
onChange={(e) => handleArrayChange(index, 'name', e.target.value, 'fields')}
|
|
||||||
className="flex-1 p-2 border rounded"
|
|
||||||
placeholder="Field name"
|
|
||||||
/>
|
|
||||||
<select
|
|
||||||
value={field.type}
|
|
||||||
onChange={(e) => handleArrayChange(index, 'type', e.target.value, 'fields')}
|
|
||||||
className="w-24 p-2 border rounded"
|
|
||||||
>
|
|
||||||
<option value="string">String</option>
|
|
||||||
<option value="number">Number</option>
|
|
||||||
<option value="boolean">Boolean</option>
|
|
||||||
<option value="date">Date</option>
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
onClick={() => removeField(index, 'fields')}
|
|
||||||
className="p-2 text-red-500 hover:bg-red-50 rounded"
|
|
||||||
>
|
|
||||||
<Trash className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="flex gap-2 mt-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={newField.name}
|
|
||||||
onChange={(e) => setNewField({ ...newField, name: e.target.value })}
|
|
||||||
className="flex-1 p-2 border rounded"
|
|
||||||
placeholder="New field name"
|
|
||||||
/>
|
|
||||||
<select
|
|
||||||
value={newField.type}
|
|
||||||
onChange={(e) => setNewField({ ...newField, type: e.target.value })}
|
|
||||||
className="w-24 p-2 border rounded"
|
|
||||||
>
|
|
||||||
<option value="string">String</option>
|
|
||||||
<option value="number">Number</option>
|
|
||||||
<option value="boolean">Boolean</option>
|
|
||||||
<option value="date">Date</option>
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
onClick={() => addField('fields')}
|
|
||||||
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
<Plus className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'variable':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mb-4">
|
|
||||||
<label className="block text-sm font-medium mb-1">Variable Name</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
value={node.data.name || ''}
|
value={node.data.name || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
placeholder="myVariable"
|
placeholder="myVariable"
|
||||||
@@ -298,7 +457,7 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<label className="block text-sm font-medium mb-1">Type</label>
|
<label className="block text-sm font-medium mb-1">Type</label>
|
||||||
<select
|
<select
|
||||||
name="type"
|
name="type"
|
||||||
value={node.data.type || 'string'}
|
value={node.data.type || "string"}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
>
|
>
|
||||||
@@ -310,11 +469,13 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Default Value</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Default Value
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="defaultValue"
|
name="defaultValue"
|
||||||
value={node.data.defaultValue || ''}
|
value={node.data.defaultValue || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
placeholder="Default value"
|
placeholder="Default value"
|
||||||
@@ -323,13 +484,15 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'logic':
|
case "logic":
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">JavaScript Code</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
JavaScript Code
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="code"
|
name="code"
|
||||||
value={node.data.code || ''}
|
value={node.data.code || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded h-40 font-mono text-sm"
|
className="w-full p-2 border rounded h-40 font-mono text-sm"
|
||||||
placeholder="// Write your JavaScript code here"
|
placeholder="// Write your JavaScript code here"
|
||||||
@@ -337,19 +500,33 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'db-find':
|
case "db-find":
|
||||||
case 'db-query':
|
case "db-query":
|
||||||
return renderDatabaseFields();
|
|
||||||
|
|
||||||
case 'db-insert':
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderDatabaseFields()}
|
{renderDatabaseFields()}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Variables</label>
|
<button
|
||||||
|
onClick={copyQueryFields}
|
||||||
|
className="px-3 py-1 text-sm bg-gray-100 hover:bg-gray-200 rounded"
|
||||||
|
>
|
||||||
|
Copy Fields
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "db-insert":
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderDatabaseFields()}
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Variables
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="variables"
|
name="variables"
|
||||||
value={node.data.variables || ''}
|
value={node.data.variables || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded h-20 font-mono text-sm"
|
className="w-full p-2 border rounded h-20 font-mono text-sm"
|
||||||
placeholder="name: string age: number"
|
placeholder="name: string age: number"
|
||||||
@@ -358,8 +535,8 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'db-update':
|
case "db-update":
|
||||||
case 'db-delete':
|
case "db-delete":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderDatabaseFields()}
|
{renderDatabaseFields()}
|
||||||
@@ -368,18 +545,20 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="idField"
|
name="idField"
|
||||||
value={node.data.idField || ''}
|
value={node.data.idField || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
placeholder="id"
|
placeholder="id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{node.type === 'db-update' && (
|
{node.type === "db-update" && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-1">Variables</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Variables
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="variables"
|
name="variables"
|
||||||
value={node.data.variables || ''}
|
value={node.data.variables || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full p-2 border rounded h-20 font-mono text-sm"
|
className="w-full p-2 border rounded h-20 font-mono text-sm"
|
||||||
placeholder="name: string age: number"
|
placeholder="name: string age: number"
|
||||||
@@ -405,4 +584,4 @@ export function ConfigPanel({ node, onClose, onUpdateNode }: ConfigPanelProps) {
|
|||||||
{renderFields()}
|
{renderFields()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ export function DefaultTablesModal({
|
|||||||
onClose,
|
onClose,
|
||||||
}: DefaultTablesModalProps) {
|
}: DefaultTablesModalProps) {
|
||||||
const [selectedTables, setSelectedTables] = useState<string[]>([]);
|
const [selectedTables, setSelectedTables] = useState<string[]>([]);
|
||||||
const { addModel } = useFlowStore();
|
const [includeAdminRole, setIncludeAdminRole] = useState(true);
|
||||||
|
const [includeMemberRole, setIncludeMemberRole] = useState(true);
|
||||||
|
const { addModel, addRole } = useFlowStore();
|
||||||
|
|
||||||
const handleToggleTable = (tableId: string) => {
|
const handleToggleTable = (tableId: string) => {
|
||||||
setSelectedTables((prev) =>
|
setSelectedTables((prev) =>
|
||||||
@@ -88,6 +90,42 @@ export function DefaultTablesModal({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add roles based on checkbox selection
|
||||||
|
if (selectedTables.includes("user")) {
|
||||||
|
if (includeAdminRole) {
|
||||||
|
addRole({
|
||||||
|
id: `role_admin_${Date.now()}`,
|
||||||
|
name: "Admin",
|
||||||
|
slug: "admin",
|
||||||
|
permissions: {
|
||||||
|
authRequired: true,
|
||||||
|
routes: [],
|
||||||
|
canCreateUsers: true,
|
||||||
|
canEditUsers: true,
|
||||||
|
canDeleteUsers: true,
|
||||||
|
canManageRoles: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeMemberRole) {
|
||||||
|
addRole({
|
||||||
|
id: `role_member_${Date.now()}`,
|
||||||
|
name: "Member",
|
||||||
|
slug: "member",
|
||||||
|
permissions: {
|
||||||
|
authRequired: true,
|
||||||
|
routes: [],
|
||||||
|
canCreateUsers: false,
|
||||||
|
canEditUsers: false,
|
||||||
|
canDeleteUsers: false,
|
||||||
|
canManageRoles: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,6 +165,39 @@ export function DefaultTablesModal({
|
|||||||
<div className="mt-2 text-sm text-gray-500">
|
<div className="mt-2 text-sm text-gray-500">
|
||||||
Fields: {table.fields.map((f) => f.name).join(", ")}
|
Fields: {table.fields.map((f) => f.name).join(", ")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Add role options when User table is selected */}
|
||||||
|
{table.id === "user" && selectedTables.includes("user") && (
|
||||||
|
<div className="mt-3 pl-2 border-l-2 border-gray-200">
|
||||||
|
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Include roles:
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="flex items-center space-x-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={includeAdminRole}
|
||||||
|
onChange={(e) =>
|
||||||
|
setIncludeAdminRole(e.target.checked)
|
||||||
|
}
|
||||||
|
className="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
||||||
|
/>
|
||||||
|
<span>Admin Role</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center space-x-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={includeMemberRole}
|
||||||
|
onChange={(e) =>
|
||||||
|
setIncludeMemberRole(e.target.checked)
|
||||||
|
}
|
||||||
|
className="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
||||||
|
/>
|
||||||
|
<span>Member Role</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useRef, useEffect } from 'react';
|
import React, { useCallback, useRef, useEffect } from "react";
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
Controls,
|
Controls,
|
||||||
@@ -9,13 +9,13 @@ import ReactFlow, {
|
|||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
Panel,
|
Panel,
|
||||||
} from 'reactflow';
|
} from "reactflow";
|
||||||
import { ArrowLeft, Save } from 'lucide-react';
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import { useFlowStore } from '../store/flowStore';
|
import { useFlowStore } from "../store/flowStore";
|
||||||
import { ConfigPanel } from './ConfigPanel';
|
import { ConfigPanel } from "./ConfigPanel";
|
||||||
import { ComponentsPanel } from './ComponentsPanel';
|
import { ComponentsPanel } from "./ComponentsPanel";
|
||||||
import CustomNode from './CustomNode';
|
import CustomNode from "./CustomNode";
|
||||||
import 'reactflow/dist/style.css';
|
import "reactflow/dist/style.css";
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
auth: CustomNode,
|
auth: CustomNode,
|
||||||
@@ -23,11 +23,11 @@ const nodeTypes = {
|
|||||||
output: CustomNode,
|
output: CustomNode,
|
||||||
logic: CustomNode,
|
logic: CustomNode,
|
||||||
variable: CustomNode,
|
variable: CustomNode,
|
||||||
'db-find': CustomNode,
|
"db-find": CustomNode,
|
||||||
'db-insert': CustomNode,
|
"db-insert": CustomNode,
|
||||||
'db-update': CustomNode,
|
"db-update": CustomNode,
|
||||||
'db-delete': CustomNode,
|
"db-delete": CustomNode,
|
||||||
'db-query': CustomNode,
|
"db-query": CustomNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FlowEditorContentProps {
|
interface FlowEditorContentProps {
|
||||||
@@ -55,7 +55,18 @@ function FlowEditorContent({ route, onClose }: FlowEditorContentProps) {
|
|||||||
setNodes(route.flowData.nodes);
|
setNodes(route.flowData.nodes);
|
||||||
setEdges(route.flowData.edges);
|
setEdges(route.flowData.edges);
|
||||||
} else {
|
} else {
|
||||||
setNodes([]);
|
// Create default URL node for new routes
|
||||||
|
const defaultNode = {
|
||||||
|
id: `node_${Date.now()}`,
|
||||||
|
type: "url",
|
||||||
|
position: { x: 100, y: 100 },
|
||||||
|
data: {
|
||||||
|
label: "URL",
|
||||||
|
path: route.url,
|
||||||
|
method: route.method,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setNodes([defaultNode]);
|
||||||
setEdges([]);
|
setEdges([]);
|
||||||
}
|
}
|
||||||
}, [route, setNodes, setEdges]);
|
}, [route, setNodes, setEdges]);
|
||||||
@@ -77,14 +88,14 @@ function FlowEditorContent({ route, onClose }: FlowEditorContentProps) {
|
|||||||
|
|
||||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.dataTransfer.dropEffect = 'move';
|
event.dataTransfer.dropEffect = "move";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(event: React.DragEvent) => {
|
(event: React.DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const type = event.dataTransfer.getData('application/reactflow');
|
const type = event.dataTransfer.getData("application/reactflow");
|
||||||
if (!type || !reactFlowWrapper.current) return;
|
if (!type || !reactFlowWrapper.current) return;
|
||||||
|
|
||||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
@@ -97,7 +108,9 @@ function FlowEditorContent({ route, onClose }: FlowEditorContentProps) {
|
|||||||
id: `node_${Date.now()}`,
|
id: `node_${Date.now()}`,
|
||||||
type,
|
type,
|
||||||
position,
|
position,
|
||||||
data: { label: type.charAt(0).toUpperCase() + type.slice(1).replace('-', ' ') },
|
data: {
|
||||||
|
label: type.charAt(0).toUpperCase() + type.slice(1).replace("-", " "),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
setNodes((nds) => [...nds, newNode]);
|
setNodes((nds) => [...nds, newNode]);
|
||||||
@@ -194,4 +207,4 @@ export function RouteFlowEditor({ route, onClose }: FlowEditorContentProps) {
|
|||||||
<FlowEditorContent route={route} onClose={onClose} />
|
<FlowEditorContent route={route} onClose={onClose} />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import { X, Trash2 } from 'lucide-react';
|
import { X, Trash2 } from "lucide-react";
|
||||||
import { useFlowStore } from '../store/flowStore';
|
import { useFlowStore } from "../store/flowStore";
|
||||||
|
|
||||||
interface RouteModalProps {
|
interface RouteModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -15,9 +15,9 @@ interface RouteModalProps {
|
|||||||
|
|
||||||
const createInitialFormData = () => ({
|
const createInitialFormData = () => ({
|
||||||
id: `route_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
id: `route_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||||
name: '',
|
name: "",
|
||||||
url: '',
|
url: "",
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
|
||||||
export function RouteModal({ isOpen, onClose, route }: RouteModalProps) {
|
export function RouteModal({ isOpen, onClose, route }: RouteModalProps) {
|
||||||
@@ -32,7 +32,9 @@ export function RouteModal({ isOpen, onClose, route }: RouteModalProps) {
|
|||||||
}
|
}
|
||||||
}, [route, isOpen]);
|
}, [route, isOpen]);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
const handleChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
|
) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
@@ -63,12 +65,9 @@ export function RouteModal({ isOpen, onClose, route }: RouteModalProps) {
|
|||||||
<div className="bg-white rounded-lg w-full max-w-md">
|
<div className="bg-white rounded-lg w-full max-w-md">
|
||||||
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
{route ? 'Edit Route' : 'Add New Route'}
|
{route ? "Edit Route" : "Add New Route"}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button onClick={onClose} className="p-1 hover:bg-gray-100 rounded">
|
||||||
onClick={onClose}
|
|
||||||
className="p-1 hover:bg-gray-100 rounded"
|
|
||||||
>
|
|
||||||
<X className="w-5 h-5" />
|
<X className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,7 +75,9 @@ export function RouteModal({ isOpen, onClose, route }: RouteModalProps) {
|
|||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-1">Route Name</label>
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Route Name
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
@@ -138,11 +139,11 @@ export function RouteModal({ isOpen, onClose, route }: RouteModalProps) {
|
|||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
{route ? 'Update' : 'Save'} Route
|
{route ? "Update" : "Save"} Route
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { Plus, Edit2, Code } from 'lucide-react';
|
import { Plus, Edit2, Code } from "lucide-react";
|
||||||
import { useFlowStore } from '../store/flowStore';
|
import { useFlowStore } from "../store/flowStore";
|
||||||
import { RouteModal } from './RouteModal';
|
import { RouteModal } from "./RouteModal";
|
||||||
import { RouteFlowEditor } from './RouteFlowEditor';
|
import { RouteFlowEditor } from "./RouteFlowEditor";
|
||||||
|
|
||||||
export function RoutesPanel() {
|
export function RoutesPanel() {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
@@ -11,55 +11,59 @@ export function RoutesPanel() {
|
|||||||
const { routes } = useFlowStore();
|
const { routes } = useFlowStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col h-full">
|
||||||
{!editingRoute ? (
|
{!editingRoute ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<div className="p-4">
|
||||||
onClick={() => {
|
<button
|
||||||
setSelectedRoute(null);
|
onClick={() => {
|
||||||
setIsModalOpen(true);
|
setSelectedRoute(null);
|
||||||
}}
|
setIsModalOpen(true);
|
||||||
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
}}
|
||||||
>
|
className="flex items-center justify-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 w-full"
|
||||||
<Plus className="w-4 h-4" />
|
>
|
||||||
Add Route
|
<Plus className="w-4 h-4" />
|
||||||
</button>
|
Add Route
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="flex-1 overflow-y-auto px-4">
|
||||||
{routes.map((route) => (
|
<div className="space-y-3">
|
||||||
<div
|
{routes.map((route) => (
|
||||||
key={route.id}
|
<div
|
||||||
className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow"
|
key={route.id}
|
||||||
>
|
className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow"
|
||||||
<div className="flex justify-between items-start">
|
>
|
||||||
<div>
|
<div className="flex justify-between items-start">
|
||||||
<h3 className="font-medium">{route.name}</h3>
|
<div>
|
||||||
<p className="text-sm text-gray-500">
|
<h3 className="font-medium">{route.name}</h3>
|
||||||
{route.method} {route.url}
|
<p className="text-sm text-gray-500">
|
||||||
</p>
|
{route.method} {route.url}
|
||||||
</div>
|
</p>
|
||||||
<div className="flex gap-2">
|
</div>
|
||||||
<button
|
<div className="flex gap-2">
|
||||||
onClick={() => {
|
<button
|
||||||
setSelectedRoute(route);
|
onClick={() => {
|
||||||
setIsModalOpen(true);
|
setSelectedRoute(route);
|
||||||
}}
|
setIsModalOpen(true);
|
||||||
className="p-1 hover:bg-gray-100 rounded"
|
}}
|
||||||
title="Edit Route"
|
className="p-1 hover:bg-gray-100 rounded"
|
||||||
>
|
title="Edit Route"
|
||||||
<Edit2 className="w-4 h-4" />
|
>
|
||||||
</button>
|
<Edit2 className="w-4 h-4" />
|
||||||
<button
|
</button>
|
||||||
onClick={() => setEditingRoute(route)}
|
<button
|
||||||
className="p-1 hover:bg-gray-100 rounded"
|
onClick={() => setEditingRoute(route)}
|
||||||
title="Edit Components"
|
className="p-1 hover:bg-gray-100 rounded"
|
||||||
>
|
title="Edit Components"
|
||||||
<Code className="w-4 h-4" />
|
>
|
||||||
</button>
|
<Code className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RouteModal
|
<RouteModal
|
||||||
@@ -81,4 +85,4 @@ export function RoutesPanel() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user