Skip to content

Commit

Permalink
feature: Configure creating posts
Browse files Browse the repository at this point in the history
- Configure creation of a new posts
- Enhance api interceptor
- Take basic user info to navbar
- Enhance RichEditor to return his value
- Correct roots
- Make strong nullcheck in MenuButton

Refs: CU-86973uu60
Signed-off-by: Patryk Kłosiński <[email protected]>
  • Loading branch information
JimTheCat committed Dec 16, 2024
1 parent 0a92e0c commit d55424e
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 40 deletions.
2 changes: 1 addition & 1 deletion frontend/src/Components/Buttons/Menu/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const MenuButton = (props: MenuButtonProps) => {
const navigate = useNavigate();

// check if the current location is a part of the href
const isActive = location.pathname.includes(props.href || "") && props.href !== undefined;
const isActive = location.pathname.includes(props.href ?? "") && props.href !== undefined;

return (
<Button
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/Components/Cards/Post/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {useNavigate} from "react-router-dom";
import {ImageWithSkeleton} from "../../ImageWithSkeleton/ImageWithSkeleton.tsx";

type PostProps = {
userId: string;
ownerLogin: string;
contentHtml: string;
photosUrls?: string[];
createdAt: string;
Expand All @@ -29,7 +29,7 @@ type PostProps = {
export const Post = (props: PostProps) => {

const auth = useAuthStore();
const isOwner = auth.user?.login === props.userId;
const isOwner = auth.user?.login === props.ownerLogin;
const navigate = useNavigate();

/*Render this element each time when number of photos will change*/
Expand Down
19 changes: 15 additions & 4 deletions frontend/src/Components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {MenuButton} from "../Buttons/Menu";
import {
IconHome,
IconMail,
IconPencil,
IconSettings,
IconUserHeart,
IconUserPlus,
Expand All @@ -15,17 +14,29 @@ import {
import {LogOut} from "../Buttons/LogOut/LogOut.tsx";
import {useAuthStore} from "../../Services/authStore.ts";
import {useNavigate} from "react-router-dom";
import {CreatePost} from "../../Pages/CreatePost";
import {BasicUserInfo} from "../../Services/DTOs/User.tsx";
import {useEffect, useState} from "react";
import api from "../../Services/api.ts";

export const Navbar = () => {

const auth = useAuthStore();
const nickname = auth.user?.login;
const navigate = useNavigate();
const [basicUserInfo, setBasicUserInfo] = useState<BasicUserInfo | null>(null);

const handleProfileClick = () => {
navigate(`/profile/${auth.user?.tag}`);
}

useEffect(() => {
if (auth.user) {
api.get<BasicUserInfo>('/api/users/get-basic-user-info', {params: {login: auth.user.login}}).then((response) => {
setBasicUserInfo(response.data);
});
}
}, [auth.user]);

return (
<>

Expand All @@ -49,7 +60,7 @@ export const Navbar = () => {
<Avatar radius={180} size={"xl"}/>
<Stack justify={"center"} gap={0}>
<Text>Witaj</Text>
<Text>{nickname}!</Text>
<Text>{basicUserInfo?.name} {basicUserInfo?.surname}</Text>
</Stack>
</Group>
</Card>
Expand All @@ -61,7 +72,7 @@ export const Navbar = () => {
<AppShell.Section grow component={ScrollArea}>
<MenuButton icon={<IconHome/>} text={"Strona główna"} href={"/mainpage"}/>
<MenuButton icon={<IconZoom/>} text={"Wyszukaj"} href={"/search"}/>
<MenuButton icon={<IconPencil/>} text={"Napisz post"} href={"/createpost"}/>
<CreatePost/>
<MenuButton icon={<IconUsers/>} text={"Znajomi"} href={"/friends"}/>
<MenuButton icon={<IconUsersGroup/>} text={"Grupy"} href={"/groups"}/>
<MenuButton icon={<IconUserPlus/>} text={"Obserwowani"} href={"/following"}/>
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/Components/RichEditor/RichEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
import Placeholder from "@tiptap/extension-placeholder";

export const RichEditor = () => {
export const RichEditor = ({onContentChange}: { onContentChange?: (html: string) => void }) => {
const editor = useEditor({
extensions: [
StarterKit,
Expand All @@ -20,10 +20,13 @@ export const RichEditor = () => {
TextAlign.configure({types: ['heading', 'paragraph']}),
Placeholder.configure({placeholder: 'What\'s on your mind?'}),
],
onUpdate({editor}) {
onContentChange && onContentChange(editor.getHTML());
},
});

return (
<RichTextEditor editor={editor}>
<RichTextEditor editor={editor} variant={"subtle"}>
<RichTextEditor.Toolbar>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold/>
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/Pages/CreatePost/CreatePost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {RichEditor} from "../../Components/RichEditor";
import {Button, Divider, Group, Modal} from "@mantine/core";
import {useDisclosure} from "@mantine/hooks";
import {IconPencil} from "@tabler/icons-react";
import {useState} from "react";
import api from "../../Services/api.ts";
import {useAlert} from "../../Providers/AlertProvider.tsx";

export const CreatePost = () => {
const [opened, {open, close}] = useDisclosure(false);
const [content, setContent] = useState('');
const alert = useAlert();

const handleContentChange = (html: string) => {
setContent(html);
}

const handleCreatePost = () => {
console.log('Create post: ', content)
if (!content) {
alert.showError('Post is empty!');
return;
}

api.post('/api/posts/create', null, {params: {content}}).then((response) => {
if (response.status === 200) {
close();
}
});
}

return (
<>
<Modal opened={opened} onClose={close} title="Create post" size={"auto"} centered closeOnClickOutside={false}>
<RichEditor onContentChange={handleContentChange}/>
<Divider my={"md"}/>
<Group justify={"space-between"}>
<Button color="red" variant="light" onClick={close}>Cancel</Button>
<Button color="blue" variant="light" onClick={handleCreatePost}>Create</Button>
</Group>
</Modal>

<Button
variant={"subtle"}
size={"md"}
leftSection={<IconPencil/>}
autoContrast
fullWidth
justify={"flex-start"}
color={"gray"}
onClick={open}
>
Napisz post
</Button>
</>
);
}
1 change: 1 addition & 0 deletions frontend/src/Pages/CreatePost/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './CreatePost.tsx';
2 changes: 1 addition & 1 deletion frontend/src/Pages/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const MainPage = () => {

{/*Posts*/}
{posts.map((post) => (
<Post key={post.ownerLogin} userId={post.ownerLogin} contentHtml={post.content} createdAt={post.createdAt}
<Post key={post.ownerLogin} ownerLogin={post.ownerLogin} contentHtml={post.content} createdAt={post.createdAt}
photosUrls={
// generate 100 random photos
Array.from({length: 100}, () => {
Expand Down
21 changes: 0 additions & 21 deletions frontend/src/Pages/Post/Post.tsx

This file was deleted.

1 change: 0 additions & 1 deletion frontend/src/Pages/Post/index.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions frontend/src/Pages/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {NotFound} from "../NotFound";
import {Recovery} from "../Recovery";
import {Profile} from "../Profile";
import {Search} from "../Search";
import {Post} from "../Post";
import {Following} from "../Following";
import {Groups} from "../Groups";
import {Friends} from "../Friends";
Expand Down Expand Up @@ -37,7 +36,6 @@ export const Root = () => {
<Route path="/mainpage" element={<MainPage/>}/>
<Route path="/search" element={<Search/>}/>
<Route path="/profile/:userTag" element={<Profile/>}/>
<Route path="/createpost" element={<Post/>}/>
<Route path="/following" element={<Following/>}/>
<Route path="/groups" element={<Groups/>}/>
<Route path="/friends" element={<Friends/>}/>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/Services/DTOs/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,12 @@ export type User = {
email?: string;
birthDate?: string;
gender?: string;
}

export type BasicUserInfo = {
"id": string,
"name": string,
"surname": string,
"login": string,
"profilePicture": string | null
}
41 changes: 35 additions & 6 deletions frontend/src/Services/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import axios from 'axios';
import {CookieExtractor} from "./Utils/CookieExtractor.tsx";

const api = axios.create({
// baseURL: import.meta.env.VITE_API_URL,
withCredentials: true, // Cookie handling
});

Expand All @@ -12,11 +10,42 @@ export const setApiErrorHandler = (handler: (message: string) => void) => {
showError = handler;
};

// Cache for CSRF Token
let csrfTokenPromise: Promise<string | null> | null = null;

// Fetch CSRF token from server
const fetchCsrfToken = async (): Promise<string | null> => {
try {
const response = await axios.get('/api/csrf-token', {withCredentials: true});
if (response.data.token && response.data.headerName) {
// Set up cookie with CSRF token
localStorage.setItem('X-XSRF-TOKEN', response.data.token);
return response.data.token;
}
return null;
} catch (error) {
console.error('Failed to fetch CSRF token:', error);
if (showError) {
showError('Failed to fetch CSRF token');
}
return null;
}
};

// Interceptor which adds token to request headers
api.interceptors.request.use((config) => {
const csrfToken = CookieExtractor('csrf-token'); // Get csrf token from cookies
api.interceptors.request.use(async (config) => {
let csrfToken = localStorage.getItem('X-XSRF-TOKEN');

if (!csrfToken) {
if (!csrfTokenPromise) {
csrfTokenPromise = fetchCsrfToken(); // Start fetching token
}
csrfToken = await csrfTokenPromise; // Wait for token
csrfTokenPromise = null; // Reset promise
}

if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken;
config.headers['X-XSRF-TOKEN'] = csrfToken;
}

const token = localStorage.getItem('token'); // Get token from localStorage
Expand All @@ -41,6 +70,6 @@ api.interceptors.response.use(
}
);

api.defaults.withXSRFToken = true
api.defaults.withXSRFToken = false;

export default api;

0 comments on commit d55424e

Please sign in to comment.