Skip to content

Commit

Permalink
Add commenting to web
Browse files Browse the repository at this point in the history
  • Loading branch information
conrad-vanl committed Apr 4, 2024
1 parent 057dc72 commit e84ff81
Show file tree
Hide file tree
Showing 13 changed files with 597 additions and 235 deletions.
12 changes: 9 additions & 3 deletions micro-service/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ html {
html,
body {
height: 100%;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
background-color: #f8f8fa;
}

* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

img {
Expand All @@ -36,4 +40,6 @@ input:focus-visible {
outline: none;
}

::selection { background: rgba(110,197,184, 0.62); /* base.tertiary */ }
::selection {
background: rgba(110, 197, 184, 0.62); /* base.tertiary */
}
136 changes: 136 additions & 0 deletions packages/web-shared/components/Comments/AddComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { gql, useMutation } from '@apollo/client';
import { useState, useRef, useEffect } from 'react';
import { Spinner, PaperPlaneTilt } from '@phosphor-icons/react';
import styled, { withTheme } from 'styled-components';
import { themeGet } from '@styled-system/theme-get';
import Color from 'color';
import { TypeStyles } from '../../ui-kit/Typography';
import { system } from '../../ui-kit/_lib/system';
import { Avatar, Box, Button, H5 } from '../../ui-kit';
import { useCurrentUser } from '../../hooks';

const ADD_COMMENT = gql`
mutation addComment($parentId: ID!, $text: String!) {
addComment(parentId: $parentId, text: $text) {
id
isLiked
text
person {
id
firstName
lastName
photo {
uri
}
}
}
}
`;

const TextArea = withTheme(styled.textarea`
${TypeStyles.BodyText}
padding: 0;
padding-right: 8px;
outline: none;
flex-grow: 1;
max-height: 200px;
transition: all ${themeGet('timing.xl')} ease-out;
placeholder-text-color: ${({ theme }) => Color(theme.colors.text.secondary).alpha(0)};
caret-color: ${themeGet('colors.base.primary')};
border: none;
resize: none;
${system};
`);

const AddComment = ({ parent, onAdd }) => {
const { currentUser } = useCurrentUser();
const [comment, setComment] = useState('');
const textAreaRef = useRef(null);

const [addComment, { data, loading, error }] = useMutation(ADD_COMMENT, {
update(cache, { data: { addComment } }) {
cache.modify({
id: cache.identify(parent),
fields: {
comments(existingComments = []) {
const newCommentRef = cache.writeFragment({
data: addComment,
fragment: gql`
fragment NewComment on Comment {
id
isLiked
text
person {
id
firstName
lastName
photo {
uri
}
}
}
`,
});
return [...existingComments, newCommentRef];
},
},
});
},
});

const handleInputChange = (e) => {
setComment(e.target.value);
};

const handleAddComment = async () => {
await addComment({ variables: { parentId: parent.id, text: comment } });
setComment('');
textAreaRef.current.style.height = 'auto';
if (onAdd) onAdd(data);
};

useEffect(() => {
if (textAreaRef.current) {
// We need to reset the height momentarily to get the correct scrollHeight for the textarea
textAreaRef.current.style.height = '0px';
const scrollHeight = textAreaRef.current.scrollHeight;

// We then set the height directly, outside of the render loop
// Trying to set this with state or a ref will product an incorrect value.
textAreaRef.current.style.height = scrollHeight + 'px';
}
}, [textAreaRef, comment]);

return (
<Box p="xs" borderTop="1px solid" borderColor="text.quaternary">
<Box display="flex" flexDirection="row" alignItems="center" gridGap={8} pb="xs">
<Avatar
src={currentUser?.profile?.photo?.uri}
firstName={currentUser?.profile?.firstName}
lastName={currentUser?.profile?.lastName}
width={48}
/>
<H5>
{currentUser?.profile.firstName} {currentUser?.profile?.lastName}
</H5>
</Box>
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<TextArea
value={comment}
onChange={handleInputChange}
placeholder="Add a response"
ref={textAreaRef}
/>
<Button
disabled={!comment}
onClick={comment ? handleAddComment : undefined}
icon={loading ? <Spinner /> : <PaperPlaneTilt weight="fill" />}
padding={6}
/>
</Box>
</Box>
);
};

export default AddComment;
29 changes: 29 additions & 0 deletions packages/web-shared/components/Comments/Comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Box, H5, Avatar } from '../../ui-kit';

// turn line breaks into <br> tags and strip out any other html
function formatText(text) {
return text.split('\n').map((line, i) => (
<span key={i}>
{line}
<br />
</span>
));
}
const Comment = ({ text, person }) => (
<Box p="xs" borderBottom="1px solid" borderColor="text.quaternary">
<Box display="flex" flexDirection="row" alignItems="center" gridGap={8} pb="xs">
<Avatar
src={person?.photo?.uri}
firstName={person.firstName}
lastName={person.lastName}
width={48}
/>
<H5>
{person.firstName} {person.lastName}
</H5>
</Box>
<p>{formatText(text)}</p>
</Box>
);

export default Comment;
94 changes: 94 additions & 0 deletions packages/web-shared/components/Comments/Comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useRef, useState } from 'react';
import { Box, H4, Button } from '../../ui-kit';
import { X, ArrowRight, ChatsCircle } from '@phosphor-icons/react';
import { useCurrentUser } from '../../hooks';
import { AuthManager } from '../../components';

import { useAuth } from '../../providers/AuthProvider';
import authSteps from '../Auth/authSteps';

import Comment from './Comment';
import AddComment from './AddComment';

export const SIDEBAR_WITH = 400;

const Comments = ({ visible, parent, comments, onClose }) => {
const { currentUser } = useCurrentUser();
const [state] = useAuth();
const scrollRef = useRef(null);
const [showAuth, setShowAuth] = useState(false);

const scrollToBottom = () => {
requestAnimationFrame(() => scrollRef.current.scrollIntoView(false));
};

return (
<Box
width={{ md: SIDEBAR_WITH }}
position="fixed"
top={58}
right={0}
bottom={0}
left={{ _: 0, md: 'auto' }}
backgroundColor="fill.paper"
borderLeftStyle={{ _: 'none', md: 'solid' }}
borderLeftWidth={{ _: '0', md: '1px' }}
borderLeftColor="text.quaternary"
display="flex"
flexDirection="column"
>
<Box
p="xs"
borderBottom="1px solid"
borderColor="text.quaternary"
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
>
<H4>Responses</H4>
<Box cursor="pointer" display="flex" color="text.secondary" onClick={onClose}>
<X />
</Box>
</Box>
{currentUser ? (
<>
<Box style={{ overflowY: 'scroll' }}>
{comments.map((comment) => (
<Comment key={comment.id} {...comment} />
))}
<div ref={scrollRef} style={{ height: 1 }} />
</Box>
<AddComment parent={parent} onAdd={scrollToBottom} />
</>
) : (
<Box
p="md"
flexGrow="1"
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
>
<Box color="base.primary">
<ChatsCircle size={90} weight="fill" />
</Box>
<H4>Join the conversation</H4>
<Button
variant="secondary"
title="Sign up or Login"
onClick={() => setShowAuth(true)}
color="base.primary"
icon={<ArrowRight size={24} />}
mt="base"
/>
</Box>
)}
{showAuth && state.step !== authSteps.Success ? (
<AuthManager onClose={() => setShowAuth(false)} />
) : null}
</Box>
);
};

export default Comments;
5 changes: 5 additions & 0 deletions packages/web-shared/components/Comments/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Comments, { SIDEBAR_WITH } from './Comments';

export { SIDEBAR_WITH };

export default Comments;
Loading

0 comments on commit e84ff81

Please sign in to comment.