mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(settings-ui): fix settings ui, add upload status (#1691)
This commit is contained in:
+26
-33
@@ -238,7 +238,7 @@ export function Copilot() {
|
||||
<div className='relative flex h-full flex-col'>
|
||||
{/* Sticky Header with API Keys (only for hosted) */}
|
||||
{isHosted && (
|
||||
<div className='sticky top-0 z-10 border-b bg-background px-6 py-4'>
|
||||
<div className='sticky top-0 z-10 bg-background px-6 py-4'>
|
||||
<div className='space-y-3'>
|
||||
{/* API Keys Header */}
|
||||
<div className='flex items-center justify-between'>
|
||||
@@ -273,27 +273,25 @@ export function Copilot() {
|
||||
</div>
|
||||
) : (
|
||||
keys.map((k) => (
|
||||
<div
|
||||
key={k.id}
|
||||
className='flex items-center justify-between gap-4 rounded-lg border bg-muted/30 px-3 py-2'
|
||||
>
|
||||
<div className='flex min-w-0 items-center gap-3'>
|
||||
<code className='truncate font-mono text-foreground text-xs'>
|
||||
{k.displayKey}
|
||||
</code>
|
||||
<div key={k.id} className='flex flex-col gap-2'>
|
||||
<div className='flex items-center justify-between gap-4'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex h-8 items-center rounded-[8px] bg-muted px-3'>
|
||||
<code className='font-mono text-foreground text-xs'>{k.displayKey}</code>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
setDeleteKey(k)
|
||||
setShowDeleteDialog(true)
|
||||
}}
|
||||
className='h-8 text-muted-foreground hover:text-foreground'
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
setDeleteKey(k)
|
||||
setShowDeleteDialog(true)
|
||||
}}
|
||||
className='h-7 flex-shrink-0 text-muted-foreground text-xs hover:text-foreground'
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -308,15 +306,6 @@ export function Copilot() {
|
||||
{/* Models Header */}
|
||||
<div>
|
||||
<h3 className='font-semibold text-foreground text-sm'>Models</h3>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{isModelsLoading ? (
|
||||
<Skeleton className='mt-0.5 h-3 w-32' />
|
||||
) : (
|
||||
<span>
|
||||
{enabledCount} of {totalCount} enabled
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Models List */}
|
||||
@@ -473,9 +462,13 @@ export function Copilot() {
|
||||
|
||||
function CopilotKeySkeleton() {
|
||||
return (
|
||||
<div className='flex items-center justify-between gap-4 rounded-lg border bg-muted/30 px-3 py-2'>
|
||||
<Skeleton className='h-4 w-48' />
|
||||
<Skeleton className='h-7 w-14' />
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center justify-between gap-4'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<Skeleton className='h-8 w-20 rounded-[8px]' />
|
||||
</div>
|
||||
<Skeleton className='h-8 w-14' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+6
-3
@@ -707,7 +707,10 @@ export function EnvironmentVariables({
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter className='flex'>
|
||||
<AlertDialogCancel onClick={handleCancel} className='h-9 w-full rounded-[8px]'>
|
||||
<AlertDialogCancel
|
||||
onClick={handleCancel}
|
||||
className='h-9 w-full rounded-[8px] bg-red-500 text-white transition-all duration-200 hover:bg-red-600 dark:bg-red-500 dark:hover:bg-red-600'
|
||||
>
|
||||
Discard Changes
|
||||
</AlertDialogCancel>
|
||||
{hasConflicts ? (
|
||||
@@ -715,7 +718,7 @@ export function EnvironmentVariables({
|
||||
<TooltipTrigger asChild>
|
||||
<AlertDialogAction
|
||||
disabled={true}
|
||||
className='h-9 w-full cursor-not-allowed rounded-[8px] opacity-50 transition-all duration-200'
|
||||
className='h-9 w-full cursor-not-allowed rounded-[8px] bg-primary text-white opacity-50 transition-all duration-200'
|
||||
>
|
||||
Save Changes
|
||||
</AlertDialogAction>
|
||||
@@ -725,7 +728,7 @@ export function EnvironmentVariables({
|
||||
) : (
|
||||
<AlertDialogAction
|
||||
onClick={handleSave}
|
||||
className='h-9 w-full rounded-[8px] transition-all duration-200'
|
||||
className='h-9 w-full rounded-[8px] bg-primary text-white transition-all duration-200 hover:bg-primary/90'
|
||||
>
|
||||
Save Changes
|
||||
</AlertDialogAction>
|
||||
|
||||
+15
-4
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Download, Search, Trash2, Upload } from 'lucide-react'
|
||||
import { Download, Search, Trash2 } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Input } from '@/components/ui'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -46,6 +46,7 @@ export function FileUploads() {
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [deletingFileId, setDeletingFileId] = useState<string | null>(null)
|
||||
const [uploadError, setUploadError] = useState<string | null>(null)
|
||||
const [uploadProgress, setUploadProgress] = useState({ completed: 0, total: 0 })
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const { permissions: workspacePermissions, loading: permissionsLoading } =
|
||||
@@ -94,9 +95,12 @@ export function FileUploads() {
|
||||
if (!ok) unsupported.push(f.name)
|
||||
return ok
|
||||
})
|
||||
|
||||
setUploadProgress({ completed: 0, total: allowedFiles.length })
|
||||
let lastError: string | null = null
|
||||
|
||||
for (const selectedFile of allowedFiles) {
|
||||
for (let i = 0; i < allowedFiles.length; i++) {
|
||||
const selectedFile = allowedFiles[i]
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', selectedFile)
|
||||
@@ -109,6 +113,8 @@ export function FileUploads() {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
lastError = data.error || 'Upload failed'
|
||||
} else {
|
||||
setUploadProgress({ completed: i + 1, total: allowedFiles.length })
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error uploading file:', err)
|
||||
@@ -127,6 +133,7 @@ export function FileUploads() {
|
||||
setTimeout(() => setUploadError(null), 5000)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
setUploadProgress({ completed: 0, total: 0 })
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = ''
|
||||
}
|
||||
@@ -225,6 +232,7 @@ export function FileUploads() {
|
||||
onChange={handleFileChange}
|
||||
disabled={uploading}
|
||||
accept={ACCEPT_ATTR}
|
||||
multiple
|
||||
/>
|
||||
<Button
|
||||
onClick={handleUploadClick}
|
||||
@@ -232,8 +240,11 @@ export function FileUploads() {
|
||||
variant='ghost'
|
||||
className='h-9 rounded-[8px] border bg-background px-3 shadow-xs hover:bg-muted focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
>
|
||||
<Upload className='mr-2 h-4 w-4 stroke-[2px]' />
|
||||
{uploading ? 'Uploading...' : 'Upload File'}
|
||||
{uploading && uploadProgress.total > 0
|
||||
? `Uploading ${uploadProgress.completed}/${uploadProgress.total}...`
|
||||
: uploading
|
||||
? 'Uploading...'
|
||||
: 'Upload File'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ export function SettingsNavigation({
|
||||
|
||||
return (
|
||||
<div className='flex h-full flex-col'>
|
||||
<div className='flex-1 px-2 py-4'>
|
||||
<div className='flex-1 overflow-y-auto px-2 py-4'>
|
||||
{navigationItems.map((item) => (
|
||||
<div key={item.id} className='mb-1'>
|
||||
<button
|
||||
|
||||
+6
-1
@@ -235,7 +235,12 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
|
||||
? 'Your subscription is set to cancel at the end of the billing period. You can reactivate it or manage other settings.'
|
||||
: `You'll be redirected to Stripe to manage your subscription. You'll keep access until ${formatDate(
|
||||
periodEndDate
|
||||
)}, then downgrade to free plan.`}
|
||||
)}, then downgrade to free plan.`}{' '}
|
||||
{!isCancelAtPeriodEnd && (
|
||||
<span className='text-red-500 dark:text-red-500'>
|
||||
This action cannot be undone.
|
||||
</span>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
|
||||
+3
-2
@@ -37,7 +37,8 @@ export function RemoveMemberDialog({
|
||||
<DialogDescription>
|
||||
{isSelfRemoval
|
||||
? 'Are you sure you want to leave this organization? You will lose access to all team resources.'
|
||||
: `Are you sure you want to remove ${memberName} from the team?`}
|
||||
: `Are you sure you want to remove ${memberName} from the team?`}{' '}
|
||||
<span className='text-red-500 dark:text-red-500'>This action cannot be undone.</span>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -68,7 +69,7 @@ export function RemoveMemberDialog({
|
||||
<Button
|
||||
variant='destructive'
|
||||
onClick={() => onConfirmRemove(shouldReduceSeats)}
|
||||
className='h-9 rounded-[8px]'
|
||||
className='h-9 rounded-[8px] bg-red-500 text-white transition-all duration-200 hover:bg-red-600 dark:bg-red-500 dark:hover:bg-red-600'
|
||||
>
|
||||
{isSelfRemoval ? 'Leave Organization' : 'Remove'}
|
||||
</Button>
|
||||
|
||||
+1
-1
@@ -279,7 +279,7 @@ export function TeamMembers({
|
||||
}
|
||||
onRemoveMember(currentUserMember)
|
||||
}}
|
||||
className='w-full text-red-600 hover:bg-red-50 hover:text-red-700 dark:hover:bg-red-950/20'
|
||||
className='w-full hover:bg-muted'
|
||||
>
|
||||
<LogOut className='mr-2 h-4 w-4' />
|
||||
Leave Organization
|
||||
|
||||
Reference in New Issue
Block a user