bugfix: the flow for trial and accept invitation (org + staff)is now unified. all navigate to dshboard if succesfull.
This commit is contained in:
@@ -9,12 +9,15 @@ import { Button } from '@/components/ui/shared/Button';
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { staffApi } from '@/lib/api/staff';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { navigateIntoAppIfOrgSelected } from '@/lib/auth/postAuthRedirect';
|
||||
|
||||
function AcceptInviteContent() {
|
||||
const t = useTranslations('auth');
|
||||
const tErrors = useTranslations('errors');
|
||||
const params = useSearchParams();
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const token = useMemo(() => params.get('token') || '', [params]);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -76,18 +79,29 @@ function AcceptInviteContent() {
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
let accepted = false;
|
||||
try {
|
||||
await staffApi.acceptInvite({
|
||||
token,
|
||||
name: name.trim(),
|
||||
password,
|
||||
});
|
||||
setSuccess(t('invitationAcceptedRedirect'));
|
||||
setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1000);
|
||||
accepted = true;
|
||||
const email = inviteInfo?.email?.trim();
|
||||
if (!email) {
|
||||
throw new Error('missing-invite-email');
|
||||
}
|
||||
const { organizations } = await login(email, password);
|
||||
navigateIntoAppIfOrgSelected((href) => router.replace(href), organizations.length);
|
||||
} catch (e: unknown) {
|
||||
setError(getUserFacingError(e, tErrors, t('errorAcceptInvitation')));
|
||||
if (accepted) {
|
||||
setSuccess(t('invitationAcceptedSignInFailed'));
|
||||
setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1500);
|
||||
} else {
|
||||
setError(getUserFacingError(e, tErrors, t('errorAcceptInvitation')));
|
||||
}
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDeta
|
||||
import { RegistrationProgressSteps } from '@/components/ui/auth/RegistrationProgressSteps';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { organizationApi } from '@/lib/api/organization';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { navigateIntoAppIfOrgSelected } from '@/lib/auth/postAuthRedirect';
|
||||
|
||||
type AcceptOrganizationInviteForm = {
|
||||
ownerName: string;
|
||||
@@ -33,6 +35,7 @@ function AcceptOrganizationInviteContent() {
|
||||
const tValidation = useTranslations('validation');
|
||||
const params = useSearchParams();
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const token = useMemo(() => params.get('token') || '', [params]);
|
||||
|
||||
const acceptOrganizationInviteSchema = useMemo(
|
||||
@@ -137,6 +140,7 @@ function AcceptOrganizationInviteContent() {
|
||||
setError('');
|
||||
setSuccess('');
|
||||
setSubmitting(true);
|
||||
let accepted = false;
|
||||
try {
|
||||
await organizationApi.acceptInvite({
|
||||
token,
|
||||
@@ -146,10 +150,20 @@ function AcceptOrganizationInviteContent() {
|
||||
organizationEmail: data.organizationEmail.trim(),
|
||||
organizationType: data.organizationType,
|
||||
});
|
||||
setSuccess(t('organizationAcceptedRedirect'));
|
||||
setTimeout(() => router.replace('/login'), 1000);
|
||||
accepted = true;
|
||||
const email = inviteInfo?.ownerEmail?.trim();
|
||||
if (!email) {
|
||||
throw new Error('missing-invite-email');
|
||||
}
|
||||
const { organizations } = await login(email, data.password);
|
||||
navigateIntoAppIfOrgSelected((href) => router.replace(href), organizations.length);
|
||||
} catch (e: unknown) {
|
||||
setError(getUserFacingError(e, tErrors, t('errorAcceptInvitation')));
|
||||
if (accepted) {
|
||||
setSuccess(t('invitationAcceptedSignInFailed'));
|
||||
setTimeout(() => router.replace('/login'), 1500);
|
||||
} else {
|
||||
setError(getUserFacingError(e, tErrors, t('errorAcceptInvitation')));
|
||||
}
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Suspense, useState, useEffect, useMemo } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useRouter } from '@/i18n/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
@@ -16,10 +15,8 @@ import { AuthPageShell } from '@/components/ui/auth/AuthPageShell';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { Checkbox } from '@/components/ui/shared/Checkbox';
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import {
|
||||
consumeAuthRedirect,
|
||||
storeAuthRedirectFromPath,
|
||||
} from '@/lib/auth/postAuthRedirect';
|
||||
import { storeAuthRedirectFromPath } from '@/lib/auth/postAuthRedirect';
|
||||
import { useEnterAppWhenAuthenticated } from '@/lib/hooks/useEnterAppWhenAuthenticated';
|
||||
|
||||
type LoginForm = {
|
||||
email: string;
|
||||
@@ -32,8 +29,7 @@ function LoginPageContent() {
|
||||
const tCommon = useTranslations('common');
|
||||
const tValidation = useTranslations('validation');
|
||||
const tErrors = useTranslations('errors');
|
||||
const { login, isLoading, user, isAuthReady, organizations, currentOrganization } = useAuth();
|
||||
const router = useRouter();
|
||||
const { login, isLoading, isAuthReady } = useAuth();
|
||||
const searchParams = useSearchParams();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [savedEmail] = useState(() => getRememberedEmail());
|
||||
@@ -55,12 +51,7 @@ function LoginPageContent() {
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthReady || !user || isLoading) return;
|
||||
const orgReady = organizations.length <= 1 || currentOrganization;
|
||||
if (!orgReady) return;
|
||||
router.push(consumeAuthRedirect() ?? '/today');
|
||||
}, [isAuthReady, user, isLoading, organizations, currentOrganization, router]);
|
||||
useEnterAppWhenAuthenticated();
|
||||
|
||||
const {
|
||||
register,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Link } from '@/i18n/navigation';
|
||||
import { Mail, Lock, User, Phone } from 'lucide-react';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { useEnterAppWhenAuthenticated } from '@/lib/hooks/useEnterAppWhenAuthenticated';
|
||||
import { AuthPageShell } from '@/components/ui/auth/AuthPageShell';
|
||||
import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDetailsFields';
|
||||
import { RegistrationProgressSteps } from '@/components/ui/auth/RegistrationProgressSteps';
|
||||
@@ -38,9 +39,10 @@ export default function RegisterPage() {
|
||||
const tCommon = useTranslations('common');
|
||||
const tValidation = useTranslations('validation');
|
||||
const tErrors = useTranslations('errors');
|
||||
const { registerTrial, isLoading } = useAuth();
|
||||
const { registerTrial, isLoading, isAuthReady } = useAuth();
|
||||
const [step, setStep] = useState(1);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
useEnterAppWhenAuthenticated();
|
||||
|
||||
const registerSchema = useMemo(
|
||||
() =>
|
||||
@@ -121,6 +123,14 @@ export default function RegisterPage() {
|
||||
hide: t('hidePassword'),
|
||||
};
|
||||
|
||||
if (!isAuthReady) {
|
||||
return (
|
||||
<div className="min-h-[100dvh] app-web-bg flex items-center justify-center px-4">
|
||||
<p className="text-text-secondary">{tCommon('loading')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthPageShell
|
||||
header={
|
||||
|
||||
Reference in New Issue
Block a user