Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VueのCommentsコンポーネントをReactへと置き換えた #7449

Draft
wants to merge 44 commits into
base: vuex-related
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
998f694
zustandを追加
dowdiness Nov 28, 2023
6549ad1
WatchToggleコンポーネントをreactへと置換
dowdiness Nov 28, 2023
8075845
swrのアップデート
dowdiness Feb 27, 2024
1757df2
ProductCheckerをResponsibleMentorへと変更してZustandの使い方を改善
dowdiness Feb 27, 2024
f100bf1
useClipboardとTimeClipboardを追加
dowdiness Feb 27, 2024
e0db0c3
Markdownコンポーネントの追加
dowdiness Feb 27, 2024
b5d1dd1
更新機能の無い見た目だけのReactionsコンポーネントを追加
dowdiness Feb 27, 2024
6f10b82
Comment関連のReactコンポーネントを追加
dowdiness Feb 27, 2024
84036a4
ReactのCommentsコンポーネントを表示する
dowdiness Feb 27, 2024
9a6e215
useBeforeunloadの追加
dowdiness Feb 27, 2024
791bf7f
importするコンポーネントの名前間違いを修正
dowdiness Feb 27, 2024
ccb2730
useTextareaを追加
dowdiness Feb 27, 2024
c38b813
Comment/Commentsを使うように変更
dowdiness Feb 27, 2024
1aeb5d5
textareaをuseRefで管理するための変更
dowdiness Feb 28, 2024
8e7e12c
管理者の場合は編集と削除が出来るように変更
dowdiness Feb 28, 2024
3c22f5a
Reactionボタンに更新機能を付ける
dowdiness Feb 28, 2024
bda33c2
コメントが出来ない不具合を修正
dowdiness Feb 28, 2024
956c190
相談部屋のviewのエラーを修正
dowdiness Feb 28, 2024
5b1ca2b
viewからvueのコメントを削除
dowdiness Feb 28, 2024
77f9bcf
ResponsibleMentorのバグを修正
dowdiness Feb 28, 2024
2e37da7
useResponsibleMentorをシンプルにした
dowdiness Mar 4, 2024
b8a1699
BecomeResponsibleMentorを分かりやすくした
dowdiness Mar 4, 2024
bc01d7e
useTextareaでselectorを使うよう変更
dowdiness Mar 4, 2024
36aeabd
MarkdownでuseBeforeunloadを使うよう変更
dowdiness Mar 4, 2024
b198396
useMarkdownを追加
dowdiness Mar 4, 2024
401cd24
removePreviewLastChildをuseImperativeHandle使うよう変更
dowdiness Mar 4, 2024
142a6bb
MarkdownTextareaのpropsを改名
dowdiness Mar 4, 2024
8bf4855
CommentFormでisValidatingを活用
dowdiness Mar 4, 2024
1aaa41c
ResponsibleMentorコンポーネントをzustandを使うかどうかで別々にした
dowdiness Mar 4, 2024
8627f24
reactionのバグを修正
dowdiness Mar 5, 2024
e2f38cf
配列が期待されるwatchesの初期値がnullなためにTypeErrorが出ているので修正
dowdiness Mar 5, 2024
d296477
WatchToggleの不具合を修正
dowdiness Mar 6, 2024
07d2f94
checkでサーバーから渡されたエラーメッセージを使うように変更
dowdiness Mar 13, 2024
2336e15
MarkdownTextareaでuseTextareaを使うように変更
dowdiness Mar 13, 2024
96f6637
担当者になるかどうかのロジックのバグを修正
dowdiness Mar 13, 2024
7a8b49a
Product::CheckerTestをtoastの表示を使ってテストするよう変更
dowdiness Mar 13, 2024
d804713
コメントのテストで画面遷移する前にコメントを投稿してtoastが出ているか確認するように変更
dowdiness Mar 13, 2024
5390e78
直せるCommentのテストを直した
dowdiness Mar 13, 2024
4b9b70f
reportsテストのclick unwatchを修正
dowdiness Mar 13, 2024
599445d
RuboCopの警告を修正
dowdiness Apr 1, 2024
41b88f8
エラーと警告の修正
dowdiness Apr 1, 2024
b24b1ac
helperを使ってCommentsコンポーネントのPropsを書くように変更
dowdiness Apr 1, 2024
24aa684
不要な記述を削除
dowdiness Apr 1, 2024
96a7379
prettierの自動整形を実行
dowdiness Apr 1, 2024
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
5 changes: 5 additions & 0 deletions app/helpers/comments_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ module CommentsHelper
def user_comments_page?
controller_path == 'users/comments' && action_name == 'index'
end

def comments_props(title:, commentable_id:, commentable_type:)
user = current_user.as_json(only: %i[id login_name], methods: %i[avatar_url roles primary_role icon_title])
{ title:, commentableId: commentable_id, commentableType: commentable_type, currentUser: user, availableEmojis: Reaction.available_emojis.to_json }
end
end
96 changes: 45 additions & 51 deletions app/javascript/components/Check/Check.jsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,72 @@
import React from 'react'
import ProductChecker from '../ProductChecker/ProductChecker'
import ResponsibleMentor from '../ResponsibleMentor/SharedResponsibleMentor'
import * as Card from '../ui/Card'
import { useCheck } from './useCheck'
import { useInitializeCheck, useCheck } from './useCheck'
import clsx from 'clsx'

const Check = ({
emotion = null,
checkableId,
checkableType,
checkableLabel,
checkerId = null,
checkerName = null,
checkerAvatar = null,
checkableName,
responsibleUserId = null,
responsibleUserName = null,
responsibleUserAvatar = null,
currentUserId = null
}) => {
const { checkExists, onCreateCheck, onDeleteCheck } = useCheck(
checkableId,
checkableType
)
const buttonLabel = `${checkableLabel}${
checkExists ? 'の確認を取り消す' : 'を確認'
}`
useInitializeCheck(checkableId, checkableType)
const { isChecked, handleCreateCheck, handleDeleteCheck } = useCheck()

const handleToggleCheck = () => {
if (checkExists) {
onDeleteCheck()
} else {
const isSadEmotion = emotion === 'sad'
// TODO querySelectorを辞めてpropsから渡したい
const commentExists =
parseInt(
document.querySelector('a[href="#comments"] > span').innerHTML
) > 0
const confirmMessage =
'今日の気分は「sad」ですが、コメント無しで確認しますか?'
// 変数定義時に実行させないため関数
const isConfirmed = () => window.confirm(confirmMessage)
const isSadNoCommentNotComfirmed =
isSadEmotion && !commentExists && !isConfirmed()
if (isSadNoCommentNotComfirmed) return
onCreateCheck()
}
const handleCreateCheckClick = () => {
const isSadEmotion = emotion === 'sad'
// TODO querySelectorを辞めてpropsから渡したい
const commentExists =
parseInt(document.querySelector('a[href="#comments"] > span').innerHTML) >
0
const confirmMessage =
'今日の気分は「sad」ですが、コメント無しで確認しますか?'
// 変数定義時に実行させないため関数
const isConfirmed = () => window.confirm(confirmMessage)
const isSadNoCommentNotComfirmed =
isSadEmotion && !commentExists && !isConfirmed()
if (isSadNoCommentNotComfirmed) return
handleCreateCheck()
}

return (
<Card.Footer className="is-only-mentor">
{/* 提出物のみで使う担当ボタン */}
{checkableType === 'Product' && (
// 確認されていた場合はCSSによって非表示
<Card.FooterItem className={clsx({ hidden: checkExists })}>
<ProductChecker
checkerId={checkerId}
checkerName={checkerName}
<Card.FooterItem className={clsx({ hidden: isChecked })}>
<ResponsibleMentor
responsibleUserId={responsibleUserId}
responsibleUserName={responsibleUserName}
responsibleUserAvatar={responsibleUserAvatar}
currentUserId={currentUserId}
productId={checkableId}
checkableType={checkableType}
checkerAvatar={checkerAvatar}
/>
</Card.FooterItem>
)}
{/* 確認or確認取り消しボタン */}
<Card.FooterItem className={clsx({ 'is-sub': checkExists })}>
<button
// shortcut.jsでhotkey(ctrl+b)の設定に使うid
id="js-shortcut-check"
className={clsx(
'is-block',
checkExists
? 'card-main-actions__muted-action'
: 'a-button is-sm is-danger'
)}
onClick={handleToggleCheck}>
{buttonLabel}
</button>
<Card.FooterItem className={clsx({ 'is-sub': isChecked })}>
{isChecked ? (
<button
// shortcut.jsでhotkey(ctrl+b)の設定に使うid
id="js-shortcut-check"
className="is-block card-main-actions__muted-action"
onClick={handleDeleteCheck}>
{`${checkableName}の確認を取り消す`}
</button>
) : (
<button
// shortcut.jsでhotkey(ctrl+b)の設定に使うid
id="js-shortcut-check"
className="is-block a-button is-sm is-danger"
onClick={handleCreateCheckClick}>
{`${checkableName}を確認`}
</button>
)}
</Card.FooterItem>
</Card.Footer>
)
Expand Down
25 changes: 10 additions & 15 deletions app/javascript/components/Check/CheckStamp.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import React from 'react'
import { useCheck } from './useCheck'

const CheckStamp = ({ checkableId, checkableType }) => {
const { checkExists, createdAt, userName } = useCheck(
checkableId,
checkableType
)
const CheckStamp = () => {
const { isChecked, createdAt, checkerUserName } = useCheck()

return (
checkExists && (
<div className="stamp stamp-approve">
<h2 className="stamp__content is-title">確認済</h2>
<time className="stamp__content is-created-at">{createdAt}</time>
<div className="stamp__content is-user-name">
<div className="stamp__content-inner">{userName}</div>
</div>
return isChecked ? (
<div className="stamp stamp-approve">
<h2 className="stamp__content is-title">確認済</h2>
<time className="stamp__content is-created-at">{createdAt}</time>
<div className="stamp__content is-user-name">
<div className="stamp__content-inner">{checkerUserName}</div>
</div>
)
)
</div>
) : null
}

export default CheckStamp
72 changes: 38 additions & 34 deletions app/javascript/components/Check/checkApi.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,50 @@
import CSRF from '../../csrf'

export const checkClient = (checkId, checkableId, checkableType) => {
const createCheck = async (checkableId, checkableType) => {
const params = {
checkable_id: checkableId,
checkable_type: checkableType
}

const createCheck = async () => {
const res = await fetch('/api/checks', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': CSRF.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
if (!res.ok) {
throw new Error('確認でエラーが起こりました')
}
return res
const res = await fetch('/api/checks', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': CSRF.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
if (!res.ok) {
const check = await res.json()
throw new Error(check.message)
}
return res
}

const deleteCheck = async () => {
const res = await fetch(`/api/checks/${checkId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': CSRF.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
if (!res.ok) {
throw new Error('確認の取り消しでエラーが起こりました')
}
return res
const deleteCheck = async (checkId, checkableId, checkableType) => {
const params = {
checkable_id: checkableId,
checkable_type: checkableType
}

return { createCheck, deleteCheck }
const res = await fetch(`/api/checks/${checkId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': CSRF.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
if (!res.ok) {
throw new Error('確認の取り消しでエラーが起こりました')
}
return res
}

export { createCheck, deleteCheck }
59 changes: 32 additions & 27 deletions app/javascript/components/Check/useCheck.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { useEffect } from 'react'
import { useEffect, useCallback } from 'react'
import toast from '../../toast'
import { useZustandStore } from '../../hooks/useZustandStore.js'
import { useShallow } from 'zustand/react/shallow'
import { checkClient } from './checkApi'
import { createCheck, deleteCheck } from './checkApi'

export const useCheck = (checkableId, checkableType) => {
const [{ checkId, createdAt, userName }, setCheckable] = useZustandStore(
useShallow((state) => [state.checkable, state.setCheckable])
// 同じページ内の1箇所でのみ呼ぶこと
export const useInitializeCheck = (checkableId, checkableType) => {
const { setCheckable } = useZustandStore(
useShallow((state) => state.checkable)
)
// Apiからcheckableの値を受け取るための初期化
// もし使えるならcontextを使って初期化した方が良い
// https://docs.pmnd.rs/zustand/guides/initialize-state-with-props
useEffect(() => {
setCheckable({ checkableId, checkableType })
}, [])
}

const { createCheck, deleteCheck } = checkClient(
// useInitializeCheckを読んだ後なら同じページ内の複数箇所で使えてデータは同期されます
export const useCheck = () => {
const {
checkId,
isChecked,
createdAt,
checkerUserName,
checkableId,
checkableType
)
checkableType,
setCheckable
} = useZustandStore(useShallow((state) => state.checkable))

const checkExists = !!checkId

const onCreateCheck = () => {
createCheck()
const handleCreateCheck = useCallback(() => {
createCheck(checkableId, checkableType)
.then(() => {
setCheckable({ checkableId, checkableType })
const message = {
Expand All @@ -31,10 +43,10 @@ export const useCheck = (checkableId, checkableType) => {
console.error(error)
toast.methods.toast(error.message, 'error')
})
}
}, [checkableId, checkableType])

const onDeleteCheck = () => {
deleteCheck()
const handleDeleteCheck = useCallback(() => {
deleteCheck(checkId, checkableId, checkableType)
.then(() => {
setCheckable({ checkableId, checkableType })
const message = {
Expand All @@ -47,20 +59,13 @@ export const useCheck = (checkableId, checkableType) => {
console.error(error)
toast.methods.toast(error.message, 'error')
})
}

// Apiからcheckableの値を受け取るための初期化
// もし使えるならcontextを使って初期化した方が良い
// https://docs.pmnd.rs/zustand/guides/initialize-state-with-props
useEffect(() => {
setCheckable({ checkableId, checkableType })
}, [checkableId, checkableType])
}, [checkId, checkableId, checkableType])

return {
checkExists,
isChecked,
createdAt,
userName,
onCreateCheck,
onDeleteCheck
checkerUserName,
handleCreateCheck,
handleDeleteCheck
}
}
Loading