264 líneas
6.1 KiB
JavaScript
264 líneas
6.1 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import styled from 'styled-components';
|
|
import Post from '../components/Post/Post';
|
|
import api from '../services/api';
|
|
import { FaSpinner, FaExclamationTriangle, FaUsers } from 'react-icons/fa';
|
|
|
|
const PageContainer = styled.div`
|
|
max-width: 100%;
|
|
`;
|
|
|
|
const PageHeader = styled.div`
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 2rem;
|
|
`;
|
|
|
|
const PageTitle = styled.h1`
|
|
color: #1f2937;
|
|
margin: 0;
|
|
font-size: 1.8rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
`;
|
|
|
|
const PageDescription = styled.p`
|
|
color: #6b7280;
|
|
margin: 0.5rem 0 0 0;
|
|
line-height: 1.5;
|
|
`;
|
|
|
|
const LoadingContainer = styled.div`
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: #6b7280;
|
|
`;
|
|
|
|
const ErrorContainer = styled.div`
|
|
background: #fef2f2;
|
|
border: 1px solid #fecaca;
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin: 1rem 0;
|
|
color: #dc2626;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
`;
|
|
|
|
const LoadMoreButton = styled.button`
|
|
width: 100%;
|
|
padding: 1rem;
|
|
background: #f9fafb;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 8px;
|
|
color: #6b7280;
|
|
cursor: pointer;
|
|
margin: 1rem 0;
|
|
|
|
&:hover {
|
|
background: #f3f4f6;
|
|
}
|
|
|
|
&:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.5;
|
|
}
|
|
`;
|
|
|
|
const EmptyState = styled.div`
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: #6b7280;
|
|
|
|
.icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
color: #d1d5db;
|
|
}
|
|
|
|
h3 {
|
|
margin: 0 0 0.5rem 0;
|
|
color: #374151;
|
|
}
|
|
|
|
p {
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
}
|
|
`;
|
|
|
|
const LocalTimelinePage = () => {
|
|
const [posts, setPosts] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
|
|
useEffect(() => {
|
|
loadTimeline();
|
|
}, []);
|
|
|
|
const loadTimeline = async (maxId = null) => {
|
|
try {
|
|
if (!maxId) {
|
|
setLoading(true);
|
|
setError(null);
|
|
}
|
|
|
|
const timelineData = await api.getPublicTimeline({
|
|
max_id: maxId,
|
|
limit: 20,
|
|
local: true // Only local content
|
|
});
|
|
|
|
if (maxId) {
|
|
setPosts(prev => [...prev, ...timelineData]);
|
|
setLoadingMore(false);
|
|
} else {
|
|
setPosts(timelineData);
|
|
setLoading(false);
|
|
}
|
|
|
|
setHasMore(timelineData.length === 20);
|
|
} catch (err) {
|
|
console.error('Error loading local timeline:', err);
|
|
|
|
if (err.response?.status === 401) {
|
|
setError('Authentication required to view the local timeline.');
|
|
} else if (err.response?.status === 403) {
|
|
setError('Local timeline is not available on this instance.');
|
|
} else if (err.code === 'ECONNABORTED') {
|
|
setError('Request timeout. Please check your connection and try again.');
|
|
} else {
|
|
setError('Failed to load local timeline. Please try again.');
|
|
}
|
|
|
|
setLoading(false);
|
|
setLoadingMore(false);
|
|
}
|
|
};
|
|
|
|
const handleFavorite = async (post) => {
|
|
try {
|
|
let updatedPost;
|
|
if (post.favourited) {
|
|
updatedPost = await api.unfavoriteStatus(post.id);
|
|
} else {
|
|
updatedPost = await api.favoriteStatus(post.id);
|
|
}
|
|
|
|
setPosts(prev => prev.map(p => p.id === post.id ? updatedPost : p));
|
|
} catch (err) {
|
|
console.error('Error toggling favorite:', err);
|
|
}
|
|
};
|
|
|
|
const handleReblog = async (post) => {
|
|
try {
|
|
let updatedPost;
|
|
if (post.reblogged) {
|
|
updatedPost = await api.unreblogStatus(post.id);
|
|
} else {
|
|
updatedPost = await api.reblogStatus(post.id);
|
|
}
|
|
|
|
setPosts(prev => prev.map(p => p.id === post.id ? updatedPost : p));
|
|
} catch (err) {
|
|
console.error('Error toggling reblog:', err);
|
|
}
|
|
};
|
|
|
|
const handleLoadMore = () => {
|
|
if (posts.length > 0 && hasMore && !loadingMore) {
|
|
setLoadingMore(true);
|
|
const lastPost = posts[posts.length - 1];
|
|
loadTimeline(lastPost.id);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<PageContainer>
|
|
<PageHeader>
|
|
<PageTitle>
|
|
<FaUsers />
|
|
Local Timeline
|
|
</PageTitle>
|
|
</PageHeader>
|
|
<LoadingContainer>
|
|
<FaSpinner style={{ animation: 'spin 1s linear infinite', fontSize: '2rem', marginBottom: '1rem' }} />
|
|
<div>Loading local timeline...</div>
|
|
</LoadingContainer>
|
|
</PageContainer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<PageContainer>
|
|
<PageHeader>
|
|
<PageTitle>
|
|
<FaUsers />
|
|
Local Timeline
|
|
</PageTitle>
|
|
</PageHeader>
|
|
|
|
<PageDescription>
|
|
Public posts from users on this instance only.
|
|
This is a great way to see what your local community is talking about.
|
|
</PageDescription>
|
|
|
|
{error && (
|
|
<ErrorContainer>
|
|
<FaExclamationTriangle />
|
|
{error}
|
|
</ErrorContainer>
|
|
)}
|
|
|
|
{posts.length === 0 && !loading ? (
|
|
<EmptyState>
|
|
<div className="icon">
|
|
<FaUsers />
|
|
</div>
|
|
<h3>No local posts</h3>
|
|
<p>
|
|
There are no local public posts available right now.<br />
|
|
This instance might be new or have limited local activity.
|
|
</p>
|
|
</EmptyState>
|
|
) : (
|
|
<>
|
|
{posts.map(post => (
|
|
<Post
|
|
key={post.id}
|
|
post={post}
|
|
onFavorite={handleFavorite}
|
|
onReblog={handleReblog}
|
|
onReply={(post) => console.log('Reply to:', post)}
|
|
/>
|
|
))}
|
|
|
|
{hasMore && (
|
|
<LoadMoreButton
|
|
onClick={handleLoadMore}
|
|
disabled={loadingMore}
|
|
>
|
|
{loadingMore ? (
|
|
<>
|
|
<FaSpinner style={{ animation: 'spin 1s linear infinite', marginRight: '0.5rem' }} />
|
|
Loading more posts...
|
|
</>
|
|
) : (
|
|
'Load more posts'
|
|
)}
|
|
</LoadMoreButton>
|
|
)}
|
|
</>
|
|
)}
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default LocalTimelinePage; |