Skip to content

Commit

Permalink
feat(DIA-561): CompleteMyProfile flow (#10446)
Browse files Browse the repository at this point in the history
* feat: first steps of the complete profit :wqle

* feat: remove bio from user form

* refactor: input generation simplification

* feat: fixes broken link and minor adjusts

* feat: fix the header as a navigation header element, minor adjusts to Android, iOS layout

* feat: remove test placeholder from complete profile CTA
  • Loading branch information
araujobarret committed Jul 9, 2024
1 parent 8920134 commit 901ef2d
Show file tree
Hide file tree
Showing 25 changed files with 1,835 additions and 34 deletions.
6 changes: 6 additions & 0 deletions src/app/AppRegistry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { NewsScreen, NewsScreenQuery } from "app/Scenes/Articles/News/News"
import { BrowseSimilarWorksQueryRenderer } from "app/Scenes/Artwork/Components/BrowseSimilarWorks/BrowseSimilarWorks"
import { ArtworkListScreen } from "app/Scenes/ArtworkList/ArtworkList"
import { ArtworkRecommendationsScreen } from "app/Scenes/ArtworkRecommendations/ArtworkRecommendations"
import { CompleteMyProfile } from "app/Scenes/CompleteMyProfile/CompleteMyProfile"
import { GalleriesForYouScreen } from "app/Scenes/GalleriesForYou/GalleriesForYouScreen"
import { HomeContainer } from "app/Scenes/Home/HomeContainer"
import { AddMyCollectionArtist } from "app/Scenes/MyCollection/Screens/Artist/AddMyCollectionArtist"
Expand Down Expand Up @@ -559,6 +560,11 @@ export const modules = defineModules({
},
[MyCollectionScreenQuery]
),
CompleteMyProfile: reactModule(CompleteMyProfile, {
fullBleed: true,
hidesBackButton: true,
hidesBottomTabs: true,
}),
MyProfileEditForm: reactModule(MyProfileEditFormScreen),
MyProfilePayment: reactModule(MyProfilePaymentQueryRenderer),
MyProfileSettings: reactModule(MyProfileSettings),
Expand Down
162 changes: 162 additions & 0 deletions src/app/Scenes/CompleteMyProfile/AvatarStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {
Text,
Screen,
Spacer,
Flex,
Touchable,
Button,
CheckCircleFillIcon,
AddCircleIcon,
useSpace,
Image,
Skeleton,
SkeletonBox,
} from "@artsy/palette-mobile"
import { useActionSheet } from "@expo/react-native-action-sheet"
import {
State,
useCompleteMyProfileContext,
} from "app/Scenes/CompleteMyProfile/CompleteMyProfileProvider"
import { Footer } from "app/Scenes/CompleteMyProfile/Footer"
import { useCompleteProfile } from "app/Scenes/CompleteMyProfile/useCompleteProfile"
import { getConvertedImageUrlFromS3 } from "app/utils/getConvertedImageUrlFromS3"
import { showPhotoActionSheet } from "app/utils/requestPhotos"
import { FC, useState } from "react"

export const AvatarStep = () => {
const { goNext, isCurrentRouteDirty, setField, field } = useCompleteProfile<State["iconUrl"]>()

const handleOnImageSelect = ({
localPath,
geminiUrl,
}: {
localPath: string
geminiUrl: string
}) => {
setField({ localPath, geminiUrl })
}

return (
<Screen>
<Screen.Body>
<Flex justifyContent="space-between" height="100%">
<Flex>
<Text variant="lg">Add a profile image</Text>

<Spacer y={1} />

<Text color="black60">
Make your profile more engaging and help foster trust with galleries on Artsy.
</Text>

<Spacer y={2} />

<ImageSelector onImageSelect={handleOnImageSelect} src={field} />
</Flex>

<Footer isFormDirty={isCurrentRouteDirty} onGoNext={goNext} />
</Flex>
</Screen.Body>
</Screen>
)
}

interface ImageSelectorProps {
src?: State["iconUrl"]
onImageSelect: (props: { localPath: string; geminiUrl: string }) => void
}

const ImageSelector: FC<ImageSelectorProps> = ({ src, onImageSelect }) => {
const [isLoading, setIsLoading] = useState(false)
const space = useSpace()
const { showActionSheetWithOptions } = useActionSheet()
const { user } = useCompleteMyProfileContext()

const handleImagePress = async () => {
if (isLoading) {
return
}

try {
const images = await showPhotoActionSheet(showActionSheetWithOptions, true, false)

if (images.length === 1) {
setIsLoading(true)
const localPath = images[0].path
const geminiUrl = await getConvertedImageUrlFromS3(localPath)
onImageSelect({ localPath, geminiUrl })
}
} catch (error) {
console.error("Error when uploading an image from the device", JSON.stringify(error))
} finally {
setIsLoading(false)
}
}

const displayAvatar = !isLoading && src?.localPath && src?.geminiUrl
const displayInitials = !isLoading && !displayAvatar

return (
<Touchable aria-label="Choose a photo" accessibilityRole="button" onPress={handleImagePress}>
<Flex alignItems="center" gap={space(2)}>
<Flex alignItems="center">
<Flex
alignItems="center"
justifyContent="center"
width={AVATAR_SIZE}
height={AVATAR_SIZE}
border={displayInitials ? 1 : 0}
borderColor="black10"
borderRadius={AVATAR_SIZE / 2}
>
{!!isLoading && (
<Skeleton>
<SkeletonBox
width={AVATAR_SIZE}
height={AVATAR_SIZE}
borderRadius={AVATAR_SIZE / 2}
/>
</Skeleton>
)}

{!!displayAvatar && (
<Flex overflow="hidden" borderRadius={AVATAR_SIZE / 2}>
<Image
width={AVATAR_SIZE}
height={AVATAR_SIZE}
performResize={false}
src={src.localPath}
/>
</Flex>
)}

{!!displayInitials && <Text variant="lg-display">{user.initials}</Text>}
</Flex>

<Flex
display={isLoading ? "none" : "flex"}
position="absolute"
bottom={ICON_SIZE / 4}
right={ICON_SIZE / 4}
width={ICON_SIZE}
height={ICON_SIZE}
backgroundColor="white100"
borderRadius={ICON_SIZE / 2}
>
{!!displayAvatar && (
<CheckCircleFillIcon width={ICON_SIZE} height={ICON_SIZE} fill="green100" />
)}

{!!displayInitials && <AddCircleIcon width={ICON_SIZE} height={ICON_SIZE} />}
</Flex>
</Flex>
<Button variant="outline" size="small" onPress={handleImagePress} loading={isLoading}>
{src ? "Change Image" : "Choose an Image"}
</Button>
</Flex>
</Touchable>
)
}

const AVATAR_SIZE = 124
const ICON_SIZE = 28
154 changes: 154 additions & 0 deletions src/app/Scenes/CompleteMyProfile/ChangesSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
Button,
Flex,
Screen,
Spacer,
Text,
useSpace,
CheckCircleFillIcon,
CheckCircleIcon,
} from "@artsy/palette-mobile"
import { useCompleteMyProfileContext } from "app/Scenes/CompleteMyProfile/CompleteMyProfileProvider"
import { useCompleteProfile } from "app/Scenes/CompleteMyProfile/useCompleteProfile"
import { navigate } from "app/system/navigation/navigate"

export const ChangesSummary = () => {
const space = useSpace()
const { saveAndExit, isLoading } = useCompleteProfile()
const { progressState, progressStateWithoutUndefined, steps } = useCompleteMyProfileContext()

const handleAddArtistsToMyCollection = () => {
if (!isLoading) {
navigate(`my-collection/collected-artists/new`)
}
}

const completedStepsLength = Object.values(progressStateWithoutUndefined).length
const isCompleted = completedStepsLength === steps.length - 1
const hasLocation = !!progressState.location
const hasProfession = !!progressState.profession
const hasIconUrl = !!progressState.iconUrl
const hasIsIdentityVerified = !!progressState.isIdentityVerified

return (
<Screen>
<Screen.Body>
<Flex py={2} gap={space(2)}>
<Text variant="lg-display">
{isCompleted ? "Thank you for completing your profile." : "You’re almost there!"}
</Text>

<Text color="black60" variant="sm">
{isCompleted ? (
<>
You can update your profile at any time in{" "}
<Text
variant="sm"
backgroundColor="white100"
style={{ textDecorationLine: "underline" }}
onPress={() => navigate(`my-profile/edit`)}
suppressHighlighting
>{` Settings`}</Text>
.
</>
) : (
"Complete these details to finalize your profile:"
)}
</Text>

<Flex gap={space(1)}>
{steps.includes("LocationStep") && (
<Flex flexDirection="row" alignItems="center" gap={space(1)}>
{hasLocation ? (
<CheckCircleFillIcon fill="green100" />
) : (
<CheckCircleIcon fill="black60" />
)}
<Text variant="md" color={hasLocation ? "black100" : "black60"}>
Location
</Text>
</Flex>
)}

{steps.includes("ProfessionStep") && (
<Flex flexDirection="row" alignItems="center" gap={space(1)}>
{hasProfession ? (
<CheckCircleFillIcon fill="green100" />
) : (
<CheckCircleIcon fill="black60" />
)}
<Text variant="md" color={hasProfession ? "black100" : "black60"}>
Profession
</Text>
</Flex>
)}

{steps.includes("AvatarStep") && (
<Flex flexDirection="row" alignItems="center" gap={space(1)}>
{hasIconUrl ? (
<CheckCircleFillIcon fill="green100" />
) : (
<CheckCircleIcon fill="black60" />
)}
<Text variant="md" color={hasIconUrl ? "black100" : "black60"}>
Profile Image
</Text>
</Flex>
)}

{steps.includes("IdentityVerificationStep") && (
<Flex flexDirection="row" alignItems="center" gap={space(1)}>
{hasIsIdentityVerified ? (
<CheckCircleFillIcon fill="green100" />
) : (
<CheckCircleIcon fill="black60" />
)}
<Text variant="md" color={hasIsIdentityVerified ? "black100" : "black60"}>
ID Verification Email
</Text>
</Flex>
)}
</Flex>

<Text color="black60" variant="sm">
{isCompleted ? (
<>
Continue building your profile by adding artists or artworks to{" "}
<Text
variant="sm"
style={{ textDecorationLine: "underline" }}
onPress={() => navigate(`my-collection`)}
suppressHighlighting
>{` My Collection`}</Text>
.
</>
) : (
<>
You can update your profile at any time in
<Text
variant="sm"
style={{ textDecorationLine: "underline" }}
onPress={() => navigate(`my-profile/edit`)}
suppressHighlighting
>{` Settings`}</Text>
.
</>
)}
</Text>
</Flex>

<Flex flex={1} justifyContent="flex-end" pb={4}>
<Button onPress={saveAndExit} flex={1} loading={isLoading}>
Save and Exit
</Button>

<Spacer y={2} />

<Button onPress={handleAddArtistsToMyCollection} flex={1} variant="outline">
Add Artists to My Collection
</Button>
</Flex>
</Screen.Body>
</Screen>
)
}
Loading

0 comments on commit 901ef2d

Please sign in to comment.