Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c5a5492
feat: upgrade issue report form with file attachments and enriched me…
beran-t Feb 14, 2026
e88e099
refactor: move Report Issue button from sidebar footer to top-right h…
beran-t Feb 14, 2026
0f2a657
refactor: rename button to Contact Support, swap position with sandbo…
beran-t Feb 14, 2026
7204c81
refactor: rebrand from Report Issue to Contact Support
beran-t Feb 14, 2026
a2d3147
refactor: remove sandbox ID field and widen support dialog
beran-t Feb 15, 2026
57a079a
refactor: use Plain API attachments instead of Supabase Storage
beran-t Feb 25, 2026
8e848e1
feat: format Plain thread body with header block and account owner email
beran-t Feb 25, 2026
12c8846
feat: add Orbit link to support thread header
beran-t Feb 25, 2026
84cc5ce
refactor: move metadata to internal note and map tier names
beran-t Feb 25, 2026
b5cb5e7
fix: use sendCustomerChat for attachments instead of deprecated creat…
beran-t Feb 25, 2026
90ea7cb
fix: use description field for thread preview and ActionError for err…
beran-t Feb 25, 2026
3b785e1
fix: set thread channel to CHAT for sendCustomerChat compatibility
beran-t Feb 25, 2026
2be336d
fix: revert to createThread with components for attachment support
beran-t Feb 25, 2026
04697e5
fix: restore metadata header in thread body and add attachment upload…
beran-t Feb 25, 2026
e870e74
security: verify team membership and fetch metadata server-side
beran-t Feb 25, 2026
3fb3d02
fix: align file size limit to 10MB and move button back to sidebar
beran-t Feb 25, 2026
35e035a
style: remove border radius from file drop zone
beran-t Feb 25, 2026
4151df6
fix: pass plain object instead of FormData and reorder sidebar buttons
beran-t Feb 25, 2026
5d3049b
feat: auto-open Contact Support dialog via ?support=true URL param
beran-t Feb 26, 2026
849ed76
feat: add support tab to dashboard route for universal support link
beran-t Feb 26, 2026
83ba3ce
fix: handle support param separately to avoid double query string
beran-t Feb 26, 2026
30020c0
chore: trigger rebuild for support param forwarding
beran-t Feb 26, 2026
72a5921
Merge remote-tracking branch 'origin/main' into upgrade-issue-form
beran-t Mar 3, 2026
ff8f17d
style: fix biome formatting and lint errors
beran-t Mar 3, 2026
eb0a72f
refactor: migrate support from server action to tRPC mutation
beran-t Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/app/dashboard/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,12 @@ export async function GET(request: NextRequest) {
? urlGenerator(team.slug || team.id)
: PROTECTED_URLS.SANDBOXES(team.slug || team.id)

return NextResponse.redirect(new URL(redirectPath, request.url))
const redirectUrl = new URL(redirectPath, request.url)

// Forward ?support=true query param to auto-open the Contact Support dialog on the target page
if (searchParams.get('support') === 'true') {
redirectUrl.searchParams.set('support', 'true')
}

return NextResponse.redirect(redirectUrl)
}
129 changes: 129 additions & 0 deletions src/features/dashboard/navbar/file-drop-zone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use client'

import { Upload } from 'lucide-react'
import { useCallback, useRef, useState } from 'react'
import { cn } from '@/lib/utils'

interface FileDropZoneProps {
onFilesSelected: (files: File[]) => void
maxFiles: number
currentFileCount: number
isUploading: boolean
disabled?: boolean
accept?: string
}

export default function FileDropZone({
onFilesSelected,
maxFiles,
currentFileCount,
isUploading,
disabled = false,
accept,
}: FileDropZoneProps) {
const [isDragOver, setIsDragOver] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const remaining = maxFiles - currentFileCount

const handleFiles = useCallback(
(fileList: FileList | null) => {
if (!fileList || fileList.length === 0) return
const files = Array.from(fileList).slice(0, remaining)
if (files.length > 0) {
onFilesSelected(files)
}
},
[onFilesSelected, remaining]
)

const handleDragOver = useCallback(
(e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
if (!disabled && remaining > 0) {
setIsDragOver(true)
}
},
[disabled, remaining]
)

const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
setIsDragOver(false)
}, [])

const handleDrop = useCallback(
(e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
setIsDragOver(false)
if (!disabled && remaining > 0) {
handleFiles(e.dataTransfer.files)
}
},
[disabled, remaining, handleFiles]
)

const handleClick = useCallback(() => {
if (!disabled && remaining > 0) {
fileInputRef.current?.click()
}
}, [disabled, remaining])

const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
handleFiles(e.target.files)
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
},
[handleFiles]
)

const isDisabled = disabled || remaining <= 0

return (
<button
type="button"
disabled={isDisabled}
onClick={handleClick}
onDragOver={handleDragOver}
onDragEnter={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={cn(
'flex w-full flex-col items-center justify-center gap-1.5 border border-dashed p-4 transition-colors cursor-pointer',
isDragOver && 'border-fg-accent bg-bg-accent/10',
!isDragOver &&
!isDisabled &&
'border-stroke hover:border-fg-tertiary hover:bg-bg-hover',
isDisabled && 'cursor-not-allowed opacity-50 border-stroke'
)}
>
<Upload className="size-5 text-fg-tertiary" />
<p className="text-sm text-fg-secondary">
{isUploading
? 'Uploading...'
: remaining > 0
? 'Drag files here or click to upload'
: 'Maximum files reached'}
</p>
{remaining > 0 && !isUploading && (
<p className="text-xs text-fg-tertiary">
Up to {remaining} more file{remaining !== 1 ? 's' : ''} (max 10MB
each)
</p>
)}
<input
ref={fileInputRef}
type="file"
multiple
accept={accept}
onChange={handleInputChange}
className="hidden"
disabled={isDisabled}
/>
</button>
)
}
Loading
Loading