Skip to content

Commit 7dce356

Browse files
committed
TODO: fix
1 parent 888a544 commit 7dce356

File tree

9 files changed

+358
-103
lines changed

9 files changed

+358
-103
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { BackendClient } from "@/lib/backend-client";
2+
import { cookies } from "next/headers";
3+
import { NextRequest, NextResponse } from "next/server";
4+
5+
export async function PATCH(
6+
req: NextRequest,
7+
{ params }: { params: Promise<{ slug: string; membershipId: string }> }
8+
) {
9+
const { slug, membershipId } = await params;
10+
11+
try {
12+
const cookieStore = await cookies();
13+
const token = cookieStore.get("auth_token")?.value;
14+
15+
if (!token) {
16+
return NextResponse.json(
17+
{ error: "Unauthorized" },
18+
{ status: 401 }
19+
);
20+
}
21+
22+
const body = await req.json();
23+
24+
const response = await BackendClient.patch<any>(
25+
`/organizations/${slug}/membership/${membershipId}/role`,
26+
body,
27+
{
28+
headers: {
29+
Authorization: `Bearer ${token}`,
30+
},
31+
}
32+
);
33+
34+
return NextResponse.json(response);
35+
} catch (error: any) {
36+
console.error("Update member role error:", error);
37+
38+
const status = error.status || 500;
39+
return NextResponse.json(
40+
error,
41+
{ status: status }
42+
);
43+
}
44+
}

src/app/api/sse-token/route.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { BackendClient } from "@/lib/backend-client";
3+
import { cookies } from "next/headers";
4+
5+
export async function GET(req: NextRequest) {
6+
try {
7+
const cookieStore = await cookies();
8+
const token = cookieStore.get("auth_token")?.value;
9+
10+
if (!token) {
11+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
12+
}
13+
14+
const response = await BackendClient.get("/sse-token/obtain", {
15+
headers: {
16+
Authorization: `Bearer ${token}`,
17+
},
18+
});
19+
20+
return NextResponse.json(response.data);
21+
} catch (error: any) {
22+
console.error("Error in sse-token API route:", error);
23+
return NextResponse.json(
24+
{ message: error.message || "Internal Server Error" },
25+
{ status: error.status || 500 }
26+
);
27+
}
28+
}
29+

src/app/organizations/[slug]/members/members-view.tsx

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,57 @@ import {
3636
DialogTrigger,
3737
} from "@/components/ui/dialog";
3838
import { ChevronLeft, ChevronRight, MoreHorizontal, UserCog } from "lucide-react";
39-
import { useRouter, usePathname, useSearchParams } from "next/navigation";
39+
import { useRouter, usePathname, useSearchParams, useParams } from "next/navigation";
4040
import { useState } from "react";
41+
import { toast } from "sonner";
4142

4243
interface MembersViewProps {
4344
data: PaginatedData<OrganizationMember>;
45+
currentUserRole?: string;
4446
}
4547

4648
function ChangeRoleDialog({ member }: { member: OrganizationMember }) {
4749
const [role, setRole] = useState(member.role);
4850
const [open, setOpen] = useState(false);
51+
const [loading, setLoading] = useState(false);
52+
const params = useParams();
53+
const slug = params.slug as string;
54+
const router = useRouter();
55+
56+
const handleSave = async () => {
57+
if (role === member.role) {
58+
setOpen(false);
59+
return;
60+
}
61+
62+
setLoading(true);
63+
try {
64+
const response = await fetch(`/api/organizations/${slug}/membership/${member.membershipId}/role`, {
65+
method: 'PATCH',
66+
headers: {
67+
'Content-Type': 'application/json'
68+
},
69+
body: JSON.stringify({ role })
70+
});
71+
72+
if (!response.ok) {
73+
const err = await response.json();
74+
throw new Error(err.message || "Failed to update role");
75+
}
4976

50-
const handleSave = () => {
51-
console.log(`Changing role for ${member.username} to ${role}`);
52-
setOpen(false);
77+
toast.success("Role updated successfully");
78+
setOpen(false);
79+
router.refresh();
80+
} catch (error: any) {
81+
console.error(error);
82+
toast.error(error.message || "Failed to update role");
83+
} finally {
84+
setLoading(false);
85+
}
5386
};
5487

88+
const availableRoles = ["ADMIN", "STAFF", "MEMBER"].filter(r => r !== member.role);
89+
5590
return (
5691
<Dialog open={open} onOpenChange={setOpen}>
5792
<DialogTrigger asChild>
@@ -68,29 +103,33 @@ function ChangeRoleDialog({ member }: { member: OrganizationMember }) {
68103
</DialogDescription>
69104
</DialogHeader>
70105
<div className="py-4">
71-
<Select value={role} onValueChange={setRole}>
106+
<Select value={role === member.role ? "" : role} onValueChange={setRole}>
72107
<SelectTrigger>
73-
<SelectValue placeholder="Select a role" />
108+
<SelectValue placeholder={member.role} />
74109
</SelectTrigger>
75110
<SelectContent>
76-
<SelectItem value="ADMIN">ADMIN</SelectItem>
77-
<SelectItem value="STAFF">STAFF</SelectItem>
78-
<SelectItem value="MEMBER">MEMBER</SelectItem>
111+
{availableRoles.map((r) => (
112+
<SelectItem key={r} value={r}>
113+
{r}
114+
</SelectItem>
115+
))}
79116
</SelectContent>
80117
</Select>
81118
</div>
82119
<DialogFooter>
83-
<Button variant="outline" onClick={() => setOpen(false)}>
120+
<Button variant="outline" onClick={() => setOpen(false)} disabled={loading}>
84121
Cancel
85122
</Button>
86-
<Button onClick={handleSave}>Save changes</Button>
123+
<Button onClick={handleSave} disabled={loading || role === member.role}>
124+
{loading ? "Saving..." : "Save changes"}
125+
</Button>
87126
</DialogFooter>
88127
</DialogContent>
89128
</Dialog>
90129
);
91130
}
92131

93-
export function MembersView({ data }: MembersViewProps) {
132+
export function MembersView({ data, currentUserRole }: MembersViewProps) {
94133
const router = useRouter();
95134
const pathname = usePathname();
96135
const searchParams = useSearchParams();
@@ -218,7 +257,9 @@ export function MembersView({ data }: MembersViewProps) {
218257
</span>
219258
</TableCell>
220259
<TableCell>
221-
<ChangeRoleDialog member={member} />
260+
{currentUserRole === 'ADMIN' && (
261+
<ChangeRoleDialog member={member} />
262+
)}
222263
</TableCell>
223264
</TableRow>
224265
))}

src/app/organizations/[slug]/members/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export default async function MembersPage({ params, searchParams }: PageProps) {
5252
);
5353
}
5454

55+
const currentOrg = user.organizations.find(o => o.slug === slug);
56+
const currentRole = currentOrg?.role;
57+
5558
const data = await getMembers(slug, page, size);
5659

5760
if (!data) {
@@ -64,7 +67,7 @@ export default async function MembersPage({ params, searchParams }: PageProps) {
6467

6568
return (
6669
<Suspense fallback={<div>Loading members...</div>}>
67-
<MembersView data={data} />
70+
<MembersView data={data} currentUserRole={currentRole} />
6871
</Suspense>
6972
);
7073
}

src/app/organizations/components/organization-form.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ export function OrganizationForm({
6767
checkSlugAvailability,
6868
readOnly = false
6969
}: OrganizationFormProps) {
70+
// Some browser extensions (password managers, etc.) mutate <input> DOM before React hydrates,
71+
// which can cause hydration mismatches. We gate rendering of a few "high risk" inputs until mount.
72+
const [isMounted, setIsMounted] = useState(false)
73+
74+
useEffect(() => {
75+
setIsMounted(true)
76+
}, [])
77+
7078
const form = useForm<OrganizationFormValues>({
7179
resolver: zodResolver(organizationFormSchema),
7280
defaultValues: {
@@ -231,7 +239,25 @@ export function OrganizationForm({
231239
<div className="relative">
232240
<Mail className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
233241
<FormControl>
234-
<Input placeholder="contact@acme.com" type="email" className="pl-9" {...field} />
242+
{isMounted ? (
243+
<Input
244+
placeholder="contact@acme.com"
245+
type="email"
246+
className="pl-9"
247+
autoComplete="off"
248+
data-lpignore="true"
249+
data-1p-ignore="true"
250+
data-bwignore="true"
251+
{...field}
252+
/>
253+
) : (
254+
// Avoid rendering an actual <input> during SSR/first client render to prevent
255+
// extension-injected DOM from causing hydration mismatches.
256+
<div
257+
className="h-9 w-full rounded-md border border-input bg-transparent pl-9"
258+
aria-hidden="true"
259+
/>
260+
)}
235261
</FormControl>
236262
</div>
237263
<FormDescription>

0 commit comments

Comments
 (0)