Skip to content

Commit b200009

Browse files
authored
BA-2267: generate edit username (#221)
* feat: edit username * chore: versioning * fix: PR fixes
1 parent eff7922 commit b200009

File tree

47 files changed

+854
-65
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+854
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as withTokenSetup } from './withTokenSetup'
22
export { default as withProviders } from './withProviders'
3+
export { default as withMutationResolver } from './withMutationResolver'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, { useEffect } from 'react'
2+
3+
import { createTestEnvironment } from '@baseapp-frontend/graphql'
4+
5+
import type { StoryContext, StoryFn } from '@storybook/react'
6+
import { Observable, OperationDescriptor } from 'relay-runtime'
7+
import { MockPayloadGenerator } from 'relay-test-utils'
8+
import { MockResolvers } from 'relay-test-utils/lib/RelayMockPayloadGenerator'
9+
10+
export type DynamicMockResolvers = (operation: OperationDescriptor) => MockResolvers
11+
12+
const withMutationResolver = (Story: StoryFn, context: StoryContext) => {
13+
const { environment, queueOperationResolver } = context.parameters
14+
.relayMockEnvironment as ReturnType<typeof createTestEnvironment>
15+
16+
const mockResolvers = context.parameters.mockResolvers || undefined
17+
const dynamicMockResolvers: DynamicMockResolvers =
18+
context.parameters.dynamicMockResolvers || undefined
19+
const mutationError: string = context.parameters.mutationError || undefined
20+
21+
useEffect(() => {
22+
const originalExecuteMutation = environment.executeMutation
23+
24+
environment.executeMutation = (request) => {
25+
if (!mutationError) {
26+
if (dynamicMockResolvers) {
27+
environment.mock.queueOperationResolver(() => {
28+
return MockPayloadGenerator.generate(
29+
request.operation,
30+
dynamicMockResolvers(request.operation),
31+
)
32+
})
33+
} else if (mockResolvers) {
34+
environment.mock.queueOperationResolver(() => {
35+
return MockPayloadGenerator.generate(request.operation, mockResolvers)
36+
})
37+
}
38+
}
39+
40+
const observable = originalExecuteMutation.call(environment, request)
41+
42+
return Observable.create((sink) => {
43+
if (mutationError) {
44+
setTimeout(() => {
45+
sink.error(new Error(mutationError))
46+
}, 100)
47+
} else {
48+
observable.subscribe({
49+
complete: () => {
50+
setTimeout(() => {
51+
sink.complete()
52+
}, 100)
53+
},
54+
})
55+
}
56+
})
57+
}
58+
59+
return () => {
60+
environment.executeMutation = originalExecuteMutation
61+
}
62+
}, [environment, queueOperationResolver])
63+
64+
return <Story />
65+
}
66+
67+
export default withMutationResolver

packages/components/CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# @baseapp-frontend/components
22

3+
## 1.0.18
4+
5+
### Patch Changes
6+
7+
- Moved ProfileSettingsComponent and related queries and mutations from `baseapp-frontend-template`
8+
- Updated dependencies
9+
- @baseapp-frontend/design-system@1.0.7
10+
311
## 1.0.17
412

513
### Patch Changes

packages/components/modules/messages/web/CreateChatRoomList/ChatRoomListItem/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const ChatRoomListItem: FC<ChatRoomListItemProps> = ({ profile: profileRef, onCh
3232
<Box sx={{ display: 'grid', gridTemplateRows: 'repeat(2, minmax(0, 1fr))' }}>
3333
<TypographyWithEllipsis variant="subtitle2">{name}</TypographyWithEllipsis>
3434
<Typography variant="caption" color="text.secondary">
35-
{urlPath?.path && `@${urlPath.path}`}
35+
{urlPath?.path && `@${urlPath.path?.replace('/', '')}`}
3636
</Typography>
3737
</Box>
3838
<LoadingButton

packages/components/modules/profiles/common/constants.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ export const DEFAULT_PROFILE_FORM_VALIDATION = z.object({
3030
[PROFILE_FORM_VALUE.image]: z.any(),
3131
[PROFILE_FORM_VALUE.name]: z.string().min(1, { message: PROFILE_FORM_VALIDATION.name.empty }),
3232
[PROFILE_FORM_VALUE.phoneNumber]: z.string(),
33-
[PROFILE_FORM_VALUE.urlPath]: z.string(),
33+
[PROFILE_FORM_VALUE.urlPath]: z
34+
.string()
35+
.min(8, { message: PROFILE_FORM_VALIDATION.urlPath.empty })
36+
.regex(/^[a-zA-Z0-9]+$/, { message: PROFILE_FORM_VALIDATION.urlPath.invalid }),
3437
})
3538

3639
// .svg is not supported by the backend, so better not use 'image/*'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { graphql } from 'react-relay'
2+
3+
export const ProfilesListFragment = graphql`
4+
fragment ProfilesListFragment on User
5+
@argumentDefinitions(count: { type: "Int", defaultValue: 10 }, cursor: { type: "String" })
6+
@refetchable(queryName: "profilesListRefetchable") {
7+
id
8+
profiles(first: $count, after: $cursor) @connection(key: "ProfilesListFragment_profiles") {
9+
edges {
10+
cursor
11+
node {
12+
id
13+
...ProfileItemFragment
14+
}
15+
}
16+
pageInfo {
17+
hasNextPage
18+
endCursor
19+
}
20+
}
21+
}
22+
`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { graphql } from 'react-relay'
2+
3+
export const UserMembersListFragment = graphql`
4+
fragment UserMembersListFragment on Profile
5+
@refetchable(queryName: "userMembersListPaginationRefetchable")
6+
@argumentDefinitions(
7+
count: { type: "Int", defaultValue: 10 }
8+
cursor: { type: "String" }
9+
orderBy: { type: "String" }
10+
q: { type: "String" }
11+
) {
12+
canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole")
13+
...ProfileItemFragment
14+
members(first: $count, after: $cursor, orderBy: $orderBy, q: $q)
15+
@connection(key: "UserMembersFragment_members", filters: ["orderBy", "q"]) {
16+
totalCount
17+
edges {
18+
node {
19+
...MemberItemFragment
20+
}
21+
}
22+
pageInfo {
23+
endCursor
24+
hasNextPage
25+
}
26+
}
27+
}
28+
`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { graphql } from 'react-relay'
2+
3+
export const ProfileSettingsRelayTest = graphql`
4+
query ProfileSettingsRelayTestQuery @relay_test_operation {
5+
profile: node(id: "test-id") {
6+
...ProfileComponentFragment
7+
}
8+
}
9+
`

packages/components/modules/profiles/common/graphql/queries/ProfilesList.ts

-21
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,3 @@ export const ProfilesListQuery = graphql`
88
}
99
}
1010
`
11-
12-
export const ProfilesListFragment = graphql`
13-
fragment ProfilesListFragment on User
14-
@argumentDefinitions(count: { type: "Int", defaultValue: 10 }, cursor: { type: "String" })
15-
@refetchable(queryName: "profilesListRefetchable") {
16-
id
17-
profiles(first: $count, after: $cursor) @connection(key: "ProfilesListFragment_profiles") {
18-
edges {
19-
cursor
20-
node {
21-
id
22-
...ProfileItemFragment
23-
}
24-
}
25-
pageInfo {
26-
hasNextPage
27-
endCursor
28-
}
29-
}
30-
}
31-
`

packages/components/modules/profiles/common/graphql/queries/UserMembersList.ts

-27
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,3 @@ export const UserMembersListPaginationQuery = graphql`
1515
}
1616
}
1717
`
18-
19-
export const UserMembersListFragment = graphql`
20-
fragment UserMembersListFragment on Profile
21-
@refetchable(queryName: "userMembersListPaginationRefetchable")
22-
@argumentDefinitions(
23-
count: { type: "Int", defaultValue: 10 }
24-
cursor: { type: "String" }
25-
orderBy: { type: "String" }
26-
q: { type: "String" }
27-
) {
28-
canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole")
29-
...ProfileItemFragment
30-
members(first: $count, after: $cursor, orderBy: $orderBy, q: $q)
31-
@connection(key: "UserMembersFragment_members", filters: ["orderBy", "q"]) {
32-
totalCount
33-
edges {
34-
node {
35-
...MemberItemFragment
36-
}
37-
}
38-
pageInfo {
39-
endCursor
40-
hasNextPage
41-
}
42-
}
43-
}
44-
`

packages/components/modules/profiles/common/index.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
// exports common profiles code
22

3+
export * from './graphql/fragments/AllProfilesList'
4+
export * from './graphql/fragments/BlockToggle'
5+
export * from './graphql/fragments/FollowToggleUpdatableFragment'
6+
export * from './graphql/fragments/MemberItem'
7+
export * from './graphql/fragments/ProfileComponent'
8+
export * from './graphql/fragments/ProfileItem'
9+
export * from './graphql/fragments/ProfilesList'
10+
export * from './graphql/fragments/UserMembersList'
11+
312
export * from './graphql/mutations/BlockToggle'
413
export * from './graphql/mutations/ChangeUserRole'
514
export * from './graphql/mutations/FollowToggle'
615
export * from './graphql/mutations/OrganizationCreate'
716
export * from './graphql/mutations/ProfileUpdate'
817

918
export * from './graphql/queries/AddProfilePopover'
10-
export * from './graphql/queries/AllProfilesList'
11-
export * from './graphql/queries/BlockToggle'
12-
export * from './graphql/queries/FollowToggleUpdatableFragment'
13-
export * from './graphql/queries/MemberItem'
14-
export * from './graphql/queries/ProfileComponent'
15-
export * from './graphql/queries/ProfileItem'
1619
export * from './graphql/queries/ProfilesList'
1720
export * from './graphql/queries/UserMembersList'
1821
export * from './graphql/queries/UserProfile'

packages/components/modules/profiles/common/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ export type UploadablesObj = {
1818
image?: File | Blob
1919
bannerImage?: File | Blob
2020
}
21+
22+
export interface ProfileGetDefaultFormValues {
23+
profile?: ProfileComponentFragment$data | null
24+
removeSlashInUsername?: boolean
25+
}

packages/components/modules/profiles/common/utils.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { getInitialValues } from '@baseapp-frontend/utils'
22

3-
import { ProfileComponentFragment$data } from '../../../__generated__/ProfileComponentFragment.graphql'
43
import { DEFAULT_PROFILE_FORM_VALUES } from './constants'
4+
import { ProfileGetDefaultFormValues } from './types'
55

66
// TODO: move this to a shared location, currently the template also uses it at apps/web/components/design-system/inputs/PhoneNumberField/constants.ts
77
export const DEFAULT_PHONE_NUMBER_COUNTRY_CODE = '+1'
88

9-
export const getDefaultValues = (profile?: ProfileComponentFragment$data | null) => {
9+
export const getProfileDefaultValues = ({
10+
profile,
11+
removeSlashInUsername = true,
12+
}: ProfileGetDefaultFormValues) => {
1013
const formattedProfile = {
1114
...profile,
1215
phoneNumber: profile?.owner?.phoneNumber ?? DEFAULT_PHONE_NUMBER_COUNTRY_CODE,
@@ -15,6 +18,9 @@ export const getDefaultValues = (profile?: ProfileComponentFragment$data | null)
1518
urlPath: profile?.urlPath?.path ?? '',
1619
biography: profile?.biography ?? '',
1720
}
21+
if (removeSlashInUsername) {
22+
formattedProfile.urlPath = formattedProfile.urlPath?.replace('/', '')
23+
}
1824
const defaultValues = getInitialValues({
1925
current: formattedProfile,
2026
initial: DEFAULT_PROFILE_FORM_VALUES,

packages/components/modules/profiles/common/zod.ts

+4
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ export const PROFILE_FORM_VALIDATION = {
1515
empty: 'Please enter a phone number.',
1616
invalid: 'Invalid phone number.',
1717
},
18+
urlPath: {
19+
empty: 'Username must be at least 8 characters long.',
20+
invalid: 'Username can only contain letters and numbers',
21+
},
1822
}

packages/components/modules/profiles/native/ProfileComponent/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const ProfileComponent = ({ profile: profileRef }: ProfileComponentProps) => {
3333
{profile?.name}
3434
</Text>
3535
<Text variant="body2" color="low">
36-
@{profile?.urlPath?.path}
36+
@{profile?.urlPath?.path?.replace('/', '')}
3737
</Text>
3838
</View>
3939
<View style={styles.statsContainer}>

packages/components/modules/profiles/native/ProfileSettingsComponent/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import {
2727
PROFILE_FORM_VALUE,
2828
ProfileComponentFragment,
2929
ProfileUpdateForm,
30-
getDefaultValues,
3130
getImageUrl,
31+
getProfileDefaultValues,
3232
useProfileMutation,
3333
} from '../../common'
3434
import BottomDrawer from './BottomDrawer'
@@ -46,7 +46,7 @@ const ProfileSettingsComponent: FC<ProfileSettingsComponentProps> = ({ profile:
4646
const { sendToast } = useNotification()
4747

4848
const formReturn = useForm<ProfileUpdateForm>({
49-
defaultValues: getDefaultValues(profile),
49+
defaultValues: getProfileDefaultValues({ profile }),
5050
resolver: zodResolver(DEFAULT_PROFILE_FORM_VALIDATION),
5151
mode: 'onBlur',
5252
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useLazyLoadQuery } from 'react-relay'
2+
3+
import { ProfileSettingsRelayTestQuery as QueryType } from '../../../../../__generated__/ProfileSettingsRelayTestQuery.graphql'
4+
import { ProfileSettingsRelayTest } from '../../../common/graphql/queries/ProfileSettingsRelayTest'
5+
import ProfileSettingsComponent from '../index'
6+
import { ProfileSettingsComponentProps } from '../types'
7+
8+
const ProfileSettingsComponentWithQuery = (props: ProfileSettingsComponentProps) => {
9+
const profileRef = useLazyLoadQuery<QueryType>(ProfileSettingsRelayTest, {})
10+
11+
return <ProfileSettingsComponent {...props} profile={profileRef.profile} />
12+
}
13+
14+
export default ProfileSettingsComponentWithQuery
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export const mockResolvers = {
2+
Node: () => ({
3+
__typename: 'Profile',
4+
id: '1',
5+
name: 'Profile 1',
6+
biography: 'Profile 1 biography',
7+
image: null,
8+
bannerImage: null,
9+
canChange: true,
10+
canDelete: true,
11+
urlPath: {
12+
path: '/profile/1',
13+
},
14+
owner: {
15+
phoneNumber: '1234567890',
16+
},
17+
}),
18+
Mutation: () => ({
19+
profileUpdate: {
20+
errors: null,
21+
},
22+
}),
23+
}
24+
25+
export const mockResolversWithMutationError = {
26+
...mockResolvers,
27+
Mutation: () => ({
28+
profileUpdate: {},
29+
}),
30+
}

0 commit comments

Comments
 (0)