Recent posts

This commit is contained in:
2024-07-27 23:02:17 +02:00
parent a950f6770a
commit 2c9f8caff4
11 changed files with 62 additions and 27 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-Bw9FCd3M.js"></script>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-B1n77CT9.js"></script>
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-DXd9vB-a.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-CxHUbSMi.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-xmxrKlZO.js">
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -1,4 +1,4 @@
import { POST_LIMIT } from '../constanst';
import { POST_LIMIT, PROFILE_POST_LIMIT } from '../constanst';
import { PostAuth, PostDelete, PostListAuth, PostListNonAuth, PostNew, PostUpdate } from '../types/Post';
import { User, UserImageUpdate, UserUpdate } from '../types/User';
@@ -96,6 +96,10 @@ class ApiImpl {
return await (await this.patch(`posts/${id}`, data as Record<string, unknown>)).json();
};
public userPosts = async (id?: number): Promise<PostListAuth> => {
return await (await this.getAuth(`users/${id}/posts?l=${PROFILE_POST_LIMIT}&s=desc`)).json();
};
/* Internal */
private post = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
+14 -12
View File
@@ -28,9 +28,10 @@ import ErrorComponent from '../Error/ErrorComponent';
interface Props {
post: PostNonAuth | PostAuth;
disableActions?: boolean;
}
const Post: FC<Props> = ({ post }) => {
const Post: FC<Props> = ({ post, disableActions }) => {
const [deleteOpen, setDeleteOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
//eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -50,7 +51,7 @@ const Post: FC<Props> = ({ post }) => {
<Card>
<CardHeader
avatar={
'id' in post.user ? (
!disableActions && 'id' in post.user ? (
post.user.id !== Api.getAuthenticatedUser()?.id ? (
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
<Avatar alt={post.user.username} src={`storage/${post.user.image}`}>
@@ -71,7 +72,7 @@ const Post: FC<Props> = ({ post }) => {
)
}
title={
'id' in post.user ? (
!disableActions && 'id' in post.user ? (
post.user.id !== Api.getAuthenticatedUser()?.id ? (
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
{post.user.username}
@@ -94,15 +95,16 @@ const Post: FC<Props> = ({ post }) => {
</CardContent>
<CardActions>
{(Api.isAdmin() || ('id' in post.user && post.user.id === Api.getAuthenticatedUser()?.id)) && (
<>
<Button size="small" onClick={() => setEditOpen(true)}>
{t('Edit')}
</Button>
<PostEditDialog post={post as PostAuth} open={editOpen} onClose={() => setEditOpen(false)} />
</>
)}
{Api.isAdmin() && (
{!disableActions &&
(Api.isAdmin() || ('id' in post.user && post.user.id === Api.getAuthenticatedUser()?.id)) && (
<>
<Button size="small" onClick={() => setEditOpen(true)}>
{t('Edit')}
</Button>
<PostEditDialog post={post as PostAuth} open={editOpen} onClose={() => setEditOpen(false)} />
</>
)}
{!disableActions && Api.isAdmin() && (
<>
<Button size="small" color="error" onClick={() => setDeleteOpen(true)}>
{t('Delete')}
@@ -13,17 +13,20 @@ import {
} from '@mui/material';
import { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PostAuth } from '../../types/Post';
import { User } from '../../types/User';
import convertDate from '../../utils/date';
import UserEditDialog from '../Dialogs/UserEdit/UserEditDialog';
import UserImageDialog from '../Dialogs/UserImage/UserImageDialog';
import Post from '../Post/Post';
interface Props {
user: User;
posts: PostAuth[];
canEdit?: boolean;
}
const Profile: FC<Props> = ({ user, canEdit }) => {
const Profile: FC<Props> = ({ user, posts, canEdit }) => {
const [editOpen, setEditOpen] = useState(false);
const [imageOpen, setImageOpen] = useState(false);
@@ -74,6 +77,11 @@ const Profile: FC<Props> = ({ user, canEdit }) => {
<Typography sx={{ opacity: 0.36 }}>{t('Recent posts')}</Typography>
</Divider>
</Grid>
{posts.map((post) => (
<Grid item xs={12}>
<Post post={post} disableActions />
</Grid>
))}
</Grid>
);
};
+2
View File
@@ -1 +1,3 @@
export const POST_LIMIT = 15;
export const PROFILE_POST_LIMIT = 3;
export const POST_CHAR_LIMIT = 250;
+6
View File
@@ -11,3 +11,9 @@ export const profileQueryOptions = (id?: number) =>
queryKey: ['profile', { id }],
queryFn: () => Api.user(id),
});
export const profilePostsQueryOptions = (id: number) =>
queryOptions({
queryKey: ['profilePosts', { id }],
queryFn: () => Api.userPosts(id),
});
+10 -5
View File
@@ -4,17 +4,19 @@ import { createFileRoute, redirect } from '@tanstack/react-router';
import { t } from 'i18next';
import Api from '../../api/Api';
import Profile from '../../components/Profile/Profile';
import { profileQueryOptions } from '../../queries/profileQuery';
import { profilePostsQueryOptions, profileQueryOptions } from '../../queries/profileQuery';
import { PostAuth } from '../../types/Post';
import { ROUTES } from '../../types/Routes';
const ProfilePage = () => {
const { id } = Route.useParams();
const { data: profileQuery, isFetching } = useSuspenseQuery(profileQueryOptions(id));
const { data: profileQuery, isFetching: isFetchingProfile } = useSuspenseQuery(profileQueryOptions(id));
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(profilePostsQueryOptions(id));
return (
<>
<Snackbar open={isFetching} message={t('Updating')} />
<Profile user={profileQuery} canEdit={Api.isAdmin()} />
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
<Profile user={profileQuery} posts={profilePostsQuery.data as PostAuth[]} canEdit={Api.isAdmin()} />
</>
);
};
@@ -24,7 +26,10 @@ export const Route = createFileRoute(`${ROUTES.PROFILE}/$id`)({
parse: ({ id }) => ({ id: parseInt(id) }),
stringify: ({ id }) => ({ id: id.toString() }),
},
loader: ({ context: { queryClient }, params: { id } }) => queryClient.ensureQueryData(profileQueryOptions(id)),
loader: ({ context: { queryClient }, params: { id } }) => {
queryClient.ensureQueryData(profileQueryOptions(id));
queryClient.ensureQueryData(profilePostsQueryOptions(id));
},
beforeLoad: ({ params: { id } }) => {
if (!Api.hasAuth()) throw redirect({ to: ROUTES.INDEX });
if (id === Api.getAuthenticatedUser()?.id) throw redirect({ to: ROUTES.PROFILE });
+13 -5
View File
@@ -4,22 +4,30 @@ import { createFileRoute, redirect } from '@tanstack/react-router';
import { t } from 'i18next';
import Api from '../../api/Api';
import Profile from '../../components/Profile/Profile';
import { profileSelfQueryOptions } from '../../queries/profileQuery';
import { profilePostsQueryOptions, profileSelfQueryOptions } from '../../queries/profileQuery';
import { PostAuth } from '../../types/Post';
import { ROUTES } from '../../types/Routes';
const ProfilePage = () => {
const { data: profileQuery, isFetching } = useSuspenseQuery(profileSelfQueryOptions);
const { data: profileQuery, isFetching: isFetchingProfile } = useSuspenseQuery(profileSelfQueryOptions);
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(
profilePostsQueryOptions(Api.getAuthenticatedUser()?.id ?? 0)
);
console.log(profilePostsQuery.data);
return (
<>
<Snackbar open={isFetching} message={t('Updating')} />
<Profile user={profileQuery} canEdit={true} />
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
<Profile user={profileQuery} posts={profilePostsQuery.data as PostAuth[]} canEdit={true} />
</>
);
};
export const Route = createFileRoute(`${ROUTES.PROFILE}/`)({
loader: ({ context: { queryClient } }) => queryClient.ensureQueryData(profileSelfQueryOptions),
loader: ({ context: { queryClient } }) => {
queryClient.ensureQueryData(profileSelfQueryOptions);
queryClient.ensureQueryData(profilePostsQueryOptions(Api.getAuthenticatedUser()?.id ?? 0));
},
beforeLoad: () => {
if (!Api.hasAuth()) throw redirect({ to: ROUTES.INDEX });
},