Skip to content

Commit

Permalink
Merge pull request #30 from anuraghazra/develop
Browse files Browse the repository at this point in the history
feat: custom user color feature
  • Loading branch information
anuraghazra authored Jun 19, 2020
2 parents 094a05f + 6d96c8c commit 509c5eb
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 11 deletions.
4 changes: 3 additions & 1 deletion client/src/components/Message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ const Message: React.FC<IMessage> = ({
<Flex direction="column">
<Flex gap="medium" align="center" nowrap>
<Avatar size={35} src={author?.avatarUrl} />
<p className="textcolor--primary">{author?.name}</p>
<p className="textcolor--primary" style={{ color: author?.color }}>
{author?.name}
</p>
<span className="message__date">messaged {timeAgo(date)}</span>
{isAuthor && (
<Flex
Expand Down
1 change: 1 addition & 0 deletions client/src/contexts/AuthContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const mocks = [
result: {
data: {
me: {
color: "#ffffff",
id: "5ed7bf9ea4df011004898efb",
name: "Test user",
email: "[email protected]",
Expand Down
24 changes: 19 additions & 5 deletions client/src/graphql/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type Me = {
email: Scalars['String'];
avatarUrl: Scalars['String'];
rooms: Array<Room>;
color: Scalars['String'];
};


Expand All @@ -82,6 +83,7 @@ export type Member = {
username: Scalars['String'];
avatarUrl: Scalars['String'];
rooms: Array<Scalars['ID']>;
color: Scalars['String'];
};

export type Message = {
Expand All @@ -101,6 +103,7 @@ export type User = {
name: Scalars['String'];
username: Scalars['String'];
rooms: Array<Room>;
color: Scalars['String'];
};

export type Messages = {
Expand Down Expand Up @@ -139,6 +142,7 @@ export type InvitationDetails = {

export type Mutation = {
__typename?: 'Mutation';
setColor: Member;
logout: Scalars['Boolean'];
sendMessage: Message;
deleteMessage: Message;
Expand All @@ -153,6 +157,11 @@ export type Mutation = {
};


export type MutationSetColorArgs = {
color: Scalars['String'];
};


export type MutationSendMessageArgs = {
roomId: Scalars['ObjectId'];
content: Scalars['String'];
Expand Down Expand Up @@ -330,7 +339,7 @@ export type GetRoomQuery = (
& Pick<Message, 'id' | 'roomId' | 'content' | 'createdAt' | 'mentions'>
& { author: (
{ __typename?: 'Member' }
& Pick<Member, 'id' | 'name' | 'username' | 'avatarUrl'>
& Pick<Member, 'id' | 'name' | 'username' | 'avatarUrl' | 'color'>
) }
)> }
) }
Expand Down Expand Up @@ -390,7 +399,7 @@ export type SubscriptionMessagePartsFragment = (
& Pick<Message, 'id' | 'content' | 'roomId' | 'createdAt' | 'mentions'>
& { author: (
{ __typename?: 'Member' }
& Pick<Member, 'id' | 'name' | 'username' | 'avatarUrl'>
& Pick<Member, 'id' | 'name' | 'username' | 'avatarUrl' | 'color'>
) }
);

Expand All @@ -407,7 +416,7 @@ export type SendMessageMutation = (
& Pick<Message, 'id' | 'roomId' | 'content' | 'createdAt' | 'mentions'>
& { author: (
{ __typename?: 'Member' }
& Pick<Member, 'id' | 'name' | 'username' | 'avatarUrl'>
& Pick<Member, 'id' | 'name' | 'username' | 'avatarUrl' | 'color'>
) }
) }
);
Expand Down Expand Up @@ -494,7 +503,7 @@ export type CurrentUserQuery = (
{ __typename?: 'Query' }
& { me: (
{ __typename?: 'Me' }
& Pick<Me, 'id' | 'name' | 'email' | 'username' | 'avatarUrl'>
& Pick<Me, 'id' | 'name' | 'email' | 'username' | 'avatarUrl' | 'color'>
& { rooms: Array<(
{ __typename?: 'Room' }
& Pick<Room, 'id' | 'name' | 'owner'>
Expand All @@ -509,7 +518,7 @@ export type ListUsersQuery = (
{ __typename?: 'Query' }
& { users: Array<(
{ __typename?: 'Member' }
& Pick<Member, 'id' | 'name' | 'avatarUrl' | 'username' | 'rooms' | 'createdAt'>
& Pick<Member, 'id' | 'name' | 'avatarUrl' | 'username' | 'color' | 'rooms' | 'createdAt'>
)> }
);

Expand Down Expand Up @@ -601,6 +610,7 @@ export const SubscriptionMessagePartsFragmentDoc = gql`
name
username
avatarUrl
color
}
}
`;
Expand Down Expand Up @@ -785,6 +795,7 @@ export const GetRoomDocument = gql`
name
username
avatarUrl
color
}
}
}
Expand Down Expand Up @@ -934,6 +945,7 @@ export const SendMessageDocument = gql`
name
username
avatarUrl
color
}
}
}
Expand Down Expand Up @@ -1124,6 +1136,7 @@ export const CurrentUserDocument = gql`
email
username
avatarUrl
color
rooms {
id
name
Expand Down Expand Up @@ -1164,6 +1177,7 @@ export const ListUsersDocument = gql`
name
avatarUrl
username
color
rooms
createdAt
}
Expand Down
1 change: 1 addition & 0 deletions client/src/graphql/typeDefs/room.queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default gql`
name
username
avatarUrl
color
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions client/src/graphql/typeDefs/typeDefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const typeDefs = gql`
name
username
avatarUrl
color
}
}
Expand All @@ -39,6 +40,7 @@ const typeDefs = gql`
name
username
avatarUrl
color
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions client/src/graphql/typeDefs/user.queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default gql`
email
username
avatarUrl
color
rooms {
id
name
Expand All @@ -35,6 +36,7 @@ export default gql`
name
avatarUrl
username
color
rooms
createdAt
}
Expand Down
1 change: 1 addition & 0 deletions client/src/pages/Invitation/Invitation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const mocks = [
result: {
data: {
me: {
color: "#ffffff",
id: "5ed7bf9ea4df011004898efb",
name: "Test user",
email: "[email protected]",
Expand Down
3 changes: 2 additions & 1 deletion client/src/pages/Room/Room.helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ const sendMessageOptimisticResponse = (
roomId,
content: content,
mentions: [],
createdAt: `${Date.now()}`,
createdAt: Date.now(),
author: {
color: user.color,
id: user.id,
name: user.name,
username: user.username,
Expand Down
2 changes: 1 addition & 1 deletion client/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const timeAgo = (time: string): string | number => {
const MS_PER_YEAR = MS_PER_DAY * 365;

let current: any = new Date();
let previous: any = new Date(Number(time));
let previous: any = new Date(time);
let elapsed = current - previous

if (elapsed < MS_PER_MINUTE) {
Expand Down
3 changes: 3 additions & 0 deletions server/entities/Me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export class Me {

@Field(type => [Room])
public rooms!: Ref<Room>[];

@Field()
public color!: string;
}

export default Me;
3 changes: 3 additions & 0 deletions server/entities/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export class Member {

@Field(type => [ID])
public rooms!: Ref<Room>[];

@Field()
public color!: string;
}

export default Member;
4 changes: 4 additions & 0 deletions server/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class User {
@arrayProp({ ref: 'room' })
public rooms!: Ref<Room>[];

@Field()
@Property({ default: '#64FF8F' })
public color!: string;

@Property({ required: true, unique: true })
public email!: string;

Expand Down
24 changes: 22 additions & 2 deletions server/modules/user/user-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import "reflect-metadata";
import { ObjectID } from 'mongodb'
import { Context } from "../context.type";
import { ApolloError } from "apollo-server-express";
import { Resolver, Query, Ctx, Arg, Authorized, Mutation } from 'type-graphql';
import { Resolver, Query, Ctx, Arg, Authorized, Mutation, Args } from 'type-graphql';

import Member from "../../entities/Member";
import UserModel, { User } from "../../entities/User";
import Me from "../../entities/Me";
import { setColorArgs } from "./user.inputs";

@Resolver(of => User)
class UserResolver {
Expand All @@ -30,7 +31,7 @@ class UserResolver {
@Authorized()
@Query(() => User)
async getUser(
@Arg("id", { nullable: false }) id?: ObjectID
@Arg("id", { nullable: false }) id: ObjectID
): Promise<User> {
try {
let user = await UserModel.findOne({ _id: id }).populate("rooms");
Expand All @@ -41,6 +42,25 @@ class UserResolver {
}
}

@Authorized()
@Mutation(returns => Member)
async setColor(
@Args() { color }: setColorArgs,
@Ctx() context: Context
) {
try {
let user = await UserModel.findOneAndUpdate(
{ _id: context.currentUser.id },
{ color: color },
{ new: true }
)
if (!user) throw new ApolloError(`User not found with id`);
return user;
} catch (err) {
throw new ApolloError(err);
}
}

@Authorized()
@Mutation(returns => Boolean)
logout(@Ctx() context: Context) {
Expand Down
28 changes: 28 additions & 0 deletions server/modules/user/user.inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
IsHexColor,
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
import { isColorTooDark } from '../../utils'
import { Validate } from "class-validator";
import { ArgsType, Field } from "type-graphql";

@ValidatorConstraint({ name: "validateColor", async: false })
class ValidateColor implements ValidatorConstraintInterface {
validate(text: string, args: ValidationArguments) {
return !isColorTooDark(text);
}

defaultMessage(args: ValidationArguments) {
return "Color value ($value) is too dark";
}
}

@ArgsType()
export class setColorArgs {
@Field({ nullable: false })
@IsHexColor()
@Validate(ValidateColor)
color: string;
}
33 changes: 33 additions & 0 deletions server/modules/user/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ const queries = {
}
}
}
`,
setColor: `
mutation setColor($color: String!) {
setColor(color: $color) {
name
color
}
}
`
}

Expand Down Expand Up @@ -81,4 +89,29 @@ describe("UserResolver", () => {
}
})
})

it("should change user's color", async () => {
const OK_COLOR = '#ff5896';
const BAD_COLOR = '#000000';
let user = await gCall({
source: queries.setColor,
variableValues: { color: BAD_COLOR }
});

expect(user.errors[0].message).toBe('Argument Validation Error')

user = await gCall({
source: queries.setColor,
variableValues: { color: OK_COLOR }
});

expect(user).toMatchObject({
data: {
setColor: {
name: fakeUser.name,
color: OK_COLOR,
}
}
})
})
})
18 changes: 17 additions & 1 deletion server/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ const generateUsername = (name: string) => {
return `${sanitized}-${uniqueId}`;
};

const getColorLuminance = (color: string) => {
// https://stackoverflow.com/a/12043228/10629172
const c = color.substring(1); // strip #
const rgb = parseInt(c, 16); // convert rrggbb to decimal
const r = (rgb >> 16) & 0xff; // extract red
const g = (rgb >> 8) & 0xff; // extract green
const b = (rgb >> 0) & 0xff; // extract blue

const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
return luma;
}

const isColorTooDark = (color: string) => getColorLuminance(color) < 40;

export {
generateUsername,
};
getColorLuminance,
isColorTooDark,
};

0 comments on commit 509c5eb

Please sign in to comment.