Skip to content

Commit 22b9e53

Browse files
committed
fetch member approval req and invite link enabled flag on server
1 parent bdfef1c commit 22b9e53

File tree

5 files changed

+89
-138
lines changed

5 files changed

+89
-138
lines changed

packages/web/src/actions.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,7 +1683,7 @@ export const getMemberApprovalRequired = async (domain: string): Promise<boolean
16831683
}
16841684

16851685
return org.memberApprovalRequired;
1686-
});
1686+
});
16871687

16881688
export const setMemberApprovalRequired = async (domain: string, required: boolean): Promise<{ success: boolean } | ServiceError> => sew(async () =>
16891689
withAuth(async (userId) =>
@@ -1700,13 +1700,19 @@ export const setMemberApprovalRequired = async (domain: string, required: boolea
17001700
)
17011701
);
17021702

1703-
export const getInviteLinkEnabled = async (domain: string): Promise<boolean | ServiceError> => sew(async () =>
1704-
withAuth(async (userId) =>
1705-
withOrgMembership(userId, domain, async ({ org }) => {
1706-
return org.inviteLinkEnabled;
1707-
}, /* minRequiredRole = */ OrgRole.OWNER)
1708-
)
1709-
);
1703+
export const getInviteLinkEnabled = async (domain: string): Promise<boolean | ServiceError> => sew(async () => {
1704+
const org = await prisma.org.findUnique({
1705+
where: {
1706+
domain,
1707+
},
1708+
});
1709+
1710+
if (!org) {
1711+
return orgNotFound();
1712+
}
1713+
1714+
return org.inviteLinkEnabled;
1715+
});
17101716

17111717
export const setInviteLinkEnabled = async (domain: string, enabled: boolean): Promise<{ success: boolean } | ServiceError> => sew(async () =>
17121718
withAuth(async (userId) =>

packages/web/src/app/[domain]/settings/members/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export default async function MembersSettingsPage({ params: { domain }, searchPa
8080
</div>
8181

8282
{userRoleInOrg === OrgRole.OWNER && (
83-
<MemberApprovalRequiredToggle />
83+
<MemberApprovalRequiredToggle memberApprovalRequired={org.memberApprovalRequired} inviteLinkEnabled={org.inviteLinkEnabled} />
8484
)}
8585

8686
<InviteMemberCard

packages/web/src/app/components/inviteLinkToggle.tsx

Lines changed: 53 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,41 @@ import { useState, useEffect } from "react"
44
import { Button } from "@/components/ui/button"
55
import { Input } from "@/components/ui/input"
66
import { Switch } from "@/components/ui/switch"
7-
import { Copy, Check, Loader2 } from "lucide-react"
7+
import { Copy, Check } from "lucide-react"
88
import { useToast } from "@/components/hooks/use-toast"
99
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"
10-
import { getOrgInviteId, getInviteLinkEnabled, setInviteLinkEnabled } from "@/actions"
10+
import { getOrgInviteId, setInviteLinkEnabled } from "@/actions"
1111
import { isServiceError } from "@/lib/utils"
1212

13-
export function InviteLinkToggle() {
14-
const [enabled, setEnabled] = useState(false)
13+
interface InviteLinkToggleProps {
14+
inviteLinkEnabled: boolean
15+
}
16+
17+
export function InviteLinkToggle({ inviteLinkEnabled }: InviteLinkToggleProps) {
18+
const [enabled, setEnabled] = useState(inviteLinkEnabled)
1519
const [isLoading, setIsLoading] = useState(false)
16-
const [isInitializing, setIsInitializing] = useState(true)
1720
const [inviteLink, setInviteLink] = useState("")
1821
const [copied, setCopied] = useState(false)
1922
const { toast } = useToast()
2023

21-
// Fetch initial values on component mount
24+
// Fetch invite link when component mounts if enabled
2225
useEffect(() => {
23-
const fetchInitialValues = async () => {
24-
try {
25-
const enabledResult = await getInviteLinkEnabled(SINGLE_TENANT_ORG_DOMAIN)
26-
27-
if (isServiceError(enabledResult)) {
28-
toast({
29-
title: "Error",
30-
description: "Failed to load invite link setting",
31-
variant: "destructive",
32-
})
33-
return
34-
}
35-
36-
setEnabled(enabledResult)
37-
38-
// If enabled, also fetch the invite link
39-
if (enabledResult) {
26+
const fetchInviteLink = async () => {
27+
if (inviteLinkEnabled) {
28+
try {
4029
const inviteIdResult = await getOrgInviteId(SINGLE_TENANT_ORG_DOMAIN)
4130
if (!isServiceError(inviteIdResult)) {
4231
setInviteLink(`${window.location.origin}/invite?id=${inviteIdResult}`)
4332
}
33+
} catch (error) {
34+
setInviteLink("")
35+
console.error("Error fetching invite link:", error)
4436
}
45-
} catch (error) {
46-
console.error("Error fetching invite link setting:", error)
47-
toast({
48-
title: "Error",
49-
description: "Failed to load invite link setting",
50-
variant: "destructive",
51-
})
52-
} finally {
53-
setIsInitializing(false)
5437
}
5538
}
5639

57-
fetchInitialValues()
58-
}, [toast])
40+
fetchInviteLink()
41+
}, [inviteLinkEnabled])
5942

6043
const handleToggle = async (checked: boolean) => {
6144
setIsLoading(true)
@@ -124,56 +107,48 @@ export function InviteLinkToggle() {
124107
</div>
125108
</div>
126109
<div className="flex-shrink-0">
127-
{isInitializing ? (
128-
<div className="flex items-center justify-center w-11 h-6">
129-
<Loader2 className="animate-spin h-4 w-4 text-[var(--muted-foreground)]" />
130-
</div>
131-
) : (
132-
<Switch
133-
checked={enabled}
134-
onCheckedChange={handleToggle}
135-
disabled={isLoading}
136-
/>
137-
)}
110+
<Switch
111+
checked={enabled}
112+
onCheckedChange={handleToggle}
113+
disabled={isLoading}
114+
/>
138115
</div>
139116
</div>
140117

141-
{!isInitializing && (
142-
<div className={`transition-all duration-300 ease-in-out ${
143-
enabled
144-
? 'max-h-96 opacity-100 transform translate-y-0 mt-4'
145-
: 'max-h-0 opacity-0 transform -translate-y-2 overflow-hidden'
146-
}`}>
147-
<div className="space-y-4 pt-4 border-t border-[var(--border)]">
148-
<div className="space-y-2">
149-
<div className="flex gap-2">
150-
<Input
151-
value={inviteLink}
152-
readOnly
153-
className="flex-1 bg-[var(--muted)] border-[var(--border)] text-[var(--foreground)]"
154-
/>
155-
<Button
156-
onClick={handleCopy}
157-
variant="outline"
158-
size="icon"
159-
className="shrink-0 border-[var(--border)] hover:bg-[var(--muted)]"
160-
disabled={!inviteLink}
161-
>
162-
{copied ? (
163-
<Check className="h-4 w-4 text-[var(--chart-2)]" />
164-
) : (
165-
<Copy className="h-4 w-4" />
166-
)}
167-
</Button>
168-
</div>
118+
<div className={`transition-all duration-300 ease-in-out ${
119+
enabled
120+
? 'max-h-96 opacity-100 transform translate-y-0 mt-4'
121+
: 'max-h-0 opacity-0 transform -translate-y-2 overflow-hidden'
122+
}`}>
123+
<div className="space-y-4 pt-4 border-t border-[var(--border)]">
124+
<div className="space-y-2">
125+
<div className="flex gap-2">
126+
<Input
127+
value={inviteLink}
128+
readOnly
129+
className="flex-1 bg-[var(--muted)] border-[var(--border)] text-[var(--foreground)]"
130+
/>
131+
<Button
132+
onClick={handleCopy}
133+
variant="outline"
134+
size="icon"
135+
className="shrink-0 border-[var(--border)] hover:bg-[var(--muted)]"
136+
disabled={!inviteLink}
137+
>
138+
{copied ? (
139+
<Check className="h-4 w-4 text-[var(--chart-2)]" />
140+
) : (
141+
<Copy className="h-4 w-4" />
142+
)}
143+
</Button>
169144
</div>
170-
171-
<p className="text-sm text-[var(--muted-foreground)]">
172-
You can find this link again in the <strong>Settings → Members</strong> page.
173-
</p>
174145
</div>
146+
147+
<p className="text-sm text-[var(--muted-foreground)]">
148+
You can find this link again in the <strong>Settings → Members</strong> page.
149+
</p>
175150
</div>
176-
)}
151+
</div>
177152
</div>
178153
)
179154
}

packages/web/src/app/onboard/components/memberApprovalRequiredToggle.tsx

Lines changed: 16 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,23 @@
11
"use client"
22

3-
import { useState, useEffect } from "react"
3+
import { useState } from "react"
44
import { Switch } from "@/components/ui/switch"
5-
import { Loader2 } from "lucide-react"
6-
import { setMemberApprovalRequired, getMemberApprovalRequired } from "@/actions"
5+
import { setMemberApprovalRequired } from "@/actions"
76
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"
87
import { isServiceError } from "@/lib/utils"
98
import { useToast } from "@/components/hooks/use-toast"
109
import { InviteLinkToggle } from "@/app/components/inviteLinkToggle"
1110

12-
export function MemberApprovalRequiredToggle() {
13-
const [enabled, setEnabled] = useState(false)
11+
interface MemberApprovalRequiredToggleProps {
12+
memberApprovalRequired: boolean
13+
inviteLinkEnabled: boolean
14+
}
15+
16+
export function MemberApprovalRequiredToggle({ memberApprovalRequired, inviteLinkEnabled }: MemberApprovalRequiredToggleProps) {
17+
const [enabled, setEnabled] = useState(memberApprovalRequired)
1418
const [isLoading, setIsLoading] = useState(false)
15-
const [isInitializing, setIsInitializing] = useState(true)
1619
const { toast } = useToast()
1720

18-
// Fetch initial value on component mount
19-
useEffect(() => {
20-
const fetchInitialValue = async () => {
21-
try {
22-
const result = await getMemberApprovalRequired(SINGLE_TENANT_ORG_DOMAIN)
23-
24-
if (isServiceError(result)) {
25-
toast({
26-
title: "Error",
27-
description: "Failed to load member approval setting",
28-
variant: "destructive",
29-
})
30-
return
31-
}
32-
33-
setEnabled(result)
34-
} catch (error) {
35-
console.error("Error fetching member approval setting:", error)
36-
toast({
37-
title: "Error",
38-
description: "Failed to load member approval setting",
39-
variant: "destructive",
40-
})
41-
} finally {
42-
setIsInitializing(false)
43-
}
44-
}
45-
46-
fetchInitialValue()
47-
}, [toast])
48-
4921
const handleToggle = async (checked: boolean) => {
5022
setIsLoading(true)
5123
try {
@@ -96,27 +68,21 @@ export function MemberApprovalRequiredToggle() {
9668
</div>
9769
</div>
9870
<div className="flex-shrink-0">
99-
{isInitializing ? (
100-
<div className="flex items-center justify-center w-11 h-6">
101-
<Loader2 className="animate-spin h-4 w-4 text-[var(--muted-foreground)]" />
102-
</div>
103-
) : (
104-
<Switch
105-
checked={enabled}
106-
onCheckedChange={handleToggle}
107-
disabled={isLoading}
108-
/>
109-
)}
71+
<Switch
72+
checked={enabled}
73+
onCheckedChange={handleToggle}
74+
disabled={isLoading}
75+
/>
11076
</div>
11177
</div>
11278
</div>
11379

11480
<div className={`transition-all duration-300 ease-in-out overflow-hidden ${
115-
enabled && !isInitializing
81+
enabled
11682
? 'max-h-96 opacity-100 transform translate-y-0'
11783
: 'max-h-0 opacity-0 transform -translate-y-2'
11884
}`}>
119-
<InviteLinkToggle />
85+
<InviteLinkToggle inviteLinkEnabled={inviteLinkEnabled} />
12086
</div>
12187
</div>
12288
)

packages/web/src/app/onboard/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export default async function Onboarding({ searchParams }: OnboardingProps) {
4343
const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN);
4444
const session = await auth();
4545

46+
if (!org) {
47+
return <div>Error loading organization</div>;
48+
}
49+
4650
if (org && org.isOnboarded) {
4751
redirect('/');
4852
}
@@ -138,7 +142,7 @@ export default async function Onboarding({ searchParams }: OnboardingProps) {
138142
subtitle: "Set up your organization preferences and security settings.",
139143
component: (
140144
<div className="space-y-6">
141-
<MemberApprovalRequiredToggle />
145+
<MemberApprovalRequiredToggle memberApprovalRequired={org.memberApprovalRequired} inviteLinkEnabled={org.inviteLinkEnabled} />
142146
<Button asChild className="w-full h-11 bg-primary hover:bg-primary/90 text-primary-foreground transition-all duration-200 font-medium">
143147
<a href="/onboard?step=3">Continue →</a>
144148
</Button>

0 commit comments

Comments
 (0)