From 7af8234c269b80b7134cb3a68a7380440775c102 Mon Sep 17 00:00:00 2001 From: Mehmet Ali Bekooglu Date: Mon, 24 Apr 2023 02:10:05 +0200 Subject: [PATCH 1/6] fix: Added file upload to comment --- src/components/comment.tsx | 74 ++++++++++++++++++++++++++++------ src/components/mumble-card.tsx | 29 +++++++++---- src/services/posts.ts | 23 ++++++----- src/state/card-reducer.ts | 41 ++++++++++++++++++- src/state/state-types.ts | 15 ++++++- 5 files changed, 148 insertions(+), 34 deletions(-) diff --git a/src/components/comment.tsx b/src/components/comment.tsx index f9209dc..f2c2a15 100644 --- a/src/components/comment.tsx +++ b/src/components/comment.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, FC } from 'react'; +import React, { ChangeEvent, FC, useState } from 'react'; import { Button, ProfileHeader, @@ -10,14 +10,40 @@ import { } from '@smartive-education/design-system-component-library-hello-world-team'; import { User } from 'next-auth'; import Link from 'next/link'; +import { ModalFileUpload } from './modal-file-upload'; +import { CardForm, FileData } from '../state/state-types'; interface CurrentUser { user?: User; - handleCommentChanged: (e: ChangeEvent) => void; + handleCommentChanged: (f: CardForm) => void; submitComment: () => void; + isSubmitting: boolean; + form: CardForm; } -export const CommentMumble: FC = ({ user, handleCommentChanged, submitComment }) => { +export const CommentMumble: FC = ({ user, handleCommentChanged, submitComment, isSubmitting, form }) => { + const [isOpen, setIsOpen] = useState(false); + + const onTextCommentChanged = (e: ChangeEvent) => { + e.preventDefault(); + handleCommentChanged({ + comment: e.target.value, + }); + }; + const onFileHandler = (file: FileData) => { + handleCommentChanged({ + file: file.file, + filename: file.filename, + }); + }; + + const fileUploadClick = () => { + handleCommentChanged({ + commentError: '', + }); + setIsOpen(true); + }; + return ( <>
@@ -33,18 +59,40 @@ export const CommentMumble: FC = ({ user, handleCommentChanged, sub href={`/profile/${user?.id}`} />
- - -
- - -
+ onTextCommentChanged(e)} /> + {form.filename ? ( + + {'Bild hinzugefügt: ' + form.filename} + + ) : null} + {form.commentError ? ( + + {form.commentError} + + ) : null} +
+ + +
+ setIsOpen(e)} + onSubmitFile={onFileHandler} + isSubmitting={isSubmitting} + /> ); }; diff --git a/src/components/mumble-card.tsx b/src/components/mumble-card.tsx index d79c213..fbc8d6e 100644 --- a/src/components/mumble-card.tsx +++ b/src/components/mumble-card.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, FC, useReducer } from 'react'; +import React, { FC, useReducer } from 'react'; import { CommentButton, CopyButton, @@ -16,6 +16,7 @@ import Link from 'next/link'; import Image from 'next/image'; import { cardReducer } from '../state/card-reducer'; import { MumbleTextContent } from './mumble-text-content'; +import { CardForm } from '../state/state-types'; interface MumbleCard { mumble: Mumble; @@ -26,7 +27,12 @@ interface MumbleCard { export const MumbleCard: FC = ({ mumble, showComments, commentSubmitted }) => { const { data: session } = useSession(); - const [state, dispatch] = useReducer(cardReducer, { showComments, mumble, comment: '' }); + const [state, dispatch] = useReducer(cardReducer, { + form: { comment: '', commentError: '', filename: '', file: null }, + showComments, + mumble, + isSubmitting: false, + }); const likedPost = async () => { await likePost({ @@ -37,21 +43,28 @@ export const MumbleCard: FC = ({ mumble, showComments, commentSubmit dispatch({ type: 'post_liked', likedByUser: !state.mumble.likedByUser }); }; - const handleCommentChanged = (e: ChangeEvent) => { - dispatch({ type: 'comment_changed', comment: e.target.value }); + const handleCommentChanged = (f: CardForm) => { + if (f.comment) { + dispatch({ type: 'comment_changed', comment: f.comment }); + } else if (f.file && f.filename) { + dispatch({ type: 'file_changed', file: f.file, name: f.filename }); + } else if (f.commentError) { + dispatch({ type: 'comment_error', error: f.commentError }); + } }; const copyMumbleUrl = () => navigator.clipboard.writeText(`${window.location.href}mumble/${state.mumble.id}`); const submitComment = async () => { + dispatch({ type: 'comment_submitting' }); const newPost = await commentPost({ postId: state.mumble.id, - comment: state.comment, + comment: state.form.comment, + file: state.form.file, accessToken: session?.accessToken, }); - dispatch({ type: 'comment_submitted', newPost }); - commentSubmitted && commentSubmitted(newPost); + dispatch({ type: 'comment_submitted', newPost }); }; return ( @@ -127,6 +140,8 @@ export const MumbleCard: FC = ({ mumble, showComments, commentSubmit user={session?.user} handleCommentChanged={handleCommentChanged} submitComment={submitComment} + form={state.form} + isSubmitting={state.isSubmitting} > )} diff --git a/src/services/posts.ts b/src/services/posts.ts index 5a23185..320ba9a 100644 --- a/src/services/posts.ts +++ b/src/services/posts.ts @@ -62,22 +62,23 @@ export const createPost = async (postArgs: PostArgs) => { } }; -export const commentPost = async (params: { postId: string; comment: string; accessToken?: string }) => { - const { postId, comment, accessToken } = params || {}; +export const commentPost = async (params: { postId: string; comment: string; file: File | null; accessToken?: string }) => { + const { postId, comment, file, accessToken } = params || {}; + const formData = new FormData(); + formData.append('text', comment); + if (file) { + formData.append('image', file); + } if (!accessToken) { throw new Error('No access token'); } - const res = await axios.post( - `${process.env.NEXT_PUBLIC_QWACKER_API_URL}posts/${postId}`, - { text: comment }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - } - ); + const res = await axios.post(`${process.env.NEXT_PUBLIC_QWACKER_API_URL}posts/${postId}`, formData, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); const reply = (await res.data) as Reply; diff --git a/src/state/card-reducer.ts b/src/state/card-reducer.ts index 3b20321..231b5d3 100644 --- a/src/state/card-reducer.ts +++ b/src/state/card-reducer.ts @@ -5,6 +5,9 @@ type CardAction = | { type: 'add_comment' } | { type: 'comment' } | { type: 'comment_changed'; comment: string } + | { type: 'comment_error'; error: string } + | { type: 'file_changed'; file: File; name: string } + | { type: 'comment_submitting' } | { type: 'comment_submitted'; newPost: Reply } | { type: 'post_liked'; likedByUser: boolean }; @@ -25,13 +28,47 @@ export function cardReducer(state: CardState, action: CardAction) { case 'comment_changed': { return { ...state, - comment: action.comment, + form: { + ...state.form, + comment: action.comment, + }, + }; + } + case 'comment_error': { + return { + ...state, + form: { + ...state.form, + commentError: action.error, + }, + }; + } + case 'file_changed': { + return { + ...state, + form: { + ...state.form, + file: action.file, + filename: action.name, + }, + }; + } + case 'comment_submitting': { + return { + ...state, + isSubmitting: true, }; } case 'comment_submitted': { return { ...state, - comment: '', + form: { + comment: '', + commentError: '', + file: null, + filename: '', + }, + isSubmitting: false, }; } case 'post_liked': { diff --git a/src/state/state-types.ts b/src/state/state-types.ts index 855346b..69bba81 100644 --- a/src/state/state-types.ts +++ b/src/state/state-types.ts @@ -1,9 +1,22 @@ import { Mumble, Reply, User } from '../services/service-types'; +export type CardForm = { + file?: File | null; + filename?: string; + comment?: string; + commentError?: string; +}; + export type CardState = { - comment: string; + form: { + file: File | null; + filename: string; + comment: string; + commentError: string; + }; mumble: Mumble; showComments?: boolean; + isSubmitting: boolean; }; export type FileState = { From 58080a714ffc5be25e48e9a42951ccc94df4ba7c Mon Sep 17 00:00:00 2001 From: Mehmet Ali Bekooglu Date: Mon, 24 Apr 2023 16:19:30 +0200 Subject: [PATCH 2/6] fix: Added upload of new comments --- src/components/mumble-list.tsx | 4 ++-- src/pages/mumble/[id].tsx | 18 ++++++++++++------ src/state/list-reducer.ts | 6 +++--- src/utils/creator-to.ts | 19 +++++++++++++++++-- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/components/mumble-list.tsx b/src/components/mumble-list.tsx index aaaead6..d1b9a0c 100644 --- a/src/components/mumble-list.tsx +++ b/src/components/mumble-list.tsx @@ -5,7 +5,7 @@ import { MumbleCard } from './mumble-card'; import { fetchMumbles, fetchMumblesSearch } from '../services/posts'; import InfiniteScroll from 'react-infinite-scroller'; import { WriteCard } from './write-card'; -import { addCreatorToMumble } from '../utils/creator-to'; +import { addCreatorToMumbles } from '../utils/creator-to'; import { listReducer } from '../state/list-reducer'; import { useSession } from 'next-auth/react'; @@ -19,7 +19,7 @@ interface MumbleList { } export const MumbleList: FC = ({ mumbles, users, totalMumbles, mumbleKey, userId, showWriteCard = false }) => { - const mumblesWithCreator = addCreatorToMumble(mumbles, users); + const mumblesWithCreator = addCreatorToMumbles(mumbles, users); const { data: session } = useSession(); const [state, dispatch] = useReducer(listReducer, { diff --git a/src/pages/mumble/[id].tsx b/src/pages/mumble/[id].tsx index 7b44278..715883e 100644 --- a/src/pages/mumble/[id].tsx +++ b/src/pages/mumble/[id].tsx @@ -3,24 +3,29 @@ import { getToken } from 'next-auth/jwt'; import { MumbleCard } from '../../components/mumble-card'; import { fetchUsers } from '../../services/users'; import { fetchMumbleById, fetchReplies } from '../../services/posts'; -import { Mumble, Reply } from '../../services/service-types'; +import { Mumble, Reply, User } from '../../services/service-types'; import { BorderType, Card, Size } from '@smartive-education/design-system-component-library-hello-world-team'; import { useReducer } from 'react'; import { mumblePageReducer } from '../../state/mumble-page-reducer'; -import { addCreatorToReply } from '../../utils/creator-to'; import { MumblePageState } from '../../state/state-types'; +import { addCreatorToReplies, addCreatorToReply } from '../../utils/creator-to'; type Props = { mumble: Mumble; replies?: Reply[]; + users: User[]; }; -export default function MumblePage({ mumble, replies }: Props): InferGetServerSidePropsType { +export default function MumblePage({ + mumble, + replies, + users, +}: Props): InferGetServerSidePropsType { const initialMumbleCardState: MumblePageState = { mumble, replies: replies ?? [] }; const [state, dispatch] = useReducer(mumblePageReducer, initialMumbleCardState); const commentSubmitted = (newReply: Reply) => { - dispatch({ type: 'comment_submitted', newReply }); + dispatch({ type: 'comment_submitted', newReply: addCreatorToReply(newReply, users) }); }; return ( @@ -30,7 +35,7 @@ export default function MumblePage({ mumble, replies }: Props): InferGetServerSi
- +
{replies && replies.length > 0 && (
    @@ -65,7 +70,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, query: { id fetchUsers({ accessToken: session?.accessToken }), ]); - const repliesWithUserInfo = addCreatorToReply(replies, users); + const repliesWithUserInfo = addCreatorToReplies(replies, users); const creator = users?.find((user) => user.id === mumble.creator); const mumbleWithUserInfo = { @@ -84,6 +89,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, query: { id props: { mumble: mumbleWithUserInfo, replies: repliesWithUserInfo, + users, }, }; }; diff --git a/src/state/list-reducer.ts b/src/state/list-reducer.ts index d0bea40..3862021 100644 --- a/src/state/list-reducer.ts +++ b/src/state/list-reducer.ts @@ -1,5 +1,5 @@ import { Mumble } from '../services/service-types'; -import { addCreatorToMumble } from '../utils/creator-to'; +import { addCreatorToMumbles } from '../utils/creator-to'; import { ListState } from './state-types'; type ListAction = @@ -11,14 +11,14 @@ export function listReducer(state: ListState, action: ListAction) { case 'load_new_mumbles': { return { ...state, - mumbles: [...addCreatorToMumble(action.loadNewMumbles, state.users), ...state.mumbles], + mumbles: [...addCreatorToMumbles(action.loadNewMumbles, state.users), ...state.mumbles], totalMumbles: state.totalMumbles + action.count, }; } case 'reload_mumbles': { return { ...state, - mumbles: [...state.mumbles, ...addCreatorToMumble(action.reloadedMumbles, state.users)], + mumbles: [...state.mumbles, ...addCreatorToMumbles(action.reloadedMumbles, state.users)], nextOffset: state.nextOffset + 10, }; } diff --git a/src/utils/creator-to.ts b/src/utils/creator-to.ts index 87e1f00..e741d18 100644 --- a/src/utils/creator-to.ts +++ b/src/utils/creator-to.ts @@ -1,6 +1,6 @@ import { Mumble, Reply, User } from '../services/service-types'; -export function addCreatorToMumble(mumbles: Mumble[], users: User[]) { +export function addCreatorToMumbles(mumbles: Mumble[], users: User[]) { return mumbles.map((mumble) => { const creator = users?.find((user) => user.id === mumble.creator); return { @@ -17,7 +17,7 @@ export function addCreatorToMumble(mumbles: Mumble[], users: User[]) { }); } -export function addCreatorToReply(replies: Reply[], users: User[] | undefined) { +export function addCreatorToReplies(replies: Reply[], users: User[] | undefined) { return replies.map((reply) => { const creator = users?.find((user) => user.id === reply.creator); return { @@ -33,3 +33,18 @@ export function addCreatorToReply(replies: Reply[], users: User[] | undefined) { }; }); } + +export function addCreatorToReply(reply: Reply, users: User[] | undefined) { + const creator = users?.find((user) => user.id === reply.creator); + return { + ...reply, + creatorProfile: { + id: creator?.id, + userName: creator?.userName, + firstName: creator?.firstName, + lastName: creator?.lastName, + fullName: `${creator?.firstName} ${creator?.lastName}`, + avatarUrl: creator?.avatarUrl, + }, + }; +} From bea86bd1b555d27037fda3d0f3a53af4a95f34a7 Mon Sep 17 00:00:00 2001 From: Mehmet Ali Bekooglu Date: Mon, 24 Apr 2023 19:21:00 +0200 Subject: [PATCH 3/6] fix: Rearranged if check, otherwise not everything is deleted --- src/components/mumble-card.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/mumble-card.tsx b/src/components/mumble-card.tsx index fbc8d6e..b00a7c7 100644 --- a/src/components/mumble-card.tsx +++ b/src/components/mumble-card.tsx @@ -44,12 +44,12 @@ export const MumbleCard: FC = ({ mumble, showComments, commentSubmit }; const handleCommentChanged = (f: CardForm) => { - if (f.comment) { - dispatch({ type: 'comment_changed', comment: f.comment }); - } else if (f.file && f.filename) { + if (f.file && f.filename) { dispatch({ type: 'file_changed', file: f.file, name: f.filename }); } else if (f.commentError) { dispatch({ type: 'comment_error', error: f.commentError }); + } else { + dispatch({ type: 'comment_changed', comment: f.comment || '' }); } }; From aa0fca1678d1176d7309646bdd4cd14aeec08344 Mon Sep 17 00:00:00 2001 From: Mehmet Ali Bekooglu Date: Mon, 24 Apr 2023 21:03:30 +0200 Subject: [PATCH 4/6] fix: Fixed forgotten comma --- src/components/mumble-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mumble-card.tsx b/src/components/mumble-card.tsx index 44bd788..3fd167a 100644 --- a/src/components/mumble-card.tsx +++ b/src/components/mumble-card.tsx @@ -33,7 +33,7 @@ export const MumbleCard: FC = ({ mumble, showComments, commentSubmit form: { comment: '', commentError: '', filename: '', file: null }, showComments, mumble, - copiedActive: false + copiedActive: false, isSubmitting: false, }); From 1028623ee6d01af637af1ec3d0b80c2be4c70e65 Mon Sep 17 00:00:00 2001 From: Mehmet Ali Bekooglu Date: Mon, 24 Apr 2023 22:08:55 +0200 Subject: [PATCH 5/6] fix: newest version of storybook --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f9818b..f1d278c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "cas-fee-adv-qwacker-tpl", "version": "0.0.0-development", "dependencies": { - "@smartive-education/design-system-component-library-hello-world-team": "^1.20.21", + "@smartive-education/design-system-component-library-hello-world-team": "^1.20.22", "@tanstack/react-query": "^4.28.0", "@tanstack/react-table": "^8.8.0", "@types/node": "18.11.9", @@ -2141,9 +2141,9 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" }, "node_modules/@smartive-education/design-system-component-library-hello-world-team": { - "version": "1.20.21", - "resolved": "https://npm.pkg.github.com/download/@smartive-education/design-system-component-library-hello-world-team/1.20.21/89d4bd8c85bdcfe316333c9eaaf96d10011657f7", - "integrity": "sha512-g+Jpk9UxihkJqr7yLlF5vDF0NEvppKDbPvqO++lJaKi99pf+v45lkHr3/V5rXlYMhij8G56hMeSf0/LvnI9MPA==", + "version": "1.20.22", + "resolved": "https://npm.pkg.github.com/download/@smartive-education/design-system-component-library-hello-world-team/1.20.22/3e31e029aed7f455fe3086a7b9ae387f86fcb7e3", + "integrity": "sha512-8uXalCOlIqJh9jpPz/RtLR74V0XR/lo3mqJ6sAhAmVDRr7FVBzrCn9BKTbkh5EN6hN8n7ooe8+zLG2DzO+id8g==", "dependencies": { "ci": "^2.2.0", "react": "^18.2.0", @@ -9765,9 +9765,9 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" }, "@smartive-education/design-system-component-library-hello-world-team": { - "version": "1.20.21", - "resolved": "https://npm.pkg.github.com/download/@smartive-education/design-system-component-library-hello-world-team/1.20.21/89d4bd8c85bdcfe316333c9eaaf96d10011657f7", - "integrity": "sha512-g+Jpk9UxihkJqr7yLlF5vDF0NEvppKDbPvqO++lJaKi99pf+v45lkHr3/V5rXlYMhij8G56hMeSf0/LvnI9MPA==", + "version": "1.20.22", + "resolved": "https://npm.pkg.github.com/download/@smartive-education/design-system-component-library-hello-world-team/1.20.22/3e31e029aed7f455fe3086a7b9ae387f86fcb7e3", + "integrity": "sha512-8uXalCOlIqJh9jpPz/RtLR74V0XR/lo3mqJ6sAhAmVDRr7FVBzrCn9BKTbkh5EN6hN8n7ooe8+zLG2DzO+id8g==", "requires": { "ci": "^2.2.0", "react": "^18.2.0", diff --git a/package.json b/package.json index e0dc761..01df554 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "depcruise": "npx depcruise --config .dependency-cruiser.js src" }, "dependencies": { - "@smartive-education/design-system-component-library-hello-world-team": "^1.20.21", + "@smartive-education/design-system-component-library-hello-world-team": "^1.20.22", "@tanstack/react-query": "^4.28.0", "@tanstack/react-table": "^8.8.0", "@types/node": "18.11.9", From cfb1d74d79b19c4e39c7b8e2e1c23468aecb07f2 Mon Sep 17 00:00:00 2001 From: Mehmet Ali Bekooglu Date: Tue, 25 Apr 2023 08:01:49 +0200 Subject: [PATCH 6/6] feat: Refactored after PR. Added loading spinner after comment is posted --- src/components/comment.tsx | 100 ++++++++++++++++++++++--------------- src/utils/creator-to.ts | 13 +---- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/components/comment.tsx b/src/components/comment.tsx index 31ad26d..1fb5fa7 100644 --- a/src/components/comment.tsx +++ b/src/components/comment.tsx @@ -14,6 +14,7 @@ import Image from 'next/image'; import { profileAvatar } from '../utils/profile-avatar'; import { ModalFileUpload } from './modal-file-upload'; import { CardForm, FileData } from '../state/state-types'; +import { Oval } from 'react-loader-spinner'; interface CurrentUser { user?: User; @@ -48,46 +49,67 @@ export const CommentMumble: FC = ({ user, handleCommentChanged, sub return ( <> -
    - -
    - onTextCommentChanged(e)} /> - {form.filename ? ( - - {'Bild hinzugefügt: ' + form.filename} - - ) : null} - {form.commentError ? ( - - {form.commentError} - - ) : null} - -
    - - + {isSubmitting ? ( +
    +
    -
    + ) : ( +
    + +
    + onTextCommentChanged(e)} + /> + {form.filename ? ( + + {'Bild hinzugefügt: ' + form.filename} + + ) : null} + {form.commentError ? ( + + {form.commentError} + + ) : null} + +
    + + +
    +
    + )} { - const creator = users?.find((user) => user.id === reply.creator); - return { - ...reply, - creatorProfile: { - id: creator?.id, - userName: creator?.userName, - firstName: creator?.firstName, - lastName: creator?.lastName, - fullName: `${creator?.firstName} ${creator?.lastName}`, - avatarUrl: creator?.avatarUrl, - }, - }; + return addCreatorToReply(reply, users); }); }