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
170 changes: 107 additions & 63 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,94 +25,99 @@
</template>

<script>
import { ref, onMounted, computed } from 'vue'
import { api } from './api'
import { useAuth } from './composables/useAuth'
import { useSidebar } from './composables/useSidebar'
import AppSidebar from './components/AppSidebar.vue'
import ProfileDetailsModal from './components/ProfileDetailsModal.vue'
import TasksModal from './components/TasksModal.vue'
import { ref, onMounted, computed } from "vue";
import { api } from "./api";
import { useAuth } from "./composables/useAuth";
import { useSidebar } from "./composables/useSidebar";
import { useDarkMode } from "./composables/useDarkMode";
import AppSidebar from "./components/AppSidebar.vue";
import ProfileDetailsModal from "./components/ProfileDetailsModal.vue";
import TasksModal from "./components/TasksModal.vue";

export default {
name: 'App',
name: "App",
components: {
AppSidebar,
ProfileDetailsModal,
TasksModal
TasksModal,
},
setup() {
const { currentUser } = useAuth()
const { isCollapsed } = useSidebar()
const showProfileDetails = ref(false)
const showTasks = ref(false)
const apiTasks = ref([])
const { currentUser } = useAuth();
const { isCollapsed } = useSidebar();
useDarkMode(); // initializes dark class on <html>
const showProfileDetails = ref(false);
const showTasks = ref(false);
const apiTasks = ref([]);

// Merge mock tasks from currentUser with API tasks
const tasks = computed(() => {
return [...currentUser.value.tasks, ...apiTasks.value]
})
return [...currentUser.value.tasks, ...apiTasks.value];
});

const loadTasks = async () => {
try {
apiTasks.value = await api.getTasks()
apiTasks.value = await api.getTasks();
} catch (err) {
console.error('Failed to load tasks:', err)
console.error("Failed to load tasks:", err);
}
}
};

const addTask = async (taskData) => {
try {
const newTask = await api.createTask(taskData)
const newTask = await api.createTask(taskData);
// Add new task to the beginning of the array
apiTasks.value.unshift(newTask)
apiTasks.value.unshift(newTask);
} catch (err) {
console.error('Failed to add task:', err)
console.error("Failed to add task:", err);
}
}
};

const deleteTask = async (taskId) => {
try {
// Check if it's a mock task (from currentUser)
const isMockTask = currentUser.value.tasks.some(t => t.id === taskId)
const isMockTask = currentUser.value.tasks.some((t) => t.id === taskId);

if (isMockTask) {
// Remove from mock tasks
const index = currentUser.value.tasks.findIndex(t => t.id === taskId)
const index = currentUser.value.tasks.findIndex(
(t) => t.id === taskId,
);
if (index !== -1) {
currentUser.value.tasks.splice(index, 1)
currentUser.value.tasks.splice(index, 1);
}
} else {
// Remove from API tasks
await api.deleteTask(taskId)
apiTasks.value = apiTasks.value.filter(t => t.id !== taskId)
await api.deleteTask(taskId);
apiTasks.value = apiTasks.value.filter((t) => t.id !== taskId);
}
} catch (err) {
console.error('Failed to delete task:', err)
console.error("Failed to delete task:", err);
}
}
};

const toggleTask = async (taskId) => {
try {
// Check if it's a mock task (from currentUser)
const mockTask = currentUser.value.tasks.find(t => t.id === taskId)
const mockTask = currentUser.value.tasks.find((t) => t.id === taskId);

if (mockTask) {
// Toggle mock task status
mockTask.status = mockTask.status === 'pending' ? 'completed' : 'pending'
mockTask.status =
mockTask.status === "pending" ? "completed" : "pending";
} else {
// Toggle API task
const updatedTask = await api.toggleTask(taskId)
const index = apiTasks.value.findIndex(t => t.id === taskId)
const updatedTask = await api.toggleTask(taskId);
const index = apiTasks.value.findIndex((t) => t.id === taskId);
if (index !== -1) {
apiTasks.value[index] = updatedTask
apiTasks.value[index] = updatedTask;
}
}
} catch (err) {
console.error('Failed to toggle task:', err)
console.error("Failed to toggle task:", err);
}
}
};

onMounted(loadTasks)
onMounted(loadTasks);

return {
showProfileDetails,
Expand All @@ -121,22 +126,61 @@ export default {
addTask,
deleteTask,
toggleTask,
isCollapsed
}
}
}
isCollapsed,
};
},
};
</script>

<style>
:root {
--bg-page: #f8fafc;
--bg-card: #ffffff;
--bg-card-hover: #f8fafc;
--border-card: #e2e8f0;
--border-card-hover: #cbd5e1;
--text-primary: #0f172a;
--text-secondary: #64748b;
--text-body: #334155;
--table-head-bg: #f8fafc;
--table-border: #e2e8f0;
--table-row-border: #f1f5f9;
--table-row-hover: #f8fafc;
}

html.dark {
--bg-page: #0f172a;
--bg-card: #1e293b;
--bg-card-hover: #243349;
--border-card: rgba(255, 255, 255, 0.08);
--border-card-hover: rgba(255, 255, 255, 0.15);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-body: #cbd5e1;
--table-head-bg: #1e293b;
--table-border: rgba(255, 255, 255, 0.08);
--table-row-border: rgba(255, 255, 255, 0.04);
--table-row-hover: #243349;
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #f8fafc;
font-family:
"Inter",
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
sans-serif;
background: var(--bg-page);
color: #1e293b;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
Expand Down Expand Up @@ -168,13 +212,13 @@ body {
.page-header h2 {
font-size: 1.875rem;
font-weight: 700;
color: #0f172a;
color: var(--text-primary);
margin-bottom: 0.375rem;
letter-spacing: -0.025em;
}

.page-header p {
color: #64748b;
color: var(--text-secondary);
font-size: 0.938rem;
}

Expand All @@ -186,20 +230,20 @@ body {
}

.stat-card {
background: white;
background: var(--bg-card);
padding: 1.25rem;
border-radius: 10px;
border: 1px solid #e2e8f0;
border: 1px solid var(--border-card);
transition: all 0.2s ease;
}

.stat-card:hover {
border-color: #cbd5e1;
border-color: var(--border-card-hover);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}

.stat-label {
color: #64748b;
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
Expand All @@ -210,7 +254,7 @@ body {
.stat-value {
font-size: 2.25rem;
font-weight: 700;
color: #0f172a;
color: var(--text-primary);
letter-spacing: -0.025em;
}

Expand All @@ -231,10 +275,10 @@ body {
}

.card {
background: white;
background: var(--bg-card);
border-radius: 10px;
padding: 1.25rem;
border: 1px solid #e2e8f0;
border: 1px solid var(--border-card);
margin-bottom: 1.25rem;
}

Expand All @@ -244,13 +288,13 @@ body {
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.875rem;
border-bottom: 1px solid #e2e8f0;
border-bottom: 1px solid var(--border-card);
}

.card-title {
font-size: 1.125rem;
font-weight: 700;
color: #0f172a;
color: var(--text-primary);
letter-spacing: -0.025em;
}

Expand All @@ -264,25 +308,25 @@ table {
}

thead {
background: #f8fafc;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
background: var(--table-head-bg);
border-top: 1px solid var(--table-border);
border-bottom: 1px solid var(--table-border);
}

th {
text-align: left;
padding: 0.5rem 0.75rem;
font-weight: 600;
color: #475569;
color: var(--text-secondary);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}

td {
padding: 0.5rem 0.75rem;
border-top: 1px solid #f1f5f9;
color: #334155;
border-top: 1px solid var(--table-row-border);
color: var(--text-body);
font-size: 0.875rem;
}

Expand All @@ -291,7 +335,7 @@ tbody tr {
}

tbody tr:hover {
background: #f8fafc;
background: var(--table-row-hover);
}

.badge {
Expand Down Expand Up @@ -357,7 +401,7 @@ tbody tr:hover {
.loading {
text-align: center;
padding: 3rem;
color: #64748b;
color: var(--text-secondary);
font-size: 0.938rem;
}

Expand Down
Loading
Loading