Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
64181e3
Initial plan
Copilot Dec 19, 2025
3f11e8b
Add application service and GraphQL mutation for updating community s…
Copilot Dec 19, 2025
4290e5e
Add admin layout with community settings page
Copilot Dec 19, 2025
0ffbfeb
Fix linting issue in settings-general container
Copilot Dec 19, 2025
e20e335
Update container and presentational component implementation guidelin…
nnoce14 Dec 22, 2025
d9d4132
Update components to follow GraphQL integration guidelines from instr…
Copilot Dec 22, 2025
5c4ae78
Update container and presentational component guidelines for GraphQL …
nnoce14 Dec 22, 2025
193b915
Fix NodeEventBus broadcast to await listener promises
Copilot Dec 22, 2025
63c587f
Add Storybook stories and fix async/await pattern in container
Copilot Dec 22, 2025
2863933
Fix member creation by pre-populating community in adapter
Copilot Dec 22, 2025
626e5b8
Refactor community handling in MemberDomainAdapter and EndUserRoleDom…
nnoce14 Dec 22, 2025
d486a09
Align admin routing and layout with legacy codebase structure
Copilot Dec 22, 2025
6acf123
Align admin components with legacy Owner Community implementation
Copilot Dec 22, 2025
656db1a
Enhance community settings page: update mutation response handling, i…
nnoce14 Dec 23, 2025
70ffa6c
Update container and presentational component guidelines: clarify loa…
nnoce14 Dec 23, 2025
261a633
Fix mutation response handling in settings-general container
Copilot Dec 23, 2025
a85bbe9
Align admin section-layout with legacy implementation
Copilot Dec 23, 2025
29bfc45
Refactor community settings components to use App hook for message ha…
nnoce14 Dec 23, 2025
c0e1aeb
Changes before error encountered
Copilot Dec 23, 2025
c088a62
Changes before error encountered
Copilot Dec 23, 2025
98beac2
Enhance community settings and dropdown components; update routing pa…
nnoce14 Dec 23, 2025
3bd3b16
Changes before error encountered
Copilot Dec 23, 2025
d76ce44
Address linting issues and fix ui-community package vitest issue; boo…
nnoce14 Jan 5, 2026
6352694
Update express dependency specifier to ^4.22.0 in pnpm-lock.yaml
nnoce14 Jan 5, 2026
3cd571a
Refactor path handling to use replaceAll for consistency in MenuCompo…
nnoce14 Jan 5, 2026
1991382
Remove pnpm strict-ssl configuration step from Copilot setup workflow
nnoce14 Jan 8, 2026
e6d2af6
Add step to display proxy and CA related environment variables in Cop…
nnoce14 Jan 8, 2026
181c05a
Refactor 'Show proxy / CA related env' step to remove unnecessary lin…
nnoce14 Jan 8, 2026
98556d5
Fix grep command in 'Show proxy / CA related env' step for improved c…
nnoce14 Jan 8, 2026
ac578fb
Improve proxy environment variable display step by adding error handl…
nnoce14 Jan 8, 2026
109a0de
Fix grep command in 'Show proxy / CA related env' step to ensure prop…
nnoce14 Jan 8, 2026
35e9f84
Update CA certificates installation step to ensure proper environment…
nnoce14 Jan 8, 2026
1e2fe4d
Merge branch 'main' into copilot/implement-community-settings-page
nnoce14 Jan 8, 2026
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
13 changes: 12 additions & 1 deletion .github/instructions/ui/container-components.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,19 @@ applyTo: "packages/ui-*/src/components/**/*.container.tsx"
- Component name must match file name in PascalCase.
- Each container must define a `{ComponentName}ContainerProps` type for its props.
- Use strict TypeScript types for all state, props, and API responses.
- Pass GraphQL query results directly to the presentational component's props without explicit mapping or transformation. Rendering logic and data formatting should be handled by the presentational component.
- When performing mutations or queries, pass the `loading` state from the Apollo hooks (`useQuery`, `useMutation`) directly to the presentational component to ensure accurate UI feedback. This is critical for mutations to ensure that buttons or actions triggered by the user show a loading state and are disabled during processing. Avoid creating redundant local state for loading.
- After a mutation that creates, updates, or deletes data, ensure the Apollo cache is updated so the UI reflects the changes. Note that Apollo automatically handles cache updates for single documents when the `id` and `__typename` match. Manual cache updates via the `update` function are typically only required for queries/mutations involving lists of documents (e.g., adding/removing items). Prefer manual updates over `refetchQueries` for better performance and immediate UI updates in these scenarios.
- When handling mutations or async operations, use `async/await` consistently. Avoid mixing `.then()` with `await`.
- **Mutation Response Handling**: Container components are responsible for processing mutation results and providing user feedback.
- **REQUIRED**: Use the `App.useApp()` hook from `antd` to access the `message`, `notification`, or `modal` instances. Do NOT use static imports like `import { message } from 'antd'`.
- Always check the response for a `status` object (e.g., `result.data?.mutationName?.status`).
- Use `message.success()` when `status.success` is true.
- Use `message.error()` when `status.success` is false, displaying the `status.errorMessage` if available.
- Wrap mutation calls in `try/catch` blocks to handle network or execution errors, displaying them via `message.error()`.
- Use kebab-case for file and directory names.
- Provide handler functions through display component props for all relevant actions (e.g., handleClick, handleChange, handleSubmit, handleSave).
- **Knip Compliance**: To satisfy `knip` (unused export detection) while maintaining exports for Storybook/Testing, use the presentational component's exported `Props` type to define a typed object before passing it to the component. Prefer `<Component {...props} />` with a typed `props` object over inline casting like `<Component data={data as PropType} />`.

## State Management

Expand All @@ -31,7 +42,7 @@ applyTo: "packages/ui-*/src/components/**/*.container.tsx"
## Data Fetching

- Use Apollo Client hooks for GraphQL queries and mutations.
- Leverage the shared `ComponentQueryLoader` component for consistent data fetching, loading, and error handling.
- Leverage the shared `ComponentQueryLoader` component for consistent data fetching, loading, and error handling. Ensure `ComponentQueryLoader` is used for all data-fetching containers, providing a `noDataComponent` where appropriate.

## Error Handling

Expand Down
1 change: 1 addition & 0 deletions .github/instructions/ui/graphql-ui.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ applyTo: "**/ui-*/src/components/**/*.graphql"


- Import `.graphql` files into TypeScript/JS files using codegen-generated types for type safety.
- Presentational components should use the generated fragment types from their corresponding `.container.graphql` file to type the data they expect in their props. The presentational component is responsible for any necessary data conversion or formatting (e.g., date formatting) for display.
- Use Apollo Client hooks (`useQuery`, `useMutation`, etc.) with imported queries/mutations.
- Co-locate fragments with the components that use them for maintainability.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ applyTo: "**/ui-*/src/components/**/!(*.container).tsx"
- Use functional components and React hooks for local UI state only.
- Component name must match file name in PascalCase.
- Define a `{ComponentName}Props` type for all props.
- When a component receives data from a container's GraphQL query, use the generated fragment type from the corresponding `.container.graphql` file to type the data property in `{ComponentName}Props`. The presentational component is responsible for any necessary data conversion or formatting (e.g., date formatting) for display.
- For components containing forms, use the `data` prop (typed with the GraphQL fragment) to populate the form's `initialValues`. Do not use `defaultValue` on individual `Input` or `Form.Item` components if `initialValues` is provided at the `Form` level.
- Prefer derived state over `useState` for data that can be computed from props. If you must use local state for filtering or searching, ensure the component correctly reacts to prop changes (e.g., by using the props directly in the render logic or using `useMemo`).
- Avoid managing local loading state for operations triggered by handler props (e.g., `onSave`). Instead, accept a `loading` prop from the container to reflect the actual state of the operation (e.g., from `useMutation`). **REQUIRED**: Any button or action that triggers a mutation MUST apply this `loading` prop directly to the UI component's `loading` property (e.g., `<Button loading={loading} ... />`). This ensures the effect is visible on the UI (e.g., showing a spinner) and prevents multiple submissions while the operation is in progress.
- REQUIRED: Every presentational component MUST have a corresponding `.stories.tsx` file and a `.test.tsx` file. Do not consider a component complete without these.
- Use strict TypeScript types for all props and local state.
- Use kebab-case for file and directory names.
- Do not perform side effects, API calls, or business logic in presentational components.
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-

- name: Refresh CA certificates
run: |
sudo apt-get update
sudo apt-get install -y ca-certificates
sudo update-ca-certificates

- name: Install JavaScript dependencies
run: pnpm install --frozen-lockfile

Expand Down
7 changes: 6 additions & 1 deletion .snyk
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ ignore:
- '* > qs':
reason: 'Transitive dependency in express, @docusaurus/core, @apollo/server, apollo-link-rest; not exploitable in current usage.'
expires: '2026-01-19T00:00:00.000Z'
created: '2026-01-05T09:39:00.000Z'
created: '2026-01-05T09:39:00.000Z'
'SNYK-JS-PNPMNPMCONF-14897556':
- '* > @pnpm/npm-conf':
reason: 'Transitive dependency in @docusaurus/core; not exploitable in current usage.'
expires: '2026-01-22T00:00:00.000Z'
created: '2026-01-08T11:04:00.000Z'
199 changes: 100 additions & 99 deletions apps/ui-community/.storybook/apollo-mocks.ts
Original file line number Diff line number Diff line change
@@ -1,116 +1,117 @@
import {
AccountsCommunityListContainerCommunitiesForCurrentEndUserDocument,
AccountsCommunityListContainerMembersForCurrentEndUserDocument,
AccountsUserInfoContainerCurrentEndUserAndCreateIfNotExistsDocument,
AccountsCommunityListContainerCommunitiesForCurrentEndUserDocument,
AccountsCommunityListContainerMembersForCurrentEndUserDocument,
AccountsUserInfoContainerCurrentEndUserAndCreateIfNotExistsDocument,
} from '../src/generated';

// Mock data for communities
const mockCommunities = [
{
__typename: 'Community' as const,
name: 'Sample Community 1',
domain: 'sample1.example.com',
whiteLabelDomain: null,
handle: 'sample1',
publicContentBlobUrl: 'https://example.com/content/sample1',
schemaVersion: '1.0.0',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
id: 'community-1',
},
{
__typename: 'Community' as const,
name: 'Sample Community 2',
domain: 'sample2.example.com',
whiteLabelDomain: 'custom.sample2.com',
handle: 'sample2',
publicContentBlobUrl: 'https://example.com/content/sample2',
schemaVersion: '1.0.0',
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
id: 'community-2',
},
{
__typename: 'Community' as const,
name: 'Sample Community 1',
domain: 'sample1.example.com',
whiteLabelDomain: null,
handle: 'sample1',
publicContentBlobUrl: 'https://example.com/content/sample1',
schemaVersion: '1.0.0',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
id: 'community-1',
},
{
__typename: 'Community' as const,
name: 'Sample Community 2',
domain: 'sample2.example.com',
whiteLabelDomain: 'custom.sample2.com',
handle: 'sample2',
publicContentBlobUrl: 'https://example.com/content/sample2',
schemaVersion: '1.0.0',
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
id: 'community-2',
},
];

// Mock data for members
const mockMembers = [
{
__typename: 'Member' as const,
memberName: 'John Doe',
isAdmin: true,
id: 'member-1',
community: {
__typename: 'Community' as const,
id: 'community-1',
},
},
{
__typename: 'Member' as const,
memberName: 'Jane Smith',
isAdmin: false,
id: 'member-2',
community: {
__typename: 'Community' as const,
id: 'community-1',
},
},
{
__typename: 'Member' as const,
memberName: 'Bob Johnson',
isAdmin: true,
id: 'member-3',
community: {
__typename: 'Community' as const,
id: 'community-2',
},
},
{
__typename: 'Member' as const,
memberName: 'John Doe',
isAdmin: true,
id: 'member-1',
community: {
__typename: 'Community' as const,
id: 'community-1',
},
},
{
__typename: 'Member' as const,
memberName: 'Jane Smith',
isAdmin: false,
id: 'member-2',
community: {
__typename: 'Community' as const,
id: 'community-1',
},
},
{
__typename: 'Member' as const,
memberName: 'Bob Johnson',
isAdmin: true,
id: 'member-3',
community: {
__typename: 'Community' as const,
id: 'community-2',
},
},
];

// Mock data for current end user
const mockCurrentEndUser = {
__typename: 'EndUser' as const,
externalId: 'user-123',
id: 'enduser-1',
personalInformation: {
__typename: 'EndUserPersonalInformation' as const,
identityDetails: {
__typename: 'EndUserIdentityDetails' as const,
lastName: 'Doe',
restOfName: 'John',
},
},
__typename: 'EndUser' as const,
externalId: 'user-123',
id: 'enduser-1',
personalInformation: {
__typename: 'EndUserPersonalInformation' as const,
identityDetails: {
__typename: 'EndUserIdentityDetails' as const,
lastName: 'Doe',
restOfName: 'John',
},
},
};

// Apollo Client mocks for Storybook
export const apolloMocks = [
{
request: {
query: AccountsCommunityListContainerCommunitiesForCurrentEndUserDocument,
},
result: {
data: {
communitiesForCurrentEndUser: mockCommunities,
},
},
},
{
request: {
query: AccountsCommunityListContainerMembersForCurrentEndUserDocument,
},
result: {
data: {
membersForCurrentEndUser: mockMembers,
},
},
},
{
request: {
query: AccountsUserInfoContainerCurrentEndUserAndCreateIfNotExistsDocument,
},
result: {
data: {
currentEndUserAndCreateIfNotExists: mockCurrentEndUser,
},
},
},
];
{
request: {
query: AccountsCommunityListContainerCommunitiesForCurrentEndUserDocument,
},
result: {
data: {
communitiesForCurrentEndUser: mockCommunities,
},
},
},
{
request: {
query: AccountsCommunityListContainerMembersForCurrentEndUserDocument,
},
result: {
data: {
membersForCurrentEndUser: mockMembers,
},
},
},
{
request: {
query:
AccountsUserInfoContainerCurrentEndUserAndCreateIfNotExistsDocument,
},
result: {
data: {
currentEndUserAndCreateIfNotExists: mockCurrentEndUser,
},
},
},
];
41 changes: 19 additions & 22 deletions apps/ui-community/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import { dirname, join} from "node:path"
import { dirname, join } from 'node:path';
import type { StorybookConfig } from '@storybook/react-vite';


/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string) {
return dirname(require.resolve(join(value, 'package.json')))
return dirname(require.resolve(join(value, 'package.json')));
}
const config: StorybookConfig = {
stories: [
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
addons: [
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-docs'),
getAbsolutePath('@storybook/addon-onboarding'),
getAbsolutePath("@storybook/addon-a11y"),
getAbsolutePath("@storybook/addon-vitest"),
getAbsolutePath('storybook-addon-apollo-client')
],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
options: {}
}
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-docs'),
getAbsolutePath('@storybook/addon-onboarding'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('@storybook/addon-vitest'),
getAbsolutePath('storybook-addon-apollo-client'),
],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
options: {},
},
};
export default config;
export default config;
Loading