Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
bab6665
Integrate Better Auth as enterprise auth provider
scopsy Dec 18, 2025
c5fad11
Refactor better-auth token handling and update dependencies
scopsy Dec 18, 2025
52b29c6
Add organization dropdown and user button components
scopsy Dec 18, 2025
243bd87
Add custom organization create component to better-auth
scopsy Dec 18, 2025
da40699
Replace useNavigate with window.location for redirects
scopsy Dec 19, 2025
42f9bad
Add invitation acceptance and team management features
scopsy Dec 19, 2025
91c8c69
Add session refresh and agent logging to invitation flow
scopsy Dec 19, 2025
d0ea007
Remove agent logging and improve invitation flow
scopsy Dec 21, 2025
70a4ec8
Refactor better-auth client and context usage
scopsy Dec 21, 2025
5572efe
Integrate Better Auth organization and user settings
scopsy Dec 21, 2025
070dde4
Implement RBAC role-permission mapping and UI updates
scopsy Dec 21, 2025
706266d
Update .source
scopsy Dec 21, 2025
375299e
Merge branch 'next' into better-auth-integration
scopsy Dec 22, 2025
b29928f
Remove Better Auth docs and update auth/cors logic
scopsy Dec 23, 2025
fa3fb66
Update cors.config.ts
scopsy Dec 23, 2025
c6d48ad
Add @novu/providers and @novu/stateless to auth package
scopsy Dec 23, 2025
41eb68a
Add forgot and reset password flows to dashboard
scopsy Dec 23, 2025
002dd1b
Add SSO sign-in support to dashboard app
scopsy Dec 23, 2025
4b97bda
Update .source
scopsy Dec 23, 2025
4ab3945
Delete vite.config.ts.timestamp-1766066097527-6e75268139a32.mjs
scopsy Dec 23, 2025
1f77b7c
Update .source
scopsy Dec 23, 2025
5001bee
Add email verification flow to dashboard auth
scopsy Dec 23, 2025
bb981ce
Merge branch 'next' into better-auth-integration
scopsy Jan 7, 2026
a89cbfb
Merge branch 'next' into better-auth-integration
scopsy Jan 11, 2026
c1633a4
Refactor region imports and update team member invite logic
scopsy Jan 11, 2026
3e7d05e
Handle pending invitation redirects during auth flow
scopsy Jan 11, 2026
24004ab
Remove mapRoleToApiFormat and simplify invite role handling
scopsy Jan 11, 2026
7498f63
Update auth checks and self-hosted env variable
scopsy Jan 11, 2026
917430e
Update .source
scopsy Jan 11, 2026
8769373
Update verify-email.tsx
scopsy Jan 11, 2026
7b3d3a3
Update .source
scopsy Jan 11, 2026
796b5e9
Switch auth pages to better-auth for non-Clerk providers
scopsy Jan 11, 2026
5a70c70
1.4.10 update
scopsy Jan 11, 2026
be3f1f8
Revert "1.4.10 update"
scopsy Jan 12, 2026
6b26502
Update .source
scopsy Jan 12, 2026
88b4006
Update auth and feature flag logic for enterprise/self-hosted
scopsy Jan 12, 2026
2933f0f
Merge branch 'next' into better-auth-integration
scopsy Jan 14, 2026
f2c4268
Merge branch 'next' into better-auth-integration
scopsy Jan 16, 2026
ca02dd3
Merge branch 'next' into better-auth-integration
scopsy Jan 18, 2026
18eb801
Update .source
scopsy Jan 18, 2026
9c783c3
Fix type safety and scroll handling in auth components
scopsy Jan 18, 2026
c21f16d
Enforce permission checks for org settings and members
scopsy Jan 18, 2026
6884564
Pin zod dependency to version 3.23.8
scopsy Jan 18, 2026
73291ad
Remove debug console logs and add permission usage check
scopsy Jan 18, 2026
00fdfcc
Move role permissions to shared package
scopsy Jan 18, 2026
778cac9
Update zod to version 3.25.0
scopsy Jan 18, 2026
b68b7d7
Fixes ad
scopsy Jan 18, 2026
b2398c0
Migrate dashboard to zod v4 and update resolvers
scopsy Jan 18, 2026
b1b4cf7
Update better-auth to ^1.3.0 and refresh lockfile
scopsy Jan 18, 2026
effa45a
Refactor Zod schema and validation type usage
scopsy Jan 18, 2026
7d8b39a
Refactor form validation to use standardSchemaResolver
scopsy Jan 18, 2026
26bfcc1
Improve type safety for workflow test payloads
scopsy Jan 18, 2026
6cc0e8f
Update .cspell.json
scopsy Jan 18, 2026
776d848
Update test-workflow-instructions.tsx
scopsy Jan 18, 2026
bb4bd1b
Merge branch 'next' into better-auth-integration
scopsy Jan 25, 2026
30fe477
Update sign-in.tsx
scopsy Jan 25, 2026
5c8075f
Update .source
scopsy Jan 25, 2026
cab81dd
Update .source
scopsy Jan 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add invitation acceptance and team management features
Introduces an invitation acceptance flow, including a new route, page, and UI component for handling organization invitations. Updates the sign-up process to support joining via invitation and improves the team members management UI with member listing, invitation management, and removal actions. Refactors exports and routing to support these new features.
  • Loading branch information
scopsy committed Dec 19, 2025
commit 42f9badf25d18f966b5d4632c6d99559b6adcd85
5 changes: 5 additions & 0 deletions apps/dashboard/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CreateWorkflowPage,
ErrorPage,
IntegrationsListPage,
InvitationAcceptPage,
LayoutsPage,
OrganizationListPage,
SettingsPage,
Expand Down Expand Up @@ -87,6 +88,10 @@ const router = createBrowserRouter([
path: ROUTES.SIGNUP_ORGANIZATION_LIST,
element: <OrganizationListPage />,
},
{
path: ROUTES.INVITATION_ACCEPT,
element: <InvitationAcceptPage />,
},
],
},
{
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './create-layout';
export * from './create-workflow';
export * from './error-page';
export * from './integrations-list-page';
export * from './invitation-accept';
export * from './layouts';
export * from './organization-list';
export * from './questionnaire-page';
Expand Down
20 changes: 20 additions & 0 deletions apps/dashboard/src/pages/invitation-accept.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { InvitationAccept } from '@clerk/clerk-react';
import { AuthSideBanner } from '@/components/auth/auth-side-banner';
import { PageMeta } from '@/components/page-meta';

export function InvitationAcceptPage() {
return (
<div className="flex min-h-screen w-full flex-col md:max-w-[1100px] md:flex-row md:gap-36">
<PageMeta title="Accept Invitation" />
<div className="w-full md:w-auto">
<AuthSideBanner />
</div>
<div className="flex flex-1 justify-end px-4 py-8 md:items-center md:px-0 md:py-0">
<div className="flex w-full max-w-[500px] flex-col items-start justify-start">
<InvitationAccept />
</div>
</div>
</div>
);
}

7 changes: 5 additions & 2 deletions apps/dashboard/src/utils/better-auth/client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { createAuthClient } from 'better-auth/client';
import { organizationClient } from 'better-auth/client/plugins';
import { BETTER_AUTH_BASE_URL } from '@/config';
import { API_HOSTNAME, BETTER_AUTH_BASE_URL } from '@/config';

const baseURL = BETTER_AUTH_BASE_URL || API_HOSTNAME || 'http://localhost:3000';
const fullBaseURL = `${baseURL}/v1/better-auth`;

export const authClient = createAuthClient({
baseURL: BETTER_AUTH_BASE_URL + '/v1/better-auth',
baseURL: fullBaseURL,
plugins: [organizationClient()],
fetchOptions: {
credentials: 'include',
Expand Down
10 changes: 6 additions & 4 deletions apps/dashboard/src/utils/better-auth/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export * from './sign-in';
export * from './sign-up';
export * from './invitation-accept';
export * from './organization-create';
export * from './organization-dropdown';
export * from './organization-list';
export * from './organization-switcher';
export * from './sign-in';
export * from './sign-up';
export * from './team-members';
export * from './user-button';
export * from './organization-dropdown';
export * from './organization-create';
101 changes: 101 additions & 0 deletions apps/dashboard/src/utils/better-auth/components/invitation-accept.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { useCallback, useEffect, useState } from 'react';
import { RiCloseLine, RiLoader4Line } from 'react-icons/ri';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Button } from '@/components/primitives/button';
import { showSuccessToast } from '@/components/primitives/sonner-helpers';
import { ROUTES } from '@/utils/routes';
import { authClient } from '../client';
import { useAuth } from '../index';

export function InvitationAccept() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { isSignedIn, isLoaded } = useAuth();

const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

const invitationId = searchParams.get('id');

const loadInvitation = useCallback(async () => {
if (!invitationId) {
setError('Invalid invitation link. No invitation ID provided.');
setIsLoading(false);

return;
}

if (!isSignedIn) {
sessionStorage.setItem('pendingInvitationId', invitationId);
navigate(`${ROUTES.SIGN_UP}?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`);

return;
}

try {
setIsLoading(true);

const { data: acceptData, error: acceptError } = await authClient.organization.acceptInvitation({
invitationId,
});

if (acceptError) {
throw new Error(acceptError.message || 'Failed to accept invitation');
}

const organizationId = acceptData?.invitation?.organizationId;
if (organizationId) {
await authClient.organization.setActive({
organizationId,
});
}

showSuccessToast('You have joined the organization', 'Invitation Accepted');
sessionStorage.removeItem('pendingInvitationId');

window.location.href = ROUTES.ROOT;
} catch (e) {
console.error('Failed to accept invitation:', e);
setError(e instanceof Error ? e.message : 'Failed to accept invitation');
} finally {
setIsLoading(false);
}
}, [invitationId, isSignedIn, navigate]);

useEffect(() => {
if (isLoaded) {
loadInvitation();
}
}, [isLoaded, loadInvitation]);

if (isLoading) {
return (
<div className="flex min-h-[400px] items-center justify-center">
<div className="text-center">
<RiLoader4Line className="mx-auto size-12 animate-spin text-primary-base" />
<h2 className="mt-6 text-xl font-semibold text-foreground-950">Accepting Invitation</h2>
<p className="mt-2 text-sm text-foreground-600">Please wait while we add you to the organization...</p>
</div>
</div>
);
}

if (error) {
return (
<div className="flex min-h-[400px] items-center justify-center">
<div className="max-w-md text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
<RiCloseLine className="size-8 text-destructive" />
</div>
<h2 className="mb-2 text-xl font-semibold text-foreground-950">Failed to Accept Invitation</h2>
<p className="mb-6 text-sm text-foreground-600">{error}</p>
<Button variant="secondary" mode="outline" onClick={() => navigate(ROUTES.ROOT)}>
Go to Dashboard
</Button>
</div>
</div>
);
}

return null;
}
50 changes: 35 additions & 15 deletions apps/dashboard/src/utils/better-auth/components/sign-up.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export function SignUp() {
const [isLoading, setIsLoading] = useState(false);
const [passwordError, setPasswordError] = useState<string | null>(null);
const [isSubmitted, setIsSubmitted] = useState(false);

const pendingInvitationId = sessionStorage.getItem('pendingInvitationId');
const hasInvitation = !!pendingInvitationId;

const validatePassword = (password: string) => {
const hasUpperCase = /[A-Z]/.test(password);
Expand Down Expand Up @@ -63,7 +66,7 @@ export function SignUp() {
return;
}

if (!organizationName.trim()) {
if (!hasInvitation && !organizationName.trim()) {
setError('Organization name is required.');
setIsLoading(false);

Expand All @@ -87,6 +90,14 @@ export function SignUp() {

localStorage.setItem('better-auth-session-token', signUpData.token);

const storedPendingInvitationId = sessionStorage.getItem('pendingInvitationId');

if (storedPendingInvitationId) {
window.location.href = `${ROUTES.INVITATION_ACCEPT}?id=${storedPendingInvitationId}`;

return;
}

const { data: orgData, error: orgError } = await authClient.organization.create({
name: organizationName,
slug: organizationName.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
Expand Down Expand Up @@ -177,20 +188,29 @@ export function SignUp() {
Min. 8 characters, include uppercase, lowercase, number, and special character.
</p>
</div>
<div>
<label htmlFor="organizationName" className="mb-1 block text-sm font-medium text-gray-700">
Organization Name <span className="text-red-600">*</span>
</label>
<Input
type="text"
id="organizationName"
value={organizationName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setOrganizationName(e.target.value)}
placeholder="Your Company"
required
className="w-full"
/>
</div>
{!hasInvitation && (
<div>
<label htmlFor="organizationName" className="mb-1 block text-sm font-medium text-gray-700">
Organization Name <span className="text-red-600">*</span>
</label>
<Input
type="text"
id="organizationName"
value={organizationName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setOrganizationName(e.target.value)}
placeholder="Your Company"
required
className="w-full"
/>
</div>
)}
{hasInvitation && (
<div className="rounded-md bg-blue-50 p-4">
<p className="text-sm text-blue-700">
You'll be joining an organization after creating your account.
</p>
</div>
)}
{error && (
<div className="rounded-md bg-red-50 p-4" role="alert">
<p className="text-sm text-red-600">{error}</p>
Expand Down
Loading
Loading