Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/ui-community/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@dr.pogodin/react-helmet": "^3.0.2",
"@graphql-typed-document-node/core": "^3.2.0",
"@ocom/ui-components": "workspace:*",
"antd": "^5.27.0",
"antd": "^6.0.0",
"apollo-link-rest": "^0.9.0",
"dayjs": "^1.11.19",
"less": "^4.4.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
mutation AdminMemberAddContainerMemberAdd($input: MemberAddInput!) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (review_instructions): This container GraphQL file’s path includes an apps/ prefix, so it doesn’t strictly follow the specified ui-<PortalName>/src/... path pattern.

The convention states that container GraphQL files should be at:

ui-<PortalName>/src/components/layouts/<AreaName>/components/<componentName>.container.graphql.

This file is at apps/ui-community/src/components/layouts/admin/components/member-add-modal.container.graphql, which doesn’t strictly adhere to that pattern because of the apps/ prefix. If the documented convention is still current, consider relocating/renaming so the path conforms to the expected structure.

Review instructions:

Path patterns: **/*.container.graphql

Instructions:
container graphql files should be found in the following path pattern: ui-/src/components/layouts//components/.container.graphql

memberAdd(input: $input) {
status {
success
errorMessage
}
member {
...AdminMemberAddContainerMemberFields
}
}
}

fragment AdminMemberAddContainerMemberFields on Member {
id
memberName
accounts {
id
firstName
lastName
statusCode
}
createdAt
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { App } from 'antd';
import { useMutation } from '@apollo/client';
import { AdminMemberAddContainerMemberAddDocument, AdminMemberListContainerMembersByCommunityIdDocument } from '../../../../generated.tsx';
import { MemberAddModal } from './member-add-modal.tsx';

export const MemberAddModalContainer: React.FC<{ communityId: string; open: boolean; onClose: () => void }> = ({ communityId, open, onClose }) => {
const { message } = App.useApp();

const [addMember, { loading }] = useMutation(AdminMemberAddContainerMemberAddDocument, {
refetchQueries: [{ query: AdminMemberListContainerMembersByCommunityIdDocument, variables: { communityId } }],
});

const handleAdd = async (values: { memberName: string; firstName: string; lastName?: string; userExternalId: string }) => {
try {
const result = await addMember({
variables: {
input: {
communityId,
memberName: values.memberName,
firstName: values.firstName,
lastName: values.lastName,
userExternalId: values.userExternalId,
},
},
});
if (result.data?.memberAdd?.status?.success) {
void message.success('Member added successfully');
onClose();
} else {
void message.error(result.data?.memberAdd?.status?.errorMessage ?? 'Failed to add member');
}
} catch (error) {
void message.error((error as Error).message ?? 'Failed to add member');
}
};

return (
<MemberAddModal
open={open}
loading={loading}
onAdd={handleAdd}
onCancel={onClose}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from 'storybook/test';
import { MemberAddModal } from './member-add-modal.tsx';

const meta: Meta<typeof MemberAddModal> = {
title: 'Admin/MemberAddModal',
component: MemberAddModal,
args: {
open: true,
loading: false,
onAdd: fn(),
onCancel: fn(),
},
};

export default meta;
type Story = StoryObj<typeof MemberAddModal>;

export const Default: Story = {};

export const Loading: Story = {
args: {
loading: true,
},
};

export const Closed: Story = {
args: {
open: false,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Button, Form, Input, Modal } from 'antd';

interface MemberAddFormValues {
memberName: string;
firstName: string;
lastName?: string;
userExternalId: string;
}

export const MemberAddModal: React.FC<{ open: boolean; loading?: boolean; onAdd: (values: MemberAddFormValues) => void; onCancel: () => void }> = ({ open, loading, onAdd, onCancel }) => {
const [form] = Form.useForm<MemberAddFormValues>();

const handleOk = () => {
form.validateFields().then((values) => {
onAdd(values);
form.resetFields();
});
};

const handleCancel = () => {
form.resetFields();
onCancel();
};

return (
<Modal
title="Add Member"
open={open}
onCancel={handleCancel}
footer={[
<Button
key="cancel"
onClick={handleCancel}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
loading={loading}
onClick={handleOk}
>
Add Member
</Button>,
]}
>
<Form
form={form}
layout="vertical"
name="member-add-form"
>
<Form.Item
name="memberName"
label="Member Name"
rules={[{ required: true, message: 'Please enter a member name' }]}
>
<Input placeholder="e.g. John Doe" />
</Form.Item>
<Form.Item
name="firstName"
label="First Name"
rules={[{ required: true, message: 'Please enter first name' }]}
>
<Input placeholder="First name" />
</Form.Item>
<Form.Item
name="lastName"
label="Last Name"
>
<Input placeholder="Last name (optional)" />
</Form.Item>
<Form.Item
name="userExternalId"
label="User External ID"
rules={[{ required: true, message: 'Please enter the user external ID' }]}
>
<Input placeholder="e.g. auth0|123456" />
</Form.Item>
</Form>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
query AdminMemberListContainerMembersByCommunityId($communityId: ObjectID!) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (review_instructions): This container GraphQL file is under apps/ui-community/..., which doesn’t match the specified ui-<PortalName>/src/... path pattern.

Per the instructions, container GraphQL files should live under a path like ui-<PortalName>/src/components/layouts/<AreaName>/components/<componentName>.container.graphql.

This file is currently located at apps/ui-community/src/components/layouts/admin/components/member-list.container.graphql, which introduces an extra apps/ segment and doesn’t strictly match the required pattern.

If the convention is still accurate, this file should be moved/renamed so its path matches the ui-<PortalName>/src/... pattern (for example, ui-community/src/components/layouts/admin/components/member-list.container.graphql).

Review instructions:

Path patterns: **/*.container.graphql

Instructions:
container graphql files should be found in the following path pattern: ui-/src/components/layouts//components/.container.graphql

membersByCommunityId(communityId: $communityId) {
...AdminMemberListContainerMemberFields
}
}

mutation AdminMemberListContainerMemberRemove($input: MemberRemoveInput!) {
memberRemove(input: $input) {
status {
success
errorMessage
}
member {
...AdminMemberListContainerMemberFields
}
}
}

fragment AdminMemberListContainerMemberFields on Member {
id
memberName
isAdmin
createdAt
updatedAt
profile {
name
email
avatarDocumentId
}
accounts {
id
firstName
lastName
statusCode
createdAt
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { App } from 'antd';
import { useMutation, useQuery } from '@apollo/client';
import { ComponentQueryLoader } from '@cellix/ui-core';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import type { AdminMemberListContainerMemberFieldsFragment } from '../../../../generated.tsx';
import { AdminMemberListContainerMemberRemoveDocument, AdminMemberListContainerMembersByCommunityIdDocument } from '../../../../generated.tsx';
import { MemberList, type MemberListProps } from './member-list.tsx';
import { MemberAddModalContainer } from './member-add-modal.container.tsx';

export const MemberListContainer: React.FC = () => {
const { message } = App.useApp();
const params = useParams();
// biome-ignore lint:useLiteralKeys
const communityId = params['communityId'] ?? '';
const [addModalOpen, setAddModalOpen] = useState(false);

const {
data: membersData,
loading: membersLoading,
error: membersError,
} = useQuery(AdminMemberListContainerMembersByCommunityIdDocument, {
variables: { communityId },
skip: !communityId,
});

const [removeMember, { loading: removeLoading }] = useMutation(AdminMemberListContainerMemberRemoveDocument, {
refetchQueries: [{ query: AdminMemberListContainerMembersByCommunityIdDocument, variables: { communityId } }],
});

const handleRemove = async (memberId: string) => {
try {
const result = await removeMember({ variables: { input: { memberId } } });
if (result.data?.memberRemove?.status?.success) {
void message.success('Member removed successfully');
} else {
void message.error(result.data?.memberRemove?.status?.errorMessage ?? 'Failed to remove member');
}
} catch (error) {
void message.error((error as Error).message ?? 'Failed to remove member');
}
};

const memberListProps: MemberListProps = {
data: (membersData?.membersByCommunityId ?? []) as AdminMemberListContainerMemberFieldsFragment[],
onAdd: () => setAddModalOpen(true),
onRemove: handleRemove,
removeLoading,
};

return (
<>
<ComponentQueryLoader
loading={membersLoading}
hasData={membersData}
hasDataComponent={<MemberList {...memberListProps} />}
error={membersError}
noDataComponent={
<MemberList
data={[]}
onAdd={() => setAddModalOpen(true)}
/>
}
/>
<MemberAddModalContainer
communityId={communityId}
open={addModalOpen}
onClose={() => setAddModalOpen(false)}
/>
</>
);
};
Loading
Loading