Fix sign up and accept invite
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
118
apps/console/src/__generated__/iam/InvitationCardMutation.graphql.ts
generated
Normal file
118
apps/console/src/__generated__/iam/InvitationCardMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<8c8ece60966bd2f39668d4ee4459ae86>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type AcceptInvitationInput = {
|
||||||
|
invitationId: string;
|
||||||
|
};
|
||||||
|
export type InvitationCardMutation$variables = {
|
||||||
|
input: AcceptInvitationInput;
|
||||||
|
};
|
||||||
|
export type InvitationCardMutation$data = {
|
||||||
|
readonly acceptInvitation: {
|
||||||
|
readonly membershipEdge: {
|
||||||
|
readonly node: {
|
||||||
|
readonly id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} | null | undefined;
|
||||||
|
};
|
||||||
|
export type InvitationCardMutation = {
|
||||||
|
response: InvitationCardMutation$data;
|
||||||
|
variables: InvitationCardMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "AcceptInvitationPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "acceptInvitation",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "MembershipEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "membershipEdge",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Membership",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "InvitationCardMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "InvitationCardMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "592fad5ce23af0f457d7e0ccf5f7123f",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "InvitationCardMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation InvitationCardMutation(\n $input: AcceptInvitationInput!\n) {\n acceptInvitation(input: $input) {\n membershipEdge {\n node {\n id\n }\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "00a20b1db5dd8e98838c8c9659560bb9";
|
||||||
|
|
||||||
|
export default node;
|
||||||
107
apps/console/src/__generated__/iam/SignUpFromInvitationPageMutation.graphql.ts
generated
Normal file
107
apps/console/src/__generated__/iam/SignUpFromInvitationPageMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<59df18257cc2bae52aefd576c20f03cf>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type SignUpFromInvitationInput = {
|
||||||
|
fullName: string;
|
||||||
|
password: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
export type SignUpFromInvitationPageMutation$variables = {
|
||||||
|
input: SignUpFromInvitationInput;
|
||||||
|
};
|
||||||
|
export type SignUpFromInvitationPageMutation$data = {
|
||||||
|
readonly signUpFromInvitation: {
|
||||||
|
readonly identity: {
|
||||||
|
readonly id: string;
|
||||||
|
} | null | undefined;
|
||||||
|
} | null | undefined;
|
||||||
|
};
|
||||||
|
export type SignUpFromInvitationPageMutation = {
|
||||||
|
response: SignUpFromInvitationPageMutation$data;
|
||||||
|
variables: SignUpFromInvitationPageMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "SignUpFromInvitationPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "signUpFromInvitation",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "identity",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "SignUpFromInvitationPageMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "SignUpFromInvitationPageMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "9bdca3bd56cb86a2e9c0627379f7f58f",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "SignUpFromInvitationPageMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation SignUpFromInvitationPageMutation(\n $input: SignUpFromInvitationInput!\n) {\n signUpFromInvitation(input: $input) {\n identity {\n id\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "418fe53fbdc92e4ce5d2256062f08322";
|
||||||
|
|
||||||
|
export default node;
|
||||||
107
apps/console/src/__generated__/iam/SignUpPageMutation.graphql.ts
generated
Normal file
107
apps/console/src/__generated__/iam/SignUpPageMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<450f7808784d82c4ee82e21f72c57465>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type SignUpInput = {
|
||||||
|
email: string;
|
||||||
|
fullName: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
export type SignUpPageMutation$variables = {
|
||||||
|
input: SignUpInput;
|
||||||
|
};
|
||||||
|
export type SignUpPageMutation$data = {
|
||||||
|
readonly signUp: {
|
||||||
|
readonly identity: {
|
||||||
|
readonly id: string;
|
||||||
|
} | null | undefined;
|
||||||
|
} | null | undefined;
|
||||||
|
};
|
||||||
|
export type SignUpPageMutation = {
|
||||||
|
response: SignUpPageMutation$data;
|
||||||
|
variables: SignUpPageMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "SignUpPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "signUp",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "identity",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "SignUpPageMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "SignUpPageMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "266f2f87a14a72bb166348ae9142a885",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "SignUpPageMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation SignUpPageMutation(\n $input: SignUpInput!\n) {\n signUp(input: $input) {\n identity {\n id\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "b8ec213c1c715d5a7fbfa5896047785d";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -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 { 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({
|
const schema = z.object({
|
||||||
fullName: z.string().min(2),
|
|
||||||
password: z.string().min(8),
|
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 { __ } = useTranslate();
|
||||||
const navigate = useNavigate();
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
const fullNameFromParams = searchParams.get("fullName") || "";
|
||||||
|
|
||||||
const { register, handleSubmit, formState, reset } = useFormWithSchema(
|
usePageTitle(__("Sign up"));
|
||||||
schema,
|
|
||||||
{
|
|
||||||
defaultValues: {
|
|
||||||
fullName: "",
|
|
||||||
password: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||||
const fullNameFromParams = searchParams.get("fullName") || "";
|
defaultValues: {
|
||||||
if (fullNameFromParams) {
|
password: "",
|
||||||
reset({
|
fullName: fullNameFromParams,
|
||||||
fullName: fullNameFromParams,
|
},
|
||||||
password: "",
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [searchParams, reset]);
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const [signUpFromInvitation] = useMutation(signUpFromIvitationMutation);
|
||||||
|
|
||||||
|
const onSubmit = (data: FormData) => {
|
||||||
const token = searchParams.get("token");
|
const token = searchParams.get("token");
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
toast({
|
toast({
|
||||||
title: __("Signup failed"),
|
title: __("Signup failed"),
|
||||||
@@ -49,40 +55,33 @@ export default function SignupFromInvitationPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch("/connect/signup-from-invitation", {
|
signUpFromInvitation({
|
||||||
method: "POST",
|
variables: {
|
||||||
headers: {
|
input: {
|
||||||
"Content-Type": "application/json",
|
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 (
|
return (
|
||||||
<div className="space-y-6 w-full max-w-md mx-auto">
|
<div className="space-y-6 w-full max-w-md mx-auto">
|
||||||
@@ -93,7 +92,7 @@ export default function SignupFromInvitationPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={onSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={__("Full Name")}
|
label={__("Full Name")}
|
||||||
type="text"
|
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 { 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({
|
const schema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
@@ -11,10 +23,15 @@ const schema = z.object({
|
|||||||
fullName: z.string().min(2),
|
fullName: z.string().min(2),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function RegisterPage() {
|
type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export default function SignUpPage() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const navigate = useNavigate();
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
usePageTitle(__("Sign up"));
|
||||||
|
|
||||||
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
@@ -23,36 +40,32 @@ export default function RegisterPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const [signUp] = useMutation(signUpMutation);
|
||||||
const response = await fetch("/connect/register", {
|
|
||||||
method: "POST",
|
const onSubmit = (data: FormData) => {
|
||||||
headers: {
|
signUp({
|
||||||
"Content-Type": "application/json",
|
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 (
|
return (
|
||||||
<div className="space-y-6 w-full max-w-md mx-auto">
|
<div className="space-y-6 w-full max-w-md mx-auto">
|
||||||
@@ -63,7 +76,7 @@ export default function RegisterPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={onSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={__("Full Name")}
|
label={__("Full Name")}
|
||||||
type="text"
|
type="text"
|
||||||
@@ -11,6 +11,7 @@ import { useMemo, useState } from "react";
|
|||||||
import { InvitationCard } from "./_components/InvitationCard";
|
import { InvitationCard } from "./_components/InvitationCard";
|
||||||
import { MembershipCard } from "./_components/MembershipCard";
|
import { MembershipCard } from "./_components/MembershipCard";
|
||||||
import type { MembershipsPageQuery } from "/__generated__/iam/MembershipsPageQuery.graphql";
|
import type { MembershipsPageQuery } from "/__generated__/iam/MembershipsPageQuery.graphql";
|
||||||
|
import { usePageTitle } from "@probo/hooks";
|
||||||
|
|
||||||
export const membershipsPageQuery = graphql`
|
export const membershipsPageQuery = graphql`
|
||||||
query MembershipsPageQuery {
|
query MembershipsPageQuery {
|
||||||
@@ -48,6 +49,8 @@ export function MembershipsPage(props: {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
usePageTitle(__("Select an organization"));
|
||||||
|
|
||||||
const { queryRef } = props;
|
const { queryRef } = props;
|
||||||
const {
|
const {
|
||||||
viewer: {
|
viewer: {
|
||||||
@@ -78,12 +81,7 @@ export function MembershipsPage(props: {
|
|||||||
{__("Pending invitations")}
|
{__("Pending invitations")}
|
||||||
</h2>
|
</h2>
|
||||||
{invitations.map(({ node }) => (
|
{invitations.map(({ node }) => (
|
||||||
<InvitationCard
|
<InvitationCard key={node.id} fKey={node} />
|
||||||
key={node.id}
|
|
||||||
fKey={node}
|
|
||||||
// onAccept={handleAcceptInvitation}
|
|
||||||
// isAccepting={isAccepting}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { formatDate } from "@probo/helpers";
|
import { formatDate } from "@probo/helpers";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Card } from "@probo/ui";
|
import { Button, Card } from "@probo/ui";
|
||||||
import { graphql } from "relay-runtime";
|
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 type { InvitationCardFragment$key } from "/__generated__/iam/InvitationCardFragment.graphql";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
const fragment = graphql`
|
const fragment = graphql`
|
||||||
fragment InvitationCardFragment on Invitation {
|
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 {
|
interface InvitationCardProps {
|
||||||
fKey: InvitationCardFragment$key;
|
fKey: InvitationCardFragment$key;
|
||||||
// onAccept: (invitationId: string, organizationId: string) => void;
|
|
||||||
// isAccepting: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InvitationCard(props: InvitationCardProps) {
|
export function InvitationCard(props: InvitationCardProps) {
|
||||||
const { fKey } = props;
|
const { fKey } = props;
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
const invitation = useFragment<InvitationCardFragment$key>(fragment, fKey);
|
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 (
|
return (
|
||||||
<Card padded className="w-full">
|
<Card padded className="w-full">
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
@@ -44,12 +75,9 @@ export function InvitationCard(props: InvitationCardProps) {
|
|||||||
{__("Invited on")} {formatDate(invitation.createdAt)}
|
{__("Invited on")} {formatDate(invitation.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* <Button
|
<Button onClick={handleAccept} disabled={isAccepting}>
|
||||||
onClick={() => onAccept(invitation.id, invitation.organization.id)}
|
|
||||||
disabled={isAccepting}
|
|
||||||
>
|
|
||||||
{isAccepting ? __("Accepting...") : __("Accept invitation")}
|
{isAccepting ? __("Accepting...") : __("Accept invitation")}
|
||||||
</Button> */}
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "register",
|
path: "register",
|
||||||
Component: lazy(() => import("./pages/auth/RegisterPage")),
|
Component: lazy(() => import("./pages/iam/auth/SignUpPage")),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// path: "confirm-email",
|
// path: "confirm-email",
|
||||||
@@ -77,7 +77,9 @@ const routes = [
|
|||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
path: "signup-from-invitation",
|
path: "signup-from-invitation",
|
||||||
Component: lazy(() => import("./pages/auth/SignupFromInvitationPage")),
|
Component: lazy(
|
||||||
|
() => import("./pages/iam/auth/SignUpFromInvitationPage"),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "forgot-password",
|
path: "forgot-password",
|
||||||
|
|||||||
@@ -257,6 +257,19 @@ func (s *AccountService) AcceptInvitation(
|
|||||||
return fmt.Errorf("cannot create membership: %w", err)
|
return fmt.Errorf("cannot create membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profile := &coredata.MembershipProfile{
|
||||||
|
ID: gid.New(tenantID, coredata.MembershipProfileEntityType),
|
||||||
|
MembershipID: membership.ID,
|
||||||
|
FullName: identity.FullName,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = profile.Insert(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot insert profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
invitation.AcceptedAt = &now
|
invitation.AcceptedAt = &now
|
||||||
err = invitation.Update(ctx, tx, scope)
|
err = invitation.Update(ctx, tx, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -425,7 +425,10 @@ func (s *OrganizationService) InviteMember(
|
|||||||
return fmt.Errorf("cannot parse base URL: %w", err)
|
return fmt.Errorf("cannot parse base URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
invitationURL := baseurl.WithPath("/auth/signup-from-invitation").WithQuery("token", invitationToken).MustString()
|
invitationURL := baseurl.WithPath("/auth/signup-from-invitation").
|
||||||
|
WithQuery("token", invitationToken).
|
||||||
|
WithQuery("fullName", invitation.FullName).
|
||||||
|
MustString()
|
||||||
|
|
||||||
subject, textBody, htmlBody, err := emails.RenderInvitation(
|
subject, textBody, htmlBody, err := emails.RenderInvitation(
|
||||||
s.baseURL,
|
s.baseURL,
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ input SignUpInput {
|
|||||||
input SignUpFromInvitationInput {
|
input SignUpFromInvitationInput {
|
||||||
token: String!
|
token: String!
|
||||||
password: String!
|
password: String!
|
||||||
|
fullName: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input ForgotPasswordInput {
|
input ForgotPasswordInput {
|
||||||
|
|||||||
@@ -2455,6 +2455,7 @@ input SignUpInput {
|
|||||||
input SignUpFromInvitationInput {
|
input SignUpFromInvitationInput {
|
||||||
token: String!
|
token: String!
|
||||||
password: String!
|
password: String!
|
||||||
|
fullName: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input ForgotPasswordInput {
|
input ForgotPasswordInput {
|
||||||
@@ -12877,7 +12878,7 @@ func (ec *executionContext) unmarshalInputSignUpFromInvitationInput(ctx context.
|
|||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"token", "password"}
|
fieldsInOrder := [...]string{"token", "password", "fullName"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -12898,6 +12899,13 @@ func (ec *executionContext) unmarshalInputSignUpFromInvitationInput(ctx context.
|
|||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
it.Password = data
|
it.Password = data
|
||||||
|
case "fullName":
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
|
||||||
|
data, err := ec.unmarshalNString2string(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
|
it.FullName = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -410,6 +410,7 @@ type SignOutPayload struct {
|
|||||||
type SignUpFromInvitationInput struct {
|
type SignUpFromInvitationInput struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignUpFromInvitationPayload struct {
|
type SignUpFromInvitationPayload struct {
|
||||||
|
|||||||
@@ -440,6 +440,7 @@ func (r *mutationResolver) SignUpFromInvitation(ctx context.Context, input types
|
|||||||
&iam.CreateIdentityFromInvitationRequest{
|
&iam.CreateIdentityFromInvitationRequest{
|
||||||
InvitationToken: input.Token,
|
InvitationToken: input.Token,
|
||||||
Password: input.Password,
|
Password: input.Password,
|
||||||
|
FullName: input.FullName,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user