Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/components/.storybook/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as withTokenSetup } from './withTokenSetup'
export { default as withProviders } from './withProviders'
export { default as withMutationResolver } from './withMutationResolver'
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useEffect } from 'react'

import { createTestEnvironment } from '@baseapp-frontend/graphql'

import type { StoryContext, StoryFn } from '@storybook/react'
import { Observable, OperationDescriptor } from 'relay-runtime'
import { MockPayloadGenerator } from 'relay-test-utils'
import { MockResolvers } from 'relay-test-utils/lib/RelayMockPayloadGenerator'

export type DynamicMockResolvers = (operation: OperationDescriptor) => MockResolvers

const withMutationResolver = (Story: StoryFn, context: StoryContext) => {
const { environment, queueOperationResolver } = context.parameters
.relayMockEnvironment as ReturnType<typeof createTestEnvironment>

const mockResolvers = context.parameters.mockResolvers || undefined
const dynamicMockResolvers: DynamicMockResolvers =
context.parameters.dynamicMockResolvers || undefined
const mutationError: string = context.parameters.mutationError || undefined

useEffect(() => {
const originalExecuteMutation = environment.executeMutation

environment.executeMutation = (request) => {
if (!mutationError) {
if (dynamicMockResolvers) {
environment.mock.queueOperationResolver(() => {
return MockPayloadGenerator.generate(
request.operation,
dynamicMockResolvers(request.operation),
)
})
} else if (mockResolvers) {
environment.mock.queueOperationResolver(() => {
return MockPayloadGenerator.generate(request.operation, mockResolvers)
})
}
}

const observable = originalExecuteMutation.call(environment, request)

return Observable.create((sink) => {
if (mutationError) {
setTimeout(() => {
sink.error(new Error(mutationError))
}, 100)
} else {
observable.subscribe({
complete: () => {
setTimeout(() => {
sink.complete()
}, 100)
},
})
}
})
}

return () => {
environment.executeMutation = originalExecuteMutation
}
}, [environment, queueOperationResolver])

return <Story />
}

export default withMutationResolver
8 changes: 8 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @baseapp-frontend/components

## 1.0.18

### Patch Changes

- Moved ProfileSettingsComponent and related queries and mutations from `baseapp-frontend-template`
- Updated dependencies
- @baseapp-frontend/design-system@1.0.7

## 1.0.17

### Patch Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const ChatRoomListItem: FC<ChatRoomListItemProps> = ({ profile: profileRef, onCh
<Box sx={{ display: 'grid', gridTemplateRows: 'repeat(2, minmax(0, 1fr))' }}>
<TypographyWithEllipsis variant="subtitle2">{name}</TypographyWithEllipsis>
<Typography variant="caption" color="text.secondary">
{urlPath?.path && `@${urlPath.path}`}
{urlPath?.path && `@${urlPath.path?.replace('/', '')}`}
</Typography>
</Box>
<LoadingButton
Expand Down
5 changes: 4 additions & 1 deletion packages/components/modules/profiles/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export const DEFAULT_PROFILE_FORM_VALIDATION = z.object({
[PROFILE_FORM_VALUE.image]: z.any(),
[PROFILE_FORM_VALUE.name]: z.string().min(1, { message: PROFILE_FORM_VALIDATION.name.empty }),
[PROFILE_FORM_VALUE.phoneNumber]: z.string(),
[PROFILE_FORM_VALUE.urlPath]: z.string(),
[PROFILE_FORM_VALUE.urlPath]: z
.string()
.min(8, { message: PROFILE_FORM_VALIDATION.urlPath.empty })
.regex(/^[a-zA-Z0-9]+$/, { message: PROFILE_FORM_VALIDATION.urlPath.invalid }),
})

// .svg is not supported by the backend, so better not use 'image/*'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { graphql } from 'react-relay'

export const ProfilesListFragment = graphql`
fragment ProfilesListFragment on User
@argumentDefinitions(count: { type: "Int", defaultValue: 10 }, cursor: { type: "String" })
@refetchable(queryName: "profilesListRefetchable") {
id
profiles(first: $count, after: $cursor) @connection(key: "ProfilesListFragment_profiles") {
edges {
cursor
node {
id
...ProfileItemFragment
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { graphql } from 'react-relay'

export const UserMembersListFragment = graphql`
fragment UserMembersListFragment on Profile
@refetchable(queryName: "userMembersListPaginationRefetchable")
@argumentDefinitions(
count: { type: "Int", defaultValue: 10 }
cursor: { type: "String" }
orderBy: { type: "String" }
q: { type: "String" }
) {
canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole")
...ProfileItemFragment
members(first: $count, after: $cursor, orderBy: $orderBy, q: $q)
@connection(key: "UserMembersFragment_members", filters: ["orderBy", "q"]) {
totalCount
edges {
node {
...MemberItemFragment
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { graphql } from 'react-relay'

export const ProfileSettingsRelayTest = graphql`
query ProfileSettingsRelayTestQuery @relay_test_operation {
profile: node(id: "test-id") {
...ProfileComponentFragment
}
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,3 @@ export const ProfilesListQuery = graphql`
}
}
`

export const ProfilesListFragment = graphql`
fragment ProfilesListFragment on User
@argumentDefinitions(count: { type: "Int", defaultValue: 10 }, cursor: { type: "String" })
@refetchable(queryName: "profilesListRefetchable") {
id
profiles(first: $count, after: $cursor) @connection(key: "ProfilesListFragment_profiles") {
edges {
cursor
node {
id
...ProfileItemFragment
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,3 @@ export const UserMembersListPaginationQuery = graphql`
}
}
`

export const UserMembersListFragment = graphql`
fragment UserMembersListFragment on Profile
@refetchable(queryName: "userMembersListPaginationRefetchable")
@argumentDefinitions(
count: { type: "Int", defaultValue: 10 }
cursor: { type: "String" }
orderBy: { type: "String" }
q: { type: "String" }
) {
canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole")
...ProfileItemFragment
members(first: $count, after: $cursor, orderBy: $orderBy, q: $q)
@connection(key: "UserMembersFragment_members", filters: ["orderBy", "q"]) {
totalCount
edges {
node {
...MemberItemFragment
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`
15 changes: 9 additions & 6 deletions packages/components/modules/profiles/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// exports common profiles code

export * from './graphql/fragments/AllProfilesList'
export * from './graphql/fragments/BlockToggle'
export * from './graphql/fragments/FollowToggleUpdatableFragment'
export * from './graphql/fragments/MemberItem'
export * from './graphql/fragments/ProfileComponent'
export * from './graphql/fragments/ProfileItem'
export * from './graphql/fragments/ProfilesList'
export * from './graphql/fragments/UserMembersList'

export * from './graphql/mutations/BlockToggle'
export * from './graphql/mutations/ChangeUserRole'
export * from './graphql/mutations/FollowToggle'
export * from './graphql/mutations/OrganizationCreate'
export * from './graphql/mutations/ProfileUpdate'

export * from './graphql/queries/AddProfilePopover'
export * from './graphql/queries/AllProfilesList'
export * from './graphql/queries/BlockToggle'
export * from './graphql/queries/FollowToggleUpdatableFragment'
export * from './graphql/queries/MemberItem'
export * from './graphql/queries/ProfileComponent'
export * from './graphql/queries/ProfileItem'
export * from './graphql/queries/ProfilesList'
export * from './graphql/queries/UserMembersList'
export * from './graphql/queries/UserProfile'
Expand Down
5 changes: 5 additions & 0 deletions packages/components/modules/profiles/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ export type UploadablesObj = {
image?: File | Blob
bannerImage?: File | Blob
}

export interface ProfileGetDefaultFormValues {
profile?: ProfileComponentFragment$data | null
removeSlashInUsername?: boolean
}
10 changes: 8 additions & 2 deletions packages/components/modules/profiles/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { getInitialValues } from '@baseapp-frontend/utils'

import { ProfileComponentFragment$data } from '../../../__generated__/ProfileComponentFragment.graphql'
import { DEFAULT_PROFILE_FORM_VALUES } from './constants'
import { ProfileGetDefaultFormValues } from './types'

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

export const getDefaultValues = (profile?: ProfileComponentFragment$data | null) => {
export const getProfileDefaultValues = ({
profile,
removeSlashInUsername = true,
}: ProfileGetDefaultFormValues) => {
const formattedProfile = {
...profile,
phoneNumber: profile?.owner?.phoneNumber ?? DEFAULT_PHONE_NUMBER_COUNTRY_CODE,
Expand All @@ -15,6 +18,9 @@ export const getDefaultValues = (profile?: ProfileComponentFragment$data | null)
urlPath: profile?.urlPath?.path ?? '',
biography: profile?.biography ?? '',
}
if (removeSlashInUsername) {
formattedProfile.urlPath = formattedProfile.urlPath?.replace('/', '')
}
const defaultValues = getInitialValues({
current: formattedProfile,
initial: DEFAULT_PROFILE_FORM_VALUES,
Expand Down
4 changes: 4 additions & 0 deletions packages/components/modules/profiles/common/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ export const PROFILE_FORM_VALIDATION = {
empty: 'Please enter a phone number.',
invalid: 'Invalid phone number.',
},
urlPath: {
empty: 'Username must be at least 8 characters long.',
invalid: 'Username can only contain letters and numbers',
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const ProfileComponent = ({ profile: profileRef }: ProfileComponentProps) => {
{profile?.name}
</Text>
<Text variant="body2" color="low">
@{profile?.urlPath?.path}
@{profile?.urlPath?.path?.replace('/', '')}
</Text>
</View>
<View style={styles.statsContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import {
PROFILE_FORM_VALUE,
ProfileComponentFragment,
ProfileUpdateForm,
getDefaultValues,
getImageUrl,
getProfileDefaultValues,
useProfileMutation,
} from '../../common'
import BottomDrawer from './BottomDrawer'
Expand All @@ -46,7 +46,7 @@ const ProfileSettingsComponent: FC<ProfileSettingsComponentProps> = ({ profile:
const { sendToast } = useNotification()

const formReturn = useForm<ProfileUpdateForm>({
defaultValues: getDefaultValues(profile),
defaultValues: getProfileDefaultValues({ profile }),
resolver: zodResolver(DEFAULT_PROFILE_FORM_VALIDATION),
mode: 'onBlur',
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useLazyLoadQuery } from 'react-relay'

import { ProfileSettingsRelayTestQuery as QueryType } from '../../../../../__generated__/ProfileSettingsRelayTestQuery.graphql'
import { ProfileSettingsRelayTest } from '../../../common/graphql/queries/ProfileSettingsRelayTest'
import ProfileSettingsComponent from '../index'
import { ProfileSettingsComponentProps } from '../types'

const ProfileSettingsComponentWithQuery = (props: ProfileSettingsComponentProps) => {
const profileRef = useLazyLoadQuery<QueryType>(ProfileSettingsRelayTest, {})

return <ProfileSettingsComponent {...props} profile={profileRef.profile} />
}

export default ProfileSettingsComponentWithQuery
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const mockResolvers = {
Node: () => ({
__typename: 'Profile',
id: '1',
name: 'Profile 1',
biography: 'Profile 1 biography',
image: null,
bannerImage: null,
canChange: true,
canDelete: true,
urlPath: {
path: '/profile/1',
},
owner: {
phoneNumber: '1234567890',
},
}),
Mutation: () => ({
profileUpdate: {
errors: null,
},
}),
}

export const mockResolversWithMutationError = {
...mockResolvers,
Mutation: () => ({
profileUpdate: {},
}),
}
Loading
Loading