Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
61abb75
feat(settings): redesign the general tab with a save bar, data region…
Makisuo Oct 17, 2026
8700af9
feat(settings): redesign the members tab with inline role confirm and…
Makisuo Oct 17, 2026
98b8709
fix(settings): show skeleton rows while members load
Makisuo Oct 17, 2026
97fe288
fix(settings): disable invitation resend while it runs and ignore rep…
Makisuo Oct 17, 2026
20250e0
fix(settings): skip the unsaved changes prompt after deleting or leav…
Makisuo Oct 17, 2026
843f6e0
Merge branch 'feat/settings-redesign-foundation' into feat/settings-g…
Makisuo Oct 17, 2026
59246e7
fix(settings): keep the saved org name on screen until Clerk catches up
Makisuo Oct 17, 2026
1dab14e
Merge branch 'feat/settings-redesign-foundation' into feat/settings-g…
Makisuo Oct 17, 2026
7c15232
Merge branch 'feat/settings-redesign-foundation' into feat/settings-g…
Makisuo Oct 17, 2026
f2b08a5
fix(settings): release the org name draft when the save trimmed it
Makisuo Oct 17, 2026
5ea3f60
Merge branch 'feat/settings-redesign-foundation' into feat/settings-g…
Makisuo Oct 17, 2026
704f6df
chore: stop ignoring the settings save bar and admin notice in knip
Makisuo Oct 17, 2026
1dde32d
Merge branch 'feat/settings-redesign-foundation' into feat/settings-g…
Makisuo Oct 17, 2026
3adcd4e
fix(settings): commit the org name with the draft the save submitted
Makisuo Oct 17, 2026
fb99fd7
Merge branch 'feat/settings-redesign-foundation' into feat/settings-g…
Makisuo Oct 17, 2026
258b767
fix(settings): track the org name save as in flight until it settles
Makisuo Oct 17, 2026
1408fd2
fix(settings): list the history and settings an org delete keeps
Makisuo Oct 17, 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
74 changes: 48 additions & 26 deletions apps/web/src/components/common/image-dropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ interface ImageDropzoneProps {
/** Label of the upload button, e.g. "Change picture". */
changeLabel: string
hint?: ReactNode
/** Row layout: the title and hint sit beside the target and the actions move to the right. */
title?: ReactNode
/** Shows the image only: no drop target, picker or actions. */
readOnly?: boolean
}

/** Square click-or-drop image target with upload/remove actions, shared by avatar and logo settings. */
Expand All @@ -40,10 +44,12 @@ export function ImageDropzone({
targetLabel,
changeLabel,
hint = "Drop an image or click to upload. PNG, JPG, WEBP or GIF, up to 10 MB.",
title,
readOnly = false,
}: ImageDropzoneProps) {
const [isDragging, setIsDragging] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const interactive = !disabled && !uploading
const interactive = !disabled && !uploading && !readOnly

function selectFile(file: File | undefined | null) {
if (!interactive || !file) return
Expand Down Expand Up @@ -79,13 +85,33 @@ export function ImageDropzone({
selectFile(e.dataTransfer.files?.[0])
}

const actions = (
<div className="flex shrink-0 items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={openFilePicker}
loading={uploading}
disabled={disabled}
>
<UploadIcon className="mr-1.5" />
{changeLabel}
</Button>
{onRemove && (
<Button variant="ghost" size="sm" onClick={onRemove} disabled={!interactive}>
Remove
</Button>
)}
</div>
)

return (
<div className="flex items-center gap-4">
<div
role="button"
role={readOnly ? undefined : "button"}
tabIndex={interactive ? 0 : -1}
aria-label={targetLabel}
aria-disabled={!interactive}
aria-label={readOnly ? undefined : targetLabel}
aria-disabled={readOnly ? undefined : !interactive}
onClick={openFilePicker}
onKeyDown={handleKeyDown}
onDragOver={(e) => {
Expand All @@ -96,9 +122,11 @@ export function ImageDropzone({
onDrop={handleDrop}
className={cn(
"relative flex size-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-dashed p-1 outline-none transition-colors",
interactive
? "cursor-pointer hover:border-primary focus-visible:ring-2 focus-visible:ring-ring"
: "cursor-not-allowed opacity-60",
readOnly
? null
: interactive
? "cursor-pointer hover:border-primary focus-visible:ring-2 focus-visible:ring-ring"
: "cursor-not-allowed opacity-60",
isDragging ? "border-primary ring-2 ring-primary" : "border-border",
)}
>
Expand All @@ -109,26 +137,20 @@ export function ImageDropzone({
</div>
)}
</div>
<div className="space-y-1.5">
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={openFilePicker}
loading={uploading}
disabled={disabled}
>
<UploadIcon className="mr-1.5" />
{changeLabel}
</Button>
{onRemove && (
<Button variant="ghost" size="sm" onClick={onRemove} disabled={!interactive}>
Remove
</Button>
)}
{title !== undefined ? (
<>
<div className="min-w-0 flex-1 space-y-1">
<div className="text-sm font-medium text-foreground">{title}</div>
{hint && <p className="text-xs text-muted-foreground">{hint}</p>}
</div>
{readOnly ? null : actions}
</>
) : (
<div className="space-y-1.5">
{readOnly ? null : actions}
{hint && <p className="text-xs text-muted-foreground">{hint}</p>}
</div>
{hint && <p className="text-xs text-muted-foreground">{hint}</p>}
</div>
)}
<input
ref={fileInputRef}
type="file"
Expand Down
Loading
Loading