blob: 8c8921b8890eae80dfe6c20930082b901fa3cf3a [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useState } from 'react';
import { Row, Col } from 'react-bootstrap';
import {
useParams,
useSearchParams,
useNavigate,
useLocation,
} from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Pagination, CustomSidebar } from '@/components';
import { loggedUserInfoStore, toastStore } from '@/stores';
import { scrollToElementTop, scrollToDocTop } from '@/utils';
import { usePageTags, usePageUsers, useSkeletonControl } from '@/hooks';
import type {
ListResult,
QuestionDetailRes,
AnswerItem,
} from '@/common/interface';
import { questionDetail, getAnswers } from '@/services';
import {
Question,
Answer,
AnswerHead,
RelatedQuestions,
WriteAnswer,
Alert,
ContentLoader,
InviteToAnswer,
LinkedQuestions,
} from './components';
import './index.scss';
const Index = () => {
const navigate = useNavigate();
const { t } = useTranslation('translation');
const { qid = '', slugPermalink = '' } = useParams();
/**
* Note: Compatible with Permalink
*/
let { aid = '' } = useParams();
if (!aid && slugPermalink) {
aid = slugPermalink;
}
const [urlSearch] = useSearchParams();
const page = Number(urlSearch.get('page') || 0);
const order = urlSearch.get('order') || '';
const [question, setQuestion] = useState<QuestionDetailRes | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const { isSkeletonShow } = useSkeletonControl(isLoading);
const [answers, setAnswers] = useState<ListResult<AnswerItem>>({
count: -1,
list: [],
});
const { setUsers } = usePageUsers();
const userInfo = loggedUserInfoStore((state) => state.user);
const isAuthor = userInfo?.username === question?.user_info?.username;
const isAdmin = userInfo?.role_id === 2;
const isModerator = userInfo?.role_id === 3;
const isLogged = Boolean(userInfo?.access_token);
const loggedUserRank = userInfo?.rank;
const location = useLocation();
useEffect(() => {
if (location.state?.isReview) {
toastStore.getState().show({
msg: t('review', { keyPrefix: 'toast' }),
variant: 'warning',
});
// remove state isReview
const newLocation = { ...location };
delete newLocation.state;
window.history.replaceState(null, '', newLocation.pathname);
}
}, [location.state]);
const requestAnswers = async () => {
const res = await getAnswers({
order: order === 'updated' || order === 'created' ? order : 'default',
question_id: qid,
page: 1,
page_size: 999,
});
if (res) {
res.list = res.list?.filter((v) => {
// delete answers only show to author and admin and has search params aid
if (v.status === 10) {
if (
(v?.user_info?.username === userInfo?.username || isAdmin) &&
aid === v.id
) {
return v;
}
return null;
}
return v;
});
setAnswers({ ...res, count: res.list.length });
if (page > 0 || order) {
// scroll into view;
const element = document.getElementById('answerHeader');
scrollToElementTop(element);
}
res.list.forEach((item) => {
setUsers([
{
displayName: item.user_info?.display_name,
userName: item.user_info?.username,
},
{
displayName: item?.update_user_info?.display_name,
userName: item?.update_user_info?.username,
},
]);
});
}
};
const getDetail = async () => {
setIsLoading(true);
try {
const res = await questionDetail(qid);
if (res) {
setUsers([
{
id: res.user_info?.id,
displayName: res.user_info?.display_name,
userName: res.user_info?.username,
avatar_url: res.user_info?.avatar,
},
{
id: res?.update_user_info?.id,
displayName: res?.update_user_info?.display_name,
userName: res?.update_user_info?.username,
avatar_url: res?.update_user_info?.avatar,
},
{
id: res?.last_answered_user_info?.id,
displayName: res?.last_answered_user_info?.display_name,
userName: res?.last_answered_user_info?.username,
avatar_url: res?.last_answered_user_info?.avatar,
},
]);
setQuestion(res);
}
setIsLoading(false);
} catch (e) {
setIsLoading(false);
}
};
const initPage = (type: string) => {
if (type === 'delete_question') {
setTimeout(() => {
navigate('/', { replace: true });
}, 1000);
return;
}
if (type === 'default') {
scrollToDocTop();
getDetail();
return;
}
if (type === 'delete_answer') {
getDetail();
}
requestAnswers();
};
const writeAnswerCallback = (obj: AnswerItem) => {
setAnswers({
count: answers.count + 1,
list: [...answers.list, obj],
});
if (question) {
setQuestion({
...question,
answered: true,
first_answer_id: question.first_answer_id
? question.first_answer_id
: obj.id,
});
}
};
useEffect(() => {
if (!qid) {
return;
}
getDetail();
requestAnswers();
}, [qid]);
useEffect(() => {
if (page || order) {
requestAnswers();
}
}, [page, order]);
usePageTags({
title: question?.title,
description: question?.description,
keywords: question?.tags.map((_) => _.slug_name).join(','),
});
const showInviteToAnswer = question?.id;
const showLinkedQuestions = question?.id && question.id !== '';
let canInvitePeople = false;
if (showInviteToAnswer && Array.isArray(question.extends_actions)) {
const inviteAct = question.extends_actions.find((op) => {
return op.action === 'invite_other_to_answer';
});
if (inviteAct) {
canInvitePeople = true;
}
}
return (
<Row className="questionDetailPage pt-4 mb-5">
<Col className="page-main flex-auto">
{question?.operation?.level && <Alert data={question.operation} />}
{isSkeletonShow ? (
<ContentLoader />
) : (
<Question
data={question}
initPage={initPage}
hasAnswer={answers.count > 0}
isLogged={isLogged}
/>
)}
{!isLoading && answers.count > 0 && (
<>
<AnswerHead count={answers.count} order={order} />
{answers?.list?.map((item) => {
return (
<Answer
aid={aid}
key={item?.id}
data={item}
questionTitle={question?.title || ''}
canAccept={isAuthor || isAdmin || isModerator}
callback={initPage}
isLogged={isLogged}
/>
);
})}
</>
)}
{!isLoading && Math.ceil(answers.count / 15) > 1 && (
<div className="d-flex justify-content-center answer-item pt-4">
<Pagination
currentPage={Number(page || 1)}
pageSize={15}
totalSize={answers?.count || 0}
/>
</div>
)}
{!isLoading &&
Number(question?.status) !== 2 &&
!question?.operation?.type && (
<WriteAnswer
data={{
qid,
answered: question?.answered,
loggedUserRank,
first_answer_id: question?.first_answer_id,
}}
callback={writeAnswerCallback}
/>
)}
</Col>
<Col className="page-right-side mt-4 mt-xl-0">
<CustomSidebar />
{showInviteToAnswer ? (
<InviteToAnswer
questionId={question.id}
readOnly={!canInvitePeople}
/>
) : null}
{showLinkedQuestions ? <LinkedQuestions id={question.id} /> : null}
<RelatedQuestions id={question?.id || ''} />
</Col>
</Row>
);
};
export default Index;