Skip to content

Commit

Permalink
Merge branch 'develop' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
LoisChen68 committed Nov 28, 2024
2 parents 093d953 + 2e447b5 commit 41d1137
Show file tree
Hide file tree
Showing 15 changed files with 195 additions and 79 deletions.
30 changes: 10 additions & 20 deletions src/components/program/ProgramCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ import { Box, Icon, Text } from '@chakra-ui/react'
import { MultiLineTruncationMixin } from 'lodestar-app-element/src/components/common'
import PriceLabel from 'lodestar-app-element/src/components/labels/PriceLabel'
import { useApp } from 'lodestar-app-element/src/contexts/AppContext'
import { useAuth } from 'lodestar-app-element/src/contexts/AuthContext'
import { useAdaptedReviewable } from 'lodestar-app-element/src/hooks/review'
import React from 'react'
import { AiOutlineClockCircle, AiOutlineUser } from 'react-icons/ai'
import { useIntl } from 'react-intl'
import { Link, useHistory } from 'react-router-dom'
import styled, { css } from 'styled-components'
import { durationFormatter } from '../../helpers'
import { useProgramEnrollmentAggregate } from '../../hooks/program'
import { useProductEditorIds, useReviewAggregate } from '../../hooks/review'
import { useReviewAggregate } from '../../hooks/review'
import EmptyCover from '../../images/empty-cover.png'
import { ReactComponent as StarIcon } from '../../images/star-current-color.svg'
import { Category } from '../../types/general'
import { ProgramBriefProps, ProgramPlan, ProgramRole } from '../../types/program'
import { CustomRatioImage } from '../common/Image'
import MemberAvatar from '../common/MemberAvatar'
import StarRating from '../common/StarRating'
import ReviewScoreStarRow from '../review/ReviewScoreStarRow'
import programMessages from './translation'

const InstructorPlaceHolder = styled.div`
Expand Down Expand Up @@ -150,8 +150,6 @@ const PrimaryCard: React.VFC<ProgramCardProps & SharedProps> = ({
programLink,
}) => {
const { formatMessage } = useIntl()
const { currentMemberId, currentUserRole } = useAuth()
const { productEditorIds } = useProductEditorIds(program.id)
const { enabledModules, settings } = useApp()
const history = useHistory()

Expand All @@ -165,7 +163,10 @@ const PrimaryCard: React.VFC<ProgramCardProps & SharedProps> = ({
: undefined
const periodAmount = program.plans.length > 1 ? program.plans[0]?.periodAmount : null
const periodType = program.plans.length > 1 ? program.plans[0]?.periodType : null
const { averageScore, reviewCount } = useReviewAggregate(`/programs/${program.id}`)

const { id: appId } = useApp()
const path = `/programs/${program.id}`
const { data: reviewable, loading: reviewableLoading } = useAdaptedReviewable(path, appId)
const { data: enrolledCount } = useProgramEnrollmentAggregate(program.id, { skip: !program.isEnrolledCountVisible })

const programAdditionalSoldHeadcountSetting = settings['program.additional.sold.headcount'] || '[]'
Expand Down Expand Up @@ -197,6 +198,8 @@ const PrimaryCard: React.VFC<ProgramCardProps & SharedProps> = ({
textColor: '#585858',
}

if (reviewableLoading) return <></>

return (
<>
{!noInstructor && instructorId && (
Expand Down Expand Up @@ -245,20 +248,7 @@ const PrimaryCard: React.VFC<ProgramCardProps & SharedProps> = ({
</Link>
</StyledTitle>

{enabledModules.customer_review ? (
currentUserRole === 'app-owner' ||
(currentMemberId && productEditorIds.includes(currentMemberId)) ||
reviewCount >= (settings.review_lower_bound ? Number(settings.review_lower_bound) : 3) ? (
<StyledReviewRating className="d-flex mb-2">
<StarRating score={Math.round((Math.round(averageScore * 10) / 10) * 2) / 2} max={5} size="20px" />
<span>({formatMessage(programMessages.ProgramCard.reviewCount, { count: reviewCount })})</span>
</StyledReviewRating>
) : (
<StyledReviewRating className="mb-2">
{formatMessage(programMessages.ProgramCard.noReviews)}
</StyledReviewRating>
)
) : null}
<ReviewScoreStarRow path={path} appId={appId} />

{renderCustomDescription && renderCustomDescription()}
<StyledDescription>{program.abstract}</StyledDescription>
Expand Down
35 changes: 12 additions & 23 deletions src/components/review/ReviewCollectionBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import { Divider, Icon } from '@chakra-ui/react'
import { Skeleton } from 'antd'
import { useApp } from 'lodestar-app-element/src/contexts/AppContext'
import { useAuth } from 'lodestar-app-element/src/contexts/AuthContext'
import { useAdaptedReviewable } from 'lodestar-app-element/src/hooks/review'
import React, { useRef } from 'react'
import { useIntl } from 'react-intl'
import styled from 'styled-components'
import hasura from '../../hasura'
import { reviewMessages } from '../../helpers/translation'
import { useProductEditorIds, useReviewAggregate } from '../../hooks/review'
import { ReactComponent as StarEmptyIcon } from '../../images/star-empty.svg'
import { ReactComponent as StarIcon } from '../../images/star.svg'
import { MemberReviewProps } from '../../types/review'
import ReviewAdminItemCollection from './ReviewAdminItemCollection'
import ReviewMemberItemCollection, { ReviewMemberItemRef } from './ReviewMemberItemCollection'
import ReviewModal from './ReviewModal'
import ReviewPublicItemCollection from './ReviewPublicItemCollection'
import ReviewScorePanel from './ReviewScorePanel'

const Wrapper = styled.div`
div {
Expand All @@ -32,16 +33,7 @@ export const StyledTitle = styled.h2`
letter-spacing: 0.2px;
font-weight: bold;
`
export const StyledAvgScore = styled.div`
font-weight: bold;
font-size: 40px;
letter-spacing: 1px;
`
export const StyledReviewAmount = styled.div`
color: #9b9b9b;
font-size: 14px;
letter-spacing: 0.4px;
`

const StyledEmptyText = styled.div`
color: #9b9b9b;
font-size: 14px;
Expand All @@ -67,6 +59,7 @@ const ReviewCollectionBlock: React.VFC<{
const { formatMessage } = useIntl()
const { currentMemberId, currentUserRole } = useAuth()
const { settings, id: appId } = useApp()
const { data: reviewable, loading: reviewableLoading } = useAdaptedReviewable(path, appId)
const { loadingReviewAggregate, averageScore, reviewCount, refetchReviewAggregate } = useReviewAggregate(path)
const { loadingIsCurrentMemberEnrollment, isCurrentMemberEnrollment } = useIsCurrentMemberEnrollment(
targetId,
Expand All @@ -91,6 +84,7 @@ const ReviewCollectionBlock: React.VFC<{
)

if (
reviewableLoading ||
loadingIsCurrentMemberEnrollment ||
loadingProductEditorIds ||
loadingReviewAggregate ||
Expand All @@ -105,23 +99,18 @@ const ReviewCollectionBlock: React.VFC<{
)
}

return (
return !isProductAdmin &&
!reviewable?.is_score_viewable &&
!reviewable?.is_item_viewable &&
!reviewable?.is_writable ? (
<></>
) : (
<>
<StyledTitle>{title || formatMessage(reviewMessages.title.programReview)}</StyledTitle>
<StyledDivider mt={1} />

<div className="d-flex align-items-center my-3">
<StyledAvgScore className="mr-1">
{isMoreThanReviewLowerBound || isProductAdmin ? (averageScore === 0 ? 0 : averageScore?.toFixed(1)) : 0}
</StyledAvgScore>
<div className="mr-2">
<Icon as={StarIcon} w="24px" h="24px" />
</div>
<StyledReviewAmount className="flex-grow-1">
{formatMessage(reviewMessages.text.reviewAmount, {
amount: isMoreThanReviewLowerBound || isProductAdmin ? reviewCount : 0,
})}
</StyledReviewAmount>
{isMoreThanReviewLowerBound || isProductAdmin ? <ReviewScorePanel path={path} appId={appId} /> : <></>}
{isCurrentMemberEnrollment ? (
<ReviewModal
path={path}
Expand Down
34 changes: 30 additions & 4 deletions src/components/review/ReviewMemberItemCollection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { gql, useQuery } from '@apollo/client'
import { Box, Button, SkeletonCircle, SkeletonText } from '@chakra-ui/react'
import { useAuth } from 'lodestar-app-element/src/contexts/AuthContext'
import { useAdaptedReviewable } from 'lodestar-app-element/src/hooks/review'
import React, { HTMLAttributes, useState } from 'react'
import { useIntl } from 'react-intl'
import hasura from '../../hasura'
Expand All @@ -24,17 +25,31 @@ const ReviewMemberItemCollection: React.ForwardRefRenderFunction<
const { formatMessage } = useIntl()
const { currentMemberId } = useAuth()
const [loading, setLoading] = useState(false)

const { data: reviewable, loading: reviewableLoading } = useAdaptedReviewable(path, appId)

const {
loadingReviews: loadingCurrentMemberReviews,
memberReviews: currentMemberReviews,
memberPrivateContent: currentMemberPrivateContent,
onRefetch: onRefetchCurrentMemberReviews,
} = useReviewMemberCollection(path, appId, currentMemberId, reviewable?.is_item_viewable, true)

const { loadingReviews, memberReviews, memberPrivateContent, onRefetch, loadMoreReviews } = useReviewMemberCollection(
path,
appId,
currentMemberId,
reviewable?.is_item_viewable,
)

React.useImperativeHandle(ref, () => ({
onRefetchReviewMemberItem: () => onRefetch(),
onRefetchReviewMemberItem: () => {
onRefetch()
onRefetchCurrentMemberReviews()
},
}))

if (loadingReviews) {
if (loadingCurrentMemberReviews && loadingReviews && reviewableLoading) {
return (
<Box padding="6" boxShadow="lg" bg="white">
<SkeletonCircle size="36" />
Expand All @@ -46,7 +61,7 @@ const ReviewMemberItemCollection: React.ForwardRefRenderFunction<
return (
<>
<div>
{memberReviews.map(v => (
{currentMemberReviews.concat(memberReviews).map(v => (
<div key={v.id} className="review-item">
<ReviewItem
isLiked={v.isLiked}
Expand Down Expand Up @@ -91,10 +106,21 @@ const ReviewMemberItemCollection: React.ForwardRefRenderFunction<
)
}

const useReviewMemberCollection = (path: string, appId: string, currentMemberId: string | null) => {
const useReviewMemberCollection = (
path: string,
appId: string,
currentMemberId: string | null,
isItemViewable: boolean = true,
isBelongedToCurrentMember: boolean = false,
) => {
const condition: hasura.GET_REVIEW_MEMBERVariables['condition'] = {
path: { _eq: path },
app_id: { _eq: appId },
member_id: isBelongedToCurrentMember
? { _eq: currentMemberId }
: isItemViewable
? { _neq: currentMemberId }
: { _eq: '' },
}

const { loading, error, data, refetch, fetchMore } = useQuery<
Expand Down
66 changes: 36 additions & 30 deletions src/components/review/ReviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import BraftEditor, { EditorState } from 'braft-editor'
import { useApp } from 'lodestar-app-element/src/contexts/AppContext'
import { useAuth } from 'lodestar-app-element/src/contexts/AuthContext'
import { useAdaptedReviewable } from 'lodestar-app-element/src/hooks/review'
import React, { useState } from 'react'
import { Controller, useForm } from 'react-hook-form'
import { AiOutlineEdit as EditIcon } from 'react-icons/ai'
Expand Down Expand Up @@ -125,6 +126,8 @@ const ReviewModal: React.VFC<{
},
})

const { data: reviewable, loading: reviewableLoading } = useAdaptedReviewable(path, appId)

const validateTitle = (value: string) => !!value || formatMessage(reviewMessages.validate.titleIsRequired)

const handleSave = handleSubmit(({ starRating, title, content, privateContent }) => {
Expand Down Expand Up @@ -169,47 +172,50 @@ const ReviewModal: React.VFC<{
onClose()
})
} else {
insertReview({
variables: {
path,
memberId: currentMemberId,
score: starRating,
title,
content: content.toRAW(),
privateContent: privateContent.toRAW(),
appId: appId,
},
})
.then(() => {
toast({
title: formatMessage(commonMessages.event.successfullySaved),
status: 'success',
duration: 3000,
isClosable: false,
position: 'top',
})
reset()
onRefetchReviewMemberItem?.()
onRefetchReviewAggregate?.()
window.location.replace(`/programs/${programId}?moveToBlock=customer-review&visitIntro=1`)
})
.catch(error => process.env.NODE_ENV === 'development' && console.error(error))
.finally(() => {
setIsSubmitting(false)
onClose()
reviewable.is_item_viewable &&
insertReview({
variables: {
path,
memberId: currentMemberId,
score: starRating,
title,
content: content.toRAW(),
privateContent: privateContent.toRAW(),
appId: appId,
},
})
.then(() => {
toast({
title: formatMessage(commonMessages.event.successfullySaved),
status: 'success',
duration: 3000,
isClosable: false,
position: 'top',
})
reset()
onRefetchReviewMemberItem?.()
onRefetchReviewAggregate?.()
window.location.replace(`/programs/${programId}?moveToBlock=customer-review&visitIntro=1`)
})
.catch(error => process.env.NODE_ENV === 'development' && console.error(error))
.finally(() => {
setIsSubmitting(false)
onClose()
})
}
})

if (reviewableLoading) return <></>

return (
<>
{authToken && (
{authToken && (reviewable?.is_writable || memberReviews?.length > 0) && (
<StyledButtonReview
variant={memberReviews && memberReviews.length !== 0 ? 'outline' : 'primary'}
reviewed={(!!(memberReviews !== null && memberReviews.length !== 0)).toString()}
onClick={onOpen}
>
{memberReviews && memberReviews.length !== 0
{memberReviews?.length > 0
? formatMessage(reviewMessages.button.editReview)
: formatMessage(reviewMessages.button.toReview)}
</StyledButtonReview>
Expand Down
8 changes: 6 additions & 2 deletions src/components/review/ReviewPublicItemCollection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { gql, useQuery } from '@apollo/client'
import { Box, Button, SkeletonCircle, SkeletonText } from '@chakra-ui/react'
import { useAdaptedReviewable } from 'lodestar-app-element/src/hooks/review'
import React, { useState } from 'react'
import { useIntl } from 'react-intl'
import hasura from '../../hasura'
Expand All @@ -17,8 +18,9 @@ const ReviewPublicItemCollection: React.VFC<{
const [loading, setLoading] = useState(false)

const { loadingReviews, publicReviews, loadMoreReviews } = useReviewPublicCollection(path, appId)
const { data: reviewable, loading: reviewableLoading } = useAdaptedReviewable(path, appId)

if (loadingReviews) {
if (loadingReviews && reviewableLoading) {
return (
<Box padding="6" boxShadow="lg" bg="white">
<SkeletonCircle size="36" />
Expand All @@ -27,7 +29,9 @@ const ReviewPublicItemCollection: React.VFC<{
)
}

return (
return !reviewable?.is_item_viewable ? (
<></>
) : (
<>
<div>
{publicReviews.map(v => (
Expand Down
Loading

0 comments on commit 41d1137

Please sign in to comment.