@korajs/cli 0.3.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +1839 -829
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1107 -146
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-HNVCNPER.js → chunk-3WNFM3QB.js} +107 -97
- package/dist/chunk-3WNFM3QB.js.map +1 -0
- package/dist/{chunk-ULTO5NFA.js → chunk-PSRM56B7.js} +2 -2
- package/dist/chunk-PSRM56B7.js.map +1 -0
- package/dist/{chunk-MIMLCXSX.js → chunk-SOTZIWIF.js} +267 -228
- package/dist/chunk-SOTZIWIF.js.map +1 -0
- package/dist/create.cjs +58 -48
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +322 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -29
- package/dist/index.d.ts +31 -29
- package/dist/index.js +2 -2
- package/package.json +9 -3
- package/templates/react-basic/README.md.hbs +10 -1
- package/templates/react-basic/src/App.tsx +112 -126
- package/templates/react-basic/src/index.css +165 -159
- package/templates/react-basic/src/kora-worker.ts +0 -1
- package/templates/react-basic/src/main.tsx +12 -12
- package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-basic/src/schema.ts +6 -12
- package/templates/react-basic/src/vite-env.d.ts +17 -0
- package/templates/react-basic/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-basic/vite.config.ts +57 -50
- package/templates/react-sync/.env.example +19 -0
- package/templates/react-sync/README.md.hbs +43 -1
- package/templates/react-sync/package.json.hbs +3 -0
- package/templates/react-sync/server.ts +90 -26
- package/templates/react-sync/src/App.tsx +132 -130
- package/templates/react-sync/src/auth.ts +25 -0
- package/templates/react-sync/src/index.css +212 -165
- package/templates/react-sync/src/kora-worker.ts +0 -1
- package/templates/react-sync/src/main.tsx +34 -18
- package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-sync/src/schema.ts +6 -12
- package/templates/react-sync/src/vite-env.d.ts +17 -0
- package/templates/react-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-sync/vite.config.ts +67 -60
- package/templates/react-tailwind/README.md.hbs +10 -1
- package/templates/react-tailwind/src/App.tsx +186 -197
- package/templates/react-tailwind/src/index.css +3 -3
- package/templates/react-tailwind/src/kora-worker.ts +0 -1
- package/templates/react-tailwind/src/main.tsx +19 -12
- package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-tailwind/src/schema.ts +6 -12
- package/templates/react-tailwind/src/vite-env.d.ts +17 -0
- package/templates/react-tailwind/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-tailwind/vite.config.ts +57 -50
- package/templates/react-tailwind-sync/.env.example +19 -0
- package/templates/react-tailwind-sync/README.md.hbs +43 -1
- package/templates/react-tailwind-sync/package.json.hbs +3 -0
- package/templates/react-tailwind-sync/server.ts +90 -26
- package/templates/react-tailwind-sync/src/App.tsx +246 -222
- package/templates/react-tailwind-sync/src/auth.ts +25 -0
- package/templates/react-tailwind-sync/src/index.css +3 -3
- package/templates/react-tailwind-sync/src/kora-worker.ts +0 -1
- package/templates/react-tailwind-sync/src/main.tsx +48 -18
- package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-tailwind-sync/src/schema.ts +6 -12
- package/templates/react-tailwind-sync/src/vite-env.d.ts +17 -0
- package/templates/react-tailwind-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-tailwind-sync/vite.config.ts +67 -60
- package/templates/tauri-react/.env.example +20 -0
- package/templates/tauri-react/README.md.hbs +36 -1
- package/templates/tauri-react/dev.ts +11 -11
- package/templates/tauri-react/package.json.hbs +2 -0
- package/templates/tauri-react/server.ts +86 -12
- package/templates/tauri-react/src/App.tsx +769 -544
- package/templates/tauri-react/src/AppShell.tsx +125 -83
- package/templates/tauri-react/src/SetupScreen.tsx +257 -208
- package/templates/tauri-react/src/auth.ts +45 -0
- package/templates/tauri-react/src/main.tsx +3 -3
- package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
- package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
- package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
- package/templates/tauri-react/src/schema.ts +6 -12
- package/templates/tauri-react/src/sync-config.ts +47 -47
- package/templates/tauri-react/src/updater.ts +15 -15
- package/templates/tauri-react/src/vite-env.d.ts +17 -0
- package/templates/tauri-react/src-tauri/tauri.conf.json +2 -2
- package/templates/tauri-react/tsconfig.json.hbs +14 -0
- package/templates/tauri-react/vite.config.ts +9 -9
- package/dist/chunk-HNVCNPER.js.map +0 -1
- package/dist/chunk-MIMLCXSX.js.map +0 -1
- package/dist/chunk-ULTO5NFA.js.map +0 -1
- package/templates/tauri-react/tsconfig.json +0 -15
package/dist/index.d.cts
CHANGED
|
@@ -117,6 +117,7 @@ interface ProjectConfig {
|
|
|
117
117
|
region: string | null;
|
|
118
118
|
environment: 'preview' | 'production';
|
|
119
119
|
confirm: boolean;
|
|
120
|
+
deployTarget?: 'full-stack' | 'sync-server';
|
|
120
121
|
}
|
|
121
122
|
interface ProvisionResult {
|
|
122
123
|
applicationId: string;
|
|
@@ -425,7 +426,7 @@ declare function resetDeployState(projectRoot: string): Promise<void>;
|
|
|
425
426
|
interface DockerfileOptions {
|
|
426
427
|
nodeVersion?: string;
|
|
427
428
|
port?: number;
|
|
428
|
-
clientDirectory?: string;
|
|
429
|
+
clientDirectory?: string | null;
|
|
429
430
|
serverBundleFile?: string;
|
|
430
431
|
/** Native modules that must be installed at runtime (externalized from bundle). */
|
|
431
432
|
nativeDependencies?: Record<string, string>;
|
|
@@ -569,6 +570,32 @@ declare function isAuthValue(value: string): value is AuthOption;
|
|
|
569
570
|
declare function isDatabaseValue(value: string): value is DatabaseOption;
|
|
570
571
|
declare function isDatabaseProviderValue(value: string): value is DatabaseProviderOption;
|
|
571
572
|
|
|
573
|
+
interface CreatePreferences {
|
|
574
|
+
platform: PlatformOption;
|
|
575
|
+
framework: FrameworkOption;
|
|
576
|
+
tailwind: boolean;
|
|
577
|
+
sync: boolean;
|
|
578
|
+
db: DatabaseOption;
|
|
579
|
+
dbProvider: DatabaseProviderOption;
|
|
580
|
+
auth: AuthOption;
|
|
581
|
+
packageManager: PackageManager;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Preference store for scaffold-time defaults in `create-kora-app`.
|
|
585
|
+
*/
|
|
586
|
+
declare class PreferenceStore {
|
|
587
|
+
private readonly store;
|
|
588
|
+
constructor();
|
|
589
|
+
getCreatePreferences(): CreatePreferences | null;
|
|
590
|
+
saveCreatePreferences(preferences: CreatePreferences): void;
|
|
591
|
+
clearCreatePreferences(): void;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Gets preferences from storage or returns defaults when not available.
|
|
595
|
+
*/
|
|
596
|
+
declare function getCreatePreferencesOrDefault(store: PreferenceStore): CreatePreferences;
|
|
597
|
+
declare function getDefaultCreatePreferences(): CreatePreferences;
|
|
598
|
+
|
|
572
599
|
interface SelectOption<T extends string> {
|
|
573
600
|
label: string;
|
|
574
601
|
value: T;
|
|
@@ -615,32 +642,6 @@ declare class ClackPromptClient implements PromptClient {
|
|
|
615
642
|
outro(message: string): void;
|
|
616
643
|
}
|
|
617
644
|
|
|
618
|
-
interface CreatePreferences {
|
|
619
|
-
platform: PlatformOption;
|
|
620
|
-
framework: FrameworkOption;
|
|
621
|
-
tailwind: boolean;
|
|
622
|
-
sync: boolean;
|
|
623
|
-
db: DatabaseOption;
|
|
624
|
-
dbProvider: DatabaseProviderOption;
|
|
625
|
-
auth: AuthOption;
|
|
626
|
-
packageManager: PackageManager;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Preference store for scaffold-time defaults in `create-kora-app`.
|
|
630
|
-
*/
|
|
631
|
-
declare class PreferenceStore {
|
|
632
|
-
private readonly store;
|
|
633
|
-
constructor();
|
|
634
|
-
getCreatePreferences(): CreatePreferences | null;
|
|
635
|
-
saveCreatePreferences(preferences: CreatePreferences): void;
|
|
636
|
-
clearCreatePreferences(): void;
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Gets preferences from storage or returns defaults when not available.
|
|
640
|
-
*/
|
|
641
|
-
declare function getCreatePreferencesOrDefault(store: PreferenceStore): CreatePreferences;
|
|
642
|
-
declare function getDefaultCreatePreferences(): CreatePreferences;
|
|
643
|
-
|
|
644
645
|
interface CreateFlags {
|
|
645
646
|
platform?: string;
|
|
646
647
|
framework?: string;
|
|
@@ -685,8 +686,9 @@ interface SyncProviderPresetOptions {
|
|
|
685
686
|
/**
|
|
686
687
|
* Applies provider-specific sync scaffolding adjustments after template copy.
|
|
687
688
|
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
689
|
+
* The sync server template is environment-driven: SQLite is used when
|
|
690
|
+
* DATABASE_URL is absent, and PostgreSQL is used when DATABASE_URL is present.
|
|
691
|
+
* Provider presets only add provider-specific guidance.
|
|
690
692
|
*/
|
|
691
693
|
declare function applySyncProviderPreset(options: SyncProviderPresetOptions): Promise<void>;
|
|
692
694
|
|
package/dist/index.d.ts
CHANGED
|
@@ -117,6 +117,7 @@ interface ProjectConfig {
|
|
|
117
117
|
region: string | null;
|
|
118
118
|
environment: 'preview' | 'production';
|
|
119
119
|
confirm: boolean;
|
|
120
|
+
deployTarget?: 'full-stack' | 'sync-server';
|
|
120
121
|
}
|
|
121
122
|
interface ProvisionResult {
|
|
122
123
|
applicationId: string;
|
|
@@ -425,7 +426,7 @@ declare function resetDeployState(projectRoot: string): Promise<void>;
|
|
|
425
426
|
interface DockerfileOptions {
|
|
426
427
|
nodeVersion?: string;
|
|
427
428
|
port?: number;
|
|
428
|
-
clientDirectory?: string;
|
|
429
|
+
clientDirectory?: string | null;
|
|
429
430
|
serverBundleFile?: string;
|
|
430
431
|
/** Native modules that must be installed at runtime (externalized from bundle). */
|
|
431
432
|
nativeDependencies?: Record<string, string>;
|
|
@@ -569,6 +570,32 @@ declare function isAuthValue(value: string): value is AuthOption;
|
|
|
569
570
|
declare function isDatabaseValue(value: string): value is DatabaseOption;
|
|
570
571
|
declare function isDatabaseProviderValue(value: string): value is DatabaseProviderOption;
|
|
571
572
|
|
|
573
|
+
interface CreatePreferences {
|
|
574
|
+
platform: PlatformOption;
|
|
575
|
+
framework: FrameworkOption;
|
|
576
|
+
tailwind: boolean;
|
|
577
|
+
sync: boolean;
|
|
578
|
+
db: DatabaseOption;
|
|
579
|
+
dbProvider: DatabaseProviderOption;
|
|
580
|
+
auth: AuthOption;
|
|
581
|
+
packageManager: PackageManager;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Preference store for scaffold-time defaults in `create-kora-app`.
|
|
585
|
+
*/
|
|
586
|
+
declare class PreferenceStore {
|
|
587
|
+
private readonly store;
|
|
588
|
+
constructor();
|
|
589
|
+
getCreatePreferences(): CreatePreferences | null;
|
|
590
|
+
saveCreatePreferences(preferences: CreatePreferences): void;
|
|
591
|
+
clearCreatePreferences(): void;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Gets preferences from storage or returns defaults when not available.
|
|
595
|
+
*/
|
|
596
|
+
declare function getCreatePreferencesOrDefault(store: PreferenceStore): CreatePreferences;
|
|
597
|
+
declare function getDefaultCreatePreferences(): CreatePreferences;
|
|
598
|
+
|
|
572
599
|
interface SelectOption<T extends string> {
|
|
573
600
|
label: string;
|
|
574
601
|
value: T;
|
|
@@ -615,32 +642,6 @@ declare class ClackPromptClient implements PromptClient {
|
|
|
615
642
|
outro(message: string): void;
|
|
616
643
|
}
|
|
617
644
|
|
|
618
|
-
interface CreatePreferences {
|
|
619
|
-
platform: PlatformOption;
|
|
620
|
-
framework: FrameworkOption;
|
|
621
|
-
tailwind: boolean;
|
|
622
|
-
sync: boolean;
|
|
623
|
-
db: DatabaseOption;
|
|
624
|
-
dbProvider: DatabaseProviderOption;
|
|
625
|
-
auth: AuthOption;
|
|
626
|
-
packageManager: PackageManager;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Preference store for scaffold-time defaults in `create-kora-app`.
|
|
630
|
-
*/
|
|
631
|
-
declare class PreferenceStore {
|
|
632
|
-
private readonly store;
|
|
633
|
-
constructor();
|
|
634
|
-
getCreatePreferences(): CreatePreferences | null;
|
|
635
|
-
saveCreatePreferences(preferences: CreatePreferences): void;
|
|
636
|
-
clearCreatePreferences(): void;
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Gets preferences from storage or returns defaults when not available.
|
|
640
|
-
*/
|
|
641
|
-
declare function getCreatePreferencesOrDefault(store: PreferenceStore): CreatePreferences;
|
|
642
|
-
declare function getDefaultCreatePreferences(): CreatePreferences;
|
|
643
|
-
|
|
644
645
|
interface CreateFlags {
|
|
645
646
|
platform?: string;
|
|
646
647
|
framework?: string;
|
|
@@ -685,8 +686,9 @@ interface SyncProviderPresetOptions {
|
|
|
685
686
|
/**
|
|
686
687
|
* Applies provider-specific sync scaffolding adjustments after template copy.
|
|
687
688
|
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
689
|
+
* The sync server template is environment-driven: SQLite is used when
|
|
690
|
+
* DATABASE_URL is absent, and PostgreSQL is used when DATABASE_URL is present.
|
|
691
|
+
* Provider presets only add provider-specific guidance.
|
|
690
692
|
*/
|
|
691
693
|
declare function applySyncProviderPreset(options: SyncProviderPresetOptions): Promise<void>;
|
|
692
694
|
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
writeDockerfileArtifact,
|
|
28
28
|
writeFlyTomlArtifact,
|
|
29
29
|
writeRailwayJsonArtifact
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-SOTZIWIF.js";
|
|
31
31
|
import {
|
|
32
32
|
ClackPromptClient,
|
|
33
33
|
CliError,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
saveResolvedPreferences,
|
|
55
55
|
shouldSavePreferences,
|
|
56
56
|
validateProjectName
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-3WNFM3QB.js";
|
|
58
58
|
export {
|
|
59
59
|
ClackPromptClient,
|
|
60
60
|
CliError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korajs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Kora.js CLI tooling and project scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -36,14 +36,20 @@
|
|
|
36
36
|
"conf": "^15.1.0",
|
|
37
37
|
"esbuild": "^0.28.0",
|
|
38
38
|
"validate-npm-package-name": "^7.0.2",
|
|
39
|
-
"@korajs/core": "0.
|
|
39
|
+
"@korajs/core": "0.5.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
42
43
|
"@types/node": "^25.5.2",
|
|
43
44
|
"@types/validate-npm-package-name": "^4.0.2",
|
|
45
|
+
"better-sqlite3": "^12.8.0",
|
|
44
46
|
"tsup": "^8.3.6",
|
|
45
47
|
"typescript": "^5.7.3",
|
|
46
|
-
"vitest": "^3.0.4"
|
|
48
|
+
"vitest": "^3.0.4",
|
|
49
|
+
"@korajs/store": "0.5.0",
|
|
50
|
+
"@korajs/sync": "0.5.0",
|
|
51
|
+
"@korajs/merge": "0.5.0",
|
|
52
|
+
"korajs": "0.5.0"
|
|
47
53
|
},
|
|
48
54
|
"license": "MIT",
|
|
49
55
|
"scripts": {
|
|
@@ -14,14 +14,23 @@ npm run dev
|
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
src/
|
|
17
|
-
schema.ts #
|
|
17
|
+
schema.ts # Schema entry point
|
|
18
18
|
main.tsx # App initialization and Kora setup
|
|
19
19
|
App.tsx # Main application component
|
|
20
|
+
modules/
|
|
21
|
+
todos/
|
|
22
|
+
todo.schema.ts # Todo collection definition
|
|
23
|
+
todo.queries.ts # Reusable todo query builders
|
|
24
|
+
todo.mutations.ts # Todo mutation helpers
|
|
25
|
+
useTodos.ts # React hook connecting todos to the UI
|
|
20
26
|
kora-worker.ts # SQLite WASM web worker
|
|
21
27
|
index.css # Styles
|
|
22
28
|
kora.config.ts # Kora configuration
|
|
23
29
|
```
|
|
24
30
|
|
|
31
|
+
In a feature module, `schema` defines data shape, `queries` contain reads only, `mutations` contain
|
|
32
|
+
writes, and `useTodos.ts` is the React binding that exposes todo data and actions to components.
|
|
33
|
+
|
|
25
34
|
## Scripts
|
|
26
35
|
|
|
27
36
|
| Script | Description |
|
|
@@ -1,146 +1,132 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { useTodos } from './modules/todos/useTodos'
|
|
3
3
|
|
|
4
4
|
type Filter = 'all' | 'active' | 'completed'
|
|
5
5
|
|
|
6
6
|
export function App() {
|
|
7
|
-
|
|
8
|
-
const allTodos = useQuery(todos.where({}).orderBy('createdAt', 'desc'))
|
|
9
|
-
const { mutate: addTodo } = useMutation(
|
|
10
|
-
(data: { title: string }) => todos.insert(data)
|
|
11
|
-
)
|
|
12
|
-
const { mutate: toggleTodo } = useMutation(
|
|
13
|
-
(id: string, data: { completed: boolean }) => todos.update(id, data)
|
|
14
|
-
)
|
|
15
|
-
const { mutate: deleteTodo } = useMutation(
|
|
16
|
-
(id: string) => todos.delete(id)
|
|
17
|
-
)
|
|
7
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = useTodos()
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
const [filter, setFilter] = useState<Filter>('all')
|
|
10
|
+
const [input, setInput] = useState('')
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const filteredTodos =
|
|
25
|
-
filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
|
|
12
|
+
const filteredTodos =
|
|
13
|
+
filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
16
|
+
e.preventDefault()
|
|
17
|
+
const title = input.trim()
|
|
18
|
+
if (title) {
|
|
19
|
+
addTodo.mutate({ title })
|
|
20
|
+
setInput('')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
const clearCompleted = () => {
|
|
25
|
+
for (const todo of completedTodos) {
|
|
26
|
+
deleteTodo.mutate(todo.id)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
30
|
+
return (
|
|
31
|
+
<div className="app">
|
|
32
|
+
<div className="header">
|
|
33
|
+
<h1>My Tasks</h1>
|
|
34
|
+
<span className="local-badge">Local-first</span>
|
|
35
|
+
</div>
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
37
|
+
<div className="stats">
|
|
38
|
+
<div className="stat-card">
|
|
39
|
+
<div className="label">Total</div>
|
|
40
|
+
<div className="value muted">{allTodos.length}</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="stat-card">
|
|
43
|
+
<div className="label">Remaining</div>
|
|
44
|
+
<div className="value warning">{activeTodos.length}</div>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="stat-card">
|
|
47
|
+
<div className="label">Done</div>
|
|
48
|
+
<div className="value success">{completedTodos.length}</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
63
51
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
52
|
+
<form className="add-form" onSubmit={handleSubmit}>
|
|
53
|
+
<input
|
|
54
|
+
type="text"
|
|
55
|
+
value={input}
|
|
56
|
+
onChange={(e) => setInput(e.target.value)}
|
|
57
|
+
placeholder="What needs to be done?"
|
|
58
|
+
/>
|
|
59
|
+
<button type="submit" disabled={!input.trim()}>
|
|
60
|
+
Add
|
|
61
|
+
</button>
|
|
62
|
+
</form>
|
|
75
63
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
64
|
+
<div className="filters">
|
|
65
|
+
{(['all', 'active', 'completed'] as const).map((f) => {
|
|
66
|
+
const count =
|
|
67
|
+
f === 'all'
|
|
68
|
+
? allTodos.length
|
|
69
|
+
: f === 'active'
|
|
70
|
+
? activeTodos.length
|
|
71
|
+
: completedTodos.length
|
|
72
|
+
return (
|
|
73
|
+
<button
|
|
74
|
+
key={f}
|
|
75
|
+
className={`filter-btn ${filter === f ? 'active' : ''}`}
|
|
76
|
+
onClick={() => setFilter(f)}
|
|
77
|
+
>
|
|
78
|
+
{f.charAt(0).toUpperCase() + f.slice(1)}
|
|
79
|
+
<span className="badge">{count}</span>
|
|
80
|
+
</button>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
91
84
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
>
|
|
118
|
-
\u00d7
|
|
119
|
-
</button>
|
|
120
|
-
</div>
|
|
121
|
-
))
|
|
122
|
-
)}
|
|
123
|
-
</div>
|
|
85
|
+
<div className="todo-list">
|
|
86
|
+
{filteredTodos.length === 0 ? (
|
|
87
|
+
<div className="empty-state">
|
|
88
|
+
{filter === 'all' && 'No tasks yet. Add one above!'}
|
|
89
|
+
{filter === 'active' && 'All caught up! No active tasks.'}
|
|
90
|
+
{filter === 'completed' && 'No completed tasks yet.'}
|
|
91
|
+
</div>
|
|
92
|
+
) : (
|
|
93
|
+
filteredTodos.map((todo) => (
|
|
94
|
+
<div key={todo.id} className="todo-item">
|
|
95
|
+
<button
|
|
96
|
+
className={`toggle ${todo.completed ? 'checked' : ''}`}
|
|
97
|
+
onClick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
|
|
98
|
+
>
|
|
99
|
+
{todo.completed ? '\u2713' : ''}
|
|
100
|
+
</button>
|
|
101
|
+
<span className={`title ${todo.completed ? 'done' : ''}`}>{String(todo.title)}</span>
|
|
102
|
+
{todo.createdAt && <span className="time">{formatTime(Number(todo.createdAt))}</span>}
|
|
103
|
+
<button className="delete-btn" onClick={() => deleteTodo.mutate(todo.id)}>
|
|
104
|
+
\u00d7
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
))
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
124
110
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
111
|
+
{allTodos.length > 0 && (
|
|
112
|
+
<div className="footer">
|
|
113
|
+
<span>
|
|
114
|
+
{activeTodos.length} item{activeTodos.length !== 1 ? 's' : ''} left
|
|
115
|
+
</span>
|
|
116
|
+
{completedTodos.length > 0 && <button onClick={clearCompleted}>Clear completed</button>}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
133
119
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
120
|
+
<p className="branding">Powered by Kora — offline-first, real-time sync</p>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
137
123
|
}
|
|
138
124
|
|
|
139
125
|
function formatTime(timestamp: number): string {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
126
|
+
const date = new Date(timestamp)
|
|
127
|
+
const now = new Date()
|
|
128
|
+
if (date.toDateString() === now.toDateString()) {
|
|
129
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
130
|
+
}
|
|
131
|
+
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
146
132
|
}
|