diff --git a/public/style.css b/public/css/style.css
similarity index 100%
rename from public/style.css
rename to public/css/style.css
diff --git a/public/index.html b/public/index.html
index aea40dd..4e99742 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,16 +1,12 @@
-
+
Fredriks todos
-
-
-
-
-
-
+
+
@@ -69,6 +65,10 @@
-
+
+
+
+
+
diff --git a/public/js/auth.js b/public/js/auth.js
new file mode 100644
index 0000000..28d1062
--- /dev/null
+++ b/public/js/auth.js
@@ -0,0 +1,25 @@
+export function checkSession() {
+ return fetch('/check-session')
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.json();
+ });
+}
+
+export function login(username, password) {
+ return fetch('/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Basic ' + btoa(username + ':' + password)
+ }
+ });
+}
+
+export function logout() {
+ return fetch('/logout', {
+ method: 'POST'
+ });
+}
\ No newline at end of file
diff --git a/public/js/main.js b/public/js/main.js
new file mode 100644
index 0000000..4f7bab4
--- /dev/null
+++ b/public/js/main.js
@@ -0,0 +1,171 @@
+import { checkSession, login, logout } from './auth.js';
+import { saveTask } from './tasks.js';
+import { saveTags, loadTags } from './tags.js';
+
+document.addEventListener('DOMContentLoaded', function() {
+ const loginForm = document.getElementById('loginForm');
+ const loginContainer = document.getElementById('loginContainer');
+ const appContainer = document.getElementById('appContainer');
+ const loginMessage = document.getElementById('loginMessage');
+ const logoutButton = document.getElementById('logoutButton');
+ const taskForm = document.getElementById('taskForm');
+ const sidenav = document.querySelector('.sidenav');
+
+ if (!loginForm || !loginContainer || !appContainer || !loginMessage || !logoutButton || !taskForm || !sidenav) {
+ console.error('One or more elements are missing in the DOM');
+ return;
+ }
+
+ // Initialize Materialize components
+ M.Sidenav.init(sidenav);
+
+ // Calculate tomorrow's date
+ const today = new Date();
+ const tomorrow = new Date(today);
+ tomorrow.setDate(today.getDate() + 1);
+
+ // Initialize datepicker with tomorrow as the default date
+ M.Datepicker.init(document.querySelectorAll('.datepicker'), {
+ format: 'yyyy-mm-dd',
+ defaultDate: tomorrow,
+ setDefaultDate: true,
+ firstDay: 1
+ });
+
+ // Initialize timepicker
+ M.Timepicker.init(document.querySelectorAll('.timepicker'), {
+ twelveHour: false // Use 24-hour format
+ });
+
+ // Check if user is already logged in
+ checkSession()
+ .then(data => {
+ if (data.loggedIn) {
+ loginContainer.style.display = 'none';
+ appContainer.style.display = 'block';
+ loadTags().then(tags => {
+ localStorage.setItem('tags', JSON.stringify(tags));
+ const autocompleteData = {};
+ tags.forEach(tag => {
+ autocompleteData[tag] = null; // Materialize autocomplete requires a key-value pair
+ });
+
+ const tagsInput = document.getElementById('tags');
+ M.Autocomplete.init(tagsInput, {
+ data: autocompleteData,
+ onAutocomplete: function(selectedTag) {
+ const currentTags = tagsInput.value.split(',').map(tag => tag.trim()).filter(tag => tag);
+ if (!currentTags.includes(selectedTag)) {
+ currentTags.push(selectedTag);
+ tagsInput.value = currentTags.join(', ');
+ }
+ }
+ });
+ });
+ } else {
+ loginContainer.style.display = 'block';
+ appContainer.style.display = 'none';
+ }
+ })
+ .catch(error => {
+ console.error('Error checking session:', error);
+ loginMessage.textContent = 'Error checking session. Please try again later.';
+ });
+
+ loginForm.addEventListener('submit', function(e) {
+ e.preventDefault();
+
+ const username = document.getElementById('username').value;
+ const password = document.getElementById('password').value;
+
+ login(username, password)
+ .then(response => {
+ if (response.ok) {
+ sessionStorage.setItem('loggedIn', 'true');
+ loginContainer.style.display = 'none';
+ appContainer.style.display = 'block';
+ loadTags().then(tags => {
+ localStorage.setItem('tags', JSON.stringify(tags));
+ const autocompleteData = {};
+ tags.forEach(tag => {
+ autocompleteData[tag] = null; // Materialize autocomplete requires a key-value pair
+ });
+
+ const tagsInput = document.getElementById('tags');
+ M.Autocomplete.init(tagsInput, {
+ data: autocompleteData,
+ onAutocomplete: function(selectedTag) {
+ const currentTags = tagsInput.value.split(',').map(tag => tag.trim()).filter(tag => tag);
+ if (!currentTags.includes(selectedTag)) {
+ currentTags.push(selectedTag);
+ tagsInput.value = currentTags.join(', ');
+ }
+ }
+ });
+ });
+ } else {
+ loginMessage.textContent = 'Invalid username or password';
+ }
+ })
+ .catch(error => {
+ loginMessage.textContent = 'Error logging in';
+ });
+ });
+
+ logoutButton.addEventListener('click', function() {
+ logout()
+ .then(response => {
+ if (response.ok) {
+ sessionStorage.removeItem('loggedIn');
+ loginContainer.style.display = 'block';
+ appContainer.style.display = 'none';
+ }
+ })
+ .catch(error => {
+ console.error('Error logging out:', error);
+ });
+ });
+
+ taskForm.addEventListener('submit', async function(e) {
+ e.preventDefault();
+
+ // Get form values
+ const subject = document.getElementById('subject').value;
+ const description = document.getElementById('description').value;
+ const scheduled = document.getElementById('scheduled').value;
+ const time = document.getElementById('time').value;
+ const tagsInput = document.getElementById('tags').value;
+ const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag).join(':');
+
+ // Combine scheduled date and time if time is provided
+ const scheduledDateTime = time ? `${scheduled}T${time}:00` : scheduled;
+
+ // Structure data for Org mode
+ const taskData = {
+ subject: tags ? `${subject} :${tags}:` : subject,
+ description,
+ scheduled: scheduledDateTime
+ };
+
+ // Save tags to server
+ saveTags(tags.split(',').map(tag => tag.trim()).filter(tag => tag))
+ .then(() => {
+ loadTags(); // Force refresh tags after saving
+ });
+
+ // Save task to server or IndexedDB if offline
+ try {
+ const data = await saveTask(taskData);
+ document.getElementById('responseMessage').textContent = data.message;
+ taskForm.reset(); // Reset the form after saving the task
+ } catch (error) {
+ if (error.status === 401) {
+ sessionStorage.removeItem('loggedIn');
+ loginContainer.style.display = 'block';
+ appContainer.style.display = 'none';
+ } else {
+ document.getElementById('responseMessage').textContent = "Error saving task!";
+ }
+ }
+ });
+});
\ No newline at end of file
diff --git a/public/js/tags.js b/public/js/tags.js
new file mode 100644
index 0000000..8803b89
--- /dev/null
+++ b/public/js/tags.js
@@ -0,0 +1,14 @@
+export function saveTags(tags) {
+ return fetch('/save-tags', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ tags })
+ });
+}
+
+export function loadTags() {
+ return fetch('/get-tags')
+ .then(response => response.json());
+}
\ No newline at end of file
diff --git a/public/js/tasks.js b/public/js/tasks.js
new file mode 100644
index 0000000..141ad00
--- /dev/null
+++ b/public/js/tasks.js
@@ -0,0 +1,30 @@
+import { idb } from './utils.js';
+
+export async function saveTask(taskData) {
+ if (navigator.onLine) {
+ try {
+ const response = await fetch('/add-task', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(taskData)
+ });
+ return await response.json();
+ } catch (error) {
+ throw error;
+ }
+ } else {
+ try {
+ const db = await idb.openDB('org-todo-pwa', 1, {
+ upgrade(db) {
+ db.createObjectStore('tasks', { keyPath: 'id', autoIncrement: true });
+ }
+ });
+ await db.add('tasks', taskData);
+ return { message: "Task saved offline!" };
+ } catch (error) {
+ throw error;
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/js/utils.js b/public/js/utils.js
new file mode 100644
index 0000000..d7763e0
--- /dev/null
+++ b/public/js/utils.js
@@ -0,0 +1,16 @@
+export const idb = {
+ openDB(name, version, { upgrade }) {
+ return new Promise((resolve, reject) => {
+ const request = indexedDB.open(name, version);
+ request.onupgradeneeded = (event) => {
+ upgrade(request.result, event.oldVersion, event.newVersion, request.transaction);
+ };
+ request.onsuccess = () => {
+ resolve(request.result);
+ };
+ request.onerror = () => {
+ reject(request.error);
+ };
+ });
+ }
+};
\ No newline at end of file