Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions frontend-ssr/components/SkeletonImage/SkeletonImage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@import "../../styles/variables";
@import "../../styles/animations";

.skeleton__image {
position: relative;
padding: 0;
animation: skeletonOpacity 1s linear infinite;

&:after {
content: '';
width: 100%;
height: 0px;
display: block;
position: absolute;
box-shadow: 0 0 1px 3px lighten(color(grey), 20%);
top: 50%;
left: 0;
}
}
16 changes: 16 additions & 0 deletions frontend-ssr/components/SkeletonImage/SkeletonImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import styles from './SkeletonImage.module.scss';

interface Props {
width: string,
height: string,
}

const SkeletonImage = ({ width, height } : Props) => (
<div
style={{ width, height }}
className={`${styles.skeleton__image} bg-grey overflow-hidden`}
/>
);

export default SkeletonImage;
1 change: 1 addition & 0 deletions frontend-ssr/components/SkeletonImage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './SkeletonImage';
109 changes: 109 additions & 0 deletions frontend-ssr/components/lessons/LessonComments/LessonComments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { RootState } from '~/redux/root.reducer';

import {
loadComments, addComment,
} from '~/redux/lesson-comments/lesson-comments.actions';
import LessonCommentsService from '~/services/LessonComments.service';
import SweetAlertService from '~/services/sweet-alert/SweetAlert.service';

import SkeletonImage from '~/components/SkeletonImage';
import CommentBlock from './comment/CommentBlock';
import NewComment from './newComment/NewComment';

interface State {
loading: boolean;
}

interface Props extends PropsFromRedux{
idLesson: string,
}

class LessonComments extends React.Component<Props, State> {
constructor(props) {
super(props);

this.state = {
loading: props.comments.lessonId === undefined,
};
}

componentDidMount() {
const { comments, idLesson } = this.props;

if (comments.lessonId === undefined) {
this.loadLessonComments(idLesson);
}
}

loadLessonComments = async (id) => {
const { dispatch, idLesson } = this.props;

this.setState({ loading: true });

try {
const newComments = await LessonCommentsService.getComments(id);
dispatch(loadComments(idLesson, newComments));
} catch (err) {
SweetAlertService.toast({ type: 'error', text: err });
} finally {
this.setState({ loading: false });
}
};

addLessonComment = async (id, newComment) => {
const { dispatch, idLesson } = this.props;

this.setState({ loading: true });

try {
await LessonCommentsService.addComment(id);
dispatch(addComment(idLesson, newComment));
} catch (err) {
SweetAlertService.toast({ type: 'error', text: err });
} finally {
this.setState({ loading: false });
}
}

render() {
const { comments, idLesson } = this.props;
const { loading } = this.state;

return (
<div style={{ border: '5px solid black' }}>
<ul>
{comments[idLesson] && comments[idLesson].map((commentProps) => (
<CommentBlock
{...commentProps}
key={commentProps._id}
/>
))}
{loading && (
<li style={{ padding: '0.5em 1em', backgroundColor: 'rgba(192,192,192, 0.9)' }}>
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
</li>
)}
<NewComment onComment={this.addLessonComment} />
</ul>
</div>
);
}
}

function mapStateToProps(state: RootState) {
return {
comments: state.comments,
};
}

const connector = connect(mapStateToProps);

type PropsFromRedux = ConnectedProps<typeof connector>

export default connector(LessonComments);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import '../../../../styles/variables';

.comment {
line-height: 1.5em;
border-bottom: 2px solid color(darker-white);
font-size: 0.8em;
margin-bottom: 0.5em;
padding: 0.5em 1em;

img {
margin-right: 0.8em;
}

:global .body {
white-space: initial;
}

.user {
margin-right: 0.4em;
border-bottom: 1px solid color(silver);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import Link from 'next/link';
import { timeAgo } from '~/services/Utils';

import styles from './CommentBlock.module.scss';

interface Props {
avatar: string;
username: string;
comment: string;
timestamp: number;
}

const CommentBlock = ({
avatar,
username,
comment,
timestamp,
}: Props) => (
<li className={styles.comment}>
<div className="d-flex">
<Link href="#">
<a href="#" className="d-flex no-underline">
<img
width="42"
height="42"
src={avatar}
alt={`${username} avatar`}
/>
<span className={styles.user}>{username}</span>
</a>
</Link>
</div>
<div className="body">
<div className="m-0">
<p>{comment}</p>
</div>
<time className="text-silver">{timeAgo(new Date(timestamp))}</time>
</div>
</li>
);

export default CommentBlock;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.new-comment {
form {
display: block;
max-width: 100%;

div {
margin: 1em 0;
}

textarea {
border: 1px solid color(silver);
padding: 1em;
min-height: 10em;
width: 100%;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { Comment } from '~/redux/lesson-comments/types';
import { uuid } from '~/services/Utils';

import styles from './NewComment.module.scss';

interface Props {
onComment: (_id, comment: Comment) => Promise<void>;
}

const NewComment = ({ onComment } : Props) => {
const handleSubmit = (e) => {
e.preventDefault();

const form = e.target;

const newComment = {
_id: uuid(),
username: 'Raluca99',
name: 'Raluca',
avatar: 'https://joeschmoe.io/api/v1/jane',
comment: e.target.comment.value,
timestamp: Date.now(),
};

onComment(newComment._id, newComment);

form.reset();
};

return (
<div className={styles['new-comment']}>
<form onSubmit={handleSubmit}>
<div>
<textarea
name="comment"
id="comment"
placeholder="message"
/>
</div>
<button type="submit" className="btn btn--blue">Adauga un nou comentariu</button>
</form>
</div>
);
};

export default NewComment;
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
markNotificationAsRead,
markNotificationAsUnread,
markAllAsRead,
} from '../../redux/user/user.actions';
import UserService from '../../services/User.service';
} from '~/redux/user/user.actions';
import UserService from '~/services/User.service';

import NotificationSkeleton from './notification/NotificationSkeleton';
import SkeletonImage from '~/components/SkeletonImage';
import { RootState } from '~/redux/root.reducer';
import SweetAlertService from '~/services/sweet-alert/SweetAlert.service';
import Notification from './notification/Notification';
Expand Down Expand Up @@ -166,13 +166,13 @@ class NotificationsTooltip extends React.Component<
/>
))}
{loading && (
<>
<NotificationSkeleton />
<NotificationSkeleton />
<NotificationSkeleton />
<NotificationSkeleton />
<NotificationSkeleton />
</>
<li style={{ padding: '0.5em 1em', backgroundColor: 'rgba(192,192,192, 0.9)' }}>
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
<SkeletonImage width="1.6em" height="1.6em" />
</li>
)}
<li className="invisible" ref={this.hiddenRef} />
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,3 @@
}
}

.skeleton {
padding: 0.5em 1em;
background-color: lighten(color(silver), 10%);
}

.skeleton__image {
width: 1.6em;
height: 1.6em;
position: relative;
padding: 0;

&:after {
content: '';
width: 100%;
height: 0px;
display: block;
position: absolute;
box-shadow: 0 0 1px 3px lighten(color(grey), 20%);
top: 50%;
left: 0;
animation: skeletonOpacity 2s ease-in-out infinite;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import Link from 'next/link';
import { timeAgo } from '../../../services/Utils';
import { timeAgo } from '~/services/Utils';

import styles from './Notification.module.scss';

Expand Down
34 changes: 34 additions & 0 deletions frontend-ssr/redux/lesson-comments/lesson-comments.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Comment } from './types';

const ADD = 'lesson-comments/ADD';
const REMOVE = 'lesson-comments/REMOVE';
const LOAD = 'lesson-comments/LOAD';

export const LESSON_COMMENTS = {
ADD,
REMOVE,
LOAD,
};

export const addComment = (id: string, comments: Comment | Comment[], index = 0) => ({
type: ADD,
payload: {
id,
comments,
index,
},
});
export const removeComment = (id: string | string[]) => ({
type: REMOVE,
payload: {
id,
},
});

export const loadComments = (id: string, newComments: Comment[]) => ({
type: LOAD,
payload: {
id,
newComments,
},
});
Loading