diff --git a/apps/the_monkeys/next.config.mjs b/apps/the_monkeys/next.config.mjs index a128a23a..b1a8e356 100644 --- a/apps/the_monkeys/next.config.mjs +++ b/apps/the_monkeys/next.config.mjs @@ -24,6 +24,11 @@ const nextConfig = { hostname: '127.0.0.1', port: '8081', }, + { + protocol: 'http', + hostname: 'localhost', + port: '8081', + }, ], }, experimental: { diff --git a/apps/the_monkeys/package.json b/apps/the_monkeys/package.json index f9d4ea38..02744bf7 100644 --- a/apps/the_monkeys/package.json +++ b/apps/the_monkeys/package.json @@ -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", @@ -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", @@ -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", diff --git a/apps/the_monkeys/src/app/(marketing)/cookies/CookiesContent.tsx b/apps/the_monkeys/src/app/(marketing)/cookies/CookiesContent.tsx index e5568dad..ca508ac0 100644 --- a/apps/the_monkeys/src/app/(marketing)/cookies/CookiesContent.tsx +++ b/apps/the_monkeys/src/app/(marketing)/cookies/CookiesContent.tsx @@ -8,8 +8,8 @@ export const CookiesContent = () => { return ( } /> diff --git a/apps/the_monkeys/src/app/(marketing)/privacy/PrivacyContent.tsx b/apps/the_monkeys/src/app/(marketing)/privacy/PrivacyContent.tsx index 315435c4..b099caed 100644 --- a/apps/the_monkeys/src/app/(marketing)/privacy/PrivacyContent.tsx +++ b/apps/the_monkeys/src/app/(marketing)/privacy/PrivacyContent.tsx @@ -9,7 +9,7 @@ export const PrivacyContent = () => { } /> diff --git a/apps/the_monkeys/src/app/(marketing)/terms/TermsContent.tsx b/apps/the_monkeys/src/app/(marketing)/terms/TermsContent.tsx index e2b6cac8..814605a2 100644 --- a/apps/the_monkeys/src/app/(marketing)/terms/TermsContent.tsx +++ b/apps/the_monkeys/src/app/(marketing)/terms/TermsContent.tsx @@ -7,7 +7,11 @@ import { MDXProvider } from '@mdx-js/react'; export const TermsContent = () => { return ( - } /> + } + /> ); }; diff --git a/apps/the_monkeys/src/app/api/[[...path]]/route.ts b/apps/the_monkeys/src/app/api/[[...path]]/route.ts index 2555e205..7ddd6560 100644 --- a/apps/the_monkeys/src/app/api/[[...path]]/route.ts +++ b/apps/the_monkeys/src/app/api/[[...path]]/route.ts @@ -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); diff --git a/apps/the_monkeys/src/app/blog/components/BlogRecommendations.tsx b/apps/the_monkeys/src/app/blog/components/BlogRecommendations.tsx index 01465383..ecfeabb5 100644 --- a/apps/the_monkeys/src/app/blog/components/BlogRecommendations.tsx +++ b/apps/the_monkeys/src/app/blog/components/BlogRecommendations.tsx @@ -36,7 +36,7 @@ export const BlogRecommendations = ({ }; fetchBlogs(); - }, []); + }, [topics]); if (blogsLoading) { return ; diff --git a/apps/the_monkeys/src/app/edit/[blogId]/page.tsx b/apps/the_monkeys/src/app/edit/[blogId]/page.tsx index d03d7d44..29baedc5 100644 --- a/apps/the_monkeys/src/app/edit/[blogId]/page.tsx +++ b/apps/the_monkeys/src/app/edit/[blogId]/page.tsx @@ -55,7 +55,6 @@ const EditPage = ({ params }: { params: { blogId: string } }) => { const [isSaving, setIsSaving] = useState(false); const [blogPublishLoading, setBlogPublishLoading] = useState(false); const [blogTopics, setBlogTopics] = useState([]); - const [token, setToken] = useState(null); const [isConnected, setIsConnected] = useState(false); const [connectionStatus, setConnectionStatus] = useState('Connecting...'); @@ -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[]) => { @@ -122,14 +92,13 @@ const EditPage = ({ params }: { params: { blogId: string } }) => { slug: blogSlug, }; }, - [] + [blogId] ); const [editorConfig, setEditorConfig] = useState(null); - // WebSocket management useEffect(() => { - if (!token || !blogId) return; + if (!blogId) return; let isMounted = true; const MAX_RETRIES = 5; @@ -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; @@ -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); @@ -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(() => { @@ -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]); diff --git a/apps/the_monkeys/src/app/globals.css b/apps/the_monkeys/src/app/globals.css index 9f97bc65..38a86f01 100644 --- a/apps/the_monkeys/src/app/globals.css +++ b/apps/the_monkeys/src/app/globals.css @@ -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; } diff --git a/apps/the_monkeys/src/app/read/pdf/page.tsx b/apps/the_monkeys/src/app/read/pdf/page.tsx new file mode 100644 index 00000000..c915f521 --- /dev/null +++ b/apps/the_monkeys/src/app/read/pdf/page.tsx @@ -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 ( +
+ +

No PDF URL provided

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

+ {title} +

+

Monkeys Secure PDF Reader

+
+
+
+
+ + Read Only Mode +
+
+
+ +
+