Skip to content

Commit

Permalink
Allow users to customize their identicon
Browse files Browse the repository at this point in the history
Users can now influence their identicon by entering a string of their
choice under Advanced Settings in the Edit Profile modal.
  • Loading branch information
sweinstein22 committed Jan 18, 2021
1 parent a1a4be8 commit 9607226
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 6 deletions.
5 changes: 5 additions & 0 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ service cloud.firestore {
allow write: if false;
}

match /additionalUserInfo/{userId} {
allow read: if authenticated()
allow write: if request.auth.uid != null && userId == request.auth.uid;
}

match /teamMembers/{teamId} {
allow read: if authenticated() && isTeamMember(teamId);
allow write: if false;
Expand Down
8 changes: 6 additions & 2 deletions src/actions/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { auth, funcs } from '../firebase';
import { auth, funcs, db } from '../firebase';

const updateUserProfileFunc = funcs.httpsCallable('updateUserProfile');

Expand All @@ -20,9 +20,13 @@ export async function resetPassword(email: string) {
await auth.sendPasswordResetEmail(email);
}

export async function updateProfile(profile: { displayName?: string; photoURL?: string }) {
export async function updateProfile(profile: { displayName?: string; photoURL?: string }, additionalOptions: { identiconString?: string }) {
if (!auth.currentUser) return;
await updateUserProfileFunc(profile);
await db.collection('additionalUserInfo').doc(auth.currentUser.uid).set(
additionalOptions,
{merge: true}
);
}

export async function logOut() {
Expand Down
55 changes: 53 additions & 2 deletions src/components/EditProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { css } from 'astroturf';
import { FormEvent, useState } from 'react';
import { FormEvent, useEffect, useState } from 'react';
import * as userActions from '../actions/user';
import { useModal } from '../hooks/useModal';
import { useSession } from '../hooks/useSession';
import { useAdditionalUserInfo } from '../hooks/useAdditionalUserInfo';
import { cn } from '../helpers';
import { Triangle } from 'react-feather';
import Button from './Button';
import FormField from './FormField';
import Input from './Input';
import ModalBody from './ModalBody';
import ModalFooter from './ModalFooter';
import ModalHeader from './ModalHeader';
import IconButton from './IconButton';

export default function EditProfile() {
const { loaded, userId, email, displayName, photoURL } = useSession();
const { identiconString } = useAdditionalUserInfo(userId);
const [, setModalContent] = useModal();
const [localDisplayName, setLocalDisplayName] = useState(displayName || '');
const [localPhotoURL, setLocalPhotoURL] = useState(photoURL || '');
const [localIdenticonString, setLocalIdenticonString] = useState(identiconString || '');
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [submitting, setSubmitting] = useState(false);

useEffect(() => {
setLocalIdenticonString(identiconString);
}, [identiconString]);

function cancel() {
setModalContent(null);
}
Expand All @@ -30,6 +41,8 @@ export default function EditProfile() {
await userActions.updateProfile({
displayName: localDisplayName,
photoURL: localPhotoURL,
}, {
identiconString: localIdenticonString,
});

// HACK: for some reason I can't yet figure out, the user image/display name
Expand All @@ -53,7 +66,8 @@ export default function EditProfile() {
{localPhotoURL ? (
<img className={styles.image} src={localPhotoURL} alt="Profile" />
) : (
<svg className={styles.image} width="80" height="80" data-jdenticon-value={userId} />
<svg className={styles.image} width="80" height="80"
data-jdenticon-value={localIdenticonString ? localIdenticonString : userId} />
)}
</div>
<div>
Expand All @@ -76,6 +90,22 @@ export default function EditProfile() {
onChange={(evt) => setLocalPhotoURL(evt.target.value)}
/>
</FormField>
<div className={styles.advancedOptionsSection}>
<IconButton
label="Advanced options"
icon={<Triangle fill="dark-gray" className={cn(styles.optionsIcon, showAdvancedOptions && styles.optionsIconClicked)} />}
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)} />
<span className={styles.advancedOptionsHeader}><b>Advanced Options</b></span>
</div>
{showAdvancedOptions &&
<FormField label="Identicon string">
<Input
id="edit-identicon-string"
value={localIdenticonString}
onChange={(evt) => setLocalIdenticonString(evt.target.value)}
/>
</FormField>
}
</div>
</ModalBody>

Expand All @@ -99,6 +129,8 @@ export default function EditProfile() {
}

const styles = css`
@import '../variables.scss';
.heading {
margin-top: 0;
margin-bottom: 16px;
Expand All @@ -115,4 +147,23 @@ const styles = css`
width: 192px;
height: 192px;
}
.advancedOptionsSection {
display: flex;
margin-top: $unit;
}
.advancedOptionsHeader {
margin-left: $unit-half;
padding-top: $unit;
}
.optionsIcon {
transition: 0.9s;
transform: rotate(90deg);
}
.optionsIconClicked {
transform: rotate(180deg);
}
`;
5 changes: 4 additions & 1 deletion src/components/Person.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Lock, Trash, Unlock } from 'react-feather';
import * as personActions from '../actions/person';
import * as teamActions from '../actions/team';
import { useModal } from '../hooks/useModal';
import { useAdditionalUserInfo } from '../hooks/useAdditionalUserInfo';
import { DragEvent } from 'react';

interface Props {
Expand All @@ -20,6 +21,7 @@ interface Props {

export default function Person(props: Props) {
const { displayName, teamId, isLocked, userId } = props;
const { identiconString } = useAdditionalUserInfo(userId);
const [, setModalContent] = useModal();

const name = displayName || '(no display name)';
Expand Down Expand Up @@ -58,7 +60,8 @@ export default function Person(props: Props) {
{props.photoURL ? (
<img className={styles.img} src={props.photoURL} alt={name} draggable={false} />
) : (
<svg className={styles.img} width="80" height="80" data-jdenticon-value={props.userId} />
<svg className={styles.img} width="80" height="80"
data-jdenticon-value={identiconString ? identiconString : props.userId} />
)}
</div>

Expand Down
4 changes: 3 additions & 1 deletion src/components/UserDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useHistory } from 'react-router-dom';
import * as userActions from '../actions/user';
import { useModal } from '../hooks/useModal';
import { useSession } from '../hooks/useSession';
import { useAdditionalUserInfo } from '../hooks/useAdditionalUserInfo';
import Dropdown from './Dropdown';
import DropdownItem from './DropdownItem';
import EditProfile from './EditProfile';
Expand All @@ -12,6 +13,7 @@ import HeaderButton from './HeaderButton';
export default function UserDropdown() {
const session = useSession();
const history = useHistory();
const { identiconString } = useAdditionalUserInfo(session.userId);
const [, setModalContent] = useModal();

if (!session.loaded) {
Expand All @@ -24,7 +26,7 @@ export default function UserDropdown() {

const displayName = session.displayName || session.email || '';
const imageURL = session.photoURL || '';
const imageHash = session.userId || '';
const imageHash = identiconString ? identiconString : session.userId || '';

async function logOut() {
await userActions.logOut();
Expand Down
18 changes: 18 additions & 0 deletions src/hooks/useAdditionalUserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useDocumentData } from 'react-firebase-hooks/firestore';
import { db } from '../firebase';
import { AdditionalUserInfo } from '../types';

export function useAdditionalUserInfo(userId: string) {
const [value = {identiconString: ''}, loading, error] = useDocumentData<AdditionalUserInfo>(
db.collection('additionalUserInfo').doc(userId || '-')
);

if (error || loading) {
error && console.error(error);
return value;
}

return {
identiconString: value.identiconString
};
}
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export interface RouteParams {
teamId?: string;
}

export interface AdditionalUserInfo {
identiconString: string;
};

export interface TeamData {
teamId: string;
teamName: string;
Expand Down

0 comments on commit 9607226

Please sign in to comment.