Files
2026-04-26 22:57:58 +02:00

183 lines
6.8 KiB
JavaScript

import { useState, useRef, useEffect } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import Editor, { loader } from "@monaco-editor/react";
import { SITE_CONFIG } from '../config';
import { nanoid } from 'nanoid';
export default function Home() {
const [filename, setFilename] = useState('script.js');
const [code, setCode] = useState('');
const [lang, setLang] = useState('javascript');
const [indentMode, setIndentMode] = useState('spaces');
const [indentSize, setIndentSize] = useState(4);
const [expiry, setExpiry] = useState(SITE_CONFIG.expiryOptions[0].value);
const [allowDiscussions, setAllowDiscussions] = useState(false);
const [availableLanguages, setAvailableLanguages] = useState([]);
const [mounted, setMounted] = useState(false);
const editorRef = useRef(null);
const router = useRouter();
useEffect(() => {
setMounted(true);
loader.init().then((monaco) => {
const languages = monaco.languages.getLanguages();
const sorted = languages.map(l => l.id).sort((a, b) => a.localeCompare(b));
setAvailableLanguages(sorted);
});
}, []);
const handleIndentSizeChange = (newSize) => {
const oldSize = indentSize;
setIndentSize(newSize);
if (editorRef.current && indentMode === 'spaces') {
const model = editorRef.current.getModel();
const currentCode = model.getValue();
const updatedLines = currentCode.split('\n').map(line => {
const match = line.match(/^ +/);
if (match) {
const spaceCount = match[0].length;
const newSpaceCount = Math.round((spaceCount / oldSize) * newSize);
return ' '.repeat(newSpaceCount) + line.slice(spaceCount);
}
return line;
});
model.setValue(updatedLines.join('\n'));
}
};
const save = async () => {
if (!code.trim()) return alert("Paste cannot be empty!");
const deleteToken = nanoid(32);
const res = await fetch('/api/pastes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: code,
language: lang,
filename,
expiry,
allowDiscussions,
deleteToken
})
});
const data = await res.json();
localStorage.setItem(`delete_token_${data.id}`, deleteToken);
router.push(`/paste/${data.id}`);
};
if (!mounted) return null;
return (
<div className="min-h-screen bg-zinc-950 text-zinc-200 p-8 font-sans">
<Head>
<title>{SITE_CONFIG.pageTitle}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="max-w-6xl mx-auto">
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-3">
<img src="/logo.png" alt="DevBin Logo" className="h-12 w-auto" />
<div>
<h1 className="text-3xl font-bold tracking-tight text-white">DEVBIN</h1>
<p className="text-zinc-500 text-xs uppercase tracking-widest">Private Code Sharing</p>
</div>
</div>
<p className="text-zinc-500 text-sm font-mono">{SITE_CONFIG.editorTitle}</p>
</div>
<div className="flex flex-wrap items-center gap-4 mb-0 bg-zinc-900 p-2 rounded-t-lg border border-zinc-800 border-b-0">
<input
className="bg-zinc-950 border border-zinc-800 p-1.5 px-3 rounded text-sm focus:outline-none focus:border-blue-500 w-64 text-white font-mono"
placeholder="filename.txt"
value={filename}
onChange={(e) => setFilename(e.target.value)}
/>
<div className="flex items-center gap-4">
<select
className="bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer"
value={expiry}
onChange={(e) => setExpiry(e.target.value)}
>
{SITE_CONFIG.expiryOptions.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<label className="flex items-center gap-2 px-2 cursor-pointer hover:bg-zinc-800 rounded py-1 transition-colors">
<input
type="checkbox"
className="accent-blue-600"
checked={allowDiscussions}
onChange={(e) => setAllowDiscussions(e.target.checked)}
/>
<span className="text-xs font-semibold text-zinc-400 uppercase tracking-tight">Discussions</span>
</label>
</div>
<div className="flex-grow"></div>
<div className="flex items-center gap-2">
<select
className="bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer"
value={indentMode}
onChange={(e) => setIndentMode(e.target.value)}
>
<option value="spaces">Spaces</option>
<option value="tabs">Tabs</option>
</select>
<select
className={`bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer ${indentMode === 'tabs' ? 'opacity-50' : ''}`}
value={indentSize}
disabled={indentMode === 'tabs'}
onChange={(e) => handleIndentSizeChange(Number(e.target.value))}
>
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
</select>
<select
className="bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer capitalize"
value={lang}
onChange={e => setLang(e.target.value)}
>
{availableLanguages.map(id => <option key={id} value={id}>{id}</option>)}
</select>
</div>
</div>
<div className="border border-zinc-800 rounded-b-lg overflow-hidden shadow-2xl">
<Editor
height="70vh"
theme="vs-dark"
language={lang}
value={code}
onChange={setCode}
onMount={(ed) => { editorRef.current = ed }}
options={{
tabSize: indentSize,
insertSpaces: indentMode === 'spaces',
fontSize: 14,
minimap: { enabled: false },
padding: { top: 16 },
automaticLayout: true
}}
/>
</div>
<div className="flex justify-end mt-4">
<button onClick={save} className="bg-blue-600 px-8 py-2.5 rounded font-bold hover:bg-blue-700 text-white shadow-lg transition-all active:scale-95">
Create Paste
</button>
</div>
</div>
</div>
);
}