Skip to content

Commit 689fb6f

Browse files
committed
fix: auto join redirect issue
1 parent c2c9b85 commit 689fb6f

File tree

2 files changed

+60
-5
lines changed

2 files changed

+60
-5
lines changed

src/lib/membership-service.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ export async function checkTenantMembership(userId: string, tenantSlug: string):
3232

3333
/**
3434
* Associates a user with a tenant directly (no invitation tokens)
35+
* @param userId - The user's UUID
36+
* @param tenantIdOrSlug - Either the tenant UUID or slug
37+
* @param _inviteToken - Unused, kept for backward compatibility
3538
*/
3639
export async function associateUserWithTenant(
3740
userId: string,
38-
tenantId: string,
39-
role: string = "member",
41+
tenantIdOrSlug: string,
42+
_inviteToken?: string,
4043
): Promise<void> {
4144
try {
4245
// Verify auth session before attempting to join
@@ -52,6 +55,23 @@ export async function associateUserWithTenant(
5255
throw new Error("Session user ID mismatch");
5356
}
5457

58+
// Determine if we have a UUID or slug
59+
// UUIDs are 36 chars with hyphens in specific positions
60+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
61+
tenantIdOrSlug,
62+
);
63+
64+
let tenantId = tenantIdOrSlug;
65+
66+
if (!isUuid) {
67+
// It's a slug, lookup the tenant
68+
const tenant = await getTenantBySlug(tenantIdOrSlug);
69+
if (!tenant) {
70+
throw new Error(`Tenant not found: ${tenantIdOrSlug}`);
71+
}
72+
tenantId = tenant.id;
73+
}
74+
5575
// Use the join_tenant_as_member function to bypass RLS issues
5676
// This function does all the security checks and performs the insert as postgres
5777
const { error } = await supabase.rpc("join_tenant_as_member", {

src/pages/tenant/AuthPage.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { associateUserWithTenant } from "@/lib/membership-service";
1111
import { useTranslation } from "react-i18next";
1212
import { AuthFlowStep } from "@/hooks/useTenantAuthFlow";
1313
import { useToast } from "@/hooks/use-toast";
14+
import { useInvalidateTenants } from "@/hooks/useTenants";
1415

1516
export default function AuthPage() {
1617
const { slug } = useParams<{ slug: string }>();
@@ -26,6 +27,7 @@ export default function AuthPage() {
2627
const [hasAttemptedAssociation, setHasAttemptedAssociation] = useState(false);
2728
const { t } = useTranslation();
2829
const { toast } = useToast();
30+
const invalidateTenants = useInvalidateTenants();
2931

3032
// Get the flow step from URL query parameter
3133
const flowStep = searchParams.get("flow") as AuthFlowStep | null;
@@ -81,6 +83,7 @@ export default function AuthPage() {
8183
try {
8284
await associateUserWithTenant(user.id, tenant.id);
8385
setHasAttemptedAssociation(true);
86+
invalidateTenants();
8487

8588
toast({
8689
title: t("auth:joinedChurch"),
@@ -106,13 +109,32 @@ export default function AuthPage() {
106109
hasAttemptedAssociation,
107110
t,
108111
toast,
112+
invalidateTenants,
109113
]);
110114

111115
useEffect(() => {
112116
const checkUserMembership = async () => {
113117
if (!isLoading && user && tenant && slug) {
114118
try {
115-
const hasAccess = await checkUserTenantAccess(user.id, slug);
119+
const isJoinFlow = flowStep === "signup" || flowStep === "join-signin";
120+
121+
// Prevent premature "no permission" during OAuth auto-join
122+
if (!inviteToken && isJoinFlow) {
123+
if (isAssociating) return;
124+
// Wait for the auto-join attempt to complete before deciding access
125+
if (!hasAttemptedAssociation) return;
126+
}
127+
128+
// Optional reliability: retry a few times after join attempt to smooth out timing
129+
let hasAccess = false;
130+
const maxAttempts = !inviteToken && isJoinFlow && hasAttemptedAssociation ? 5 : 1;
131+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
132+
hasAccess = await checkUserTenantAccess(user.id, slug);
133+
if (hasAccess) break;
134+
if (attempt < maxAttempts - 1) {
135+
await new Promise((resolve) => setTimeout(resolve, 250));
136+
}
137+
}
116138

117139
if (hasAccess) {
118140
// Redirect to original page if specified
@@ -121,7 +143,7 @@ export default function AuthPage() {
121143
} else {
122144
navigate(`/tenant/${slug}`);
123145
}
124-
} else if (!inviteToken) {
146+
} else if (!inviteToken && !(isJoinFlow && !hasAttemptedAssociation)) {
125147
// ADD: User-facing error message instead of console.log
126148
setError(t("auth:noPermissionToEnterChurch"));
127149
toast({
@@ -137,7 +159,20 @@ export default function AuthPage() {
137159
};
138160

139161
checkUserMembership();
140-
}, [user, isLoading, navigate, tenant, slug, inviteToken, redirectTo, t, toast]);
162+
}, [
163+
user,
164+
isLoading,
165+
navigate,
166+
tenant,
167+
slug,
168+
inviteToken,
169+
redirectTo,
170+
t,
171+
toast,
172+
flowStep,
173+
isAssociating,
174+
hasAttemptedAssociation,
175+
]);
141176

142177
const handleAuthSuccess = () => {
143178
// If redirect parameter exists and is a valid tenant path, use it

0 commit comments

Comments
 (0)