List other user's tunes and profile, closes: #858
This commit is contained in:
parent
d10dabf04e
commit
328f8e12a1
|
@ -63,6 +63,7 @@ const ResetPasswordConfirmation = lazy(() => import('./pages/auth/ResetPasswordC
|
||||||
const EmailVerification = lazy(() => import('./pages/auth/EmailVerification'));
|
const EmailVerification = lazy(() => import('./pages/auth/EmailVerification'));
|
||||||
const OauthCallback = lazy(() => import('./pages/auth/OauthCallback'));
|
const OauthCallback = lazy(() => import('./pages/auth/OauthCallback'));
|
||||||
const About = lazy(() => import('./pages/About'));
|
const About = lazy(() => import('./pages/About'));
|
||||||
|
const User = lazy(() => import('./pages/User'));
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
@ -202,6 +203,7 @@ const App = ({ ui, tuneData }: { ui: UIState, tuneData: TuneDataState }) => {
|
||||||
<Route path={Routes.PROFILE} element={<ContentFor element={<Profile />} />} />
|
<Route path={Routes.PROFILE} element={<ContentFor element={<Profile />} />} />
|
||||||
<Route path={Routes.RESET_PASSWORD} element={<ContentFor element={<ResetPassword />} />} />
|
<Route path={Routes.RESET_PASSWORD} element={<ContentFor element={<ResetPassword />} />} />
|
||||||
<Route path={Routes.ABOUT} element={<ContentFor element={<About />} />} />
|
<Route path={Routes.ABOUT} element={<ContentFor element={<About />} />} />
|
||||||
|
<Route path={Routes.USER_ROOT} element={<ContentFor element={<User />} />} />
|
||||||
|
|
||||||
<Route path={Routes.EMAIL_VERIFICATION} element={<ContentFor element={<EmailVerification />} />} />
|
<Route path={Routes.EMAIL_VERIFICATION} element={<ContentFor element={<EmailVerification />} />} />
|
||||||
<Route path={Routes.RESET_PASSWORD_CONFIRMATION} element={<ContentFor element={<ResetPasswordConfirmation />} />} />
|
<Route path={Routes.RESET_PASSWORD_CONFIRMATION} element={<ContentFor element={<ResetPasswordConfirmation />} />} />
|
||||||
|
|
|
@ -196,7 +196,8 @@ select:-webkit-autofill:focus {
|
||||||
animation: wiggle 2s linear 1;
|
animation: wiggle 2s linear 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-row {
|
.ant-table-row,
|
||||||
|
.ant-list-item {
|
||||||
&.unlisted {
|
&.unlisted {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ const useDb = () => {
|
||||||
const list = await tunesCollection.getList(page, perPage, {
|
const list = await tunesCollection.getList(page, perPage, {
|
||||||
sort: '-updated',
|
sort: '-updated',
|
||||||
filter: `author = "${userId}"`,
|
filter: `author = "${userId}"`,
|
||||||
|
expand: 'author',
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Grid,
|
Grid,
|
||||||
|
@ -166,6 +167,11 @@ const Hub = () => {
|
||||||
dataIndex: 'authorUsername',
|
dataIndex: 'authorUsername',
|
||||||
key: 'authorUsername',
|
key: 'authorUsername',
|
||||||
responsive: ['sm'],
|
responsive: ['sm'],
|
||||||
|
render: (userName: string, record: TunesRecordFull) => (
|
||||||
|
<Link to={generatePath(Routes.USER_ROOT, { userId: record.author })}>
|
||||||
|
{userName}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Signature',
|
title: 'Signature',
|
||||||
|
@ -188,7 +194,7 @@ const Hub = () => {
|
||||||
{
|
{
|
||||||
dataIndex: 'tuneId',
|
dataIndex: 'tuneId',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (tuneId: string, record) => {
|
render: (tuneId: string, record: TunesRecordFull) => {
|
||||||
const isOwner = currentUser?.id === record.author;
|
const isOwner = currentUser?.id === record.author;
|
||||||
const size = isOwner ? 'small' : 'middle';
|
const size = isOwner ? 'small' : 'middle';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import {
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
generatePath,
|
||||||
|
useMatch,
|
||||||
|
useNavigate,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
List,
|
||||||
|
Pagination,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
import { Routes } from '../routes';
|
||||||
|
import { formatTime } from '../utils/time';
|
||||||
|
import useDb from '../hooks/useDb';
|
||||||
|
import { aspirationMapper } from '../utils/tune/mappers';
|
||||||
|
import {
|
||||||
|
TunesRecordFull,
|
||||||
|
UsersRecordFull,
|
||||||
|
} from '../types/dbData';
|
||||||
|
|
||||||
|
const tunePath = (tuneId: string) => generatePath(Routes.TUNE_TUNE, { tuneId });
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const route = useMatch(Routes.USER_ROOT);
|
||||||
|
const { getUserTunes } = useDb();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [isTunesLoading, setIsTunesLoading] = useState(false);
|
||||||
|
const [tunesDataSource, setTunesDataSource] = useState<TunesRecordFull[]>([]);
|
||||||
|
const [username, setUsername] = useState();
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
setIsTunesLoading(true);
|
||||||
|
try {
|
||||||
|
const { items, totalItems } = await getUserTunes(route?.params.userId!, page, pageSize);
|
||||||
|
setTotal(totalItems);
|
||||||
|
setUsername((items[0].expand.author as UsersRecordFull).username);
|
||||||
|
const mapped = items.map((tune) => ({
|
||||||
|
...tune,
|
||||||
|
key: tune.tuneId,
|
||||||
|
year: tune.year,
|
||||||
|
displacement: `${tune.displacement}l`,
|
||||||
|
aspiration: aspirationMapper[tune.aspiration],
|
||||||
|
published: formatTime(tune.updated),
|
||||||
|
}));
|
||||||
|
setTunesDataSource(mapped as any);
|
||||||
|
} catch (error) {
|
||||||
|
// request cancelled
|
||||||
|
} finally {
|
||||||
|
setIsTunesLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="small-container">
|
||||||
|
<Divider>{username ? `${username}'s tunes` : 'No tunes yet'}</Divider>
|
||||||
|
<List
|
||||||
|
dataSource={tunesDataSource}
|
||||||
|
loading={isTunesLoading}
|
||||||
|
renderItem={(tune) => (
|
||||||
|
<List.Item
|
||||||
|
actions={[
|
||||||
|
<Button icon={<ArrowRightOutlined />} onClick={() => navigate(tunePath(tune.tuneId))} />,
|
||||||
|
]}
|
||||||
|
className={tune.visibility}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
title={<>
|
||||||
|
{tune.vehicleName} <Typography.Text code>{tune.signature}</Typography.Text>
|
||||||
|
</>}
|
||||||
|
description={<>
|
||||||
|
{tune.engineMake}, {tune.engineCode}, {tune.displacement}, {tune.aspiration}
|
||||||
|
</>}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Typography.Text italic>{tune.published}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
footer={
|
||||||
|
<div style={{ textAlign: 'right' }}>
|
||||||
|
<Pagination
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
pageSize={pageSize}
|
||||||
|
current={page}
|
||||||
|
total={total}
|
||||||
|
onChange={(newPage, newPageSize) => {
|
||||||
|
setIsTunesLoading(true);
|
||||||
|
setPage(newPage);
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
|
@ -217,10 +217,7 @@ const Profile = () => {
|
||||||
{tune.vehicleName} <Typography.Text code>{tune.signature}</Typography.Text>
|
{tune.vehicleName} <Typography.Text code>{tune.signature}</Typography.Text>
|
||||||
</>}
|
</>}
|
||||||
description={<>
|
description={<>
|
||||||
{tune.engineMake},
|
{tune.engineMake}, {tune.engineCode}, {tune.displacement}, {tune.aspiration}
|
||||||
{tune.engineCode},
|
|
||||||
{tune.displacement},
|
|
||||||
{tune.aspiration}
|
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -26,6 +26,7 @@ export enum Routes {
|
||||||
OAUTH_CALLBACK = '/auth/oauth-callback/:provider',
|
OAUTH_CALLBACK = '/auth/oauth-callback/:provider',
|
||||||
|
|
||||||
ABOUT = '/about',
|
ABOUT = '/about',
|
||||||
|
USER_ROOT = '/user/:userId',
|
||||||
|
|
||||||
REDIRECT_PAGE_OAUTH_CALLBACK = 'oauth',
|
REDIRECT_PAGE_OAUTH_CALLBACK = 'oauth',
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue