first commit

This commit is contained in:
M1ngdaXie
2025-12-29 16:29:24 -08:00
commit 37d89b13b9
48 changed files with 7334 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
import { useNavigate, useParams } from "react-router-dom";
import Editor from "../components/Editor/Editor.tsx";
import UserList from "../components/Presence/UserList.tsx";
import { useYjsDocument } from "../hooks/useYjsDocument.ts";
const EditorPage = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { providers, synced } = useYjsDocument(id!);
if (!providers) {
return <div className="loading">Connecting...</div>;
}
return (
<div className="editor-page">
<div className="page-header">
<button onClick={() => navigate("/")}> Back to Home</button>
<div className="sync-status">
{synced ? "✓ Synced" : "⟳ Syncing..."}
</div>
</div>
<div className="page-content">
<div className="main-area">
<Editor providers={providers} />
</div>
<div className="sidebar">
<UserList awareness={providers.awareness} />
</div>
</div>
</div>
);
};
export default EditorPage;

112
frontend/src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,112 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import type { DocumentType } from "../api/document.ts";
import { documentsApi } from "../api/document.ts";
import PixelIcon from "../components/PixelIcon/PixelIcon.tsx";
import FloatingGem from "../components/PixelSprites/FloatingGem.tsx";
const Home = () => {
const [documents, setDocuments] = useState<DocumentType[]>([]);
const [loading, setLoading] = useState(true);
const [creating, setCreating] = useState(false);
const navigate = useNavigate();
const loadDocuments = async () => {
try {
const { documents } = await documentsApi.list();
setDocuments(documents);
} catch (error) {
console.error("Failed to load documents:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadDocuments();
}, []);
const createDocument = async (type: "editor" | "kanban") => {
setCreating(true);
try {
const doc = await documentsApi.create({
name: `New ${type === "editor" ? "Document" : "Kanban Board"}`,
type,
});
navigate(`/${type}/${doc.id}`);
} catch (error) {
console.error("Failed to create document:", error);
} finally {
setCreating(false);
}
};
const deleteDocument = async (id: string) => {
if (!confirm("Are you sure you want to delete this document?")) return;
try {
await documentsApi.delete(id);
loadDocuments();
} catch (error) {
console.error("Failed to delete document:", error);
}
};
if (loading) {
return <div className="loading">Loading documents...</div>;
}
return (
<div className="home-page" style={{ position: 'relative' }}>
<FloatingGem position={{ top: '20px', right: '40px' }} delay={0} size={40} />
<FloatingGem position={{ top: '60px', left: '60px' }} delay={1.5} size={32} />
<FloatingGem position={{ bottom: '100px', right: '100px' }} delay={3} size={36} />
<h1>My Documents</h1>
<div className="create-buttons">
<button onClick={() => createDocument("editor")} disabled={creating}>
<PixelIcon name="plus" size={20} />
<span style={{ marginLeft: '8px' }}>New Text Document</span>
</button>
<button onClick={() => createDocument("kanban")} disabled={creating}>
<PixelIcon name="plus" size={20} />
<span style={{ marginLeft: '8px' }}>New Kanban Board</span>
</button>
</div>
<div className="document-list">
{documents.length === 0 ? (
<p>No documents yet. Create one to get started!</p>
) : (
documents.map((doc) => (
<div key={doc.id} className="document-card">
<div className="doc-info">
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
<PixelIcon name={doc.type === 'editor' ? 'document' : 'kanban'} size={24} color="var(--pixel-purple-bright)" />
<h3 style={{ margin: 0 }}>{doc.name}</h3>
</div>
<p className="doc-type">{doc.type}</p>
<p className="doc-date">
Created: {new Date(doc.created_at).toLocaleDateString()}
</p>
</div>
<div className="doc-actions">
<button onClick={() => navigate(`/${doc.type}/${doc.id}`)} aria-label={`Open ${doc.name}`}>
<PixelIcon name="back-arrow" size={16} style={{ transform: 'rotate(180deg)' }} />
<span style={{ marginLeft: '6px' }}>Open</span>
</button>
<button onClick={() => deleteDocument(doc.id)} aria-label={`Delete ${doc.name}`}>
<PixelIcon name="trash" size={16} />
<span style={{ marginLeft: '6px' }}>Delete</span>
</button>
</div>
</div>
))
)}
</div>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,36 @@
import { useNavigate, useParams } from "react-router-dom";
import KanbanBoard from "../components/Kanban/KanbanBoard.tsx";
import UserList from "../components/Presence/UserList.tsx";
import { useYjsDocument } from "../hooks/useYjsDocument.ts";
const KanbanPage = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { providers, synced } = useYjsDocument(id!);
if (!providers) {
return <div className="loading">Connecting...</div>;
}
return (
<div className="kanban-page">
<div className="page-header">
<button onClick={() => navigate("/")}> Back to Home</button>
<div className="sync-status">
{synced ? "✓ Synced" : "⟳ Syncing..."}
</div>
</div>
<div className="page-content">
<div className="main-area">
<KanbanBoard providers={providers} />
</div>
<div className="sidebar">
<UserList awareness={providers.awareness} />
</div>
</div>
</div>
);
};
export default KanbanPage;