Skip to content

Commit 770a058

Browse files
committed
redesign the rest
1 parent 4f390de commit 770a058

File tree

11 files changed

+922
-725
lines changed

11 files changed

+922
-725
lines changed
Lines changed: 97 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"use client";
22

3-
import { FloppyDiskIcon } from "@phosphor-icons/react";
3+
import { BookOpenIcon, BuildingsIcon, FloppyDiskIcon } from "@phosphor-icons/react";
44
import { useState } from "react";
55
import { toast } from "sonner";
66
import { Button } from "@/components/ui/button";
7-
87
import { Input } from "@/components/ui/input";
98
import { Label } from "@/components/ui/label";
109
import { type Organization, useOrganizations } from "@/hooks/use-organizations";
@@ -32,130 +31,123 @@ export function GeneralSettings({ organization }: GeneralSettingsProps) {
3231
setSlug(cleanSlug(value));
3332
};
3433

34+
const hasChanges = name !== organization.name || slug !== organization.slug;
35+
3536
const handleSave = async () => {
36-
if (!(name.trim() && slug.trim())) {
37-
toast.error("Name and slug are required");
37+
if (!name.trim()) {
38+
toast.error("Name is required");
39+
return;
40+
}
41+
if (!slug.trim()) {
42+
toast.error("Slug is required");
3843
return;
3944
}
4045

4146
setIsSaving(true);
4247
try {
4348
await updateOrganizationAsync({
4449
organizationId: organization.id,
45-
data: {
46-
name: name.trim(),
47-
slug: slug.trim(),
48-
},
50+
data: { name: name.trim(), slug: slug.trim() },
4951
});
50-
51-
toast.success("Organization updated successfully");
52-
53-
// If slug changed, we might need to update the URL context
54-
// but since we're using active organization, this should be handled automatically
55-
} catch (_error) {
56-
toast.error("Failed to update organization");
52+
toast.success("Settings updated");
53+
} catch {
54+
toast.error("Failed to update settings");
5755
} finally {
5856
setIsSaving(false);
5957
}
6058
};
6159

62-
const hasChanges = name !== organization.name || slug !== organization.slug;
63-
6460
return (
65-
<div className="h-full p-4 sm:p-6">
66-
<div className="space-y-6 sm:space-y-8">
67-
{/* Content Sections */}
68-
<div className="space-y-6 sm:space-y-8">
69-
{/* Logo Upload Section */}
70-
<div className="rounded border bg-card p-4 sm:p-6">
71-
<div className="space-y-3 sm:space-y-4">
72-
<div>
73-
<h3 className="font-semibold text-base sm:text-lg">
74-
Organization Logo
75-
</h3>
76-
<p className="text-muted-foreground text-xs sm:text-sm">
77-
Upload a logo to represent your organization
78-
</p>
79-
</div>
80-
<OrganizationLogoUploader organization={organization} />
61+
<div className="h-full lg:grid lg:grid-cols-[1fr_18rem]">
62+
{/* Main Content */}
63+
<div className="flex flex-col border-b lg:border-b-0 lg:border-r">
64+
<div className="flex-1 space-y-6 p-5">
65+
{/* Logo Section */}
66+
<OrganizationLogoUploader organization={organization} />
67+
68+
{/* Name & Slug */}
69+
<div className="grid gap-4 sm:grid-cols-2">
70+
<div className="space-y-2">
71+
<Label htmlFor="name">Name</Label>
72+
<Input
73+
id="name"
74+
onChange={(e) => setName(e.target.value)}
75+
placeholder="Organization name…"
76+
value={name}
77+
/>
78+
</div>
79+
<div className="space-y-2">
80+
<Label htmlFor="slug">Slug</Label>
81+
<Input
82+
id="slug"
83+
onChange={(e) => handleSlugChange(e.target.value)}
84+
placeholder="organization-slug…"
85+
value={slug}
86+
/>
87+
<p className="text-muted-foreground text-xs">
88+
Used in URLs: /{slug}
89+
</p>
8190
</div>
8291
</div>
92+
</div>
8393

84-
{/* Name and Slug Section */}
85-
<div className="rounded border bg-card p-4 sm:p-6">
86-
<div className="space-y-4 sm:space-y-6">
87-
<div>
88-
<h3 className="font-semibold text-base sm:text-lg">
89-
Basic Information
90-
</h3>
91-
<p className="text-muted-foreground text-xs sm:text-sm">
92-
Configure your organization's name and URL identifier
93-
</p>
94-
</div>
95-
96-
<div className="grid gap-4 sm:grid-cols-2 sm:gap-6">
97-
<div className="space-y-2 sm:space-y-3">
98-
<Label
99-
className="font-medium text-xs sm:text-sm"
100-
htmlFor="name"
101-
>
102-
Organization Name
103-
</Label>
104-
<Input
105-
id="name"
106-
onChange={(e) => setName(e.target.value)}
107-
placeholder="Enter organization name"
108-
value={name}
109-
/>
110-
</div>
111-
<div className="space-y-2 sm:space-y-3">
112-
<Label
113-
className="font-medium text-xs sm:text-sm"
114-
htmlFor="slug"
115-
>
116-
Organization Slug
117-
</Label>
118-
<Input
119-
id="slug"
120-
onChange={(e) => handleSlugChange(e.target.value)}
121-
placeholder="organization-slug"
122-
value={slug}
123-
/>
124-
<p className="text-muted-foreground text-xs">
125-
This will be used in your organization URL
126-
</p>
127-
</div>
128-
</div>
129-
130-
{/* Save Button */}
131-
{hasChanges && (
132-
<div className="flex justify-end border-t pt-3 sm:pt-4">
133-
<Button
134-
className="px-4 text-xs sm:px-6 sm:text-sm"
135-
disabled={isSaving}
136-
onClick={handleSave}
137-
>
138-
{isSaving ? (
139-
<>
140-
<div className="mr-2 h-3 w-3 animate-spin rounded-full border border-primary-foreground/30 border-t-primary-foreground sm:h-4 sm:w-4" />
141-
Saving...
142-
</>
143-
) : (
144-
<>
145-
<FloppyDiskIcon
146-
className="mr-2 h-3 w-3 sm:h-4 sm:w-4"
147-
size={12}
148-
/>
149-
Save Changes
150-
</>
151-
)}
152-
</Button>
153-
</div>
94+
{/* Save Footer */}
95+
{hasChanges && (
96+
<div className="flex items-center justify-between border-t bg-muted/30 px-5 py-3">
97+
<p className="text-muted-foreground text-sm">You have unsaved changes</p>
98+
<Button disabled={isSaving} onClick={handleSave} size="sm">
99+
{isSaving ? (
100+
<>
101+
<div className="mr-2 h-3 w-3 animate-spin rounded-full border-2 border-primary-foreground/30 border-t-primary-foreground" />
102+
Saving…
103+
</>
104+
) : (
105+
<>
106+
<FloppyDiskIcon className="mr-2" size={14} />
107+
Save Changes
108+
</>
154109
)}
155-
</div>
110+
</Button>
156111
</div>
157-
</div>
112+
)}
158113
</div>
114+
115+
{/* Sidebar */}
116+
<aside className="flex flex-col gap-4 bg-muted/30 p-5">
117+
{/* Org Info Card */}
118+
<div className="flex items-center gap-3 rounded border bg-background p-4">
119+
<div className="flex h-10 w-10 items-center justify-center rounded bg-primary/10">
120+
<BuildingsIcon className="text-primary" size={20} weight="duotone" />
121+
</div>
122+
<div className="min-w-0">
123+
<p className="truncate font-semibold">{organization.name}</p>
124+
<p className="truncate text-muted-foreground text-sm">
125+
/{organization.slug}
126+
</p>
127+
</div>
128+
</div>
129+
130+
{/* Docs Link */}
131+
<Button asChild className="w-full justify-start" variant="outline">
132+
<a
133+
href="https://www.databuddy.cc/docs/getting-started"
134+
rel="noopener noreferrer"
135+
target="_blank"
136+
>
137+
<BookOpenIcon className="mr-2" size={16} />
138+
Documentation
139+
</a>
140+
</Button>
141+
142+
{/* Tip */}
143+
<div className="mt-auto rounded border border-dashed bg-background/50 p-4">
144+
<p className="mb-2 font-medium text-sm">Quick tip</p>
145+
<p className="text-muted-foreground text-xs leading-relaxed">
146+
The slug is used in URLs and API requests. Keep it short and
147+
memorable.
148+
</p>
149+
</div>
150+
</aside>
159151
</div>
160152
);
161153
}

apps/dashboard/app/(main)/organizations/components/organization-provider.tsx

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ export function OrganizationProvider({
3636
title: "Organizations",
3737
description: "Manage your organizations and team collaboration",
3838
icon: BuildingsIcon,
39-
actionButton: {
40-
text: "New Organization",
41-
icon: PlusIcon,
42-
action: () => setShowCreateDialog(true),
43-
},
4439
};
4540
}
4641
if (pathname === "/organizations/members") {
@@ -49,11 +44,6 @@ export function OrganizationProvider({
4944
description: "Manage team members and their roles",
5045
icon: UsersIcon,
5146
requiresOrg: true,
52-
actionButton: {
53-
text: "Invite Member",
54-
icon: UserPlusIcon,
55-
action: () => setShowInviteMemberDialog(true),
56-
},
5747
};
5848
}
5949
if (pathname === "/organizations/invitations") {
@@ -62,11 +52,6 @@ export function OrganizationProvider({
6252
description: "View and manage pending team invitations",
6353
icon: EnvelopeIcon,
6454
requiresOrg: true,
65-
actionButton: {
66-
text: "Send Invitation",
67-
icon: UserPlusIcon,
68-
action: () => setShowInviteMemberDialog(true),
69-
},
7055
};
7156
}
7257
if (pathname === "/organizations/settings") {
@@ -105,11 +90,6 @@ export function OrganizationProvider({
10590
title: "Organizations",
10691
description: "Manage your organizations and team collaboration",
10792
icon: BuildingsIcon,
108-
actionButton: {
109-
text: "New Organization",
110-
icon: PlusIcon,
111-
action: () => setShowCreateDialog(true),
112-
},
11393
};
11494
};
11595

@@ -118,7 +98,6 @@ export function OrganizationProvider({
11898
description,
11999
icon: Icon,
120100
requiresOrg,
121-
actionButton,
122101
} = getPageInfo();
123102

124103
if (isLoading) {
@@ -172,19 +151,6 @@ export function OrganizationProvider({
172151
</div>
173152
</div>
174153
</div>
175-
{actionButton && (
176-
<Button
177-
className="w-full rounded text-xs sm:w-auto sm:text-sm"
178-
onClick={actionButton.action}
179-
size="sm"
180-
>
181-
<actionButton.icon
182-
className="mr-2 h-3 w-3 sm:h-4 sm:w-4"
183-
size={12}
184-
/>
185-
{actionButton.text}
186-
</Button>
187-
)}
188154
</div>
189155
</div>
190156

@@ -243,19 +209,6 @@ export function OrganizationProvider({
243209
</div>
244210
</div>
245211
</div>
246-
{actionButton && (
247-
<Button
248-
className="w-full rounded text-xs sm:w-auto sm:text-sm"
249-
onClick={actionButton.action}
250-
size="sm"
251-
>
252-
<actionButton.icon
253-
className="mr-2 h-3 w-3 sm:h-4 sm:w-4"
254-
size={12}
255-
/>
256-
{actionButton.text}
257-
</Button>
258-
)}
259212
</div>
260213
</div>
261214

0 commit comments

Comments
 (0)