developing #10
|
|
@ -201,7 +201,7 @@ function computeMigration(initialData: FlowchartData, shouldMigrate: boolean) {
|
|||
}
|
||||
|
||||
// Inner component that uses useReactFlow hook
|
||||
function FlowchartEditorInner({ initialData, needsMigration }: FlowchartEditorProps) {
|
||||
function FlowchartEditorInner({ projectId, initialData, needsMigration }: FlowchartEditorProps) {
|
||||
// Define custom node types - memoized to prevent re-renders
|
||||
const nodeTypes: NodeTypes = useMemo(
|
||||
() => ({
|
||||
|
|
@ -431,6 +431,7 @@ function FlowchartEditorInner({ initialData, needsMigration }: FlowchartEditorPr
|
|||
</div>
|
||||
{showSettings && (
|
||||
<ProjectSettingsModal
|
||||
projectId={projectId}
|
||||
characters={characters}
|
||||
variables={variables}
|
||||
onCharactersChange={setCharacters}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,386 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import type { Character, Variable } from '@/types/flowchart'
|
||||
|
||||
type ImportMode = 'characters' | 'variables'
|
||||
|
||||
type ProjectListItem = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type ImportFromProjectModalProps = {
|
||||
mode: ImportMode
|
||||
currentProjectId: string
|
||||
existingCharacters: Character[]
|
||||
existingVariables: Variable[]
|
||||
onImportCharacters: (characters: Character[]) => void
|
||||
onImportVariables: (variables: Variable[]) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function ImportFromProjectModal({
|
||||
mode,
|
||||
currentProjectId,
|
||||
existingCharacters,
|
||||
existingVariables,
|
||||
onImportCharacters,
|
||||
onImportVariables,
|
||||
onClose,
|
||||
}: ImportFromProjectModalProps) {
|
||||
const [projects, setProjects] = useState<ProjectListItem[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null)
|
||||
const [sourceCharacters, setSourceCharacters] = useState<Character[]>([])
|
||||
const [sourceVariables, setSourceVariables] = useState<Variable[]>([])
|
||||
const [loadingSource, setLoadingSource] = useState(false)
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
||||
const [warnings, setWarnings] = useState<string[]>([])
|
||||
|
||||
// Load user's projects on mount
|
||||
useEffect(() => {
|
||||
async function fetchProjects() {
|
||||
const supabase = createClient()
|
||||
const { data, error: fetchError } = await supabase
|
||||
.from('projects')
|
||||
.select('id, name')
|
||||
.neq('id', currentProjectId)
|
||||
.order('name')
|
||||
|
||||
if (fetchError) {
|
||||
setError('Failed to load projects')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
setProjects(data || [])
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
fetchProjects()
|
||||
}, [currentProjectId])
|
||||
|
||||
// Load source project's characters/variables when a project is selected
|
||||
const handleSelectProject = async (projectId: string) => {
|
||||
setSelectedProjectId(projectId)
|
||||
setLoadingSource(true)
|
||||
setWarnings([])
|
||||
setSelectedIds(new Set())
|
||||
|
||||
const supabase = createClient()
|
||||
const { data, error: fetchError } = await supabase
|
||||
.from('projects')
|
||||
.select('flowchart_data')
|
||||
.eq('id', projectId)
|
||||
.single()
|
||||
|
||||
if (fetchError || !data) {
|
||||
setError('Failed to load project data')
|
||||
setLoadingSource(false)
|
||||
return
|
||||
}
|
||||
|
||||
const flowchartData = data.flowchart_data || {}
|
||||
const chars: Character[] = flowchartData.characters || []
|
||||
const vars: Variable[] = flowchartData.variables || []
|
||||
|
||||
setSourceCharacters(chars)
|
||||
setSourceVariables(vars)
|
||||
setLoadingSource(false)
|
||||
|
||||
// Select all by default
|
||||
const items = mode === 'characters' ? chars : vars
|
||||
setSelectedIds(new Set(items.map((item) => item.id)))
|
||||
}
|
||||
|
||||
const handleToggleItem = (id: string) => {
|
||||
setSelectedIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) {
|
||||
next.delete(id)
|
||||
} else {
|
||||
next.add(id)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectAll = () => {
|
||||
const items = mode === 'characters' ? sourceCharacters : sourceVariables
|
||||
setSelectedIds(new Set(items.map((item) => item.id)))
|
||||
}
|
||||
|
||||
const handleSelectNone = () => {
|
||||
setSelectedIds(new Set())
|
||||
}
|
||||
|
||||
const handleImport = () => {
|
||||
const importWarnings: string[] = []
|
||||
|
||||
if (mode === 'characters') {
|
||||
const selectedCharacters = sourceCharacters.filter((c) => selectedIds.has(c.id))
|
||||
const existingNames = new Set(existingCharacters.map((c) => c.name.toLowerCase()))
|
||||
const toImport: Character[] = []
|
||||
|
||||
for (const char of selectedCharacters) {
|
||||
if (existingNames.has(char.name.toLowerCase())) {
|
||||
importWarnings.push(`Skipped "${char.name}" (already exists)`)
|
||||
} else {
|
||||
// Create new ID to avoid conflicts
|
||||
toImport.push({ ...char, id: nanoid() })
|
||||
existingNames.add(char.name.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
if (importWarnings.length > 0) {
|
||||
setWarnings(importWarnings)
|
||||
}
|
||||
|
||||
if (toImport.length > 0) {
|
||||
onImportCharacters(toImport)
|
||||
}
|
||||
|
||||
if (importWarnings.length === 0) {
|
||||
onClose()
|
||||
}
|
||||
} else {
|
||||
const selectedVariables = sourceVariables.filter((v) => selectedIds.has(v.id))
|
||||
const existingNames = new Set(existingVariables.map((v) => v.name.toLowerCase()))
|
||||
const toImport: Variable[] = []
|
||||
|
||||
for (const variable of selectedVariables) {
|
||||
if (existingNames.has(variable.name.toLowerCase())) {
|
||||
importWarnings.push(`Skipped "${variable.name}" (already exists)`)
|
||||
} else {
|
||||
// Create new ID to avoid conflicts
|
||||
toImport.push({ ...variable, id: nanoid() })
|
||||
existingNames.add(variable.name.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
if (importWarnings.length > 0) {
|
||||
setWarnings(importWarnings)
|
||||
}
|
||||
|
||||
if (toImport.length > 0) {
|
||||
onImportVariables(toImport)
|
||||
}
|
||||
|
||||
if (importWarnings.length === 0) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const items = mode === 'characters' ? sourceCharacters : sourceVariables
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[60] flex items-center justify-center">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="relative flex max-h-[80vh] w-full max-w-lg flex-col rounded-lg bg-white shadow-xl dark:bg-zinc-800">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-zinc-200 px-6 py-4 dark:border-zinc-700">
|
||||
<h3 className="text-base font-semibold text-zinc-900 dark:text-zinc-50">
|
||||
Import {mode === 'characters' ? 'Characters' : 'Variables'} from Project
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded p-1 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
{loading && (
|
||||
<p className="py-8 text-center text-sm text-zinc-500 dark:text-zinc-400">
|
||||
Loading projects...
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<p className="py-4 text-center text-sm text-red-600 dark:text-red-400">{error}</p>
|
||||
)}
|
||||
|
||||
{!loading && !error && !selectedProjectId && (
|
||||
<>
|
||||
{projects.length === 0 ? (
|
||||
<p className="py-8 text-center text-sm text-zinc-400 dark:text-zinc-500">
|
||||
No other projects found.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
<p className="mb-3 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Select a project to import from:
|
||||
</p>
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project.id}
|
||||
onClick={() => handleSelectProject(project.id)}
|
||||
className="flex w-full items-center rounded-lg border border-zinc-200 px-4 py-3 text-left text-sm font-medium text-zinc-900 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-50 dark:hover:bg-zinc-700/50"
|
||||
>
|
||||
<svg className="mr-3 h-4 w-4 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
{project.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{loadingSource && (
|
||||
<p className="py-8 text-center text-sm text-zinc-500 dark:text-zinc-400">
|
||||
Loading {mode}...
|
||||
</p>
|
||||
)}
|
||||
|
||||
{selectedProjectId && !loadingSource && (
|
||||
<>
|
||||
{/* Back button */}
|
||||
<button
|
||||
onClick={() => { setSelectedProjectId(null); setWarnings([]) }}
|
||||
className="mb-3 flex items-center gap-1 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-50"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
Back to projects
|
||||
</button>
|
||||
|
||||
{items.length === 0 ? (
|
||||
<p className="py-8 text-center text-sm text-zinc-400 dark:text-zinc-500">
|
||||
This project has no {mode} defined.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
{/* Select all/none controls */}
|
||||
<div className="mb-3 flex items-center gap-3">
|
||||
<span className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{selectedIds.size} of {items.length} selected
|
||||
</span>
|
||||
<button
|
||||
onClick={handleSelectAll}
|
||||
className="text-xs text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
Select all
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSelectNone}
|
||||
className="text-xs text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
Select none
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Item list with checkboxes */}
|
||||
<div className="space-y-1">
|
||||
{mode === 'characters'
|
||||
? sourceCharacters.map((char) => (
|
||||
<label
|
||||
key={char.id}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-zinc-200 px-4 py-2.5 hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-700/50"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.has(char.id)}
|
||||
onChange={() => handleToggleItem(char.id)}
|
||||
className="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600"
|
||||
/>
|
||||
<div
|
||||
className="h-3.5 w-3.5 rounded-full"
|
||||
style={{ backgroundColor: char.color }}
|
||||
/>
|
||||
<span className="text-sm text-zinc-900 dark:text-zinc-50">
|
||||
{char.name}
|
||||
</span>
|
||||
{char.description && (
|
||||
<span className="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
- {char.description}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
))
|
||||
: sourceVariables.map((variable) => (
|
||||
<label
|
||||
key={variable.id}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-zinc-200 px-4 py-2.5 hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-700/50"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.has(variable.id)}
|
||||
onChange={() => handleToggleItem(variable.id)}
|
||||
className="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600"
|
||||
/>
|
||||
<span className={`rounded px-1.5 py-0.5 text-xs font-medium ${
|
||||
variable.type === 'numeric'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300'
|
||||
: variable.type === 'string'
|
||||
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
|
||||
: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300'
|
||||
}`}>
|
||||
{variable.type}
|
||||
</span>
|
||||
<span className="text-sm text-zinc-900 dark:text-zinc-50">
|
||||
{variable.name}
|
||||
</span>
|
||||
<span className="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
(initial: {String(variable.initialValue)})
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Warnings */}
|
||||
{warnings.length > 0 && (
|
||||
<div className="mt-3 rounded-lg border border-orange-200 bg-orange-50 p-3 dark:border-orange-800 dark:bg-orange-900/20">
|
||||
<p className="mb-1 text-xs font-medium text-orange-700 dark:text-orange-300">
|
||||
Import warnings:
|
||||
</p>
|
||||
{warnings.map((warning, i) => (
|
||||
<p key={i} className="text-xs text-orange-600 dark:text-orange-400">
|
||||
{warning}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer with import button */}
|
||||
{selectedProjectId && !loadingSource && items.length > 0 && (
|
||||
<div className="flex items-center justify-end gap-3 border-t border-zinc-200 px-6 py-3 dark:border-zinc-700">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-md border border-zinc-300 bg-white px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-600"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleImport}
|
||||
disabled={selectedIds.size === 0}
|
||||
className="rounded-md bg-blue-500 px-4 py-2 text-sm font-medium text-white hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
||||
>
|
||||
Import {selectedIds.size > 0 ? `(${selectedIds.size})` : ''}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -3,10 +3,14 @@
|
|||
import { useState } from 'react'
|
||||
import { nanoid } from 'nanoid'
|
||||
import type { Character, Variable } from '@/types/flowchart'
|
||||
import ImportFromProjectModal from './ImportFromProjectModal'
|
||||
|
||||
type Tab = 'characters' | 'variables'
|
||||
|
||||
type ImportModalState = { open: boolean; mode: 'characters' | 'variables' }
|
||||
|
||||
type ProjectSettingsModalProps = {
|
||||
projectId: string
|
||||
characters: Character[]
|
||||
variables: Variable[]
|
||||
onCharactersChange: (characters: Character[]) => void
|
||||
|
|
@ -26,6 +30,7 @@ function randomHexColor(): string {
|
|||
}
|
||||
|
||||
export default function ProjectSettingsModal({
|
||||
projectId,
|
||||
characters,
|
||||
variables,
|
||||
onCharactersChange,
|
||||
|
|
@ -35,6 +40,15 @@ export default function ProjectSettingsModal({
|
|||
getVariableUsageCount,
|
||||
}: ProjectSettingsModalProps) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('characters')
|
||||
const [importModal, setImportModal] = useState<ImportModalState>({ open: false, mode: 'characters' })
|
||||
|
||||
const handleImportCharacters = (imported: Character[]) => {
|
||||
onCharactersChange([...characters, ...imported])
|
||||
}
|
||||
|
||||
const handleImportVariables = (imported: Variable[]) => {
|
||||
onVariablesChange([...variables, ...imported])
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
|
|
@ -90,6 +104,7 @@ export default function ProjectSettingsModal({
|
|||
characters={characters}
|
||||
onChange={onCharactersChange}
|
||||
getUsageCount={getCharacterUsageCount}
|
||||
onImport={() => setImportModal({ open: true, mode: 'characters' })}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'variables' && (
|
||||
|
|
@ -97,10 +112,23 @@ export default function ProjectSettingsModal({
|
|||
variables={variables}
|
||||
onChange={onVariablesChange}
|
||||
getUsageCount={getVariableUsageCount}
|
||||
onImport={() => setImportModal({ open: true, mode: 'variables' })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{importModal.open && (
|
||||
<ImportFromProjectModal
|
||||
mode={importModal.mode}
|
||||
currentProjectId={projectId}
|
||||
existingCharacters={characters}
|
||||
existingVariables={variables}
|
||||
onImportCharacters={handleImportCharacters}
|
||||
onImportVariables={handleImportVariables}
|
||||
onClose={() => setImportModal({ open: false, mode: importModal.mode })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -110,6 +138,7 @@ type CharactersTabProps = {
|
|||
characters: Character[]
|
||||
onChange: (characters: Character[]) => void
|
||||
getUsageCount: (characterId: string) => number
|
||||
onImport: () => void
|
||||
}
|
||||
|
||||
type CharacterFormData = {
|
||||
|
|
@ -118,7 +147,7 @@ type CharacterFormData = {
|
|||
description: string
|
||||
}
|
||||
|
||||
function CharactersTab({ characters, onChange, getUsageCount }: CharactersTabProps) {
|
||||
function CharactersTab({ characters, onChange, getUsageCount, onImport }: CharactersTabProps) {
|
||||
const [isAdding, setIsAdding] = useState(false)
|
||||
const [editingId, setEditingId] = useState<string | null>(null)
|
||||
const [formData, setFormData] = useState<CharacterFormData>({ name: '', color: randomHexColor(), description: '' })
|
||||
|
|
@ -207,12 +236,20 @@ function CharactersTab({ characters, onChange, getUsageCount }: CharactersTabPro
|
|||
Define characters that can be referenced in dialogue nodes.
|
||||
</p>
|
||||
{!isAdding && !editingId && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onImport}
|
||||
className="rounded border border-zinc-300 bg-white px-3 py-1.5 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-600"
|
||||
>
|
||||
Import from project
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setIsAdding(true); resetForm(); setDeleteConfirm(null) }}
|
||||
className="rounded bg-blue-500 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
||||
>
|
||||
Add Character
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -378,6 +415,7 @@ type VariablesTabProps = {
|
|||
variables: Variable[]
|
||||
onChange: (variables: Variable[]) => void
|
||||
getUsageCount: (variableId: string) => number
|
||||
onImport: () => void
|
||||
}
|
||||
|
||||
type VariableType = 'numeric' | 'string' | 'boolean'
|
||||
|
|
@ -406,7 +444,7 @@ function parseInitialValue(type: VariableType, raw: string): number | string | b
|
|||
}
|
||||
}
|
||||
|
||||
function VariablesTab({ variables, onChange, getUsageCount }: VariablesTabProps) {
|
||||
function VariablesTab({ variables, onChange, getUsageCount, onImport }: VariablesTabProps) {
|
||||
const [isAdding, setIsAdding] = useState(false)
|
||||
const [editingId, setEditingId] = useState<string | null>(null)
|
||||
const [formData, setFormData] = useState<VariableFormData>({ name: '', type: 'numeric', initialValue: '0', description: '' })
|
||||
|
|
@ -503,12 +541,20 @@ function VariablesTab({ variables, onChange, getUsageCount }: VariablesTabProps)
|
|||
Define variables that can be referenced in variable nodes and edge conditions.
|
||||
</p>
|
||||
{!isAdding && !editingId && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onImport}
|
||||
className="rounded border border-zinc-300 bg-white px-3 py-1.5 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-600"
|
||||
>
|
||||
Import from project
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setIsAdding(true); resetForm(); setDeleteConfirm(null) }}
|
||||
className="rounded bg-blue-500 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
||||
>
|
||||
Add Variable
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue