Skip to content

Commit 79b318f

Browse files
authored
fix(templates): fix templates details page (#1942)
* Fix template details * Fix deps
1 parent cb39e69 commit 79b318f

File tree

6 files changed

+188
-665
lines changed

6 files changed

+188
-665
lines changed

apps/sim/app/templates/[id]/template.tsx

Lines changed: 107 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import {
5151
} from 'lucide-react'
5252
import { useParams, useRouter, useSearchParams } from 'next/navigation'
5353
import ReactMarkdown from 'react-markdown'
54-
import { Tooltip } from '@/components/emcn'
5554
import { Badge } from '@/components/ui/badge'
5655
import { Button } from '@/components/ui/button'
5756
import {
@@ -60,6 +59,7 @@ import {
6059
DropdownMenuItem,
6160
DropdownMenuTrigger,
6261
} from '@/components/ui/dropdown-menu'
62+
import { useSession } from '@/lib/auth-client'
6363
import { createLogger } from '@/lib/logs/console/logger'
6464
import { cn } from '@/lib/utils'
6565
import type { CredentialRequirement } from '@/lib/workflows/credential-extractor'
@@ -110,14 +110,19 @@ const iconMap = {
110110
Award,
111111
}
112112

113-
export default function TemplateDetails() {
113+
interface TemplateDetailsProps {
114+
isWorkspaceContext?: boolean
115+
}
116+
117+
export default function TemplateDetails({ isWorkspaceContext = false }: TemplateDetailsProps) {
114118
const router = useRouter()
115119
const searchParams = useSearchParams()
116120
const params = useParams()
117121
const templateId = params?.id as string
122+
const workspaceId = isWorkspaceContext ? (params?.workspaceId as string) : null
123+
const { data: session } = useSession()
118124

119125
const [template, setTemplate] = useState<Template | null>(null)
120-
const [currentUserId, setCurrentUserId] = useState<string | null>(null)
121126
const [currentUserOrgs, setCurrentUserOrgs] = useState<string[]>([])
122127
const [currentUserOrgRoles, setCurrentUserOrgRoles] = useState<
123128
Array<{ organizationId: string; role: string }>
@@ -139,6 +144,8 @@ export default function TemplateDetails() {
139144
const [showWorkspaceSelectorForEdit, setShowWorkspaceSelectorForEdit] = useState(false)
140145
const [showWorkspaceSelectorForUse, setShowWorkspaceSelectorForUse] = useState(false)
141146

147+
const currentUserId = session?.user?.id || null
148+
142149
// Fetch template data on client side
143150
useEffect(() => {
144151
if (!templateId) {
@@ -156,28 +163,15 @@ export default function TemplateDetails() {
156163
setStarCount(data.data.stars || 0)
157164
}
158165
} catch (error) {
159-
console.error('Error fetching template:', error)
166+
logger.error('Error fetching template:', error)
160167
} finally {
161168
setLoading(false)
162169
}
163170
}
164171

165-
const fetchCurrentUser = async () => {
166-
try {
167-
const response = await fetch('/api/auth/get-session')
168-
if (response.ok) {
169-
const data = await response.json()
170-
setCurrentUserId(data?.user?.id || null)
171-
} else {
172-
setCurrentUserId(null)
173-
}
174-
} catch (error) {
175-
console.error('Error fetching session:', error)
176-
setCurrentUserId(null)
177-
}
178-
}
179-
180172
const fetchUserOrganizations = async () => {
173+
if (!currentUserId) return
174+
181175
try {
182176
const response = await fetch('/api/organizations')
183177
if (response.ok) {
@@ -192,27 +186,28 @@ export default function TemplateDetails() {
192186
setCurrentUserOrgRoles(orgRoles)
193187
}
194188
} catch (error) {
195-
console.error('Error fetching organizations:', error)
189+
logger.error('Error fetching organizations:', error)
196190
}
197191
}
198192

199193
const fetchSuperUserStatus = async () => {
194+
if (!currentUserId) return
195+
200196
try {
201197
const response = await fetch('/api/user/super-user')
202198
if (response.ok) {
203199
const data = await response.json()
204200
setIsSuperUser(data.isSuperUser || false)
205201
}
206202
} catch (error) {
207-
console.error('Error fetching super user status:', error)
203+
logger.error('Error fetching super user status:', error)
208204
}
209205
}
210206

211207
fetchTemplate()
212-
fetchCurrentUser()
213208
fetchSuperUserStatus()
214209
fetchUserOrganizations()
215-
}, [templateId])
210+
}, [templateId, currentUserId])
216211

217212
// Fetch workspaces when user is logged in
218213
useEffect(() => {
@@ -235,7 +230,7 @@ export default function TemplateDetails() {
235230
setWorkspaces(availableWorkspaces)
236231
}
237232
} catch (error) {
238-
console.error('Error fetching workspaces:', error)
233+
logger.error('Error fetching workspaces:', error)
239234
} finally {
240235
setIsLoadingWorkspaces(false)
241236
}
@@ -247,9 +242,14 @@ export default function TemplateDetails() {
247242
// Clean up URL when returning from login
248243
useEffect(() => {
249244
if (template && searchParams?.get('use') === 'true' && currentUserId) {
250-
router.replace(`/templates/${template.id}`)
245+
if (isWorkspaceContext && workspaceId) {
246+
handleWorkspaceSelectForUse(workspaceId)
247+
router.replace(`/workspace/${workspaceId}/templates/${template.id}`)
248+
} else {
249+
router.replace(`/templates/${template.id}`)
250+
}
251251
}
252-
}, [searchParams, currentUserId, template, router])
252+
}, [searchParams, currentUserId, template, isWorkspaceContext, workspaceId, router])
253253

254254
// Check if user can edit template
255255
const canEditTemplate = (() => {
@@ -355,7 +355,7 @@ export default function TemplateDetails() {
355355
/>
356356
)
357357
} catch (error) {
358-
console.error('Error rendering workflow preview:', error)
358+
logger.error('Error rendering workflow preview:', error)
359359
return (
360360
<div className='flex h-full items-center justify-center text-center'>
361361
<div className='text-muted-foreground'>
@@ -368,7 +368,11 @@ export default function TemplateDetails() {
368368
}
369369

370370
const handleBack = () => {
371-
router.push('/templates')
371+
if (isWorkspaceContext) {
372+
router.back()
373+
} else {
374+
router.push('/templates')
375+
}
372376
}
373377

374378
const handleStarToggle = async () => {
@@ -392,37 +396,59 @@ export default function TemplateDetails() {
392396

393397
const handleUseTemplate = () => {
394398
if (!currentUserId) {
395-
const callbackUrl = encodeURIComponent(`/templates/${template.id}`)
399+
const callbackUrl =
400+
isWorkspaceContext && workspaceId
401+
? encodeURIComponent(`/workspace/${workspaceId}/templates/${template.id}?use=true`)
402+
: encodeURIComponent(`/templates/${template.id}`)
396403
router.push(`/login?callbackUrl=${callbackUrl}`)
397404
return
398405
}
399-
setShowWorkspaceSelectorForUse(true)
406+
407+
// In workspace context, use current workspace directly
408+
if (isWorkspaceContext && workspaceId) {
409+
handleWorkspaceSelectForUse(workspaceId)
410+
} else {
411+
setShowWorkspaceSelectorForUse(true)
412+
}
400413
}
401414

402415
const handleEditTemplate = async () => {
403416
if (!currentUserId || !template) return
404417

405-
// Check if workflow exists and user has access
406-
if (template.workflowId) {
418+
// In workspace context with existing workflow, navigate directly
419+
if (isWorkspaceContext && workspaceId && template.workflowId) {
420+
setIsEditing(true)
421+
try {
422+
const checkResponse = await fetch(`/api/workflows/${template.workflowId}`)
423+
424+
if (checkResponse.ok) {
425+
router.push(`/workspace/${workspaceId}/w/${template.workflowId}`)
426+
return
427+
}
428+
} catch (error) {
429+
logger.error('Error checking workflow:', error)
430+
} finally {
431+
setIsEditing(false)
432+
}
433+
// If workflow doesn't exist, fall through to workspace selector
434+
}
435+
436+
// Check if workflow exists and user has access (global context)
437+
if (template.workflowId && !isWorkspaceContext) {
407438
setIsEditing(true)
408439
try {
409440
const checkResponse = await fetch(`/api/workflows/${template.workflowId}`)
410441

411442
if (checkResponse.status === 403) {
412-
// User doesn't have access to the workspace
413-
// This shouldn't happen if button is properly disabled, but handle it gracefully
414443
alert("You don't have access to the workspace containing this template")
415444
return
416445
}
417446

418447
if (checkResponse.ok) {
419-
// Workflow exists and user has access, get its workspace and navigate to it
420448
const result = await checkResponse.json()
421-
const workspaceId = result.data?.workspaceId
422-
if (workspaceId) {
423-
// Use window.location to ensure a full page load with fresh data
424-
// This avoids race conditions with client-side navigation
425-
window.location.href = `/workspace/${workspaceId}/w/${template.workflowId}`
449+
const templateWorkspaceId = result.data?.workspaceId
450+
if (templateWorkspaceId) {
451+
window.location.href = `/workspace/${templateWorkspaceId}/w/${template.workflowId}`
426452
return
427453
}
428454
}
@@ -433,8 +459,12 @@ export default function TemplateDetails() {
433459
}
434460
}
435461

436-
// Workflow doesn't exist or was deleted - show workspace selector
437-
setShowWorkspaceSelectorForEdit(true)
462+
// Workflow doesn't exist - show workspace selector or use current workspace
463+
if (isWorkspaceContext && workspaceId) {
464+
handleWorkspaceSelectForEdit(workspaceId)
465+
} else {
466+
setShowWorkspaceSelectorForEdit(true)
467+
}
438468
}
439469

440470
const handleWorkspaceSelectForUse = async (workspaceId: string) => {
@@ -505,7 +535,11 @@ export default function TemplateDetails() {
505535
// Update template status optimistically
506536
setTemplate({ ...template, status: 'approved' })
507537
// Redirect back to templates page after approval
508-
router.push('/templates')
538+
if (isWorkspaceContext && workspaceId) {
539+
router.push(`/workspace/${workspaceId}/templates`)
540+
} else {
541+
router.push('/templates')
542+
}
509543
}
510544
} catch (error) {
511545
logger.error('Error approving template:', error)
@@ -527,7 +561,11 @@ export default function TemplateDetails() {
527561
// Update template status optimistically
528562
setTemplate({ ...template, status: 'rejected' })
529563
// Redirect back to templates page after rejection
530-
router.push('/templates')
564+
if (isWorkspaceContext && workspaceId) {
565+
router.push(`/workspace/${workspaceId}/templates`)
566+
} else {
567+
router.push('/templates')
568+
}
531569
}
532570
} catch (error) {
533571
logger.error('Error rejecting template:', error)
@@ -537,7 +575,7 @@ export default function TemplateDetails() {
537575
}
538576

539577
return (
540-
<div className='flex min-h-screen flex-col'>
578+
<div className={cn('flex min-h-screen flex-col', isWorkspaceContext && 'pl-64')}>
541579
{/* Header */}
542580
<div className='border-b bg-background p-6'>
543581
<div className='mx-auto max-w-7xl'>
@@ -621,42 +659,25 @@ export default function TemplateDetails() {
621659
</Button>
622660
)}
623661

624-
{/* Edit button - for template owners (approved or pending) */}
662+
{/* Edit button - for template owners */}
625663
{canEditTemplate && currentUserId && (
626664
<>
627-
{template.workflowId && !showWorkspaceSelectorForEdit ? (
628-
<Tooltip.Root>
629-
<Tooltip.Trigger asChild>
630-
<span>
631-
<Button
632-
onClick={handleEditTemplate}
633-
disabled={isEditing || hasWorkspaceAccess === false}
634-
className={
635-
hasWorkspaceAccess === false
636-
? 'cursor-not-allowed opacity-50'
637-
: 'bg-blue-600 text-white hover:bg-blue-700'
638-
}
639-
>
640-
{isEditing ? 'Opening...' : 'Edit Template'}
641-
</Button>
642-
</span>
643-
</Tooltip.Trigger>
644-
{hasWorkspaceAccess === false && (
645-
<Tooltip.Content>
646-
<p>Don't have access to workspace to edit template</p>
647-
</Tooltip.Content>
648-
)}
649-
</Tooltip.Root>
665+
{(isWorkspaceContext || template.workflowId) && !showWorkspaceSelectorForEdit ? (
666+
<Button
667+
onClick={handleEditTemplate}
668+
disabled={isEditing || (!isWorkspaceContext && hasWorkspaceAccess === false)}
669+
className='bg-blue-600 text-white hover:bg-blue-700'
670+
>
671+
{isEditing ? 'Opening...' : 'Edit Template'}
672+
</Button>
650673
) : (
651674
<DropdownMenu
652675
open={showWorkspaceSelectorForEdit}
653676
onOpenChange={setShowWorkspaceSelectorForEdit}
654677
>
655678
<DropdownMenuTrigger asChild>
656679
<Button
657-
onClick={() =>
658-
!template.workflowId && setShowWorkspaceSelectorForEdit(true)
659-
}
680+
onClick={() => setShowWorkspaceSelectorForEdit(true)}
660681
disabled={isUsing || isLoadingWorkspaces}
661682
className='bg-blue-600 text-white hover:bg-blue-700'
662683
>
@@ -701,13 +722,26 @@ export default function TemplateDetails() {
701722
{!currentUserId ? (
702723
<Button
703724
onClick={() => {
704-
const callbackUrl = encodeURIComponent(`/templates/${template.id}`)
725+
const callbackUrl =
726+
isWorkspaceContext && workspaceId
727+
? encodeURIComponent(
728+
`/workspace/${workspaceId}/templates/${template.id}?use=true`
729+
)
730+
: encodeURIComponent(`/templates/${template.id}`)
705731
router.push(`/login?callbackUrl=${callbackUrl}`)
706732
}}
707733
className='bg-purple-600 text-white hover:bg-purple-700'
708734
>
709735
Sign in to use
710736
</Button>
737+
) : isWorkspaceContext ? (
738+
<Button
739+
onClick={handleUseTemplate}
740+
disabled={isUsing}
741+
className='bg-purple-600 text-white hover:bg-purple-700'
742+
>
743+
{isUsing ? 'Creating...' : 'Use this template'}
744+
</Button>
711745
) : (
712746
<DropdownMenu
713747
open={showWorkspaceSelectorForUse}

apps/sim/app/templates/layout.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
'use client'
2+
3+
import { Tooltip } from '@/components/emcn'
14
import { season } from '@/app/fonts/season/season'
25

36
export default function TemplatesLayout({ children }: { children: React.ReactNode }) {
4-
return <div className={`${season.variable} font-season`}>{children}</div>
7+
return (
8+
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
9+
<div className={`${season.variable} font-season`}>{children}</div>
10+
</Tooltip.Provider>
11+
)
512
}

0 commit comments

Comments
 (0)