diff --git a/README.md b/README.md index 88b5757b..2c4b03d5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@
+ image +

Timo

@@ -26,27 +28,117 @@ ## What Is Timo? + +image + +
+ Timo is a timebox-based to-do service that converts tasks into actionable, time-bound blocks β€” so you actually finish what you plan. Unlike traditional to-do lists, Timo asks you to estimate how long each task will take, then builds your day around those time blocks. As you use it, Timo learns your time patterns and helps you plan more realistically with AI-powered time recommendations. +
+ + ## Why We Built It +image + +
+ +
+ - **To-do lists aren't enough**: Writing tasks down feels productive, but without a time structure, most lists stay unfinished at end of day. - **Time estimates make plans real**: A task without a duration is just a wish. Timo pairs every task with a time block from the start. - **Feedback closes the loop**: The gap between planned and actual time is where most tools stop. Timo closes that loop by recording real execution time and feeding it back into future planning. ## Features -- **✏️ Todo + Time Estimate**: Add tasks with an expected duration and optional tags. The estimate becomes the foundation of your daily schedule. +image + +
+ +
+ +- **πŸ“ Todo + Time Estimate**: Enter what you need to do and how long you expect it to take β€” Timo turns it into a timebox that becomes part of your plan. + +
+ +
+ +image + +
+ +
+ +- **⏱️ Timer-Based Execution**: Start a timebox and a timer runs for exactly the duration you set, keeping you focused on getting it done. + +
+ +
+ +image + +
+ +
+ +- **πŸ“Š Timeline Tracking**: The actual time you spent is automatically recorded on the timeline as a time block, so you can see exactly when and how long you worked. + +
+ +
+ +image + +
+ +
+ +- **πŸ€– AI Feedback**: Timo compares your accumulated estimated vs. actual time data and recommends the most realistic duration for each task, giving you feedback to plan better. + +
+ +
+ +image + +
+ +
+ +- **▢️ Seamless Transition**: Complete a timebox and the next one starts right away, keeping your workflow moving without interruption. + +
+ +
+ +image + +
+ +
+ +- **πŸ“ˆ Daily Statistics**: See how much time you focused today, and compare estimated vs. actual time at a glance. + +
+ +
+ +image + +
+ +
-- **πŸ“¦ Automatic Timebox Scheduling**: Each task is automatically converted into a time block on your schedule β€” turning a flat to-do list into an executable plan. +- **🎯 Focus Mode**: Zoom into a single todo and work through it distraction-free. -- **⏱️ Timer-Based Execution**: Start a timebox and a timer begins. Pause, extend, or go full-screen to stay focused. When time is up, you get a notification. Actual time spent is recorded automatically. +
-- **πŸ€– AI Time Recommendations**: Timo learns how long similar tasks actually take you, then recommends more accurate time estimates β€” making future plans increasingly realistic. +
## Getting Started @@ -87,7 +179,7 @@ Timo is optimized for desktop use β€” designed for students and job seekers who - κΉ€λ―Όμ•„ + image
κΉ€λ―Όμ•„ @@ -96,7 +188,7 @@ Timo is optimized for desktop use β€” designed for students and job seekers who - κΉ€μœ λ―Ό + image
κΉ€μœ λ―Ό @@ -105,7 +197,7 @@ Timo is optimized for desktop use β€” designed for students and job seekers who - κΉ€μ •λ―Ό + image
κΉ€μ •λ―Ό @@ -114,7 +206,7 @@ Timo is optimized for desktop use β€” designed for students and job seekers who - μ΄ν˜œμ› + image
μ΄ν˜œμ› diff --git a/README_ko.md b/README_ko.md index 05c32b76..afe27409 100644 --- a/README_ko.md +++ b/README_ko.md @@ -1,4 +1,6 @@

+ image +

Timo

@@ -26,27 +28,115 @@ ## Timoλž€? +image + +
+ TimoλŠ” ν•  일을 μ‹€ν–‰ κ°€λŠ₯ν•œ μ‹œκ°„ λ‹¨μœ„(νƒ€μž„λ°•μŠ€)둜 λ³€ν™˜ν•΄μ£ΌλŠ” νˆ¬λ‘ μ„œλΉ„μŠ€μž…λ‹ˆλ‹€. λ‹¨μˆœνžˆ ν•  일 λͺ©λ‘μ„ λ‚˜μ—΄ν•˜λŠ” λŒ€μ‹ , 각 νƒœμŠ€ν¬μ— μ˜ˆμƒ μ†Œμš” μ‹œκ°„μ„ μž…λ ₯ν•˜λ©΄ ν•˜λ£¨ 일정이 μžλ™μœΌλ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€. 반볡 μ‚¬μš©μ„ 톡해 TimoλŠ” 개인의 μ‹œκ°„ μ‚¬μš© νŒ¨ν„΄μ„ ν•™μŠ΅ν•˜κ³ , AI 기반 μ‹œκ°„ μΆ”μ²œμœΌλ‘œ 점점 더 ν˜„μ‹€μ μΈ κ³„νš μˆ˜λ¦½μ„ λ•μŠ΅λ‹ˆλ‹€. +
+ ## μ™œ λ§Œλ“€μ—ˆλ‚˜μš”? +image + +
+ +
+ - **νˆ¬λ‘ λ¦¬μŠ€νŠΈλ§ŒμœΌλ‘œλŠ” λΆ€μ‘±ν•©λ‹ˆλ‹€**: ν•  일을 μ λŠ” κ²ƒλ§ŒμœΌλ‘œλŠ” μ‹€μ œλ‘œ μ™„μˆ˜ν•˜κΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€. μ‹œκ°„ ꡬ쑰 없이 μž‘μ„±λœ λͺ©λ‘μ€ λŒ€λΆ€λΆ„ κ·Έλ‚  λλ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. - **μ‹œκ°„ 좔정이 κ³„νšμ„ μ‹€ν–‰ κ°€λŠ₯ν•˜κ²Œ λ§Œλ“­λ‹ˆλ‹€**: μ†Œμš” μ‹œκ°„ μ—†λŠ” νƒœμŠ€ν¬λŠ” λ°”λžŒμΌ λΏμž…λ‹ˆλ‹€. TimoλŠ” λͺ¨λ“  νƒœμŠ€ν¬μ— μ‹œκ°„μ„ λΆ™μ—¬ ꡬ체적인 κ³„νšμœΌλ‘œ λ§Œλ“­λ‹ˆλ‹€. - **μ‹€ν–‰κ³Ό ν”Όλ“œλ°±κΉŒμ§€ μ—°κ²°ν•©λ‹ˆλ‹€**: κ³„νšν•œ μ‹œκ°„κ³Ό μ‹€μ œ μˆ˜ν–‰ μ‹œκ°„μ˜ 차이λ₯Ό μΆ”μ ν•˜κ³ , 이λ₯Ό λ‹€μŒ κ³„νšμ— λ°˜μ˜ν•΄ 점점 더 ν˜„μ‹€μ μΈ 일정을 λ§Œλ“€μ–΄ κ°‘λ‹ˆλ‹€. ## μ£Όμš” κΈ°λŠ₯ -- **✏️ νˆ¬λ‘ + μ˜ˆμƒ μ†Œμš” μ‹œκ°„ μž…λ ₯**: νƒœμŠ€ν¬λ₯Ό μž…λ ₯ν•  λ•Œ μ˜ˆμƒ μ†Œμš” μ‹œκ°„κ³Ό νƒœκ·Έλ₯Ό ν•¨κ»˜ μ„€μ •ν•©λ‹ˆλ‹€. 이 정보가 ν•˜λ£¨ μΌμ •μ˜ 기반이 λ©λ‹ˆλ‹€. +image + +
+ +
+ +- **πŸ“ νˆ¬λ‘ + μ˜ˆμƒ μ†Œμš” μ‹œκ°„ μž…λ ₯**: ν•΄μ•Ό ν•  일과 μ˜ˆμƒ μ†Œμš” μ‹œκ°„μ„ μž…λ ₯ν•˜λ©΄ νƒ€μž„λ°•μŠ€κ°€ μƒμ„±λ˜μ–΄ κ³„νšμ— λ°˜μ˜λ©λ‹ˆλ‹€. + +
+ +
+ +image + +
+ +
+ +- **⏱️ 타이머 μ‹€ν–‰**: νƒ€μž„λ°•μŠ€λ₯Ό μ‹€ν–‰ν•˜λ©΄ μ„€μ •ν•œ μ‹œκ°„λ§ŒνΌ 타이머가 μž‘λ™ν•΄ κ³„νšμ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€. + +
+ +
+ +image + +
+ +
+ +- **πŸ“Š νƒ€μž„λΌμΈ 기둝**: μ‹€μ œλ‘œ μˆ˜ν–‰ν•œ μ‹œκ°„λ§ŒνΌ νƒ€μž„λΌμΈμ— λ°•μŠ€ ν˜•νƒœλ‘œ κΈ°λ‘λ©λ‹ˆλ‹€. + +
+ +
+ +image + +
+ +
+ +- **πŸ€– AI ν”Όλ“œλ°±**: κ·Έλ™μ•ˆμ˜ μ˜ˆμƒ μ†Œμš” μ‹œκ°„κ³Ό μ‹€μ œ μ†Œμš” μ‹œκ°„ 데이터λ₯Ό 비ꡐ해 AIκ°€ κ°€μž₯ μ ν•©ν•œ μ‹œκ°„μ„ μΆ”μ²œν•˜κ³  ν”Όλ“œλ°±μ„ μ œκ³΅ν•©λ‹ˆλ‹€. + +
+ +
+ +image + +
+ +
+ +- **▢️ λ‹€μŒ νƒ€μž„λ°•μŠ€λ‘œ μžμ—°μŠ€λŸ¬μš΄ μ „ν™˜**: ν•˜λ‚˜μ˜ νƒ€μž„λ°•μŠ€λ₯Ό μ™„λ£Œν•˜λ©΄ λ°”λ‘œ λ‹€μŒ νƒ€μž„λ°•μŠ€κ°€ μ‹œμž‘λ©λ‹ˆλ‹€. + +
+ +
+ +image + +
+ +
+ +- **πŸ“ˆ 톡계**: ν•˜λ£¨μ— μ§‘μ€‘ν•œ μ‹œκ°„κ³Ό μ˜ˆμƒΒ·μ‹€μ œ μ†Œμš” μ‹œκ°„μ˜ 차이λ₯Ό ν•œλˆˆμ— 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. + +
+ +
+ +image + +
+ +
-- **πŸ“¦ νƒ€μž„λ°•μŠ€ μžλ™ 생성**: μž…λ ₯된 νƒœμŠ€ν¬λŠ” μ‹€ν–‰ κ°€λŠ₯ν•œ μ‹œκ°„ λ‹¨μœ„μ˜ νƒ€μž„λ°•μŠ€λ‘œ μžλ™ λ³€ν™˜λ©λ‹ˆλ‹€. 평면적인 λ¦¬μŠ€νŠΈκ°€ μ‹€ν–‰ κ°€λŠ₯ν•œ μΌμ •μœΌλ‘œ λ°”λ€λ‹ˆλ‹€. +- **🎯 집쀑 λͺ¨λ“œ**: ν•˜λ‚˜μ˜ νˆ¬λ‘μ— μ§‘μ€‘ν•΄μ„œ μˆ˜ν–‰ν•  수 μžˆλŠ” λͺ¨λ“œμž…λ‹ˆλ‹€. -- **⏱️ 타이머 기반 μ‹€ν–‰**: νƒ€μž„λ°•μŠ€λ₯Ό μ‹œμž‘ν•˜λ©΄ 타이머가 μž‘λ™ν•©λ‹ˆλ‹€. μΌμ‹œμ •μ§€Β·μ—°μž₯·전체화면 κΈ°λŠ₯을 μ œκ³΅ν•˜λ©°, μ™„λ£Œ μ‹œ μ•Œλ¦Όμ„ 받을 수 μžˆμŠ΅λ‹ˆλ‹€. μ‹€μ œ μˆ˜ν–‰ μ‹œκ°„μ€ μžλ™μœΌλ‘œ κΈ°λ‘λ©λ‹ˆλ‹€. +
-- **πŸ€– AI μ†Œμš” μ‹œκ°„ μΆ”μ²œ**: 반볡 μ‚¬μš©μ„ 톡해 μœ μ‚¬ νƒœμŠ€ν¬μ˜ μ‹€μ œ μ†Œμš” μ‹œκ°„ νŒ¨ν„΄μ„ ν•™μŠ΅ν•˜κ³ , 더 μ •ν™•ν•œ μ‹œκ°„ μΆ”μ²œμ„ μ œκ³΅ν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 μ‚¬μš©ν• μˆ˜λ‘ 더 ν˜„μ‹€μ μΈ κ³„νšμ„ μ„ΈμšΈ 수 μžˆμŠ΅λ‹ˆλ‹€. +
## μ‹€ν–‰ 방법 @@ -87,7 +177,7 @@ TimoλŠ” λ°μŠ€ν¬νƒ‘ ν™˜κ²½μ—μ„œ μž₯μ‹œκ°„ μžκΈ°μ£Όλ„μ  μž‘μ—…μ„ μˆ˜ν–‰ν•˜ - κΉ€λ―Όμ•„ + image
κΉ€λ―Όμ•„ @@ -96,7 +186,7 @@ TimoλŠ” λ°μŠ€ν¬νƒ‘ ν™˜κ²½μ—μ„œ μž₯μ‹œκ°„ μžκΈ°μ£Όλ„μ  μž‘μ—…μ„ μˆ˜ν–‰ν•˜ - κΉ€μœ λ―Ό + image
κΉ€μœ λ―Ό @@ -105,7 +195,7 @@ TimoλŠ” λ°μŠ€ν¬νƒ‘ ν™˜κ²½μ—μ„œ μž₯μ‹œκ°„ μžκΈ°μ£Όλ„μ  μž‘μ—…μ„ μˆ˜ν–‰ν•˜ - κΉ€μ •λ―Ό + image
κΉ€μ •λ―Ό @@ -114,7 +204,7 @@ TimoλŠ” λ°μŠ€ν¬νƒ‘ ν™˜κ²½μ—μ„œ μž₯μ‹œκ°„ μžκΈ°μ£Όλ„μ  μž‘μ—…μ„ μˆ˜ν–‰ν•˜ - μ΄ν˜œμ› + image
μ΄ν˜œμ› diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts index f3ce6441..c5786846 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts @@ -8,6 +8,7 @@ import type { CreateTodoRequest } from "@/schemas/todo/todo-schema"; import { getGetTodayQueryKey } from "@/api/generated/endpoints/home/home"; import { useCreateTodo } from "@/api/generated/endpoints/todo/todo"; +import { useStatisticsQueryInvalidation } from "@/hooks/statistics/use-statistics-query-invalidation"; const buildCreateTodoRequestBody = ( data: CreateTodoRequest, @@ -29,6 +30,7 @@ export const useCreateTodoSubmit = () => { const [isErrorToastOpen, setIsErrorToastOpen] = useState(false); const { mutate: createTodo } = useCreateTodo(); const queryClient = useQueryClient(); + const { invalidateStatistics } = useStatisticsQueryInvalidation(); const handleSubmit = (data: CreateTodoRequest) => { createTodo( @@ -36,6 +38,7 @@ export const useCreateTodoSubmit = () => { { onSuccess: () => { queryClient.invalidateQueries({ queryKey: getGetTodayQueryKey() }); + invalidateStatistics(); }, onError: () => { setIsErrorToastOpen(true); diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts index bf8975fc..a42f0ab4 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts @@ -52,6 +52,7 @@ export const useTodayTodoList = ( invalidateTimeBoxes, invalidateTodayView, invalidateFocusTodo, + invalidateStatistics, } = useTimerQueryInvalidation(); const { mutate: startTimer, isPending: isStartTimerPending } = useStartTimer({ @@ -119,6 +120,7 @@ export const useTodayTodoList = ( invalidateTimeBoxes(); invalidateTodoDetail(todoId, dateKey); invalidateFocusTodo(); + invalidateStatistics(); }, onError: (error: ErrorType) => { setTodos(previous); @@ -149,6 +151,7 @@ export const useTodayTodoList = ( invalidateTodayView(); invalidateTodoDetail(todoId, dateKey); invalidateFocusTodo(); + invalidateStatistics(); }, }, ); diff --git a/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts b/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts index ee97c800..6818c827 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts +++ b/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts @@ -47,6 +47,7 @@ export const useFocusSession = ({ invalidateTimeBoxes, invalidateTodayView, invalidateFocusTodo, + invalidateStatistics, } = useTimerQueryInvalidation(); const invalidateTodoDetail = () => { const todo = focusView.todo; @@ -96,6 +97,7 @@ export const useFocusSession = ({ invalidateTimeBoxes(); invalidateTodayView(); invalidateTodoDetail(); + invalidateStatistics(); }, onError: onMutationError, }, @@ -110,6 +112,7 @@ export const useFocusSession = ({ invalidateTimeBoxes(); invalidateTodayView(); invalidateTodoDetail(); + invalidateStatistics(); }, onError: onMutationError, }, @@ -121,6 +124,7 @@ export const useFocusSession = ({ invalidateTimeBoxes(); invalidateTodayView(); invalidateTodoDetail(); + invalidateStatistics(); }, onError: onMutationError, }, diff --git a/apps/timo-web/app/[locale]/(main)/layout.tsx b/apps/timo-web/app/[locale]/(main)/layout.tsx index 88d9c6b0..fcc5efd6 100644 --- a/apps/timo-web/app/[locale]/(main)/layout.tsx +++ b/apps/timo-web/app/[locale]/(main)/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { MainShellContainer } from "@/app/[locale]/(main)/_containers/MainShellContainer"; import { AuthGuardProvider } from "@/providers/auth/AuthGuardProvider"; +import { OnboardingRequiredGuardProvider } from "@/providers/auth/OnboardingRequiredGuardProvider"; export const metadata: Metadata = { robots: { @@ -18,7 +19,9 @@ export default function MainLayout({ children }: Readonly) { return (

- {children} + + {children} +
); diff --git a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx index ef9dfa1f..8811637b 100644 --- a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx +++ b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx @@ -1,7 +1,6 @@ "use client"; -import timoTextLogo from "@repo/timo-design-system/assets/images/logo/timo-text-logo.svg"; -import Image from "next/image"; +import { LogoTimoIcon } from "@repo/timo-design-system/icons"; import { useTranslations } from "next-intl"; import { OnboardingGoogleButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer"; @@ -23,7 +22,7 @@ export const LoginContainer = () => {
- Timo +

Less Chaos More Focus diff --git a/apps/timo-web/app/[locale]/policy/page.tsx b/apps/timo-web/app/[locale]/policy/page.tsx index 1d55e4bc..3128300b 100644 --- a/apps/timo-web/app/[locale]/policy/page.tsx +++ b/apps/timo-web/app/[locale]/policy/page.tsx @@ -1,5 +1,4 @@ -import timoTextLogo from "@repo/timo-design-system/assets/images/logo/timo-text-logo.svg"; -import Image from "next/image"; +import { LogoTimoIcon } from "@repo/timo-design-system/icons"; import { notFound } from "next/navigation"; import { hasLocale } from "next-intl"; import { getTranslations } from "next-intl/server"; @@ -49,8 +48,8 @@ export default async function PolicyPage({ searchParams }: PolicyPageProps) { return (

- - Timo + +
diff --git a/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx b/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx index 9dc98759..b81511c3 100644 --- a/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx +++ b/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx @@ -1,6 +1,5 @@ "use client"; -import timoTextLogo from "@repo/timo-design-system/assets/images/logo/timo-text-logo.svg"; import { ChartHoverIcon, ChartOffIcon, @@ -8,6 +7,7 @@ import { HomeHoverIcon, HomeOffIcon, HomeOnIcon, + LogoTimoIcon, SettingHoverIcon, SettingOffIcon, SettingOnIcon, @@ -20,7 +20,6 @@ import { } from "@repo/timo-design-system/icons"; import { TabButton } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; -import Image from "next/image"; import { useTranslations } from "next-intl"; import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext"; @@ -119,14 +118,8 @@ export const NavigationSidebar = () => { )} >
- - Timo + +