mirror of
https://github.com/bubblecup-12/VogelSocialMedia.git
synced 2025-07-06 11:08:48 +00:00
Feed done.
This commit is contained in:
parent
c3a7776fa5
commit
d11f92e11a
7 changed files with 267 additions and 43 deletions
|
@ -13,7 +13,6 @@ export const uploadPost = async (req: Request, res: Response) => {
|
||||||
const user: JwtPayload = req.user!; // Get the user from the request
|
const user: JwtPayload = req.user!; // Get the user from the request
|
||||||
const { description, status, tags } = uploadPostSchema.parse(req.body);
|
const { description, status, tags } = uploadPostSchema.parse(req.body);
|
||||||
const BUCKET = "images"; // Name of the bucket where the images are stored
|
const BUCKET = "images"; // Name of the bucket where the images are stored
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uploads = await Promise.all(
|
const uploads = await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
|
@ -58,15 +57,16 @@ export const uploadPost = async (req: Request, res: Response) => {
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
}); // create a new post in the database
|
||||||
// create a new post in the database
|
|
||||||
res.status(StatusCodes.CREATED).json({
|
res.status(StatusCodes.CREATED).json({
|
||||||
message: "Upload successful",
|
message: "Upload successful",
|
||||||
post: post,
|
post: post,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: "Upload failed" });
|
res
|
||||||
|
.status(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||||
|
.json({ error: "Upload failed" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,6 +104,20 @@ export const getPost = async (req: Request, res: Response) => {
|
||||||
postId: postId,
|
postId: postId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
let hasLiked = false;
|
||||||
|
if (user) {
|
||||||
|
const like = await prisma.like.findUnique({
|
||||||
|
where: {
|
||||||
|
postId_userId: {
|
||||||
|
postId: postId,
|
||||||
|
userId: user.sub!,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (like) {
|
||||||
|
hasLiked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!postObject) {
|
if (!postObject) {
|
||||||
res.status(StatusCodes.NOT_FOUND).json({
|
res.status(StatusCodes.NOT_FOUND).json({
|
||||||
error: "Post not found",
|
error: "Post not found",
|
||||||
|
@ -162,6 +176,7 @@ export const getPost = async (req: Request, res: Response) => {
|
||||||
updatedAt: postObject.updatedAt,
|
updatedAt: postObject.updatedAt,
|
||||||
images: images.filter((image) => image !== null), // filter out the null images
|
images: images.filter((image) => image !== null), // filter out the null images
|
||||||
following: isFollowing,
|
following: isFollowing,
|
||||||
|
hasLiked,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -262,8 +277,8 @@ export const like = async (req: Request, res: Response) => {
|
||||||
});
|
});
|
||||||
if (alreadyLiked) {
|
if (alreadyLiked) {
|
||||||
res.status(StatusCodes.CONFLICT).json({
|
res.status(StatusCodes.CONFLICT).json({
|
||||||
error: "Already following",
|
error: "Already liked",
|
||||||
details: [{ message: "You are already following this User" }],
|
details: [{ message: "You already liked this User" }],
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled, StyledEngineProvider } from '@mui/material/styles';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import CardHeader from '@mui/material/CardHeader';
|
import CardHeader from '@mui/material/CardHeader';
|
||||||
import CardMedia from '@mui/material/CardMedia';
|
import CardMedia from '@mui/material/CardMedia';
|
||||||
|
@ -15,6 +15,10 @@ import ShareIcon from '@mui/icons-material/Share';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import api from "../api/axios";
|
import api from "../api/axios";
|
||||||
|
import { Url } from 'url';
|
||||||
|
import { LogLevel } from 'vite';
|
||||||
|
import "./post.css"
|
||||||
|
|
||||||
|
|
||||||
interface ExpandMoreProps extends IconButtonProps {
|
interface ExpandMoreProps extends IconButtonProps {
|
||||||
expand: boolean;
|
expand: boolean;
|
||||||
|
@ -30,6 +34,7 @@ interface PostResponse {
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
profilePicture: Url;
|
||||||
};
|
};
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
@ -39,6 +44,7 @@ interface PostResponse {
|
||||||
url: string;
|
url: string;
|
||||||
}[];
|
}[];
|
||||||
following: boolean;
|
following: boolean;
|
||||||
|
hasLiked: boolean; // <-- add this
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpandMore = styled((props: ExpandMoreProps) => {
|
const ExpandMore = styled((props: ExpandMoreProps) => {
|
||||||
|
@ -55,17 +61,19 @@ const ExpandMore = styled((props: ExpandMoreProps) => {
|
||||||
export default function Post({ postId }: PostProps) {
|
export default function Post({ postId }: PostProps) {
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
const [post, setPost] = React.useState<PostResponse | null>(null);
|
const [post, setPost] = React.useState<PostResponse | null>(null);
|
||||||
|
const [currentImage, setCurrentImage] = React.useState(0);
|
||||||
|
const [like, setLike] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
getPostbyID();
|
getPostbyID();
|
||||||
// eslint-disable-next-line
|
|
||||||
}, [postId]);
|
}, [postId]);
|
||||||
|
|
||||||
async function getPostbyID(): Promise<void> {
|
async function getPostbyID(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await api.get<PostResponse>(`/posts/getPost/{postId}?postId=${postId}`);
|
const response = await api.get<PostResponse>(`/posts/getPost/{postId}?postId=${postId}`);
|
||||||
//const response = await api.get<PostResponse>(`http://localhost:3001/api/posts/getPost/{postId}?postId=${postId}`);
|
|
||||||
setPost(response.data);
|
setPost(response.data);
|
||||||
|
setLike(response.data.hasLiked); // <-- initialize like state
|
||||||
|
setCurrentImage(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch post:", error);
|
console.error("Failed to fetch post:", error);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +81,7 @@ export default function Post({ postId }: PostProps) {
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
return (
|
return (
|
||||||
<Card sx={{ maxWidth: 365, margin: 2, width:'100%'}}>
|
<Card sx={{ maxWidth: 400, width: '100%', margin: 2 }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography>Loading...</Typography>
|
<Typography>Loading...</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -81,8 +89,42 @@ export default function Post({ postId }: PostProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const images = post.images || [];
|
||||||
|
const hasMultipleImages = images.length > 1;
|
||||||
|
|
||||||
|
const handlePrev = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setCurrentImage((prev) => (prev === 0 ? images.length - 1 : prev - 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setCurrentImage((prev) => (prev === images.length - 1 ? 0 : prev + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLike = async () => {
|
||||||
|
try {
|
||||||
|
if (!like) {
|
||||||
|
await api.post(`/posts/like/${postId}`);
|
||||||
|
setLike(true);
|
||||||
|
setPost((prev) =>
|
||||||
|
prev ? { ...prev, likes: prev.likes + 1 } : prev
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await api.delete(`/posts/removeLike/${postId}`);
|
||||||
|
setLike(false);
|
||||||
|
setPost((prev) =>
|
||||||
|
prev ? { ...prev, likes: prev.likes - 1 } : prev
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update like:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ maxWidth: 345, margin: 2 }}>
|
<StyledEngineProvider injectFirst>
|
||||||
|
<Card className="body-l" sx={{ maxWidth: 600, width: '100%', margin: 2 }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
avatar={
|
avatar={
|
||||||
<Avatar sx={{ bgcolor: red[500] }} aria-label="user">
|
<Avatar sx={{ bgcolor: red[500] }} aria-label="user">
|
||||||
|
@ -95,39 +137,59 @@ export default function Post({ postId }: PostProps) {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
title={post.user.name}
|
title={post.user.name}
|
||||||
subheader={new Date(post.createdAt).toLocaleString()}
|
|
||||||
/>
|
/>
|
||||||
{post.images && post.images.length > 0 && (
|
{images.length > 0 && (
|
||||||
|
<div className="post-image-carousel">
|
||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
height="194"
|
image={images[currentImage].url}
|
||||||
image={post.images[0].url}
|
alt={images[currentImage].originalName}
|
||||||
alt={post.images[0].originalName}
|
className="post-image"
|
||||||
/>
|
/>
|
||||||
|
{hasMultipleImages && (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
aria-label="previous image"
|
||||||
|
onClick={handlePrev}
|
||||||
|
className="post-carousel-arrow left"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{"<"}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
aria-label="next image"
|
||||||
|
onClick={handleNext}
|
||||||
|
className="post-carousel-arrow right"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{">"}
|
||||||
|
</IconButton>
|
||||||
|
<div className="post-image-counter">
|
||||||
|
{currentImage + 1} / {images.length}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||||
{post.description}
|
{post.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
|
||||||
Status: {post.status}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
|
||||||
Likes: {post.likes}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
Tags: {post.tags.join(", ")}
|
Tags: {post.tags.join(", ")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
|
||||||
Following: {post.following ? "Ja" : "Nein"}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions disableSpacing>
|
<CardActions disableSpacing>
|
||||||
<IconButton aria-label="add to favorites">
|
<IconButton aria-label="like" onClick={handleLike}>
|
||||||
<FavoriteIcon />
|
<FavoriteIcon
|
||||||
</IconButton>
|
className="post-like-icon"
|
||||||
<IconButton aria-label="share">
|
sx={{
|
||||||
<ShareIcon />
|
color: like ? "#d32f2f" : "#fff",
|
||||||
|
stroke: !like ? "grey" : "none",
|
||||||
|
strokeWidth: !like ? 2 : 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="post-like-count">{post.likes}</span>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<ExpandMore
|
<ExpandMore
|
||||||
expand={expanded}
|
expand={expanded}
|
||||||
|
@ -140,14 +202,22 @@ export default function Post({ postId }: PostProps) {
|
||||||
</CardActions>
|
</CardActions>
|
||||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
Following: {post.following ? "Ja" : "Nein"}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
Status: {post.status}
|
||||||
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Erstellt am: {new Date(post.createdAt).toLocaleString()}
|
Erstellt am: {new Date(post.createdAt).toLocaleString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Zuletzt aktualisiert: {new Date(post.updatedAt).toLocaleString()}
|
Zuletzt aktualisiert: {new Date(post.updatedAt).toLocaleString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Card>
|
</Card>
|
||||||
|
</StyledEngineProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,12 @@ import Post from "../Post";
|
||||||
import "./feed.css";
|
import "./feed.css";
|
||||||
import api from "../../api/axios";
|
import api from "../../api/axios";
|
||||||
import { create } from "axios";
|
import { create } from "axios";
|
||||||
|
import WelcomeMessage from "./welcomeMessage/welcomeMessage";
|
||||||
|
import { useAuth } from "../../api/Auth";
|
||||||
|
import ButtonRotkehlchen from "../ButtonRotkehlchen";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface PostListItem {
|
interface PostListItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +23,8 @@ function Feed() {
|
||||||
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
||||||
const feedRef = useRef<HTMLDivElement | null>(null);
|
const feedRef = useRef<HTMLDivElement | null>(null);
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const fetchPosts = async () => {
|
const fetchPosts = async () => {
|
||||||
if (loading || !hasMore) return;
|
if (loading || !hasMore) return;
|
||||||
|
@ -24,8 +32,11 @@ function Feed() {
|
||||||
try {
|
try {
|
||||||
let url = `/feed?limit=${PAGE_SIZE}`;
|
let url = `/feed?limit=${PAGE_SIZE}`;
|
||||||
if (nextCursor) {
|
if (nextCursor) {
|
||||||
url = `/feed?createdAt=${encodeURIComponent(nextCursor)}&limit=${PAGE_SIZE}`;
|
url = `/feed?createdAt=${encodeURIComponent(
|
||||||
|
nextCursor
|
||||||
|
)}&limit=${PAGE_SIZE}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FeedResponse {
|
interface FeedResponse {
|
||||||
posts: PostListItem[];
|
posts: PostListItem[];
|
||||||
nextCursor: string | null;
|
nextCursor: string | null;
|
||||||
|
@ -64,7 +75,24 @@ function Feed() {
|
||||||
}, [loading, hasMore, nextCursor]);
|
}, [loading, hasMore, nextCursor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="feedContainer">
|
<div className={user ? "loggedInfeedContainer" : "feedContainer"}>
|
||||||
|
{!user && (
|
||||||
|
<div className="welcome-for-logged-out">
|
||||||
|
<WelcomeMessage />
|
||||||
|
<ButtonRotkehlchen
|
||||||
|
style={"secondary"}
|
||||||
|
label={"Sign Up"}
|
||||||
|
type={"button"}
|
||||||
|
onClick={() => navigate("/register")}
|
||||||
|
/>
|
||||||
|
<ButtonRotkehlchen
|
||||||
|
style={"primary"}
|
||||||
|
label={"Login"}
|
||||||
|
type={"button"}
|
||||||
|
onClick={() => navigate("/login")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<main className="feedContent" ref={feedRef}>
|
<main className="feedContent" ref={feedRef}>
|
||||||
{posts.length === 0 && !loading && <div>Keine Posts gefunden.</div>}
|
{posts.length === 0 && !loading && <div>Keine Posts gefunden.</div>}
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
.feedContainer {
|
.feedContainer {
|
||||||
display: flex;
|
min-height: 100vh;
|
||||||
flex-direction: column;
|
/* Subtract he */
|
||||||
min-height: calc(100vh - var(--Header-height));
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feedContent {
|
.feedContent {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1rem;
|
width: 100%;
|
||||||
height: 80vh;
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
.loading {
|
.loading {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -26,8 +27,21 @@
|
||||||
|
|
||||||
/* Desktop responsive behavior */
|
/* Desktop responsive behavior */
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
.feedContainer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 40% 60%;
|
||||||
|
}
|
||||||
|
.loggedInfeedContainer {
|
||||||
|
|
||||||
|
}
|
||||||
|
.welcome-for-logged-out {
|
||||||
|
position: sticky;
|
||||||
|
top: 2rem;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
.feedContent {
|
.feedContent {
|
||||||
width: 400px;
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
.welcome-title {
|
||||||
|
color: var(--Rotkehlchen-orange-default);
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: bolder;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.welcome-text {
|
||||||
|
text-align: center;
|
||||||
|
color: var( --Rotkehlchen-gray);
|
||||||
|
|
||||||
|
}
|
||||||
|
.desktop-welcome-text{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.welcome-text{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.desktop-welcome-text {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--Rotkehlchen-gray);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./welcomeMessage.css";
|
||||||
|
|
||||||
|
export default function WelcomeMessage() {
|
||||||
|
return (
|
||||||
|
<div className="welcome-message">
|
||||||
|
<h1 className="welcome-title">Welcome!</h1>
|
||||||
|
<p className="welcome-text">
|
||||||
|
Become a part of our big, bird loving community!
|
||||||
|
</p>
|
||||||
|
<p className="desktop-welcome-text">
|
||||||
|
Exchange pictures and information about birds and be part of our big bird loving community! Pellentesque vulputate a enim ac feugiat. Donec dictum magna sit amet arcu commodo, quis vehicula nunc commodo. Pellentesque varius congue varius.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
54
code/frontend/src/components/post.css
Normal file
54
code/frontend/src/components/post.css
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
.css-izap9d-MuiTypography-root {
|
||||||
|
font-family: "Inter"
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image-carousel {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
max-height: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-carousel-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: var(--Rotkehlchen-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.post-carousel-arrow.left {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.post-carousel-arrow.right {
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image-counter {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 16px;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-like-icon {
|
||||||
|
font-size: 44px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-like-count {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
color: grey;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue