@@ -137,8 +137,8 @@ function App() {
|
||||
<Route path="/" element={
|
||||
user ? <HomePage user={user} /> : <Navigate to="/login" replace />
|
||||
} />
|
||||
<Route path="/public" element={<PublicTimelinePage />} />
|
||||
<Route path="/local" element={<LocalTimelinePage />} />
|
||||
<Route path="/public" element={<PublicTimelinePage currentUser={user} />} />
|
||||
<Route path="/local" element={<LocalTimelinePage currentUser={user} />} />
|
||||
<Route path="/notifications" element={
|
||||
user ? <NotificationsPage user={user} /> : <Navigate to="/login" replace />
|
||||
} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FaHeart, FaRetweet, FaReply, FaShare, FaClock, FaGlobeAmericas, FaLock, FaUsers, FaEnvelope } from 'react-icons/fa';
|
||||
import { FaHeart, FaRetweet, FaReply, FaShare, FaClock, FaGlobeAmericas, FaLock, FaUsers, FaEnvelope, FaTrash } from 'react-icons/fa';
|
||||
|
||||
const PostContainer = styled.article`
|
||||
background: white;
|
||||
@@ -158,7 +158,7 @@ const ReblogInfo = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Post = ({ post, onFavorite, onReblog, onReply, currentUser }) => {
|
||||
const Post = ({ post, onFavorite, onReblog, onReply, onDelete, currentUser }) => {
|
||||
const isReblog = post.reblog;
|
||||
const actualPost = isReblog ? post.reblog : post;
|
||||
const originalPoster = isReblog ? post.account : null;
|
||||
@@ -281,6 +281,12 @@ const Post = ({ post, onFavorite, onReblog, onReply, currentUser }) => {
|
||||
<span>{actualPost.favourites_count || 0}</span>
|
||||
</ActionButton>
|
||||
|
||||
{currentUser && String(currentUser.id) === String(actualPost.account.id) && (
|
||||
<ActionButton onClick={() => onDelete?.(actualPost)}>
|
||||
<FaTrash />
|
||||
</ActionButton>
|
||||
)}
|
||||
|
||||
<ActionButton onClick={() => navigator.share?.({ url: actualPost.url })}>
|
||||
<FaShare />
|
||||
</ActionButton>
|
||||
|
||||
@@ -152,6 +152,20 @@ const HomePage = ({ user }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (post) => {
|
||||
if (!window.confirm('¿Estás seguro de que quieres eliminar este post? Esta acción no se puede deshacer.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteStatus(post.id);
|
||||
setPosts(prev => prev.filter(p => p.id !== post.id));
|
||||
} catch (err) {
|
||||
console.error('Error deleting post:', err);
|
||||
// Could show a toast or error message here
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (posts.length > 0 && hasMore && !loadingMore) {
|
||||
setLoadingMore(true);
|
||||
@@ -215,6 +229,7 @@ const HomePage = ({ user }) => {
|
||||
currentUser={user}
|
||||
onFavorite={handleFavorite}
|
||||
onReblog={handleReblog}
|
||||
onDelete={handleDelete}
|
||||
onReply={(post) => console.log('Reply to:', post)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -90,7 +90,7 @@ const EmptyState = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const LocalTimelinePage = () => {
|
||||
const LocalTimelinePage = ({ currentUser }) => {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -171,6 +171,20 @@ const LocalTimelinePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (post) => {
|
||||
if (!window.confirm('¿Estás seguro de que quieres eliminar este post? Esta acción no se puede deshacer.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteStatus(post.id);
|
||||
setPosts(prev => prev.filter(p => p.id !== post.id));
|
||||
} catch (err) {
|
||||
console.error('Error deleting post:', err);
|
||||
// Could show a toast or error message here
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (posts.length > 0 && hasMore && !loadingMore) {
|
||||
setLoadingMore(true);
|
||||
@@ -234,8 +248,10 @@ const LocalTimelinePage = () => {
|
||||
<Post
|
||||
key={post.id}
|
||||
post={post}
|
||||
currentUser={currentUser}
|
||||
onFavorite={handleFavorite}
|
||||
onReblog={handleReblog}
|
||||
onDelete={handleDelete}
|
||||
onReply={(post) => console.log('Reply to:', post)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { FaExclamationTriangle } from 'react-icons/fa';
|
||||
@@ -80,10 +80,18 @@ const OAuthCallbackPage = ({ onLoginSuccess }) => {
|
||||
}
|
||||
}, [hasCode, hasState]);
|
||||
|
||||
const processedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (processedRef.current) return;
|
||||
processedRef.current = true;
|
||||
console.log('OAuth callback: useEffect executed');
|
||||
|
||||
const processCallback = async () => {
|
||||
try {
|
||||
console.log('OAuth callback: Starting processCallback');
|
||||
const { code, state, error: oauthError, error_description } = oauthService.getOAuthCallbackParams();
|
||||
console.log('OAuth callback: Got params', { code: !!code, state: !!state, oauthError, error_description });
|
||||
|
||||
if (oauthError) {
|
||||
throw new Error(error_description || `OAuth error: ${oauthError}`);
|
||||
@@ -94,14 +102,23 @@ const OAuthCallbackPage = ({ onLoginSuccess }) => {
|
||||
}
|
||||
|
||||
setStatus('processing');
|
||||
console.log('OAuth callback: Status set to processing');
|
||||
|
||||
console.log('OAuth callback: Calling exchangeCodeForToken');
|
||||
const tokenData = await oauthService.exchangeCodeForToken(code, state);
|
||||
api.setAuth(tokenData.access_token, tokenData.instance_url, tokenData.refresh_token);
|
||||
console.log('OAuth callback: Token exchange successful', { access_token: !!tokenData.access_token, instance_url: tokenData.instance_url });
|
||||
|
||||
api.setAuth(tokenData.access_token, tokenData.instance_url, tokenData.refresh_token);
|
||||
console.log('OAuth callback: API auth set');
|
||||
|
||||
console.log('OAuth callback: Calling verifyCredentials');
|
||||
const userData = await api.verifyCredentials();
|
||||
console.log('OAuth callback: Credentials verified', { username: userData.username });
|
||||
|
||||
localStorage.setItem('current_user', JSON.stringify(userData));
|
||||
|
||||
setStatus('success');
|
||||
console.log('OAuth callback: Status set to success');
|
||||
|
||||
if (onLoginSuccess) {
|
||||
onLoginSuccess(userData);
|
||||
@@ -113,25 +130,15 @@ const OAuthCallbackPage = ({ onLoginSuccess }) => {
|
||||
}, 2000);
|
||||
|
||||
} catch (err) {
|
||||
console.error('OAuth callback error:', err);
|
||||
setError(err.message || 'Authentication failed');
|
||||
setStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
const processTimer = setTimeout(processCallback, 100);
|
||||
|
||||
const emergencyTimer = setTimeout(() => {
|
||||
if (status === 'processing') {
|
||||
setError('Authentication is taking too long. Please try again or check your connection.');
|
||||
setStatus('error');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(processTimer);
|
||||
clearTimeout(emergencyTimer);
|
||||
};
|
||||
}, [navigate, onLoginSuccess, status]);
|
||||
// Call immediately instead of with setTimeout
|
||||
processCallback();
|
||||
}, [navigate, onLoginSuccess]);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -209,19 +209,37 @@ const ProfilePage = ({ currentUser }) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
console.log('Loading profile for username:', username);
|
||||
|
||||
// First, search for the user by username
|
||||
const searchResults = await api.search(username, { type: 'accounts', limit: 5 });
|
||||
const searchResults = await api.search(username, { type: 'accounts', limit: 20, resolve: true });
|
||||
|
||||
let userAccount = null;
|
||||
|
||||
// Look for exact username match
|
||||
if (searchResults.accounts) {
|
||||
// Look for exact username match (try multiple variations)
|
||||
if (searchResults.accounts && searchResults.accounts.length > 0) {
|
||||
// First try exact username match
|
||||
userAccount = searchResults.accounts.find(account =>
|
||||
account.username === username || account.acct === username
|
||||
account.username === username
|
||||
);
|
||||
|
||||
// If not found, try with acct field (which includes domain)
|
||||
if (!userAccount) {
|
||||
userAccount = searchResults.accounts.find(account =>
|
||||
account.acct === username || account.acct === `${username}@${new URL(api.instanceUrl).host}`
|
||||
);
|
||||
}
|
||||
|
||||
// If still not found, try case-insensitive match
|
||||
if (!userAccount) {
|
||||
userAccount = searchResults.accounts.find(account =>
|
||||
account.username.toLowerCase() === username.toLowerCase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!userAccount) {
|
||||
console.log('User not found in search results. Available accounts:', searchResults.accounts?.map(acc => ({ username: acc.username, acct: acc.acct })));
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
@@ -265,6 +283,20 @@ const ProfilePage = ({ currentUser }) => {
|
||||
loadProfile();
|
||||
}, [loadProfile]);
|
||||
|
||||
const handleDelete = async (post) => {
|
||||
if (!window.confirm('¿Estás seguro de que quieres eliminar este post? Esta acción no se puede deshacer.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteStatus(post.id);
|
||||
setPosts(prev => prev.filter(p => p.id !== post.id));
|
||||
} catch (err) {
|
||||
console.error('Error deleting post:', err);
|
||||
// Could show a toast or error message here
|
||||
}
|
||||
};
|
||||
|
||||
const handleFollow = async () => {
|
||||
if (!currentUser || !profile) return;
|
||||
|
||||
@@ -426,6 +458,7 @@ const ProfilePage = ({ currentUser }) => {
|
||||
currentUser={currentUser}
|
||||
onFavorite={() => {}}
|
||||
onReblog={() => {}}
|
||||
onDelete={handleDelete}
|
||||
onReply={() => {}}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -90,7 +90,7 @@ const EmptyState = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const PublicTimelinePage = () => {
|
||||
const PublicTimelinePage = ({ currentUser }) => {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -171,6 +171,20 @@ const PublicTimelinePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (post) => {
|
||||
if (!window.confirm('¿Estás seguro de que quieres eliminar este post? Esta acción no se puede deshacer.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteStatus(post.id);
|
||||
setPosts(prev => prev.filter(p => p.id !== post.id));
|
||||
} catch (err) {
|
||||
console.error('Error deleting post:', err);
|
||||
// Could show a toast or error message here
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (posts.length > 0 && hasMore && !loadingMore) {
|
||||
setLoadingMore(true);
|
||||
@@ -234,8 +248,10 @@ const PublicTimelinePage = () => {
|
||||
<Post
|
||||
key={post.id}
|
||||
post={post}
|
||||
currentUser={currentUser}
|
||||
onFavorite={handleFavorite}
|
||||
onReblog={handleReblog}
|
||||
onDelete={handleDelete}
|
||||
onReply={(post) => console.log('Reply to:', post)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -158,6 +158,28 @@ const StatusPage = ({ currentUser }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (post) => {
|
||||
if (!window.confirm('¿Estás seguro de que quieres eliminar este post? Esta acción no se puede deshacer.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteStatus(post.id);
|
||||
if (post.id === status.id) {
|
||||
// If deleting the main post, redirect to home
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
// Remove from context
|
||||
setContext(prev => ({
|
||||
ancestors: prev.ancestors.filter(p => p.id !== post.id),
|
||||
descendants: prev.descendants.filter(p => p.id !== post.id)
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error deleting post:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Build reply tree structure
|
||||
const buildReplyTree = (posts, parentId = null, depth = 0) => {
|
||||
return posts
|
||||
@@ -178,6 +200,7 @@ const StatusPage = ({ currentUser }) => {
|
||||
currentUser={currentUser}
|
||||
onFavorite={handleFavorite}
|
||||
onReblog={handleReblog}
|
||||
onDelete={handleDelete}
|
||||
onReply={() => {}}
|
||||
compact={depth > 0}
|
||||
/>
|
||||
@@ -233,6 +256,7 @@ const StatusPage = ({ currentUser }) => {
|
||||
currentUser={currentUser}
|
||||
onFavorite={handleFavorite}
|
||||
onReblog={handleReblog}
|
||||
onDelete={handleDelete}
|
||||
onReply={() => {}}
|
||||
/>
|
||||
</Reply>
|
||||
@@ -246,6 +270,7 @@ const StatusPage = ({ currentUser }) => {
|
||||
currentUser={currentUser}
|
||||
onFavorite={handleFavorite}
|
||||
onReblog={handleReblog}
|
||||
onDelete={handleDelete}
|
||||
onReply={() => {}}
|
||||
showFullContent={true}
|
||||
/>
|
||||
|
||||
Referencia en una nueva incidencia
Block a user