Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lucide-react": "^0.552.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.5",
"react-router-dom": "^7.11.0",
"tailwindcss": "^4.1.16"
},
"devDependencies": {
Expand Down
25 changes: 23 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Routes, Route } from 'react-router'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import Home from './pages/Home.tsx'
import CreatePostPage from './pages/CreatePostPage.tsx'
import Navbar from './components/Navbar.tsx'
Expand All @@ -8,6 +8,7 @@ import CreateCommunityPage from './pages/CreateCommunityPage.tsx'
import {CommunityPage} from './pages/CommunityPage.tsx'
import { CommunitiesPage } from './pages/CommunitiesPage.tsx'
import MessagesPage from './pages/MessagesPage.tsx'
import NotFound from './components/NotFound.tsx' // Import the 404 page
import EventsPage from './pages/EventsPage.tsx'
import EventDetailPage from './pages/EventDetailPage.tsx'
import CreateEventPage from './pages/CreateEventPage.tsx'
Expand All @@ -21,6 +22,26 @@ import ProfilePage from './pages/ProfilePage.tsx'

function App() {
return (
<>
<div>
<Router> {/* Wrap everything with Router */}
<Navbar />
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/create" element={<CreatePostPage />} />
<Route path="/post/:id" element={<PostPage />} />
<Route path="/communities/create" element={<CreateCommunityPage />} />
<Route path="/communities" element={<CommunitiesPage />} />
<Route path="/communities/:id" element={<CommunityPage />} />
<Route path="/messages" element={<MessagesPage />} />
{/* Add 404 route at the end - catches all undefined routes */}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</Router>
</div>
</>
<div className="min-h-screen flex flex-col bg-[var(--bg-primary)] text-[var(--text-primary)] transition-colors">
<Navbar />
<main className="flex-1">
Expand Down Expand Up @@ -54,4 +75,4 @@ function App() {
)
}

export default App
export default App
65 changes: 65 additions & 0 deletions src/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// src/components/CodeBlock.tsx
import { useState } from 'react';
import { Copy, Check } from 'lucide-react';

interface CodeBlockProps {
code: string;
language?: string;
}

export const CodeBlock = ({ code, language = '' }: CodeBlockProps) => {
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy: ', err);
const textArea = document.createElement('textarea');
textArea.value = code;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};

return (
<div className="relative my-3 group">
{/* Header with language and copy button */}
<div className="flex justify-between items-center mb-1 px-1">
{language && (
<span className="text-xs font-mono text-cyan-300 px-2 py-1 bg-cyan-900/30 rounded">
{language}
</span>
)}
<button
onClick={handleCopy}
className="flex items-center gap-2 px-3 py-1 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-md text-sm font-mono transition-all duration-200 opacity-0 group-hover:opacity-100"
title="Copy code"
>
{copied ? (
<>
<Check className="w-4 h-4 text-green-400" />
<span className="text-green-400">Copied!</span>
</>
) : (
<>
<Copy className="w-4 h-4 text-cyan-300" />
<span className="text-cyan-300">Copy</span>
</>
)}
</button>
</div>

{/* Code block */}
<pre className="bg-slate-950 border border-slate-800 rounded-lg p-4 overflow-x-auto font-mono text-sm text-gray-300">
<code>{code}</code>
</pre>
</div>
);
};
23 changes: 20 additions & 3 deletions src/components/CommunityDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import PostItem from "./PostItem";
import PostSkeleton from "./PostSkeleton"; // Import the skeleton component
import { fetchCommunityPost } from "../utils/communityApi";

interface Props {
Expand Down Expand Up @@ -27,8 +28,25 @@ const CommunityDisplay = ({ communityId }: Props) => {
queryFn: () => fetchCommunityPost(communityId),
});

if (isLoading)
return <div className="text-center py-4">Loading posts...</div>;
// Skeleton loading state
if (isLoading) {
return (
<div>
{/* Skeleton header */}
<div className="mb-10 text-center justify-center animate-pulse">
<div className="h-10 bg-cyan-900/30 rounded w-48 mx-auto mb-2"></div>
<div className="h-4 bg-slate-700/30 rounded w-40 mx-auto"></div>
</div>

{/* Skeleton posts */}
<div className="mx-auto flex flex-col gap-6">
{[...Array(3)].map((_, index) => (
<PostSkeleton key={`skeleton-${index}`} />
))}
</div>
</div>
);
}

if (error)
return (
Expand All @@ -52,7 +70,6 @@ const CommunityDisplay = ({ communityId }: Props) => {
</div>

{data && data.length > 0 ? (

<div className="mx-auto flex flex-col gap-6">
{data.map((post) => (
<PostItem key={post.id} post={post} />
Expand Down
66 changes: 66 additions & 0 deletions src/components/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// src/components/CopyButton.tsx
import { useState } from 'react';
import { Copy, Check } from 'lucide-react';

interface CopyButtonProps {
text: string;
className?: string;
size?: 'sm' | 'md' | 'lg';
}

export const CopyButton = ({ text, className = '', size = 'md' }: CopyButtonProps) => {
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy text: ', err);
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};

const sizeClasses = {
sm: 'px-2 py-1 text-xs',
md: 'px-3 py-1.5 text-sm',
lg: 'px-4 py-2 text-base'
};

const iconSize = {
sm: 'w-3 h-3',
md: 'w-4 h-4',
lg: 'w-5 h-5'
};

return (
<button
onClick={handleCopy}
className={`flex items-center gap-2 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-md transition-all duration-200 ${sizeClasses[size]} ${className}`}
title="Copy to clipboard"
>
{copied ? (
<>
<Check className={`${iconSize[size]} text-green-400`} />
<span className="text-green-400 font-mono">Copied!</span>
</>
) : (
<>
<Copy className={`${iconSize[size]} text-cyan-300`} />
<span className="text-cyan-300 font-mono">Copy</span>
</>
)}
</button>
);
};

export default CopyButton;
72 changes: 72 additions & 0 deletions src/components/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { Link } from 'react-router-dom';

const NotFound = () => {
const styles = {
container: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
backgroundColor: '#f5f5f5',
padding: '20px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
} as React.CSSProperties,
content: {
textAlign: 'center' as const,
backgroundColor: 'white',
padding: '40px',
borderRadius: '12px',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.1)',
maxWidth: '500px',
width: '100%'
} as React.CSSProperties,
title: {
fontSize: '6rem',
fontWeight: 700,
color: '#dc3545',
margin: 0,
lineHeight: 1
} as React.CSSProperties,
subtitle: {
fontSize: '2rem',
fontWeight: 600,
color: '#333',
margin: '20px 0 10px'
} as React.CSSProperties,
message: {
fontSize: '1.1rem',
color: '#666',
marginBottom: '30px',
lineHeight: '1.5'
} as React.CSSProperties,
button: {
display: 'inline-block',
backgroundColor: '#007bff',
color: 'white',
padding: '12px 30px',
borderRadius: '6px',
textDecoration: 'none',
fontWeight: 500,
fontSize: '1rem',
transition: 'background-color 0.3s ease'
} as React.CSSProperties
};

return (
<div style={styles.container}>
<div style={styles.content}>
<h1 style={styles.title}>404</h1>
<h2 style={styles.subtitle}>Page Not Found</h2>
<p style={styles.message}>
Oops! The page you're looking for doesn't exist or has been moved.
</p>
<Link to="/" style={styles.button}>
Return to Feed
</Link>
</div>
</div>
);
};

export default NotFound;
Loading