diff --git a/carry-your-live/.dockerignore b/carry-your-live/.dockerignore
new file mode 100644
index 0000000..b230635
--- /dev/null
+++ b/carry-your-live/.dockerignore
@@ -0,0 +1,10 @@
+node_modules
+dist
+.expo
+.git
+*.log
+.DS_Store
+android
+ios
+coverage
+*.local
\ No newline at end of file
diff --git a/carry-your-live/Dockerfile b/carry-your-live/Dockerfile
new file mode 100644
index 0000000..edc3618
--- /dev/null
+++ b/carry-your-live/Dockerfile
@@ -0,0 +1,18 @@
+FROM node:20-alpine AS builder
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm ci
+
+COPY . .
+RUN npx expo export -p web
+
+FROM nginx:alpine AS runner
+
+COPY --from=builder /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 8081
+
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/carry-your-live/app/(tabs)/calendar.tsx b/carry-your-live/app/(tabs)/calendar.tsx
index 4bc56bc..3c760ad 100644
--- a/carry-your-live/app/(tabs)/calendar.tsx
+++ b/carry-your-live/app/(tabs)/calendar.tsx
@@ -30,7 +30,7 @@ export default function CalendarScreen() {
const { theme } = useSettings();
const { collections } = useDatabase();
const categories = useCategories();
- const { modals, openTaskMenu } = useTaskModals();
+ const { modals, openTaskEdit } = useTaskModals();
const [visibleMonth, setVisibleMonth] = useState(() => new Date());
const [selectedDate, setSelectedDate] = useState(() => new Date());
@@ -301,7 +301,7 @@ export default function CalendarScreen() {
- openTaskMenu(task)} activeOpacity={0.7}>
+ openTaskEdit(task.id)} activeOpacity={0.7} accessibilityRole="button" accessibilityLabel={`Edit ${task.title}`}>
diff --git a/carry-your-live/src/components/PrioritySelector.tsx b/carry-your-live/src/components/PrioritySelector.tsx
index 4b0d417..4f2c7d3 100644
--- a/carry-your-live/src/components/PrioritySelector.tsx
+++ b/carry-your-live/src/components/PrioritySelector.tsx
@@ -23,7 +23,7 @@ export function PrioritySelector({ value, onChange }: PrioritySelectorProps) {
return (
Priority
-
+
{priorities.map((priority) => (
onChange(priority.value)}
activeOpacity={0.8}
+ accessibilityRole="radio"
+ accessibilityLabel={`${priority.label} priority`}
+ accessibilityState={{ selected: value === priority.value }}
>
setShowPicker(true)}
disabled={disabled}
activeOpacity={0.8}
+ accessibilityRole="button"
+ accessibilityLabel="Reminders"
+ accessibilityHint="Opens a list of reminder options"
+ accessibilityState={{ disabled }}
>
- applyProfile(profile)} activeOpacity={0.8}>
+ applyProfile(profile)} activeOpacity={0.8} accessibilityRole="button" accessibilityLabel={`Apply repeat profile ${profile.name}`}>
{profile.name}
handleDeleteProfile(profile.id, profile.name)}
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
+ accessibilityRole="button"
+ accessibilityLabel={`Delete repeat profile ${profile.name}`}
>
×
@@ -144,7 +148,7 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect
)}
-
+
{REPEAT_OPTIONS.map((option) => (
selectRepeat(option.value)}
activeOpacity={0.8}
+ accessibilityRole="radio"
+ accessibilityLabel={`Repeat ${option.label.replace('No Repeat', 'none')}`}
+ accessibilityState={{ selected: value === option.value }}
>
bumpInterval(-1)}
disabled={interval <= 1}
activeOpacity={0.7}
+ accessibilityRole="button"
+ accessibilityLabel="Decrease repeat interval"
+ accessibilityState={{ disabled: interval <= 1 }}
>
−
@@ -188,6 +198,9 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect
onPress={() => bumpInterval(1)}
disabled={interval >= 30}
activeOpacity={0.7}
+ accessibilityRole="button"
+ accessibilityLabel="Increase repeat interval"
+ accessibilityState={{ disabled: interval >= 30 }}
>
= 30 ? theme.textMuted : theme.text }]}>+
@@ -210,6 +223,9 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect
]}
onPress={() => toggleDay(day)}
activeOpacity={0.8}
+ accessibilityRole="checkbox"
+ accessibilityLabel={WEEKDAY_NAMES[day]}
+ accessibilityState={{ checked: days.includes(day) }}
>
void;
onDragEnd?: (absoluteY: number) => void;
depth?: number;
+ categoryColor?: string;
}
export const SubtaskItem = React.memo(function SubtaskItem({
@@ -33,7 +34,8 @@ export const SubtaskItem = React.memo(function SubtaskItem({
onDragStart,
onDragUpdate,
onDragEnd,
- depth = 1
+ depth = 1,
+ categoryColor
}: SubtaskItemProps) {
const { theme } = useSettings();
const [expanded, setExpanded] = useState(true);
@@ -59,6 +61,7 @@ export const SubtaskItem = React.memo(function SubtaskItem({
onDragStart={onDragStart}
onDragUpdate={onDragUpdate}
onDragEnd={onDragEnd}
+ categoryColor={categoryColor}
/>
{hasChildren && expanded && (
@@ -81,6 +84,7 @@ export const SubtaskItem = React.memo(function SubtaskItem({
onDragUpdate={onDragUpdate}
onDragEnd={onDragEnd}
depth={depth + 1}
+ categoryColor={categoryColor}
/>
))}
diff --git a/carry-your-live/src/components/TaskItem.tsx b/carry-your-live/src/components/TaskItem.tsx
index a0ca61b..7a42d5d 100644
--- a/carry-your-live/src/components/TaskItem.tsx
+++ b/carry-your-live/src/components/TaskItem.tsx
@@ -41,9 +41,10 @@ interface TaskItemProps {
expanded?: boolean;
indented?: boolean;
depth?: number;
+ categoryColor?: string;
}
-export const TaskItem = React.memo(function TaskItem({ task, onToggle, onDelete, onPress, onLongPress, onMenuOpen, selected, selectionMode, completedSection, draggable, hovered, onDragStart, onDragUpdate, onDragEnd, onReorderStart, onReorderUpdate, onReorderEnd, expanded, indented, depth = 0 }: TaskItemProps) {
+export const TaskItem = React.memo(function TaskItem({ task, onToggle, onDelete, onPress, onLongPress, onMenuOpen, selected, selectionMode, completedSection, draggable, hovered, onDragStart, onDragUpdate, onDragEnd, onReorderStart, onReorderUpdate, onReorderEnd, expanded, indented, depth = 0, categoryColor }: TaskItemProps) {
const { theme } = useSettings();
const [opacityAnim] = React.useState(new Animated.Value(task.completed ? 0.5 : 1));
const [dragTranslateX] = React.useState(new Animated.Value(0));
@@ -95,7 +96,6 @@ export const TaskItem = React.memo(function TaskItem({ task, onToggle, onDelete,
Gesture.Pan()
.activateAfterLongPress(400)
.minDistance(2)
- .maxDistance(12)
.runOnJS(true)
.onStart(() => {
setDragging(true);
@@ -221,32 +221,25 @@ const renderLeftActions = (progress: Animated.AnimatedInterpolation) =>
onLongPress={onLongPress}
delayLongPress={350}
activeOpacity={0.8}
+ accessibilityRole={selectionMode ? 'checkbox' : 'button'}
+ accessibilityLabel={selectionMode ? `Select ${task.title}` : task.title}
+ accessibilityState={selectionMode ? { checked: selected } : { expanded }}
+ accessibilityHint={selectionMode ? undefined : 'Expands the task to show subtasks'}
>
- {}}
- onPressOut={() => {}}
- >
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {categoryColor ? (
+
+ ) : null}
+
{task.completed ? (
@@ -369,6 +362,10 @@ const renderLeftActions = (progress: Animated.AnimatedInterpolation) =>
style={styles.menuButton}
onPress={onMenuOpen}
activeOpacity={0.7}
+ accessibilityRole="button"
+ accessibilityLabel={`Edit ${task.title}`}
+ accessibilityHint="Opens the task editor"
+ hitSlop={8}
>
@@ -477,6 +474,18 @@ const styles = StyleSheet.create({
justifyContent: 'center',
marginRight: 8,
},
+ categoryDotSlot: {
+ width: 28,
+ height: 28,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginRight: 8,
+ },
+ categoryDot: {
+ width: 10,
+ height: 10,
+ borderRadius: 5,
+ },
title: {
fontSize: 17,
fontWeight: '500',
diff --git a/carry-your-live/src/components/TaskList.tsx b/carry-your-live/src/components/TaskList.tsx
index b36a5c0..652af58 100644
--- a/carry-your-live/src/components/TaskList.tsx
+++ b/carry-your-live/src/components/TaskList.tsx
@@ -1,6 +1,7 @@
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
import { View, Text, StyleSheet, Animated, RefreshControl, Alert, TouchableOpacity } from 'react-native';
import { useTasks } from '@/hooks/useTasks';
+import { useCategories } from '@/hooks/useDatabase';
import { useTaskModals } from '@/hooks/useTaskModals';
import { TaskItem } from './TaskItem';
import { SubtaskItem } from './SubtaskItem';
@@ -38,17 +39,24 @@ const DropIndicator = ({ theme }: { theme: any }) => (
);
export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProps) {
- const { theme, sortBy } = useSettings();
+ const { theme, sortBy, todoAheadDays } = useSettings();
- const { tasks, loading } = useTasks(categoryId, false);
- const { tasks: completedTasks } = useTasks(categoryId, true);
+ const { tasks, loading } = useTasks(categoryId, 'all', todoAheadDays);
+
+ const categories = useCategories();
+ const categoryColors = useMemo(() => {
+ const map = new Map();
+ for (const c of categories) {
+ map.set(c.id, c.color);
+ }
+ return map;
+ }, [categories]);
const [refreshing, setRefreshing] = useState(false);
const [selectionMode, setSelectionMode] = useState(false);
const [selectedIds, setSelectedIds] = useState>(new Set());
const [hoverTaskId, setHoverTaskId] = useState(null);
const [expandedTasks, setExpandedTasks] = useState>(new Set());
- const [completedShown, setCompletedShown] = useState(false);
const [subtasksMap, setSubtasksMap] = useState
@@ -349,79 +355,34 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp
toggleExpand,
toggleSelect,
enterSelection,
- openTaskMenu,
+ openTaskEdit,
handleSubtaskToggle,
openSubtaskDelete,
openSubtaskEdit,
- openSubtaskMenu,
handleDragStart,
handleDragUpdate,
handleDragEnd,
handleSubtaskDragStart,
handleSubtaskDragUpdate,
handleSubtaskDragEnd,
+ categoryColors,
]
);
const listHeader = useMemo(() => {
- if (sortedTasks.length > 0 || completedTasks.length > 0) return null;
+ if (sortedTasks.length > 0) return null;
return (
No tasks yet
Tap + to add your first task
);
- }, [sortedTasks.length, completedTasks.length, theme.textSecondary, theme.textMuted]);
+ }, [sortedTasks.length, theme.textSecondary, theme.textMuted]);
const listFooter = useMemo(() => {
- const footerContent = completedTasks.length === 0 ? null : (
- handleToggle(task.id, false)}
- onDelete={openTaskDelete}
- onMenuOpen={openTaskMenu}
- onLongPress={(task) => enterSelection(task.id)}
- selectionMode={selectionMode}
- selectedIds={selectedIds}
- onSelect={toggleSelect}
- onFetchSubtasks={fetchSubtasks}
- subtasksMap={subtasksMap}
- onSubtaskToggle={handleSubtaskToggle}
- onSubtaskDelete={openSubtaskDelete}
- onSubtaskEdit={openSubtaskEdit}
- onSubtaskMenuOpen={openSubtaskMenu}
- />
- );
-
const showDropAtEnd = dropIndicator && dropIndicator.targetId === null;
-
- return (
-
- {footerContent}
- {showDropAtEnd && }
-
- );
- }, [
- completedTasks,
- completedShown,
- handleToggle,
- openTaskDelete,
- openTaskMenu,
- enterSelection,
- selectionMode,
- selectedIds,
- toggleSelect,
- fetchSubtasks,
- subtasksMap,
- handleSubtaskToggle,
- openSubtaskDelete,
- openSubtaskEdit,
- openSubtaskMenu,
- dropIndicator,
- theme,
- ]);
+ return showDropAtEnd ? : null;
+ }, [dropIndicator, theme]);
if (loading && !refreshing) {
return (
@@ -514,6 +475,7 @@ interface TaskRowProps {
onReorderUpdate: (absoluteY: number) => void;
onReorderEnd: (absoluteY: number, translationY: number) => void;
selectedIds: Set;
+ categoryColor?: string;
}
const TaskRow = React.memo(function TaskRow({
@@ -544,6 +506,7 @@ const TaskRow = React.memo(function TaskRow({
onReorderUpdate,
onReorderEnd,
selectedIds,
+ categoryColor,
}: TaskRowProps) {
const sortedSubtasks = useMemo(
() => subtasks.slice().sort((a, b) => a.order - b.order),
@@ -572,6 +535,7 @@ const TaskRow = React.memo(function TaskRow({
onReorderStart={onReorderStart}
onReorderUpdate={onReorderUpdate}
onReorderEnd={onReorderEnd}
+ categoryColor={categoryColor}
/>
{expanded && subtasks.length > 0 && (
@@ -589,6 +553,7 @@ const TaskRow = React.memo(function TaskRow({
onDragStart={() => onSubtaskDragStart(sub.id, task.id)}
onDragUpdate={onSubtaskDragUpdate}
onDragEnd={onSubtaskDragEnd}
+ categoryColor={categoryColor}
/>
))}
@@ -597,119 +562,6 @@ const TaskRow = React.memo(function TaskRow({
);
});
-interface CompletedSectionProps {
- tasks: TaskData[];
- shown: boolean;
- onShownChange: (shown: boolean) => void;
- onToggle: (task: TaskData) => void;
- onDelete: (task: TaskData) => void;
- onMenuOpen: (task: TaskData) => void;
- onLongPress: (task: TaskData) => void;
- selectionMode: boolean;
- selectedIds: Set;
- onSelect: (taskId: string) => void;
- onFetchSubtasks: (taskId: string) => Promise;
- subtasksMap: Map;
- onSubtaskToggle: (subtaskId: string, taskId: string) => void;
- onSubtaskDelete: (subtask: SubtaskData) => void;
- onSubtaskEdit: (subtaskId: string) => void;
- onSubtaskMenuOpen: (subtask: SubtaskData) => void;
-}
-
-const CompletedSection = React.memo(function CompletedSection({ tasks, shown, onShownChange, onToggle, onDelete, onMenuOpen, onLongPress, selectionMode, selectedIds, onSelect, onFetchSubtasks, subtasksMap, onSubtaskToggle, onSubtaskDelete, onSubtaskEdit, onSubtaskMenuOpen }: CompletedSectionProps) {
- const { theme } = useSettings();
- const [openTasks, setOpenTasks] = useState>(new Set(tasks.map((t) => t.id)));
- const fetchedRef = useRef>(new Set());
-
- useEffect(() => {
- setOpenTasks((prev) => {
- const next = new Set(prev);
- let changed = false;
- for (const t of tasks) {
- if (!next.has(t.id)) {
- next.add(t.id);
- changed = true;
- }
- }
- return changed ? next : prev;
- });
- for (const t of tasks) {
- if (!fetchedRef.current.has(t.id)) {
- fetchedRef.current.add(t.id);
- onFetchSubtasks(t.id);
- }
- }
- }, [tasks, onFetchSubtasks]);
-
- const toggleOpen = useCallback((taskId: string) => {
- setOpenTasks((prev) => {
- const next = new Set(prev);
- if (next.has(taskId)) {
- next.delete(taskId);
- } else {
- next.add(taskId);
- }
- return next;
- });
- }, []);
-
- return (
-
- onShownChange(!shown)}
- >
-
- Completed ({tasks.length})
-
-
- {shown ? 'Hide' : 'Show'}
-
-
- {shown && (
-
- {tasks.map((task) => {
- const isOpen = openTasks.has(task.id);
- const subtasks = (subtasksMap.get(task.id) ?? [])
- .slice()
- .sort((a, b) => a.order - b.order);
- return (
-
- onToggle(task)}
- onDelete={() => onDelete(task)}
- onPress={() => (selectionMode ? onSelect(task.id) : toggleOpen(task.id))}
- onLongPress={() => onLongPress(task)}
- onMenuOpen={() => onMenuOpen(task)}
- selected={selectedIds.has(task.id)}
- selectionMode={selectionMode}
- completedSection
- expanded={isOpen}
- />
- {isOpen && subtasks.length > 0 && (
-
- {subtasks.map((sub) => (
- onSubtaskToggle(sub.id, task.id)}
- onDelete={() => onSubtaskDelete(sub)}
- onPress={() => onSubtaskEdit(sub.id)}
- onMenuOpen={() => onSubtaskMenuOpen(sub)}
- />
- ))}
-
- )}
-
- );
- })}
-
- )}
-
- );
-});
-
const styles = StyleSheet.create({
container: {
flex: 1,
diff --git a/carry-your-live/src/components/TaskNameInput.tsx b/carry-your-live/src/components/TaskNameInput.tsx
index d9010db..668663f 100644
--- a/carry-your-live/src/components/TaskNameInput.tsx
+++ b/carry-your-live/src/components/TaskNameInput.tsx
@@ -25,6 +25,8 @@ export function TaskNameInput({ error, ...props }: TaskNameInputProps) {
placeholderTextColor={theme.textMuted}
maxLength={100}
autoCapitalize="sentences"
+ accessibilityLabel="Task name"
+ accessibilityHint="Required field. Enter a name for the task"
{...props}
/>
{error && {error}}
diff --git a/carry-your-live/src/components/TaskOverflowMenu.tsx b/carry-your-live/src/components/TaskOverflowMenu.tsx
index fc14deb..100ae0b 100644
--- a/carry-your-live/src/components/TaskOverflowMenu.tsx
+++ b/carry-your-live/src/components/TaskOverflowMenu.tsx
@@ -119,6 +119,8 @@ export function TaskOverflowMenu({
action.onPress();
}}
activeOpacity={0.7}
+ accessibilityRole="button"
+ accessibilityLabel={`${action.label}${action.destructive ? ' (dangerous)' : ''}`}
>
{action.icon}
diff --git a/carry-your-live/src/hooks/useTasks.tsx b/carry-your-live/src/hooks/useTasks.tsx
index 7f4065f..9e66113 100644
--- a/carry-your-live/src/hooks/useTasks.tsx
+++ b/carry-your-live/src/hooks/useTasks.tsx
@@ -58,11 +58,19 @@ export function useTasksInMonth(monthDate: Date) {
return { tasks, byDay, loading };
}
-export function useTasks(categoryId?: string, showCompleted = false) {
+export function useTasks(categoryId?: string, showCompleted: boolean | 'all' = false, maxAheadDays?: number) {
const { collections } = useDatabase();
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
+ const cutoff = useMemo(() => {
+ if (maxAheadDays === undefined) return null;
+ const d = new Date();
+ d.setHours(23, 59, 59, 999);
+ d.setDate(d.getDate() + maxAheadDays);
+ return d.getTime();
+ }, [maxAheadDays]);
+
useEffect(() => {
let mounted = true;
const conditions: any[] = [];
@@ -71,12 +79,16 @@ export function useTasks(categoryId?: string, showCompleted = false) {
conditions.push(Q.where('category_id', categoryId));
}
- if (showCompleted) {
+ if (showCompleted === true) {
conditions.push(Q.where('completed', true));
- } else {
+ } else if (showCompleted === false) {
conditions.push(Q.where('completed', false));
}
+ if (cutoff !== null) {
+ conditions.push(Q.where('due_date', Q.lte(cutoff)));
+ }
+
const query = conditions.length > 0
? collections.tasks.query(Q.and(...conditions))
: collections.tasks.query();
@@ -99,7 +111,7 @@ export function useTasks(categoryId?: string, showCompleted = false) {
mounted = false;
subscription.unsubscribe();
};
- }, [collections, categoryId, showCompleted]);
+ }, [collections, categoryId, showCompleted, cutoff]);
return { tasks, loading };
}
@@ -148,55 +160,3 @@ export function useTasksByDate(date: Date) {
return { tasks, loading };
}
-
-export function useTasksInMonth(month: Date) {
- const { collections } = useDatabase();
- const [tasks, setTasks] = useState([]);
- const [loading, setLoading] = useState(true);
-
- const range = useMemo(() => {
- const start = startOfMonth(month);
- start.setHours(0, 0, 0, 0);
- const end = endOfMonth(month);
- end.setHours(23, 59, 59, 999);
- return { start: start.getTime(), end: end.getTime() };
- }, [month]);
-
- useEffect(() => {
- let mounted = true;
- const subscription = collections.tasks
- .query(
- Q.where('due_date', Q.between(range.start, range.end)),
- Q.sortBy('due_date', 'asc')
- )
- .observe()
- .subscribe({
- next: (result) => {
- if (mounted) {
- setTasks(result);
- setLoading(false);
- }
- },
- error: () => {
- if (mounted) setLoading(false);
- },
- });
-
- return () => {
- mounted = false;
- subscription.unsubscribe();
- };
- }, [collections, range]);
-
- const byDay = useMemo(() => {
- const map: Record = {};
- for (const task of tasks) {
- const day = new Date(task.dueDate).getDate();
- if (!map[day]) map[day] = [];
- map[day].push(task);
- }
- return map;
- }, [tasks]);
-
- return { tasks, byDay, loading };
-}
diff --git a/carry-your-live/src/theme.tsx b/carry-your-live/src/theme.tsx
index 4bd2798..27c88eb 100644
--- a/carry-your-live/src/theme.tsx
+++ b/carry-your-live/src/theme.tsx
@@ -116,15 +116,20 @@ interface SettingsContextType {
setApiUrl: (value: string) => void;
accentColor: string;
setAccentColor: (value: string) => void;
+ todoAheadDays: number;
+ setTodoAheadDays: (value: number) => void;
theme: ThemeColors;
}
+export const DEFAULT_TODO_AHEAD_DAYS = 7;
+
const STORAGE_KEYS = {
notifications: 'settings:notifications',
defaultCategoryId: 'settings:defaultCategoryId',
sortBy: 'settings:sortBy',
reminderPreference: 'settings:reminderPreference',
accentColor: 'settings:accentColor',
+ todoAheadDays: 'settings:todoAheadDays',
};
const SettingsContext = createContext(null);
@@ -165,6 +170,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
);
const [apiUrl, setApiUrl] = useStoredSetting(API_URL_KEY, DEFAULT_API_BASE_URL);
const [accentColor, setAccentColor] = useStoredSetting(STORAGE_KEYS.accentColor, DEFAULT_ACCENT);
+ const [todoAheadDays, setTodoAheadDays] = useStoredSetting(STORAGE_KEYS.todoAheadDays, DEFAULT_TODO_AHEAD_DAYS);
const theme = useMemo(() => colors(accentColor), [accentColor]);
@@ -182,6 +188,8 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
setApiUrl,
accentColor,
setAccentColor,
+ todoAheadDays,
+ setTodoAheadDays,
theme,
}),
[
@@ -197,6 +205,8 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
setApiUrl,
accentColor,
setAccentColor,
+ todoAheadDays,
+ setTodoAheadDays,
theme,
]
);
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..ff2ac35
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,48 @@
+version: '3.8'
+
+services:
+ postgres:
+ image: postgres:16-alpine
+ container_name: carry-your-live-db
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_DB: carry_your_live
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+ backend:
+ build:
+ context: ./backend
+ target: prod
+ container_name: carry-your-live-api
+ depends_on:
+ postgres:
+ condition: service_healthy
+ environment:
+ DATABASE_URL: postgresql://postgres:postgres@postgres:5432/carry_your_live
+ JWT_SECRET: your-super-secret-jwt-key-change-in-production-min-32-chars
+ PORT: 3000
+ NODE_ENV: production
+ FRONTEND_URL: http://localhost:8081
+ ports:
+ - "3000:3000"
+
+ frontend:
+ build:
+ context: ./carry-your-live
+ container_name: carry-your-live-web
+ depends_on:
+ - backend
+ ports:
+ - "8081:8081"
+
+volumes:
+ postgres_data:
\ No newline at end of file
diff --git a/todo.md b/todo.md
index e0984d3..b74c35c 100644
--- a/todo.md
+++ b/todo.md
@@ -1,34 +1,34 @@
-add a circle bevor the tasks so they can be checked ✓
-
-change behavior on click ✓
-
-apply swipe gestures ✓
-
-
-test server stuff ✓
-
-tell the user the sync status
-
-commit the app
-
-build an android app and notify it for updates on the app
-
-add an all-day option to tasks ✓
-fix the calendar layout
-
-implemennt that just task for today and the past can be checked ✓
-
-
-implement stats
-
-
-implemnt an android widget for quickadd tasks
-
-
-fix spacing for todo cards more space for todos
-
-
-fix edit todo form layout so it fitts nicely on phone screen
-
-fix buggy scroll for time selection
-
+- [x] Tasks should be renamed to ToDo
+- [x] top of ToDo page should say ToDo instead of TODO
+- [ ] Calendar rework
+- [x] when task with subtasks marked as done it hides the whole task and the subtask are gone and can't be seen anymore (subtask should be visible after completion)
+- [x] when dragging task to become subtask user should hold the task and not tap
+ because now it's very hard to scroll through tasks without randomly creating subtasks
+- [ ] when creating new task there is a huge gap between the create task box and the keyboard
+- [ ] after creating task keyboard should automatically be hidden again
+ now it's hard to get rid of the keyboard without clicking on other tasks
+- [ ] ToDo page button for category: hitbox very weird hard to click
+ when clicked should be framed in accent color and not get bigger like the all
+ category now
+- [ ] category all button doesn't have the correct spacing between the dot and the text
+- [ ] no way to tell what category the task has -> should be a colored dot in the left of the task box replacing the nine random dots that don't anything
+- [ ] task from the calendar page should only show up in the ToDo page if it is that day or x days in advance (x can be set in settings)
+- [ ] clicking the three dots should open the "add new task" window instead of the window with the limited options it opens currently
+- [ ] swiping task for deleting or completing
+- [x] no category required by creating task
+- [ ] calendar task arrow not centered
+- [X] page names (up top) are not centered
+- [X] tick icon in the top left needs to be removed/ replaced/ given a function
+- [ ] settings -> categories -> new / change category window needs to be centered and fully visible
+ exit window button not visible or not existent
+ user should be able to click outside of the window to close it
+- [ ] pressing the go back button android:
+ in settings go to previous page task/ calendar/ stats
+ in other (task/ calendar/ stats) stay on that page
+ at the moment calendar and stats go to tasks
+- [ ] ToDo -> select category -> scroll to the right most -> button to go to categories in settings
+- [x] accent color should be changeable
+- [ ] bottom page buttons need to be reworked looking ass currently.
+- [ ] add a task button should say "Add a Task" and (the button to add have an arrow pointing upwards instead of a cross) could look better
+- [ ] space between add task button and bottom bar should be zero of the task below should be blurred currently task visible between the two looks weird
+- [ ] Stats page rework (when time is right)
\ No newline at end of file