Skip to content
Merged
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
6 changes: 3 additions & 3 deletions scripts/check-code-health.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ const productionPaths = [
const sourceExtensions = new Set(['.astro', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx']);

const baselines = {
lintWarnings: 39,
complexity: { violations: 37, maxCcn: 68, maxLength: 606, maxParams: 11 },
duplication: { clones: 37, duplicatedLines: 493 },
lintWarnings: 38,
complexity: { violations: 35, maxCcn: 68, maxLength: 606, maxParams: 11 },
duplication: { clones: 32, duplicatedLines: 413 },
unused: {
files: 0,
exports: 0,
Expand Down
117 changes: 10 additions & 107 deletions src/components/board/ShareDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { Check, Copy, Link2, Trash2, X } from 'lucide-react';
import { useCallback, useState } from 'react';
import { ShareLinkDialog } from '../ui/ShareLinkDialog';

interface ShareDialogProps {
open: boolean;
Expand All @@ -18,111 +17,15 @@ export function ShareDialog({
shareId,
onShareIdChange,
}: ShareDialogProps) {
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);

const shareUrl = shareId
? `${typeof window !== 'undefined' ? window.location.origin : ''}/share/${shareId}`
: '';

const generate = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(`/api/boards/${boardId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shareAction: 'generate' }),
});
if (!res.ok) throw new Error();
const data = await res.json();
onShareIdChange(data.shareId);
} catch {
// silent
} finally {
setLoading(false);
}
}, [boardId, onShareIdChange]);

const revoke = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(`/api/boards/${boardId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shareAction: 'revoke' }),
});
if (!res.ok) throw new Error();
onShareIdChange(undefined);
} catch {
// silent
} finally {
setLoading(false);
}
}, [boardId, onShareIdChange]);

const copyUrl = useCallback(() => {
navigator.clipboard.writeText(shareUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}, [shareUrl]);

if (!open) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="w-full max-w-sm rounded-xl border border-gray-700 bg-gray-900 p-5 shadow-2xl">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-sm font-semibold text-white">Share Board</h3>
<button onClick={onClose} className="text-gray-400 hover:text-white">
<X className="h-4 w-4" />
</button>
</div>

{shareId ? (
<div className="space-y-3">
<p className="text-xs text-gray-400">
Anyone with this link can view this board (read-only).
</p>
<div className="flex items-center gap-2 rounded-lg border border-gray-700 bg-gray-950 px-3 py-2">
<Link2 className="h-3.5 w-3.5 shrink-0 text-gray-500" />
<span className="flex-1 truncate text-xs text-gray-300">{shareUrl}</span>
<button
onClick={copyUrl}
className="shrink-0 text-gray-400 hover:text-white"
title="Copy link"
>
{copied ? (
<Check className="h-3.5 w-3.5 text-green-400" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
</div>
<button
onClick={revoke}
disabled={loading}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-red-500/30 px-3 py-2 text-xs font-medium text-red-400 hover:bg-red-500/10 disabled:opacity-50"
>
<Trash2 className="h-3 w-3" />
Revoke Link
</button>
</div>
) : (
<div className="space-y-3">
<p className="text-xs text-gray-400">
Generate a public link so anyone can view this board without logging in.
</p>
<button
onClick={generate}
disabled={loading}
className="flex w-full items-center justify-center gap-1.5 rounded-lg bg-blue-600 px-3 py-2 text-xs font-medium text-white hover:bg-blue-500 disabled:opacity-50"
>
<Link2 className="h-3.5 w-3.5" />
{loading ? 'Generating...' : 'Generate Share Link'}
</button>
</div>
)}
</div>
</div>
<ShareLinkDialog
open={open}
onClose={onClose}
apiPath={`/api/boards/${boardId}`}
sharePathPrefix=""
entityLabel="Board"
shareId={shareId}
onShareIdChange={onShareIdChange}
/>
);
}
117 changes: 10 additions & 107 deletions src/components/reader/ArticleShareDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { Check, Copy, Link2, Trash2, X } from 'lucide-react';
import { useCallback, useState } from 'react';
import { ShareLinkDialog } from '../ui/ShareLinkDialog';

interface ArticleShareDialogProps {
open: boolean;
Expand All @@ -18,111 +17,15 @@ export function ArticleShareDialog({
shareId,
onShareIdChange,
}: ArticleShareDialogProps) {
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);

const shareUrl = shareId
? `${typeof window !== 'undefined' ? window.location.origin : ''}/share/article/${shareId}`
: '';

const generate = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(`/api/articles/${articleId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shareAction: 'generate' }),
});
if (!res.ok) throw new Error();
const data = await res.json();
onShareIdChange(data.shareId);
} catch {
// silent
} finally {
setLoading(false);
}
}, [articleId, onShareIdChange]);

const revoke = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(`/api/articles/${articleId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shareAction: 'revoke' }),
});
if (!res.ok) throw new Error();
onShareIdChange(undefined);
} catch {
// silent
} finally {
setLoading(false);
}
}, [articleId, onShareIdChange]);

const copyUrl = useCallback(() => {
navigator.clipboard.writeText(shareUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}, [shareUrl]);

if (!open) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="w-full max-w-sm rounded-xl border border-gray-700 bg-gray-900 p-5 shadow-2xl">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-sm font-semibold text-white">Share Article</h3>
<button onClick={onClose} className="text-gray-400 hover:text-white">
<X className="h-4 w-4" />
</button>
</div>

{shareId ? (
<div className="space-y-3">
<p className="text-xs text-gray-400">
Anyone with this link can view this article (read-only).
</p>
<div className="flex items-center gap-2 rounded-lg border border-gray-700 bg-gray-950 px-3 py-2">
<Link2 className="h-3.5 w-3.5 shrink-0 text-gray-500" />
<span className="flex-1 truncate text-xs text-gray-300">{shareUrl}</span>
<button
onClick={copyUrl}
className="shrink-0 text-gray-400 hover:text-white"
title="Copy link"
>
{copied ? (
<Check className="h-3.5 w-3.5 text-green-400" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
</div>
<button
onClick={revoke}
disabled={loading}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-red-500/30 px-3 py-2 text-xs font-medium text-red-400 hover:bg-red-500/10 disabled:opacity-50"
>
<Trash2 className="h-3 w-3" />
Revoke Link
</button>
</div>
) : (
<div className="space-y-3">
<p className="text-xs text-gray-400">
Generate a public link so anyone can view this article without logging in.
</p>
<button
onClick={generate}
disabled={loading}
className="flex w-full items-center justify-center gap-1.5 rounded-lg bg-blue-600 px-3 py-2 text-xs font-medium text-white hover:bg-blue-500 disabled:opacity-50"
>
<Link2 className="h-3.5 w-3.5" />
{loading ? 'Generating...' : 'Generate Share Link'}
</button>
</div>
)}
</div>
</div>
<ShareLinkDialog
open={open}
onClose={onClose}
apiPath={`/api/articles/${articleId}`}
sharePathPrefix="article/"
entityLabel="Article"
shareId={shareId}
onShareIdChange={onShareIdChange}
/>
);
}
Loading