feature: Add projects homepage and support for adding new projects

This commit is contained in:
Keith Solomon
2026-04-05 16:58:51 -05:00
parent 239a7eff64
commit 6d923b98b9
10 changed files with 492 additions and 38 deletions
+59 -24
View File
@@ -1,9 +1,67 @@
const initialStateNode = document.getElementById('initial-state');
const appRoot = document.getElementById('app');
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
const projectCreateForm = document.getElementById('project-create-form');
const fetchJson = async (url, options = {}) => {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
...(options.headers || {}),
},
...options,
});
const payload = await response.json();
if (!response.ok || payload.success === false) {
throw new Error(payload.error || 'Request failed');
}
return payload;
};
const handleError = (error) => {
window.alert(error.message || 'Something went wrong.');
};
if (projectCreateForm) {
projectCreateForm.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(projectCreateForm);
const submitButton = projectCreateForm.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.disabled = true;
}
try {
const payload = await fetchJson('/api/create-project.php', {
method: 'POST',
body: JSON.stringify({
title: formData.get('title'),
slug: formData.get('slug'),
body: formData.get('body'),
}),
});
window.location.assign(
payload.state.projectUrl || `/?project=${encodeURIComponent(payload.state.project.id)}`
);
} catch (error) {
handleError(error);
if (submitButton) {
submitButton.disabled = false;
}
}
});
}
if (initialStateNode && appRoot && initialStateNode.textContent !== 'null') {
const state = JSON.parse(initialStateNode.textContent);
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
const boardColumnsEl = document.getElementById('board-columns');
const trashColumnsEl = document.getElementById('trash-columns');
const trashDropzoneEl = document.getElementById('trash-dropzone');
@@ -39,25 +97,6 @@ if (initialStateNode && appRoot && initialStateNode.textContent !== 'null') {
const markdownPreview = (value = '') => escapeHtml(value).replaceAll('\n', '<br>');
const fetchJson = async (url, options = {}) => {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
...(options.headers || {}),
},
...options,
});
const payload = await response.json();
if (!response.ok || payload.success === false) {
throw new Error(payload.error || 'Request failed');
}
return payload;
};
const updateState = (nextState) => {
state.project = nextState.project;
state.board = nextState.board;
@@ -394,10 +433,6 @@ if (initialStateNode && appRoot && initialStateNode.textContent !== 'null') {
noteDialog.showModal();
};
const handleError = (error) => {
window.alert(error.message || 'Something went wrong.');
};
const reloadBoard = async () => {
const response = await fetch(`/api/board-state.php?project=${encodeURIComponent(projectId)}`);
updateState(await response.json());