Fix sign up and accept invite
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -1,45 +1,51 @@
|
||||
import { Link, useNavigate, useSearchParams } from "react-router";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { z } from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { Link, useNavigate, useSearchParams } from "react-router";
|
||||
import z from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutation } from "react-relay";
|
||||
|
||||
const signUpFromIvitationMutation = graphql`
|
||||
mutation SignUpFromInvitationPageMutation(
|
||||
$input: SignUpFromInvitationInput!
|
||||
) {
|
||||
signUpFromInvitation(input: $input) {
|
||||
identity {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const schema = z.object({
|
||||
fullName: z.string().min(2),
|
||||
password: z.string().min(8),
|
||||
fullName: z.string().min(2),
|
||||
});
|
||||
|
||||
export default function SignupFromInvitationPage() {
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export default function SignUpFromInvitationPage() {
|
||||
const { __ } = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const fullNameFromParams = searchParams.get("fullName") || "";
|
||||
|
||||
const { register, handleSubmit, formState, reset } = useFormWithSchema(
|
||||
schema,
|
||||
{
|
||||
defaultValues: {
|
||||
fullName: "",
|
||||
password: "",
|
||||
},
|
||||
}
|
||||
);
|
||||
usePageTitle(__("Sign up"));
|
||||
|
||||
useEffect(() => {
|
||||
const fullNameFromParams = searchParams.get("fullName") || "";
|
||||
if (fullNameFromParams) {
|
||||
reset({
|
||||
fullName: fullNameFromParams,
|
||||
password: "",
|
||||
});
|
||||
}
|
||||
}, [searchParams, reset]);
|
||||
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
password: "",
|
||||
fullName: fullNameFromParams,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
const [signUpFromInvitation] = useMutation(signUpFromIvitationMutation);
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
const token = searchParams.get("token");
|
||||
|
||||
if (!token) {
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
@@ -49,40 +55,33 @@ export default function SignupFromInvitationPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch("/connect/signup-from-invitation", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
signUpFromInvitation({
|
||||
variables: {
|
||||
input: {
|
||||
token,
|
||||
password: data.password,
|
||||
fullName: data.fullName,
|
||||
},
|
||||
},
|
||||
onCompleted: () => {
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __(
|
||||
"Account created successfully. Please accept your invitation to join the organization.",
|
||||
),
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/", { replace: true });
|
||||
},
|
||||
onError: (errorData) => {
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
description: errorData.message || __("Signup failed"),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
token: token,
|
||||
password: data.password,
|
||||
fullName: data.fullName,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
description: errorData.message || __("Signup failed"),
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __(
|
||||
"Account created successfully. Please accept your invitation to join the organization."
|
||||
),
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/", { replace: true });
|
||||
});
|
||||
|
||||
usePageTitle(__("Create your account"));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto">
|
||||
@@ -93,7 +92,7 @@ export default function SignupFromInvitationPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<Field
|
||||
label={__("Full Name")}
|
||||
type="text"
|
||||
@@ -1,9 +1,21 @@
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { z } from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import z from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutation } from "react-relay";
|
||||
|
||||
const signUpMutation = graphql`
|
||||
mutation SignUpPageMutation($input: SignUpInput!) {
|
||||
signUp(input: $input) {
|
||||
identity {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
@@ -11,10 +23,15 @@ const schema = z.object({
|
||||
fullName: z.string().min(2),
|
||||
});
|
||||
|
||||
export default function RegisterPage() {
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export default function SignUpPage() {
|
||||
const { __ } = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
|
||||
usePageTitle(__("Sign up"));
|
||||
|
||||
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
email: "",
|
||||
@@ -23,36 +40,32 @@ export default function RegisterPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
const response = await fetch("/connect/register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
const [signUp] = useMutation(signUpMutation);
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
signUp({
|
||||
variables: {
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
fullName: data.fullName,
|
||||
},
|
||||
onCompleted: () => {
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __("Account created successfully"),
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/", { replace: true });
|
||||
},
|
||||
onError: (errorData) => {
|
||||
toast({
|
||||
title: __("Registration failed"),
|
||||
description: errorData.message || __("Registration failed"),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
// Registration failed
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
toast({
|
||||
title: __("Registration failed"),
|
||||
description: errorData.message || __("Registration failed"),
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __("Account created successfully"),
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/", { replace: true });
|
||||
});
|
||||
|
||||
usePageTitle(__("Sign up"));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto">
|
||||
@@ -63,7 +76,7 @@ export default function RegisterPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<Field
|
||||
label={__("Full Name")}
|
||||
type="text"
|
||||
@@ -11,6 +11,7 @@ import { useMemo, useState } from "react";
|
||||
import { InvitationCard } from "./_components/InvitationCard";
|
||||
import { MembershipCard } from "./_components/MembershipCard";
|
||||
import type { MembershipsPageQuery } from "/__generated__/iam/MembershipsPageQuery.graphql";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
|
||||
export const membershipsPageQuery = graphql`
|
||||
query MembershipsPageQuery {
|
||||
@@ -48,6 +49,8 @@ export function MembershipsPage(props: {
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
usePageTitle(__("Select an organization"));
|
||||
|
||||
const { queryRef } = props;
|
||||
const {
|
||||
viewer: {
|
||||
@@ -78,12 +81,7 @@ export function MembershipsPage(props: {
|
||||
{__("Pending invitations")}
|
||||
</h2>
|
||||
{invitations.map(({ node }) => (
|
||||
<InvitationCard
|
||||
key={node.id}
|
||||
fKey={node}
|
||||
// onAccept={handleAcceptInvitation}
|
||||
// isAccepting={isAccepting}
|
||||
/>
|
||||
<InvitationCard key={node.id} fKey={node} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { formatDate } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Card } from "@probo/ui";
|
||||
import { Button, Card } from "@probo/ui";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useFragment } from "react-relay";
|
||||
import { useFragment, useMutation } from "react-relay";
|
||||
import type { InvitationCardFragment$key } from "/__generated__/iam/InvitationCardFragment.graphql";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const fragment = graphql`
|
||||
fragment InvitationCardFragment on Invitation {
|
||||
@@ -17,19 +18,49 @@ const fragment = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const acceptMutation = graphql`
|
||||
mutation InvitationCardMutation($input: AcceptInvitationInput!) {
|
||||
acceptInvitation(input: $input) {
|
||||
membershipEdge {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface InvitationCardProps {
|
||||
fKey: InvitationCardFragment$key;
|
||||
// onAccept: (invitationId: string, organizationId: string) => void;
|
||||
// isAccepting: boolean;
|
||||
}
|
||||
|
||||
export function InvitationCard(props: InvitationCardProps) {
|
||||
const { fKey } = props;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const invitation = useFragment<InvitationCardFragment$key>(fragment, fKey);
|
||||
|
||||
const [acceptInvitation, isAccepting] = useMutation(acceptMutation);
|
||||
|
||||
const handleAccept = () => {
|
||||
acceptInvitation({
|
||||
variables: {
|
||||
input: {
|
||||
invitationId: invitation.id,
|
||||
},
|
||||
},
|
||||
onCompleted: () => {
|
||||
navigate(`/organizations/${invitation.organization.id}`);
|
||||
},
|
||||
onError: (err) => {
|
||||
console.error("Failed to accept invitation:", err);
|
||||
alert(__("Failed to accept invitation"));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padded className="w-full">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
@@ -44,12 +75,9 @@ export function InvitationCard(props: InvitationCardProps) {
|
||||
{__("Invited on")} {formatDate(invitation.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
{/* <Button
|
||||
onClick={() => onAccept(invitation.id, invitation.organization.id)}
|
||||
disabled={isAccepting}
|
||||
>
|
||||
<Button onClick={handleAccept} disabled={isAccepting}>
|
||||
{isAccepting ? __("Accepting...") : __("Accept invitation")}
|
||||
</Button> */}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user