diff --git a/apps/console/src/pages/iam/organizations/NewOrganizationPage.tsx b/apps/console/src/pages/iam/organizations/NewOrganizationPage.tsx new file mode 100644 index 000000000..886a1d560 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/NewOrganizationPage.tsx @@ -0,0 +1,99 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Card, Field, PageHeader, useToast } from "@probo/ui"; +import { graphql, useMutation } from "react-relay"; +import type { FormEventHandler } from "react"; +import { useNavigate } from "react-router"; +import { formatError, type GraphQLError } from "@probo/helpers"; +import type { NewOrganizationPageMutation } from "./__generated__/NewOrganizationPageMutation.graphql"; + +const createOrganizationMutation = graphql` + mutation NewOrganizationPageMutation($input: CreateOrganizationInput!) { + createOrganization(input: $input) { + organization { + id + name + } + } + } +`; + +export default function Page() { + const navigate = useNavigate(); + const { toast } = useToast(); + const { __ } = useTranslate(); + + const [createOrganization, isCreating] = + useMutation(createOrganizationMutation); + + const handleSubmit: FormEventHandler = async (e) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const name = formData.get("name")?.toString(); + if (!name) { + toast({ + title: __("Error"), + description: __("Name is required"), + variant: "error", + }); + return; + } + + createOrganization({ + variables: { + input: { + name, + }, + }, + onCompleted: (r) => { + const org = r.createOrganization!.organization; + navigate(`/organizations/${org!.id}`); + toast({ + title: __("Success"), + description: __("Organization has been created successfully"), + variant: "success", + }); + }, + onError: (e: GraphQLError) => { + toast({ + title: __("Error"), + description: formatError(__("Failed to create organization"), e), + variant: "error", + }); + }, + }); + }; + + return ( +
+ + +
+

+ {__("Organization Details")} +

+

+ {__("Enter the basic information about your organization.")} +

+ + + +
+
+ ); +} diff --git a/apps/console/src/pages/iam/organizations/__generated__/NewOrganizationPageMutation.graphql.ts b/apps/console/src/pages/iam/organizations/__generated__/NewOrganizationPageMutation.graphql.ts new file mode 100644 index 000000000..3c0bbc451 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/__generated__/NewOrganizationPageMutation.graphql.ts @@ -0,0 +1,115 @@ +/** + * @generated SignedSource<<3c4bf3ac3f3eff32d0c86fe61190576e>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type CreateOrganizationInput = { + horizontalLogoFile?: any | null | undefined; + logoFile?: any | null | undefined; + name: string; +}; +export type NewOrganizationPageMutation$variables = { + input: CreateOrganizationInput; +}; +export type NewOrganizationPageMutation$data = { + readonly createOrganization: { + readonly organization: { + readonly id: string; + readonly name: string; + } | null | undefined; + } | null | undefined; +}; +export type NewOrganizationPageMutation = { + response: NewOrganizationPageMutation$data; + variables: NewOrganizationPageMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "CreateOrganizationPayload", + "kind": "LinkedField", + "name": "createOrganization", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Organization", + "kind": "LinkedField", + "name": "organization", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "NewOrganizationPageMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "NewOrganizationPageMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "e4d07eba4c1791eeb6e5e1e627c79593", + "id": null, + "metadata": {}, + "name": "NewOrganizationPageMutation", + "operationKind": "mutation", + "text": "mutation NewOrganizationPageMutation(\n $input: CreateOrganizationInput!\n) {\n createOrganization(input: $input) {\n organization {\n id\n name\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "68737a40f2993357fb169e9aac6a4262"; + +export default node; diff --git a/apps/console/src/routes.tsx b/apps/console/src/routes.tsx index c52244f1f..8b023de54 100644 --- a/apps/console/src/routes.tsx +++ b/apps/console/src/routes.tsx @@ -51,6 +51,10 @@ import { import { OrganizationsPageQuery } from "./pages/iam/organizations/OrganizationsPageQuery.tsx"; import { OrganizationLayoutQuery } from "./pages/iam/organizations/OrganizationLayoutQuery.tsx"; +const NewOrganizationPage = lazy( + () => import("./pages/iam/organizations/NewOrganizationPage"), +); + /** * Top level error boundary */ @@ -109,11 +113,7 @@ const routes = [ }, { path: "/", - Component: () => ( - - - - ), + Component: CenteredLayout, Fallback: CenteredLayoutSkeleton, ErrorBoundary: ErrorBoundary, children: [ @@ -125,12 +125,14 @@ const routes = [ ), }, - // { - // path: "organizations/new", - // Component: lazy( - // () => import("./pages/organizations/NewOrganizationPage") - // ), - // }, + { + path: "organizations/new", + Component: () => ( + + + + ), + }, { path: "documents/signing-requests", Component: lazy( diff --git a/pkg/iam/organization_service.go b/pkg/iam/organization_service.go index 85768a5f4..1bb8faa67 100644 --- a/pkg/iam/organization_service.go +++ b/pkg/iam/organization_service.go @@ -101,14 +101,18 @@ func (req CreateOrganizationRequest) Validate() error { v := validator.New() fv := filevalidation.NewValidator(filevalidation.WithCategories(filevalidation.CategoryImage)) - err := fv.Validate(req.LogoFile.Filename, req.LogoFile.ContentType, req.LogoFile.Size) - if err != nil { - return fmt.Errorf("invalid logo file: %w", err) + if req.LogoFile != nil { + err := fv.Validate(req.LogoFile.Filename, req.LogoFile.ContentType, req.LogoFile.Size) + if err != nil { + return fmt.Errorf("invalid logo file: %w", err) + } } - err = fv.Validate(req.HorizontalLogoFile.Filename, req.HorizontalLogoFile.ContentType, req.HorizontalLogoFile.Size) - if err != nil { - return fmt.Errorf("invalid horizontal logo file: %w", err) + if req.HorizontalLogoFile != nil { + err := fv.Validate(req.HorizontalLogoFile.Filename, req.HorizontalLogoFile.ContentType, req.HorizontalLogoFile.Size) + if err != nil { + return fmt.Errorf("invalid horizontal logo file: %w", err) + } } v.Check(req.Name, "name", validator.Required(), validator.SafeTextNoNewLine(255)) @@ -378,7 +382,11 @@ func (s *OrganizationService) InviteMember( return invitation, nil } -func (s *OrganizationService) CreateOrganization(ctx context.Context, identityID gid.GID, req *CreateOrganizationRequest) (*coredata.Organization, error) { +func (s *OrganizationService) CreateOrganization( + ctx context.Context, + identityID gid.GID, + req *CreateOrganizationRequest, +) (*coredata.Organization, error) { if err := req.Validate(); err != nil { return nil, fmt.Errorf("invalid request: %w", err) }