Skip to content

Commit 2cab793

Browse files
add support for custom default avatars (#1409)
* add support for custom default avatars in registration * refactor and sovereignVenueId usage for avatars * improve loading indicator condition * pass venueId to profilepicture to remove query logic * add debt for sovereign venue id hook * refactor and quality improvement
1 parent 74a451b commit 2cab793

File tree

5 files changed

+114
-48
lines changed

5 files changed

+114
-48
lines changed

src/components/molecules/ProfilePictureInput/ProfilePictureInput.tsx

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import React, { useRef, useState } from "react";
1+
import React, { useCallback, useMemo, useRef, useState } from "react";
22
import { useFirebase } from "react-redux-firebase";
3+
import { useAsync } from "react-use";
34
import { UserInfo } from "firebase/app";
45
import { FirebaseStorage } from "@firebase/storage-types";
56

7+
import { fetchSovereignVenueId } from "api/sovereignVenue";
8+
69
import {
710
ACCEPTED_IMAGE_TYPES,
11+
DEFAULT_AVATARS,
812
MAX_AVATAR_IMAGE_FILE_SIZE_BYTES,
913
} from "settings";
1014

@@ -14,7 +18,8 @@ import "./ProfilePictureInput.scss";
1418

1519
type Reference = ReturnType<FirebaseStorage["ref"]>;
1620

17-
interface PropsType {
21+
interface ProfilePictureInputProps {
22+
venueId: string;
1823
setValue: (inputName: string, value: string, rerender: boolean) => void;
1924
user: UserInfo;
2025
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -24,14 +29,8 @@ interface PropsType {
2429
register: any;
2530
}
2631

27-
const sparkleAvatars = [
28-
"default-profile-pic-1.png",
29-
"default-profile-pic-2.png",
30-
"default-profile-pic-3.png",
31-
"default-profile-pic-4.png",
32-
];
33-
34-
const ProfilePictureInput: React.FunctionComponent<PropsType> = ({
32+
const ProfilePictureInput: React.FunctionComponent<ProfilePictureInputProps> = ({
33+
venueId,
3534
setValue,
3635
user,
3736
errors,
@@ -43,6 +42,26 @@ const ProfilePictureInput: React.FunctionComponent<PropsType> = ({
4342
const firebase = useFirebase();
4443
const uploadRef = useRef<HTMLInputElement>(null);
4544

45+
// @debt Replace fetchSovereignVenueId with useSovereignVenueId, when the hook is refactored to accept venueId as a param.
46+
const {
47+
value: sovereignVenueId,
48+
loading: isLoadingSovereignVenueId,
49+
} = useAsync(async () => await fetchSovereignVenueId(venueId));
50+
51+
const {
52+
value: customAvatars,
53+
loading: isLoadingCustomAvatars,
54+
} = useAsync(async () => {
55+
const storageRef = firebase.storage().ref();
56+
const list = await storageRef
57+
.child(`/assets/avatars/${sovereignVenueId}`)
58+
.listAll();
59+
const avatars: string[] = await Promise.all(
60+
list.items.map((item) => item.getDownloadURL())
61+
);
62+
return avatars;
63+
}, [sovereignVenueId]);
64+
4665
const uploadPicture = async (profilePictureRef: Reference, file: File) => {
4766
setIsPictureUploading(true);
4867
const uploadedProfilePicture = await profilePictureRef.put(file);
@@ -75,9 +94,36 @@ const ProfilePictureInput: React.FunctionComponent<PropsType> = ({
7594
setValue("pictureUrl", pictureUrlRef, true);
7695
};
7796

78-
const uploadDefaultAvatar = async (avatar: string) => {
79-
setValue("pictureUrl", process.env.PUBLIC_URL + `/avatars/${avatar}`, true);
80-
};
97+
const uploadDefaultAvatar = useCallback(
98+
async (avatar: string) => {
99+
setValue("pictureUrl", avatar, true);
100+
},
101+
[setValue]
102+
);
103+
104+
const isLoading =
105+
(isLoadingSovereignVenueId || isLoadingCustomAvatars) &&
106+
(customAvatars !== undefined || error !== undefined);
107+
108+
const defaultAvatars = customAvatars?.length
109+
? customAvatars
110+
: DEFAULT_AVATARS;
111+
112+
const avatarImages = useMemo(() => {
113+
return defaultAvatars.map((avatar, index) => (
114+
<div
115+
key={`${avatar}-${index}`}
116+
className="profile-picture-preview-container"
117+
onClick={() => uploadDefaultAvatar(avatar)}
118+
>
119+
<img
120+
src={avatar}
121+
className="profile-icon profile-picture-preview"
122+
alt={`default avatar ${index}`}
123+
/>
124+
</div>
125+
));
126+
}, [defaultAvatars, uploadDefaultAvatar]);
81127

82128
return (
83129
<div className="profile-picture-upload-form">
@@ -110,21 +156,7 @@ const ProfilePictureInput: React.FunctionComponent<PropsType> = ({
110156
{error && <small>Error uploading: {error}</small>}
111157
<small>Or pick one from our Sparkle profile pics</small>
112158
<div className="default-avatars-container">
113-
{sparkleAvatars.map((avatar, index) => {
114-
return (
115-
<div
116-
key={`${avatar}-${index}`}
117-
className="profile-picture-preview-container"
118-
onClick={() => uploadDefaultAvatar(avatar)}
119-
>
120-
<img
121-
src={`/avatars/${avatar}`}
122-
className="profile-icon profile-picture-preview"
123-
alt="your profile"
124-
/>
125-
</div>
126-
);
127-
})}
159+
{isLoading ? <div>Loading...</div> : avatarImages}
128160
</div>
129161
<input
130162
name="pictureUrl"

src/components/organisms/EditProfileModal/EditProfileModal.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import React, { useEffect } from "react";
22
import { Modal } from "react-bootstrap";
33
import { useForm } from "react-hook-form";
4-
import "./EditProfileModal.scss";
4+
5+
import { DISPLAY_NAME_MAX_CHAR_COUNT } from "settings";
6+
7+
import { QuestionType } from "types/Question";
8+
9+
import { currentVenueSelectorData } from "utils/selectors";
10+
11+
import { useUser } from "hooks/useUser";
12+
import { useSelector } from "hooks/useSelector";
13+
import { useVenueId } from "hooks/useVenueId";
14+
515
import { ProfileFormData } from "pages/Account/Profile";
616
import { QuestionsFormData } from "pages/Account/Questions";
717
import { updateUserProfile } from "pages/Account/helpers";
8-
import { QuestionType } from "types/Question";
918
import ProfilePictureInput from "components/molecules/ProfilePictureInput";
10-
import { DISPLAY_NAME_MAX_CHAR_COUNT } from "settings";
11-
import { useUser } from "hooks/useUser";
12-
import { useSelector } from "hooks/useSelector";
13-
import { currentVenueSelectorData } from "utils/selectors";
19+
20+
import "./EditProfileModal.scss";
1421

1522
interface PropsType {
1623
show: boolean;
@@ -21,6 +28,7 @@ const EditProfileModal: React.FunctionComponent<PropsType> = ({
2128
show,
2229
onHide,
2330
}) => {
31+
const venueId = useVenueId();
2432
const { user, profile } = useUser();
2533
const profileQuestions = useSelector(
2634
(state) => currentVenueSelectorData(state)?.profile_questions
@@ -87,8 +95,9 @@ const EditProfileModal: React.FunctionComponent<PropsType> = ({
8795
less
8896
</span>
8997
)}
90-
{user && (
98+
{user && venueId && (
9199
<ProfilePictureInput
100+
venueId={venueId}
92101
setValue={setValue}
93102
user={user}
94103
errors={errors}

src/components/organisms/ProfileModal/EditProfileForm/EditProfileForm.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import React, { useEffect } from "react";
22
import { useForm } from "react-hook-form";
3-
import "./EditProfileForm.scss";
3+
4+
import { DEFAULT_PROFILE_IMAGE, DISPLAY_NAME_MAX_CHAR_COUNT } from "settings";
5+
6+
import { QuestionType } from "types/Question";
7+
8+
import { currentVenueSelectorData } from "utils/selectors";
9+
10+
import { useUser } from "hooks/useUser";
11+
import { useSelector } from "hooks/useSelector";
12+
import { useVenueId } from "hooks/useVenueId";
13+
414
import { ProfileFormData } from "pages/Account/Profile";
515
import { QuestionsFormData } from "pages/Account/Questions";
616
import { updateUserProfile } from "pages/Account/helpers";
7-
import { QuestionType } from "types/Question";
817
import ProfilePictureInput from "components/molecules/ProfilePictureInput";
9-
import { useUser } from "hooks/useUser";
10-
import { DEFAULT_PROFILE_IMAGE, DISPLAY_NAME_MAX_CHAR_COUNT } from "settings";
11-
import { useSelector } from "hooks/useSelector";
12-
import { currentVenueSelectorData } from "utils/selectors";
18+
19+
import "./EditProfileForm.scss";
1320

1421
interface PropsType {
1522
setIsEditMode: (value: boolean) => void;
@@ -18,6 +25,7 @@ interface PropsType {
1825
const EditProfileForm: React.FunctionComponent<PropsType> = ({
1926
setIsEditMode,
2027
}) => {
28+
const venueId = useVenueId();
2129
const { user, profile } = useUser();
2230
const profileQuestions = useSelector(
2331
(state) => currentVenueSelectorData(state)?.profile_questions
@@ -82,8 +90,9 @@ const EditProfileForm: React.FunctionComponent<PropsType> = ({
8290
less
8391
</span>
8492
)}
85-
{user && (
93+
{user && venueId && (
8694
<ProfilePictureInput
95+
venueId={venueId}
8796
setValue={setValue}
8897
user={user}
8998
errors={errors}

src/pages/Account/Profile.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import React from "react";
22
import { useForm } from "react-hook-form";
33
import { useHistory } from "react-router-dom";
4-
import { updateUserProfile } from "./helpers";
54
import "firebase/storage";
6-
import "./Account.scss";
7-
import ProfilePictureInput from "components/molecules/ProfilePictureInput";
8-
import { RouterLocation } from "types/RouterLocation";
9-
import { useUser } from "hooks/useUser";
5+
106
import { IS_BURN } from "secrets";
11-
import getQueryParameters from "utils/getQueryParameters";
7+
128
import { DEFAULT_VENUE, DISPLAY_NAME_MAX_CHAR_COUNT } from "settings";
9+
10+
import { RouterLocation } from "types/RouterLocation";
11+
12+
import getQueryParameters from "utils/getQueryParameters";
13+
1314
import { useVenueId } from "hooks/useVenueId";
15+
import { useUser } from "hooks/useUser";
16+
17+
import { updateUserProfile } from "./helpers";
18+
19+
import ProfilePictureInput from "components/molecules/ProfilePictureInput";
20+
21+
import "./Account.scss";
1422

1523
export interface ProfileFormData {
1624
partyName: string;
@@ -90,6 +98,7 @@ const Profile: React.FunctionComponent<PropsType> = ({ location }) => {
9098
)}
9199
{user && (
92100
<ProfilePictureInput
101+
venueId={venueId}
93102
setValue={setValue}
94103
user={user}
95104
errors={errors}

src/settings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,13 @@ export const RANDOM_AVATARS = [
558558
"avatar-12.png",
559559
];
560560

561+
export const DEFAULT_AVATARS = [
562+
"/avatars/default-profile-pic-1.png",
563+
"/avatars/default-profile-pic-2.png",
564+
"/avatars/default-profile-pic-3.png",
565+
"/avatars/default-profile-pic-4.png",
566+
];
567+
561568
export const REACTION_TIMEOUT = 5000; // time in ms
562569
export const SHOW_EMOJI_IN_REACTION_PAGE = true;
563570

0 commit comments

Comments
 (0)