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
5 changes: 5 additions & 0 deletions apps/the_monkeys/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ const nextConfig = {
hostname: '127.0.0.1',
port: '8081',
},
{
protocol: 'http',
hostname: 'localhost',
port: '8081',
},
],
},
experimental: {
Expand Down
3 changes: 2 additions & 1 deletion apps/the_monkeys/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"format": "prettier --write ."
},
"dependencies": {
"@editorjs/attaches": "^1.3.1",
"@editorjs/delimiter": "1.4.2",
"@editorjs/editorjs": "2.29.1",
"@editorjs/header": "2.8.1",
Expand All @@ -29,6 +30,7 @@
"@tanstack/react-query": "^5.66.3",
"@the-monkeys/ui": "workspace:*",
"axios": "^1.6.8",
"blurhash": "^2.0.5",
"bowser": "^2.11.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand All @@ -42,7 +44,6 @@
"next": "^14.2.13",
"next-themes": "^0.2.1",
"prismjs": "^1.30.0",
"public-ip": "^7.0.1",
"react": "^18",
"react-dom": "^18",
"react-dropzone": "^14.2.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export const CookiesContent = () => {
return (
<MDXProvider>
<LegalPage
title='Cookies Policy'
date='01-12-2024'
title='Cookie Policy'
date='30-01-2026'
content={<Content />}
/>
</MDXProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const PrivacyContent = () => {
<MDXProvider>
<LegalPage
title='Privacy Policy'
date='01-12-2024'
date='30-01-2026'
content={<Content />}
/>
</MDXProvider>
Expand Down
6 changes: 5 additions & 1 deletion apps/the_monkeys/src/app/(marketing)/terms/TermsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { MDXProvider } from '@mdx-js/react';
export const TermsContent = () => {
return (
<MDXProvider>
<LegalPage title='Terms of Use' date='01-12-2024' content={<Content />} />
<LegalPage
title='Terms of Service'
date='30-01-2026'
content={<Content />}
/>
</MDXProvider>
);
};
3 changes: 2 additions & 1 deletion apps/the_monkeys/src/app/api/[[...path]]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ async function proxyRequest(req: Request, params?: { path: string[] }) {
cache: 'no-store',
// @ts-ignore: Required for Node.js bi-directional streaming
duplex: 'half',
});
signal: AbortSignal.timeout(60 * 60 * 1000), // 1 hour timeout for large uploads
} as any);

const responseHeaders = new Headers(response.headers);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const BlogRecommendations = ({
};

fetchBlogs();
}, []);
}, [topics]);

if (blogsLoading) {
return <BlogPageRecommendationSkeleton />;
Expand Down
94 changes: 52 additions & 42 deletions apps/the_monkeys/src/app/edit/[blogId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {
const [isSaving, setIsSaving] = useState<boolean>(false);
const [blogPublishLoading, setBlogPublishLoading] = useState(false);
const [blogTopics, setBlogTopics] = useState<string[]>([]);
const [token, setToken] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [connectionStatus, setConnectionStatus] = useState('Connecting...');

Expand All @@ -69,35 +68,6 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {
blogTopicsRef.current = blogTopics;
}, [data, accountId, blogTopics]);

// Get WebSocket token
useEffect(() => {
if (!session) return;

const fetchToken = async () => {
try {
const response = await axiosInstance.get('/auth/ws-token');
setToken(response.data.token);
} catch (error) {
console.error('Failed to get WebSocket token:', error);
toast({
variant: 'destructive',
title: 'Connection Error',
description:
'Failed to establish connection. Please refresh the page.',
});
}
};

fetchToken();

// Refresh token every 5 minutes
const tokenRefreshInterval = setInterval(fetchToken, 5 * 60 * 1000);

return () => {
clearInterval(tokenRefreshInterval);
};
}, [session]);

// Format data
const formatData = useCallback(
(data: OutputData, accountId: string | undefined, blogTopics: string[]) => {
Expand All @@ -122,14 +92,13 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {
slug: blogSlug,
};
},
[]
[blogId]
);

const [editorConfig, setEditorConfig] = useState<EditorConfig | null>(null);

// WebSocket management
useEffect(() => {
if (!token || !blogId) return;
if (!blogId) return;

let isMounted = true;
const MAX_RETRIES = 5;
Expand All @@ -143,9 +112,7 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {
}

setConnectionStatus('Connecting...');
const ws = new WebSocket(
`${WSS_URL_V2}/blog/draft/${blogId}?token=${token}`
);
const ws = new WebSocket(`${WSS_URL_V2}/blog/draft/${blogId}`);

ws.onopen = () => {
if (!isMounted) return;
Expand Down Expand Up @@ -204,7 +171,7 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {

// Handle tab visibility changes
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible' && !isConnected && token) {
if (document.visibilityState === 'visible' && !isConnected) {
// Reconnect immediately when tab becomes visible
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
Expand All @@ -225,7 +192,7 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {
}
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [token, blogId, formatData]);
}, [blogId, formatData, isConnected]);

// Auto-save when data changes
useEffect(() => {
Expand Down Expand Up @@ -333,10 +300,53 @@ const EditPage = ({ params }: { params: { blogId: string } }) => {

// Initialize editor data
useEffect(() => {
if (blog && !data) {
setData(blog.blog || { time: Date.now(), blocks: [], version: '' });
setBlogTopics(blog.tags || []);
}
const initializeData = async () => {
if (blog && !data) {
const blogData = blog.blog || {
time: Date.now(),
blocks: [],
version: '',
};

// Pre-resolve V2 image URLs for EditorJS
if (blogData.blocks && blogData.blocks.length > 0) {
const resolvedBlocks = await Promise.all(
blogData.blocks.map(async (block) => {
if (
block.type === 'image' &&
block.data?.file?.url?.endsWith('/url')
) {
try {
const res = await fetch(block.data.file.url);
const urlData = await res.json();
if (urlData && urlData.url) {
return {
...block,
data: {
...block.data,
file: {
...block.data.file,
url: urlData.url,
},
},
};
}
} catch (err) {
console.error('Failed to resolve image URL in editor:', err);
}
}
return block;
})
);
blogData.blocks = resolvedBlocks;
}

setData(blogData);
setBlogTopics(blog.tags || []);
}
};

initializeData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [blog]);

Expand Down
8 changes: 4 additions & 4 deletions apps/the_monkeys/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -783,13 +783,13 @@ li.cdx-list__item {
}

/* Ensure the plus/settings buttons are visible */
.ce-toolbar__plus,
.ce-toolbar__plus,
.ce-toolbar__settings-btn {
display: inline-flex !important;
opacity: 1 !important;
display: inline-flex !important;
opacity: 1 !important;
}

/* Ensure hover state is visible */
.ce-block:hover .ce-toolbar__actions {
opacity: 1 !important;
opacity: 1 !important;
}
87 changes: 87 additions & 0 deletions apps/the_monkeys/src/app/read/pdf/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use client';

import { Suspense } from 'react';

import { useRouter, useSearchParams } from 'next/navigation';

import Icon from '@/components/icon';
import Container from '@/components/layout/Container';

const PDFReaderContent = () => {
const searchParams = useSearchParams();
const router = useRouter();
const url = searchParams.get('url');
const title = searchParams.get('title') || 'PDF Viewer';

if (!url) {
return (
<div className='flex flex-col items-center justify-center min-h-[60vh] gap-4'>
<Icon name='RiErrorWarning' size={48} className='text-alert-red' />
<h1 className='text-2xl font-bold'>No PDF URL provided</h1>
<button
onClick={() => router.back()}
className='px-6 py-2 bg-brand-orange text-white rounded-full font-medium'
>
Go Back
</button>
</div>
);
}

// Append toolbar=0 to hide browser download/print buttons
const viewerUrl = url.includes('#') ? url : `${url}#toolbar=0`;

return (
<div className='flex flex-col h-[calc(100vh-80px)]'>
<div className='bg-gray-100 dark:bg-gray-900 border-b border-border-light dark:border-border-dark p-4 flex items-center justify-between'>
<div className='flex items-center gap-3'>
<button
onClick={() => router.back()}
className='p-2 hover:bg-gray-200 dark:hover:bg-gray-800 rounded-lg transition-colors'
>
<Icon name='RiArrowLeft' size={24} />
</button>
<div>
<h1 className='font-dm_sans font-semibold text-lg line-clamp-1'>
{title}
</h1>
<p className='text-xs opacity-70'>Monkeys Secure PDF Reader</p>
</div>
</div>
<div className='flex items-center gap-2'>
<div className='hidden sm:flex items-center gap-2 px-3 py-1 bg-brand-orange/10 text-brand-orange rounded-full text-xs font-medium'>
<Icon name='RiVerifiedBadge' type='Fill' size={12} />
Read Only Mode
</div>
</div>
</div>

<div className='flex-grow bg-gray-200 dark:bg-gray-800 overflow-hidden relative'>
<iframe
src={viewerUrl}
className='w-full h-full border-none'
title={title}
/>
</div>
</div>
);
};

export default function PDFReaderPage() {
return (
<Container className='max-w-6xl py-4 sm:py-8'>
<div className='rounded-2xl overflow-hidden border border-border-light dark:border-border-dark shadow-xl bg-white dark:bg-zinc-950'>
<Suspense
fallback={
<div className='flex flex-col items-center justify-center min-h-[80vh] gap-4'>
<div className='w-12 h-12 border-4 border-brand-orange border-t-transparent rounded-full animate-spin'></div>
<p className='font-dm_sans font-medium'>Initializing reader...</p>
</div>
}
>
<PDFReaderContent />
</Suspense>
</div>
</Container>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const BlogsByTopic = ({ topic }: { topic: string }) => {
};

fetchBlogs();
}, []);
}, [topic]);

if (blogsLoading) {
return <FeedBlogCardListSkeleton />;
Expand Down
Loading