Skip to content

Commit 810e941

Browse files
committed
im tired of S3 boss
1 parent 53dc53e commit 810e941

File tree

11 files changed

+351
-1366
lines changed

11 files changed

+351
-1366
lines changed

apps/api/src/lib/clickhouse-backup.ts

Lines changed: 0 additions & 438 deletions
This file was deleted.

apps/dashboard/app/(main)/organizations/components/general-settings.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button";
88
import { Input } from "@/components/ui/input";
99
import { Label } from "@/components/ui/label";
1010
import { type Organization, useOrganizations } from "@/hooks/use-organizations";
11-
import { OrganizationLogoUploader } from "./organization-logo-uploader";
11+
import { OrganizationAvatarEditor } from "./organization-avatar-editor";
1212

1313
export function GeneralSettings({
1414
organization,
@@ -71,15 +71,15 @@ export function GeneralSettings({
7171
{/* Main Content */}
7272
<div className="flex flex-col border-b lg:border-b-0">
7373
<div className="flex-1 overflow-y-auto">
74-
{/* Logo Section */}
74+
{/* Avatar Section */}
7575
<section className="border-b px-5 py-6">
7676
<div className="mb-4">
77-
<h3 className="font-semibold text-sm">Organization Logo</h3>
77+
<h3 className="font-semibold text-sm">Organization Avatar</h3>
7878
<p className="text-muted-foreground text-xs">
79-
Upload a logo to represent your organization
79+
Customize your organization's avatar
8080
</p>
8181
</div>
82-
<OrganizationLogoUploader organization={organization} />
82+
<OrganizationAvatarEditor organization={organization} />
8383
</section>
8484

8585
{/* Organization Details */}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"use client";
2+
3+
import { ArrowsClockwiseIcon, PencilSimpleIcon } from "@phosphor-icons/react";
4+
import { nanoid } from "nanoid";
5+
import { useState } from "react";
6+
import { toast } from "sonner";
7+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
8+
import { Button } from "@/components/ui/button";
9+
import {
10+
Dialog,
11+
DialogContent,
12+
DialogFooter,
13+
DialogHeader,
14+
DialogTitle,
15+
} from "@/components/ui/dialog";
16+
import { Input } from "@/components/ui/input";
17+
import { Label } from "@/components/ui/label";
18+
import { type Organization, useOrganizations } from "@/hooks/use-organizations";
19+
import { getOrganizationInitials } from "@/lib/utils";
20+
21+
interface OrganizationAvatarEditorProps {
22+
organization: Organization;
23+
}
24+
25+
function getDiceBearUrl(seed: string): string {
26+
return `https://api.dicebear.com/9.x/glass/svg?seed=${encodeURIComponent(seed)}`;
27+
}
28+
29+
export function OrganizationAvatarEditor({
30+
organization,
31+
}: OrganizationAvatarEditorProps) {
32+
const { updateAvatarSeed, isUpdatingAvatarSeed } = useOrganizations();
33+
const [isModalOpen, setIsModalOpen] = useState(false);
34+
const [seed, setSeed] = useState(organization.logo || organization.id);
35+
36+
const currentSeed = organization.logo || organization.id;
37+
const avatarUrl = getDiceBearUrl(currentSeed);
38+
const previewUrl = getDiceBearUrl(seed);
39+
40+
const handleRandomize = () => {
41+
setSeed(nanoid(10));
42+
};
43+
44+
const handleSave = () => {
45+
updateAvatarSeed(
46+
{ organizationId: organization.id, seed },
47+
{
48+
onSuccess: () => {
49+
setIsModalOpen(false);
50+
},
51+
onError: () => {
52+
toast.error("Failed to update avatar");
53+
},
54+
}
55+
);
56+
};
57+
58+
const handleOpenChange = (open: boolean) => {
59+
if (open) {
60+
setSeed(currentSeed);
61+
}
62+
setIsModalOpen(open);
63+
};
64+
65+
return (
66+
<div className="space-y-3">
67+
<div className="flex items-center gap-3">
68+
<div className="group relative">
69+
<Avatar className="size-10">
70+
<AvatarImage alt={organization.name} src={avatarUrl} />
71+
<AvatarFallback className="bg-accent font-medium text-sm">
72+
{getOrganizationInitials(organization.name)}
73+
</AvatarFallback>
74+
</Avatar>
75+
<button
76+
aria-label="Edit organization avatar"
77+
className="absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-foreground opacity-0 transition-opacity group-hover:opacity-100"
78+
onClick={() => setIsModalOpen(true)}
79+
type="button"
80+
>
81+
<PencilSimpleIcon className="text-accent" size={16} />
82+
</button>
83+
</div>
84+
<div className="space-y-1">
85+
<p className="font-medium text-foreground text-sm">
86+
Organization avatar
87+
</p>
88+
<p className="text-muted-foreground text-xs">
89+
Click to customize your avatar.
90+
</p>
91+
</div>
92+
</div>
93+
94+
<Dialog onOpenChange={handleOpenChange} open={isModalOpen}>
95+
<DialogContent>
96+
<DialogHeader>
97+
<DialogTitle>Customize avatar</DialogTitle>
98+
</DialogHeader>
99+
<div className="flex flex-col items-center gap-4 py-4">
100+
<Avatar className="size-24">
101+
<AvatarImage alt="Avatar preview" src={previewUrl} />
102+
<AvatarFallback className="bg-accent font-medium text-lg">
103+
{getOrganizationInitials(organization.name)}
104+
</AvatarFallback>
105+
</Avatar>
106+
<div className="w-full space-y-2">
107+
<Label htmlFor="seed">Avatar seed</Label>
108+
<div className="flex gap-2">
109+
<Input
110+
id="seed"
111+
onChange={(e) => setSeed(e.target.value)}
112+
placeholder="Enter a seed…"
113+
value={seed}
114+
/>
115+
<Button
116+
onClick={handleRandomize}
117+
size="icon"
118+
type="button"
119+
variant="outline"
120+
>
121+
<ArrowsClockwiseIcon size={16} />
122+
</Button>
123+
</div>
124+
<p className="text-muted-foreground text-xs">
125+
Change the seed to generate a different avatar.
126+
</p>
127+
</div>
128+
</div>
129+
<DialogFooter>
130+
<Button onClick={() => setIsModalOpen(false)} variant="outline">
131+
Cancel
132+
</Button>
133+
<Button disabled={isUpdatingAvatarSeed} onClick={handleSave}>
134+
{isUpdatingAvatarSeed ? (
135+
<>
136+
<div className="mr-2 size-3 animate-spin rounded-full border border-primary-foreground/30 border-t-primary-foreground" />
137+
Saving...
138+
</>
139+
) : (
140+
"Save"
141+
)}
142+
</Button>
143+
</DialogFooter>
144+
</DialogContent>
145+
</Dialog>
146+
</div>
147+
);
148+
}

0 commit comments

Comments
 (0)