@@ -137,8 +137,8 @@ function App() {
|
|||||||
<Route path="/" element={
|
<Route path="/" element={
|
||||||
user ? <HomePage user={user} /> : <Navigate to="/login" replace />
|
user ? <HomePage user={user} /> : <Navigate to="/login" replace />
|
||||||
} />
|
} />
|
||||||
<Route path="/public" element={<PublicTimelinePage />} />
|
<Route path="/public" element={<PublicTimelinePage currentUser={user} />} />
|
||||||
<Route path="/local" element={<LocalTimelinePage />} />
|
<Route path="/local" element={<LocalTimelinePage currentUser={user} />} />
|
||||||
<Route path="/notifications" element={
|
<Route path="/notifications" element={
|
||||||
user ? <NotificationsPage user={user} /> : <Navigate to="/login" replace />
|
user ? <NotificationsPage user={user} /> : <Navigate to="/login" replace />
|
||||||
} />
|
} />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
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`
|
const PostContainer = styled.article`
|
||||||
background: white;
|
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 isReblog = post.reblog;
|
||||||
const actualPost = isReblog ? post.reblog : post;
|
const actualPost = isReblog ? post.reblog : post;
|
||||||
const originalPoster = isReblog ? post.account : null;
|
const originalPoster = isReblog ? post.account : null;
|
||||||
@@ -281,6 +281,12 @@ const Post = ({ post, onFavorite, onReblog, onReply, currentUser }) => {
|
|||||||
<span>{actualPost.favourites_count || 0}</span>
|
<span>{actualPost.favourites_count || 0}</span>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
||||||
|
{currentUser && String(currentUser.id) === String(actualPost.account.id) && (
|
||||||
|
<ActionButton onClick={() => onDelete?.(actualPost)}>
|
||||||
|
<FaTrash />
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
|
||||||
<ActionButton onClick={() => navigator.share?.({ url: actualPost.url })}>
|
<ActionButton onClick={() => navigator.share?.({ url: actualPost.url })}>
|
||||||
<FaShare />
|
<FaShare />
|
||||||
</ActionButton>
|
</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 = () => {
|
const handleLoadMore = () => {
|
||||||
if (posts.length > 0 && hasMore && !loadingMore) {
|
if (posts.length > 0 && hasMore && !loadingMore) {
|
||||||
setLoadingMore(true);
|
setLoadingMore(true);
|
||||||
@@ -215,6 +229,7 @@ const HomePage = ({ user }) => {
|
|||||||
currentUser={user}
|
currentUser={user}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onReblog={handleReblog}
|
onReblog={handleReblog}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={(post) => console.log('Reply to:', post)}
|
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 [posts, setPosts] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
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 = () => {
|
const handleLoadMore = () => {
|
||||||
if (posts.length > 0 && hasMore && !loadingMore) {
|
if (posts.length > 0 && hasMore && !loadingMore) {
|
||||||
setLoadingMore(true);
|
setLoadingMore(true);
|
||||||
@@ -234,8 +248,10 @@ const LocalTimelinePage = () => {
|
|||||||
<Post
|
<Post
|
||||||
key={post.id}
|
key={post.id}
|
||||||
post={post}
|
post={post}
|
||||||
|
currentUser={currentUser}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onReblog={handleReblog}
|
onReblog={handleReblog}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={(post) => console.log('Reply to:', post)}
|
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 { useNavigate } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { FaExclamationTriangle } from 'react-icons/fa';
|
import { FaExclamationTriangle } from 'react-icons/fa';
|
||||||
@@ -80,10 +80,18 @@ const OAuthCallbackPage = ({ onLoginSuccess }) => {
|
|||||||
}
|
}
|
||||||
}, [hasCode, hasState]);
|
}, [hasCode, hasState]);
|
||||||
|
|
||||||
|
const processedRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (processedRef.current) return;
|
||||||
|
processedRef.current = true;
|
||||||
|
console.log('OAuth callback: useEffect executed');
|
||||||
|
|
||||||
const processCallback = async () => {
|
const processCallback = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log('OAuth callback: Starting processCallback');
|
||||||
const { code, state, error: oauthError, error_description } = oauthService.getOAuthCallbackParams();
|
const { code, state, error: oauthError, error_description } = oauthService.getOAuthCallbackParams();
|
||||||
|
console.log('OAuth callback: Got params', { code: !!code, state: !!state, oauthError, error_description });
|
||||||
|
|
||||||
if (oauthError) {
|
if (oauthError) {
|
||||||
throw new Error(error_description || `OAuth error: ${oauthError}`);
|
throw new Error(error_description || `OAuth error: ${oauthError}`);
|
||||||
@@ -94,14 +102,23 @@ const OAuthCallbackPage = ({ onLoginSuccess }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setStatus('processing');
|
setStatus('processing');
|
||||||
|
console.log('OAuth callback: Status set to processing');
|
||||||
|
|
||||||
|
console.log('OAuth callback: Calling exchangeCodeForToken');
|
||||||
const tokenData = await oauthService.exchangeCodeForToken(code, state);
|
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();
|
const userData = await api.verifyCredentials();
|
||||||
|
console.log('OAuth callback: Credentials verified', { username: userData.username });
|
||||||
|
|
||||||
localStorage.setItem('current_user', JSON.stringify(userData));
|
localStorage.setItem('current_user', JSON.stringify(userData));
|
||||||
|
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
|
console.log('OAuth callback: Status set to success');
|
||||||
|
|
||||||
if (onLoginSuccess) {
|
if (onLoginSuccess) {
|
||||||
onLoginSuccess(userData);
|
onLoginSuccess(userData);
|
||||||
@@ -113,25 +130,15 @@ const OAuthCallbackPage = ({ onLoginSuccess }) => {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('OAuth callback error:', err);
|
||||||
setError(err.message || 'Authentication failed');
|
setError(err.message || 'Authentication failed');
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const processTimer = setTimeout(processCallback, 100);
|
// Call immediately instead of with setTimeout
|
||||||
|
processCallback();
|
||||||
const emergencyTimer = setTimeout(() => {
|
}, [navigate, onLoginSuccess]);
|
||||||
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]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -209,19 +209,37 @@ const ProfilePage = ({ currentUser }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log('Loading profile for username:', username);
|
||||||
|
|
||||||
// First, search for the user by 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;
|
let userAccount = null;
|
||||||
|
|
||||||
// Look for exact username match
|
// Look for exact username match (try multiple variations)
|
||||||
if (searchResults.accounts) {
|
if (searchResults.accounts && searchResults.accounts.length > 0) {
|
||||||
|
// First try exact username match
|
||||||
userAccount = searchResults.accounts.find(account =>
|
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) {
|
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');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +283,20 @@ const ProfilePage = ({ currentUser }) => {
|
|||||||
loadProfile();
|
loadProfile();
|
||||||
}, [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 () => {
|
const handleFollow = async () => {
|
||||||
if (!currentUser || !profile) return;
|
if (!currentUser || !profile) return;
|
||||||
|
|
||||||
@@ -426,6 +458,7 @@ const ProfilePage = ({ currentUser }) => {
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onFavorite={() => {}}
|
onFavorite={() => {}}
|
||||||
onReblog={() => {}}
|
onReblog={() => {}}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={() => {}}
|
onReply={() => {}}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const EmptyState = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PublicTimelinePage = () => {
|
const PublicTimelinePage = ({ currentUser }) => {
|
||||||
const [posts, setPosts] = useState([]);
|
const [posts, setPosts] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
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 = () => {
|
const handleLoadMore = () => {
|
||||||
if (posts.length > 0 && hasMore && !loadingMore) {
|
if (posts.length > 0 && hasMore && !loadingMore) {
|
||||||
setLoadingMore(true);
|
setLoadingMore(true);
|
||||||
@@ -234,8 +248,10 @@ const PublicTimelinePage = () => {
|
|||||||
<Post
|
<Post
|
||||||
key={post.id}
|
key={post.id}
|
||||||
post={post}
|
post={post}
|
||||||
|
currentUser={currentUser}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onReblog={handleReblog}
|
onReblog={handleReblog}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={(post) => console.log('Reply to:', post)}
|
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
|
// Build reply tree structure
|
||||||
const buildReplyTree = (posts, parentId = null, depth = 0) => {
|
const buildReplyTree = (posts, parentId = null, depth = 0) => {
|
||||||
return posts
|
return posts
|
||||||
@@ -178,6 +200,7 @@ const StatusPage = ({ currentUser }) => {
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onReblog={handleReblog}
|
onReblog={handleReblog}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={() => {}}
|
onReply={() => {}}
|
||||||
compact={depth > 0}
|
compact={depth > 0}
|
||||||
/>
|
/>
|
||||||
@@ -233,6 +256,7 @@ const StatusPage = ({ currentUser }) => {
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onReblog={handleReblog}
|
onReblog={handleReblog}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={() => {}}
|
onReply={() => {}}
|
||||||
/>
|
/>
|
||||||
</Reply>
|
</Reply>
|
||||||
@@ -246,6 +270,7 @@ const StatusPage = ({ currentUser }) => {
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onReblog={handleReblog}
|
onReblog={handleReblog}
|
||||||
|
onDelete={handleDelete}
|
||||||
onReply={() => {}}
|
onReply={() => {}}
|
||||||
showFullContent={true}
|
showFullContent={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user