Skip to content

Commit

Permalink
Merge pull request #345 from omnivore-app/OMN-190
Browse files Browse the repository at this point in the history
[Omn-190] - [Settings View] - Labels
  • Loading branch information
jacksonh committed Apr 6, 2022
2 parents c874446 + 8670bbb commit 589e639
Show file tree
Hide file tree
Showing 25 changed files with 1,675 additions and 159 deletions.
3 changes: 2 additions & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"node": "14.18.x"
},
"volta": {
"node": "14.18.x"
"node": "14.18.1",
"yarn": "1.22.18"
}
}
59 changes: 59 additions & 0 deletions packages/api/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ export type Mutation = {
signup: SignupResult;
updateHighlight: UpdateHighlightResult;
updateHighlightReply: UpdateHighlightReplyResult;
updateLabel: UpdateLabelResult;
updateLinkShareInfo: UpdateLinkShareInfoResult;
updateReminder: UpdateReminderResult;
updateSharedComment: UpdateSharedCommentResult;
Expand Down Expand Up @@ -966,6 +967,11 @@ export type MutationUpdateHighlightReplyArgs = {
};


export type MutationUpdateLabelArgs = {
input: UpdateLabelInput;
};


export type MutationUpdateLinkShareInfoArgs = {
input: UpdateLinkShareInfoInput;
};
Expand Down Expand Up @@ -1577,6 +1583,32 @@ export type UpdateHighlightSuccess = {
highlight: Highlight;
};

export type UpdateLabelError = {
__typename?: 'UpdateLabelError';
errorCodes: Array<UpdateLabelErrorCode>;
};

export enum UpdateLabelErrorCode {
BadRequest = 'BAD_REQUEST',
Forbidden = 'FORBIDDEN',
NotFound = 'NOT_FOUND',
Unauthorized = 'UNAUTHORIZED'
}

export type UpdateLabelInput = {
color: Scalars['String'];
description?: InputMaybe<Scalars['String']>;
labelId: Scalars['ID'];
name: Scalars['String'];
};

export type UpdateLabelResult = UpdateLabelError | UpdateLabelSuccess;

export type UpdateLabelSuccess = {
__typename?: 'UpdateLabelSuccess';
label: Label;
};

export type UpdateLinkShareInfoError = {
__typename?: 'UpdateLinkShareInfoError';
errorCodes: Array<UpdateLinkShareInfoErrorCode>;
Expand Down Expand Up @@ -2095,6 +2127,11 @@ export type ResolversTypes = {
UpdateHighlightReplySuccess: ResolverTypeWrapper<UpdateHighlightReplySuccess>;
UpdateHighlightResult: ResolversTypes['UpdateHighlightError'] | ResolversTypes['UpdateHighlightSuccess'];
UpdateHighlightSuccess: ResolverTypeWrapper<UpdateHighlightSuccess>;
UpdateLabelError: ResolverTypeWrapper<UpdateLabelError>;
UpdateLabelErrorCode: UpdateLabelErrorCode;
UpdateLabelInput: UpdateLabelInput;
UpdateLabelResult: ResolversTypes['UpdateLabelError'] | ResolversTypes['UpdateLabelSuccess'];
UpdateLabelSuccess: ResolverTypeWrapper<UpdateLabelSuccess>;
UpdateLinkShareInfoError: ResolverTypeWrapper<UpdateLinkShareInfoError>;
UpdateLinkShareInfoErrorCode: UpdateLinkShareInfoErrorCode;
UpdateLinkShareInfoInput: UpdateLinkShareInfoInput;
Expand Down Expand Up @@ -2326,6 +2363,10 @@ export type ResolversParentTypes = {
UpdateHighlightReplySuccess: UpdateHighlightReplySuccess;
UpdateHighlightResult: ResolversParentTypes['UpdateHighlightError'] | ResolversParentTypes['UpdateHighlightSuccess'];
UpdateHighlightSuccess: UpdateHighlightSuccess;
UpdateLabelError: UpdateLabelError;
UpdateLabelInput: UpdateLabelInput;
UpdateLabelResult: ResolversParentTypes['UpdateLabelError'] | ResolversParentTypes['UpdateLabelSuccess'];
UpdateLabelSuccess: UpdateLabelSuccess;
UpdateLinkShareInfoError: UpdateLinkShareInfoError;
UpdateLinkShareInfoInput: UpdateLinkShareInfoInput;
UpdateLinkShareInfoResult: ResolversParentTypes['UpdateLinkShareInfoError'] | ResolversParentTypes['UpdateLinkShareInfoSuccess'];
Expand Down Expand Up @@ -2937,6 +2978,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
signup?: Resolver<ResolversTypes['SignupResult'], ParentType, ContextType, RequireFields<MutationSignupArgs, 'input'>>;
updateHighlight?: Resolver<ResolversTypes['UpdateHighlightResult'], ParentType, ContextType, RequireFields<MutationUpdateHighlightArgs, 'input'>>;
updateHighlightReply?: Resolver<ResolversTypes['UpdateHighlightReplyResult'], ParentType, ContextType, RequireFields<MutationUpdateHighlightReplyArgs, 'input'>>;
updateLabel?: Resolver<ResolversTypes['UpdateLabelResult'], ParentType, ContextType, RequireFields<MutationUpdateLabelArgs, 'input'>>;
updateLinkShareInfo?: Resolver<ResolversTypes['UpdateLinkShareInfoResult'], ParentType, ContextType, RequireFields<MutationUpdateLinkShareInfoArgs, 'input'>>;
updateReminder?: Resolver<ResolversTypes['UpdateReminderResult'], ParentType, ContextType, RequireFields<MutationUpdateReminderArgs, 'input'>>;
updateSharedComment?: Resolver<ResolversTypes['UpdateSharedCommentResult'], ParentType, ContextType, RequireFields<MutationUpdateSharedCommentArgs, 'input'>>;
Expand Down Expand Up @@ -3257,6 +3299,20 @@ export type UpdateHighlightSuccessResolvers<ContextType = ResolverContext, Paren
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type UpdateLabelErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLabelError'] = ResolversParentTypes['UpdateLabelError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateLabelErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type UpdateLabelResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLabelResult'] = ResolversParentTypes['UpdateLabelResult']> = {
__resolveType: TypeResolveFn<'UpdateLabelError' | 'UpdateLabelSuccess', ParentType, ContextType>;
};

export type UpdateLabelSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLabelSuccess'] = ResolversParentTypes['UpdateLabelSuccess']> = {
label?: Resolver<ResolversTypes['Label'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type UpdateLinkShareInfoErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLinkShareInfoError'] = ResolversParentTypes['UpdateLinkShareInfoError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateLinkShareInfoErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
Expand Down Expand Up @@ -3551,6 +3607,9 @@ export type Resolvers<ContextType = ResolverContext> = {
UpdateHighlightReplySuccess?: UpdateHighlightReplySuccessResolvers<ContextType>;
UpdateHighlightResult?: UpdateHighlightResultResolvers<ContextType>;
UpdateHighlightSuccess?: UpdateHighlightSuccessResolvers<ContextType>;
UpdateLabelError?: UpdateLabelErrorResolvers<ContextType>;
UpdateLabelResult?: UpdateLabelResultResolvers<ContextType>;
UpdateLabelSuccess?: UpdateLabelSuccessResolvers<ContextType>;
UpdateLinkShareInfoError?: UpdateLinkShareInfoErrorResolvers<ContextType>;
UpdateLinkShareInfoResult?: UpdateLinkShareInfoResultResolvers<ContextType>;
UpdateLinkShareInfoSuccess?: UpdateLinkShareInfoSuccessResolvers<ContextType>;
Expand Down
25 changes: 25 additions & 0 deletions packages/api/src/generated/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ type Mutation {
signup(input: SignupInput!): SignupResult!
updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult!
updateHighlightReply(input: UpdateHighlightReplyInput!): UpdateHighlightReplyResult!
updateLabel(input: UpdateLabelInput!): UpdateLabelResult!
updateLinkShareInfo(input: UpdateLinkShareInfoInput!): UpdateLinkShareInfoResult!
updateReminder(input: UpdateReminderInput!): UpdateReminderResult!
updateSharedComment(input: UpdateSharedCommentInput!): UpdateSharedCommentResult!
Expand Down Expand Up @@ -1194,6 +1195,30 @@ type UpdateHighlightSuccess {
highlight: Highlight!
}

type UpdateLabelError {
errorCodes: [UpdateLabelErrorCode!]!
}

enum UpdateLabelErrorCode {
BAD_REQUEST
FORBIDDEN
NOT_FOUND
UNAUTHORIZED
}

input UpdateLabelInput {
color: String!
description: String
labelId: ID!
name: String!
}

union UpdateLabelResult = UpdateLabelError | UpdateLabelSuccess

type UpdateLabelSuccess {
label: Label!
}

type UpdateLinkShareInfoError {
errorCodes: [UpdateLinkShareInfoErrorCode!]!
}
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/resolvers/function_resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
updateUserResolver,
uploadFileRequestResolver,
validateUsernameResolver,
updateLabelResolver,
} from './index'
import { getShareInfoForArticle } from '../datalayer/links/share_info'
import {
Expand Down Expand Up @@ -132,6 +133,7 @@ export const functionResolvers = {
deleteReminder: deleteReminderResolver,
setDeviceToken: setDeviceTokenResolver,
createLabel: createLabelResolver,
updateLabel: updateLabelResolver,
deleteLabel: deleteLabelResolver,
login: loginResolver,
signup: signupResolver,
Expand Down
73 changes: 73 additions & 0 deletions packages/api/src/resolvers/labels/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ import {
MutationCreateLabelArgs,
MutationDeleteLabelArgs,
MutationSetLabelsArgs,
MutationUpdateLabelArgs,
SetLabelsError,
SetLabelsErrorCode,
SetLabelsSuccess,
UpdateLabelError,
UpdateLabelErrorCode,
UpdateLabelSuccess,
} from '../../generated/graphql'
import { analytics } from '../../utils/analytics'
import { env } from '../../env'
Expand Down Expand Up @@ -42,6 +46,11 @@ export const labelsResolver = authorized<LabelsSuccess, LabelsError>(
const user = await getRepository(User).findOne({
where: { id: uid },
relations: ['labels'],
order: {
labels: {
createdAt: 'DESC',
},
},
})
if (!user) {
return {
Expand Down Expand Up @@ -249,3 +258,67 @@ export const setLabelsResolver = authorized<
}
}
})

export const updateLabelResolver = authorized<
UpdateLabelSuccess,
UpdateLabelError,
MutationUpdateLabelArgs
>(async (_, { input }, { claims: { uid }, log }) => {
log.info('updateLabelResolver')

try {
const { name, color, description, labelId } = input
const user = await getRepository(User).findOneBy({ id: uid })
if (!user) {
return {
errorCodes: [UpdateLabelErrorCode.Unauthorized],
}
}

const label = await getRepository(Label).findOne({
where: { id: labelId },
relations: ['user'],
})
if (!label) {
return {
errorCodes: [UpdateLabelErrorCode.NotFound],
}
}

const result = await AppDataSource.transaction(async (t) => {
await setClaims(t, uid)
return await t.getRepository(Label).update(
{ id: labelId },
{
name: name,
description: description || undefined,
color: color,
}
)
})

log.info('Updating a label', {
result,
labels: {
source: 'resolver',
resolver: 'updateLabelResolver',
},
})

if (!result) {
log.info('failed to update')
return {
errorCodes: [UpdateLabelErrorCode.BadRequest],
}
}

log.info('updated successfully')

return { label: label }
} catch (error) {
log.error('error updating label', error)
return {
errorCodes: [UpdateLabelErrorCode.BadRequest],
}
}
})
25 changes: 25 additions & 0 deletions packages/api/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,30 @@ const schema = gql`
union DeleteLabelResult = DeleteLabelSuccess | DeleteLabelError
input UpdateLabelInput {
labelId: ID!
color: String!
description: String
name: String!
}
type UpdateLabelSuccess {
label: Label!
}
enum UpdateLabelErrorCode {
UNAUTHORIZED
BAD_REQUEST
NOT_FOUND
FORBIDDEN
}
type UpdateLabelError {
errorCodes: [UpdateLabelErrorCode!]!
}
union UpdateLabelResult = UpdateLabelSuccess | UpdateLabelError
input LoginInput {
password: String!
email: String!
Expand Down Expand Up @@ -1410,6 +1434,7 @@ const schema = gql`
deleteReminder(id: ID!): DeleteReminderResult!
setDeviceToken(input: SetDeviceTokenInput!): SetDeviceTokenResult!
createLabel(input: CreateLabelInput!): CreateLabelResult!
updateLabel(input: UpdateLabelInput!): UpdateLabelResult!
deleteLabel(id: ID!): DeleteLabelResult!
login(input: LoginInput!): LoginResult!
signup(input: SignupInput!): SignupResult!
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/resolvers/labels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('Labels API', () => {
user: { id: user.id },
})
expect(res.body.data.labels.labels).to.eql(
labels.map((label) => ({
labels.reverse().map((label) => ({
id: label.id,
name: label.name,
color: label.color,
Expand Down
17 changes: 13 additions & 4 deletions packages/web/components/elements/DropdownElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Arrow,
Label,
} from '@radix-ui/react-dropdown-menu'
import { CSS } from '@stitches/react';
import { styled } from './../tokens/stitches.config'

const itemStyles = {
Expand Down Expand Up @@ -43,7 +44,7 @@ const StyledTriggerItem = styled(TriggerItem, {
...itemStyles,
})

const DropdownContent = styled(Content, {
export const DropdownContent = styled(Content, {
minWidth: 130,
backgroundColor: '$grayBg',
borderRadius: '0.5em',
Expand All @@ -70,12 +71,16 @@ type DropdownProps = {
showArrow?: boolean
triggerElement: React.ReactNode
children: React.ReactNode
styledArrow?: boolean
align?: DropdownAlignment
disabled?: boolean
css?: CSS
}

export const DropdownSeparator = styled(Separator, {
height: 0,
height: '1px',
margin: 0,
backgroundColor: '$grayBorder',
})

type DropdownOptionProps = {
Expand All @@ -91,6 +96,7 @@ export function DropdownOption(props: DropdownOptionProps): JSX.Element {
<StyledItem onSelect={props.onSelect}>
{props.title ?? props.children}
</StyledItem>
{props.hideSeparator ? null : <DropdownSeparator />}
</>
)
}
Expand All @@ -101,12 +107,15 @@ export function Dropdown({
triggerElement,
labelText,
showArrow = true,
disabled = false,
css
}: DropdownProps): JSX.Element {
return (
<Root modal={false}>
<DropdownTrigger>{triggerElement}</DropdownTrigger>
<DropdownTrigger disabled={disabled}>{triggerElement}</DropdownTrigger>
<DropdownContent
onInteractOutside={() => {
css={css}
onInteractOutside={(event) => {
// remove focus from dropdown
;(document.activeElement as HTMLElement).blur()
}}
Expand Down

2 comments on commit 589e639

@vercel
Copy link

@vercel vercel bot commented on 589e639 Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 589e639 Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.