@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.
Files changed (104) hide show
  1. package/dist/bin.cjs +1839 -829
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +1107 -146
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-HNVCNPER.js → chunk-3WNFM3QB.js} +107 -97
  6. package/dist/chunk-3WNFM3QB.js.map +1 -0
  7. package/dist/{chunk-ULTO5NFA.js → chunk-PSRM56B7.js} +2 -2
  8. package/dist/chunk-PSRM56B7.js.map +1 -0
  9. package/dist/{chunk-MIMLCXSX.js → chunk-SOTZIWIF.js} +267 -228
  10. package/dist/chunk-SOTZIWIF.js.map +1 -0
  11. package/dist/create.cjs +58 -48
  12. package/dist/create.cjs.map +1 -1
  13. package/dist/create.js +2 -2
  14. package/dist/index.cjs +322 -273
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +31 -29
  17. package/dist/index.d.ts +31 -29
  18. package/dist/index.js +2 -2
  19. package/package.json +9 -3
  20. package/templates/react-basic/README.md.hbs +10 -1
  21. package/templates/react-basic/src/App.tsx +112 -126
  22. package/templates/react-basic/src/index.css +165 -159
  23. package/templates/react-basic/src/kora-worker.ts +0 -1
  24. package/templates/react-basic/src/main.tsx +12 -12
  25. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  26. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  27. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  28. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  29. package/templates/react-basic/src/schema.ts +6 -12
  30. package/templates/react-basic/src/vite-env.d.ts +17 -0
  31. package/templates/react-basic/{tsconfig.json → tsconfig.json.hbs} +1 -2
  32. package/templates/react-basic/vite.config.ts +57 -50
  33. package/templates/react-sync/.env.example +19 -0
  34. package/templates/react-sync/README.md.hbs +43 -1
  35. package/templates/react-sync/package.json.hbs +3 -0
  36. package/templates/react-sync/server.ts +90 -26
  37. package/templates/react-sync/src/App.tsx +132 -130
  38. package/templates/react-sync/src/auth.ts +25 -0
  39. package/templates/react-sync/src/index.css +212 -165
  40. package/templates/react-sync/src/kora-worker.ts +0 -1
  41. package/templates/react-sync/src/main.tsx +34 -18
  42. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  43. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  44. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  45. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  46. package/templates/react-sync/src/schema.ts +6 -12
  47. package/templates/react-sync/src/vite-env.d.ts +17 -0
  48. package/templates/react-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  49. package/templates/react-sync/vite.config.ts +67 -60
  50. package/templates/react-tailwind/README.md.hbs +10 -1
  51. package/templates/react-tailwind/src/App.tsx +186 -197
  52. package/templates/react-tailwind/src/index.css +3 -3
  53. package/templates/react-tailwind/src/kora-worker.ts +0 -1
  54. package/templates/react-tailwind/src/main.tsx +19 -12
  55. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  56. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  57. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  58. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  59. package/templates/react-tailwind/src/schema.ts +6 -12
  60. package/templates/react-tailwind/src/vite-env.d.ts +17 -0
  61. package/templates/react-tailwind/{tsconfig.json → tsconfig.json.hbs} +1 -2
  62. package/templates/react-tailwind/vite.config.ts +57 -50
  63. package/templates/react-tailwind-sync/.env.example +19 -0
  64. package/templates/react-tailwind-sync/README.md.hbs +43 -1
  65. package/templates/react-tailwind-sync/package.json.hbs +3 -0
  66. package/templates/react-tailwind-sync/server.ts +90 -26
  67. package/templates/react-tailwind-sync/src/App.tsx +246 -222
  68. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  69. package/templates/react-tailwind-sync/src/index.css +3 -3
  70. package/templates/react-tailwind-sync/src/kora-worker.ts +0 -1
  71. package/templates/react-tailwind-sync/src/main.tsx +48 -18
  72. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  73. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  74. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  75. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  76. package/templates/react-tailwind-sync/src/schema.ts +6 -12
  77. package/templates/react-tailwind-sync/src/vite-env.d.ts +17 -0
  78. package/templates/react-tailwind-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  79. package/templates/react-tailwind-sync/vite.config.ts +67 -60
  80. package/templates/tauri-react/.env.example +20 -0
  81. package/templates/tauri-react/README.md.hbs +36 -1
  82. package/templates/tauri-react/dev.ts +11 -11
  83. package/templates/tauri-react/package.json.hbs +2 -0
  84. package/templates/tauri-react/server.ts +86 -12
  85. package/templates/tauri-react/src/App.tsx +769 -544
  86. package/templates/tauri-react/src/AppShell.tsx +125 -83
  87. package/templates/tauri-react/src/SetupScreen.tsx +257 -208
  88. package/templates/tauri-react/src/auth.ts +45 -0
  89. package/templates/tauri-react/src/main.tsx +3 -3
  90. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  91. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  92. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  93. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  94. package/templates/tauri-react/src/schema.ts +6 -12
  95. package/templates/tauri-react/src/sync-config.ts +47 -47
  96. package/templates/tauri-react/src/updater.ts +15 -15
  97. package/templates/tauri-react/src/vite-env.d.ts +17 -0
  98. package/templates/tauri-react/src-tauri/tauri.conf.json +2 -2
  99. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  100. package/templates/tauri-react/vite.config.ts +9 -9
  101. package/dist/chunk-HNVCNPER.js.map +0 -1
  102. package/dist/chunk-MIMLCXSX.js.map +0 -1
  103. package/dist/chunk-ULTO5NFA.js.map +0 -1
  104. package/templates/tauri-react/tsconfig.json +0 -15
@@ -1,10 +1,10 @@
1
+ import { AlertCircle, ArrowRight, Loader2, Monitor, Wifi } from 'lucide-react'
1
2
  import { useState } from 'react'
2
- import { Monitor, Wifi, ArrowRight, Loader2, AlertCircle } from 'lucide-react'
3
3
  import { testConnection } from './sync-config'
4
4
 
5
5
  interface SetupScreenProps {
6
- onConnect: (syncUrl: string) => void
7
- onSkip: () => void
6
+ onConnect: (syncUrl: string) => void
7
+ onSkip: () => void
8
8
  }
9
9
 
10
10
  /**
@@ -18,222 +18,271 @@ interface SetupScreenProps {
18
18
  * connect when the server comes back.
19
19
  */
20
20
  export function SetupScreen({ onConnect, onSkip }: SetupScreenProps) {
21
- const [url, setUrl] = useState('')
22
- const [testing, setTesting] = useState(false)
23
- const [error, setError] = useState<string | null>(null)
24
- const [unreachable, setUnreachable] = useState(false)
21
+ const [url, setUrl] = useState('')
22
+ const [testing, setTesting] = useState(false)
23
+ const [error, setError] = useState<string | null>(null)
24
+ const [unreachable, setUnreachable] = useState(false)
25
25
 
26
- const validateUrl = (value: string): string | null => {
27
- if (!value.startsWith('ws://') && !value.startsWith('wss://')) {
28
- return 'URL must start with ws:// or wss://'
29
- }
30
- try {
31
- new URL(value.replace(/^ws/, 'http'))
32
- } catch {
33
- return 'Invalid URL format'
34
- }
35
- return null
36
- }
26
+ const validateUrl = (value: string): string | null => {
27
+ if (!value.startsWith('ws://') && !value.startsWith('wss://')) {
28
+ return 'URL must start with ws:// or wss://'
29
+ }
30
+ try {
31
+ new URL(value.replace(/^ws/, 'http'))
32
+ } catch {
33
+ return 'Invalid URL format'
34
+ }
35
+ return null
36
+ }
37
37
 
38
- const handleSubmit = async (e: React.FormEvent) => {
39
- e.preventDefault()
40
- const syncUrl = url.trim()
41
- if (!syncUrl) return
38
+ const handleSubmit = async (e: React.FormEvent) => {
39
+ e.preventDefault()
40
+ const syncUrl = url.trim()
41
+ if (!syncUrl) return
42
42
 
43
- const validationError = validateUrl(syncUrl)
44
- if (validationError) {
45
- setError(validationError)
46
- return
47
- }
43
+ const validationError = validateUrl(syncUrl)
44
+ if (validationError) {
45
+ setError(validationError)
46
+ return
47
+ }
48
48
 
49
- setTesting(true)
50
- setError(null)
51
- setUnreachable(false)
49
+ setTesting(true)
50
+ setError(null)
51
+ setUnreachable(false)
52
52
 
53
- const reachable = await testConnection(syncUrl)
54
- setTesting(false)
53
+ const reachable = await testConnection(syncUrl)
54
+ setTesting(false)
55
55
 
56
- if (reachable) {
57
- onConnect(syncUrl)
58
- } else {
59
- // Server unreachable — let the user save anyway
60
- setUnreachable(true)
61
- }
62
- }
56
+ if (reachable) {
57
+ onConnect(syncUrl)
58
+ } else {
59
+ // Server unreachable — let the user save anyway
60
+ setUnreachable(true)
61
+ }
62
+ }
63
63
 
64
- const handleSaveAnyway = () => {
65
- onConnect(url.trim())
66
- }
64
+ const handleSaveAnyway = () => {
65
+ onConnect(url.trim())
66
+ }
67
67
 
68
- return (
69
- <div style={{ minHeight: '100vh', background: '#0a0a0a', color: '#f3f4f6', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
70
- <div style={{ maxWidth: '480px', width: '100%', padding: '0 24px' }}>
71
- {/* Logo */}
72
- <div style={{ textAlign: 'center', marginBottom: '48px' }}>
73
- <Monitor style={{ width: '48px', height: '48px', color: '#818cf8', margin: '0 auto 16px' }} />
74
- <h1 style={{ fontSize: '28px', fontWeight: 'bold', marginBottom: '8px' }}>Welcome</h1>
75
- <p style={{ color: '#6b7280', fontSize: '15px' }}>
76
- Connect to your organization's sync server to sync data across devices.
77
- </p>
78
- </div>
68
+ return (
69
+ <div
70
+ style={{
71
+ minHeight: '100vh',
72
+ background: '#0a0a0a',
73
+ color: '#f3f4f6',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ }}
78
+ >
79
+ <div style={{ maxWidth: '480px', width: '100%', padding: '0 24px' }}>
80
+ {/* Logo */}
81
+ <div style={{ textAlign: 'center', marginBottom: '48px' }}>
82
+ <Monitor
83
+ style={{ width: '48px', height: '48px', color: '#818cf8', margin: '0 auto 16px' }}
84
+ />
85
+ <h1 style={{ fontSize: '28px', fontWeight: 'bold', marginBottom: '8px' }}>Welcome</h1>
86
+ <p style={{ color: '#6b7280', fontSize: '15px' }}>
87
+ Connect to your organization's sync server to sync data across devices.
88
+ </p>
89
+ </div>
79
90
 
80
- {/* Connection form */}
81
- <form onSubmit={handleSubmit}>
82
- <div style={{ marginBottom: '16px' }}>
83
- <label style={{ display: 'block', fontSize: '14px', fontWeight: '500', marginBottom: '8px', color: '#d1d5db' }}>
84
- Sync server URL
85
- </label>
86
- <div style={{ position: 'relative' }}>
87
- <Wifi style={{ position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)', width: '18px', height: '18px', color: '#6b7280' }} />
88
- <input
89
- type="text"
90
- value={url}
91
- onChange={(e) => { setUrl(e.target.value); setError(null); setUnreachable(false) }}
92
- placeholder="wss://your-server.example.com/kora-sync"
93
- autoFocus
94
- style={{
95
- width: '100%',
96
- borderRadius: '8px',
97
- border: `1px solid ${error ? '#ef4444' : '#374151'}`,
98
- background: '#111827',
99
- padding: '12px 16px 12px 40px',
100
- color: '#f3f4f6',
101
- outline: 'none',
102
- fontSize: '14px',
103
- boxSizing: 'border-box',
104
- }}
105
- />
106
- </div>
107
- {error && (
108
- <p style={{ color: '#ef4444', fontSize: '13px', marginTop: '8px' }}>{error}</p>
109
- )}
110
- </div>
91
+ {/* Connection form */}
92
+ <form onSubmit={handleSubmit}>
93
+ <div style={{ marginBottom: '16px' }}>
94
+ <label
95
+ htmlFor="sync-server-url"
96
+ style={{
97
+ display: 'block',
98
+ fontSize: '14px',
99
+ fontWeight: '500',
100
+ marginBottom: '8px',
101
+ color: '#d1d5db',
102
+ }}
103
+ >
104
+ Sync server URL
105
+ </label>
106
+ <div style={{ position: 'relative' }}>
107
+ <Wifi
108
+ style={{
109
+ position: 'absolute',
110
+ left: '12px',
111
+ top: '50%',
112
+ transform: 'translateY(-50%)',
113
+ width: '18px',
114
+ height: '18px',
115
+ color: '#6b7280',
116
+ }}
117
+ />
118
+ <input
119
+ id="sync-server-url"
120
+ type="text"
121
+ value={url}
122
+ onChange={(e) => {
123
+ setUrl(e.target.value)
124
+ setError(null)
125
+ setUnreachable(false)
126
+ }}
127
+ placeholder="wss://your-server.example.com/kora-sync"
128
+ style={{
129
+ width: '100%',
130
+ borderRadius: '8px',
131
+ border: `1px solid ${error ? '#ef4444' : '#374151'}`,
132
+ background: '#111827',
133
+ padding: '12px 16px 12px 40px',
134
+ color: '#f3f4f6',
135
+ outline: 'none',
136
+ fontSize: '14px',
137
+ boxSizing: 'border-box',
138
+ }}
139
+ />
140
+ </div>
141
+ {error && (
142
+ <p style={{ color: '#ef4444', fontSize: '13px', marginTop: '8px' }}>{error}</p>
143
+ )}
144
+ </div>
111
145
 
112
- {/* Server unreachable — offer to save anyway */}
113
- {unreachable && (
114
- <div style={{
115
- marginBottom: '16px',
116
- padding: '12px 16px',
117
- borderRadius: '8px',
118
- border: '1px solid #92400e',
119
- background: '#451a03',
120
- }}>
121
- <div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start' }}>
122
- <AlertCircle style={{ width: '16px', height: '16px', color: '#fbbf24', flexShrink: 0, marginTop: '2px' }} />
123
- <div>
124
- <p style={{ fontSize: '13px', color: '#fde68a', marginBottom: '8px' }}>
125
- Server is not reachable right now. This could be temporary (maintenance, network issues).
126
- </p>
127
- <div style={{ display: 'flex', gap: '8px' }}>
128
- <button
129
- type="button"
130
- onClick={handleSaveAnyway}
131
- style={{
132
- fontSize: '13px',
133
- color: '#fbbf24',
134
- background: 'none',
135
- border: '1px solid #92400e',
136
- borderRadius: '6px',
137
- padding: '4px 12px',
138
- cursor: 'pointer',
139
- }}
140
- >
141
- Save anyway — connect later
142
- </button>
143
- <button
144
- type="submit"
145
- style={{
146
- fontSize: '13px',
147
- color: '#9ca3af',
148
- background: 'none',
149
- border: '1px solid #374151',
150
- borderRadius: '6px',
151
- padding: '4px 12px',
152
- cursor: 'pointer',
153
- }}
154
- >
155
- Retry
156
- </button>
157
- </div>
158
- </div>
159
- </div>
160
- </div>
161
- )}
146
+ {/* Server unreachable — offer to save anyway */}
147
+ {unreachable && (
148
+ <div
149
+ style={{
150
+ marginBottom: '16px',
151
+ padding: '12px 16px',
152
+ borderRadius: '8px',
153
+ border: '1px solid #92400e',
154
+ background: '#451a03',
155
+ }}
156
+ >
157
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start' }}>
158
+ <AlertCircle
159
+ style={{
160
+ width: '16px',
161
+ height: '16px',
162
+ color: '#fbbf24',
163
+ flexShrink: 0,
164
+ marginTop: '2px',
165
+ }}
166
+ />
167
+ <div>
168
+ <p style={{ fontSize: '13px', color: '#fde68a', marginBottom: '8px' }}>
169
+ Server is not reachable right now. This could be temporary (maintenance, network
170
+ issues).
171
+ </p>
172
+ <div style={{ display: 'flex', gap: '8px' }}>
173
+ <button
174
+ type="button"
175
+ onClick={handleSaveAnyway}
176
+ style={{
177
+ fontSize: '13px',
178
+ color: '#fbbf24',
179
+ background: 'none',
180
+ border: '1px solid #92400e',
181
+ borderRadius: '6px',
182
+ padding: '4px 12px',
183
+ cursor: 'pointer',
184
+ }}
185
+ >
186
+ Save anyway — connect later
187
+ </button>
188
+ <button
189
+ type="submit"
190
+ style={{
191
+ fontSize: '13px',
192
+ color: '#9ca3af',
193
+ background: 'none',
194
+ border: '1px solid #374151',
195
+ borderRadius: '6px',
196
+ padding: '4px 12px',
197
+ cursor: 'pointer',
198
+ }}
199
+ >
200
+ Retry
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ )}
162
207
 
163
- {!unreachable && (
164
- <button
165
- type="submit"
166
- disabled={testing || !url.trim()}
167
- style={{
168
- width: '100%',
169
- display: 'flex',
170
- alignItems: 'center',
171
- justifyContent: 'center',
172
- gap: '8px',
173
- borderRadius: '8px',
174
- background: '#4f46e5',
175
- padding: '12px 20px',
176
- fontWeight: '500',
177
- color: 'white',
178
- border: 'none',
179
- cursor: testing || !url.trim() ? 'default' : 'pointer',
180
- opacity: testing || !url.trim() ? 0.5 : 1,
181
- fontSize: '14px',
182
- marginBottom: '12px',
183
- }}
184
- >
185
- {testing ? (
186
- <>
187
- <Loader2 style={{ width: '16px', height: '16px', animation: 'spin 1s linear infinite' }} />
188
- Testing connection...
189
- </>
190
- ) : (
191
- <>
192
- Connect
193
- <ArrowRight style={{ width: '16px', height: '16px' }} />
194
- </>
195
- )}
196
- </button>
197
- )}
198
- </form>
208
+ {!unreachable && (
209
+ <button
210
+ type="submit"
211
+ disabled={testing || !url.trim()}
212
+ style={{
213
+ width: '100%',
214
+ display: 'flex',
215
+ alignItems: 'center',
216
+ justifyContent: 'center',
217
+ gap: '8px',
218
+ borderRadius: '8px',
219
+ background: '#4f46e5',
220
+ padding: '12px 20px',
221
+ fontWeight: '500',
222
+ color: 'white',
223
+ border: 'none',
224
+ cursor: testing || !url.trim() ? 'default' : 'pointer',
225
+ opacity: testing || !url.trim() ? 0.5 : 1,
226
+ fontSize: '14px',
227
+ marginBottom: '12px',
228
+ }}
229
+ >
230
+ {testing ? (
231
+ <>
232
+ <Loader2
233
+ style={{ width: '16px', height: '16px', animation: 'spin 1s linear infinite' }}
234
+ />
235
+ Testing connection...
236
+ </>
237
+ ) : (
238
+ <>
239
+ Connect
240
+ <ArrowRight style={{ width: '16px', height: '16px' }} />
241
+ </>
242
+ )}
243
+ </button>
244
+ )}
245
+ </form>
199
246
 
200
- {/* Skip option */}
201
- <div style={{ textAlign: 'center', marginTop: '24px' }}>
202
- <button
203
- onClick={onSkip}
204
- style={{
205
- background: 'none',
206
- border: 'none',
207
- color: '#6b7280',
208
- cursor: 'pointer',
209
- fontSize: '14px',
210
- textDecoration: 'underline',
211
- textUnderlineOffset: '3px',
212
- }}
213
- >
214
- Skip — use offline only
215
- </button>
216
- <p style={{ color: '#374151', fontSize: '12px', marginTop: '8px' }}>
217
- You can connect to a sync server later from settings.
218
- </p>
219
- </div>
247
+ {/* Skip option */}
248
+ <div style={{ textAlign: 'center', marginTop: '24px' }}>
249
+ <button
250
+ onClick={onSkip}
251
+ style={{
252
+ background: 'none',
253
+ border: 'none',
254
+ color: '#6b7280',
255
+ cursor: 'pointer',
256
+ fontSize: '14px',
257
+ textDecoration: 'underline',
258
+ textUnderlineOffset: '3px',
259
+ }}
260
+ >
261
+ Skip — use offline only
262
+ </button>
263
+ <p style={{ color: '#374151', fontSize: '12px', marginTop: '8px' }}>
264
+ You can connect to a sync server later from settings.
265
+ </p>
266
+ </div>
220
267
 
221
- {/* Help text */}
222
- <div style={{
223
- marginTop: '48px',
224
- padding: '16px',
225
- borderRadius: '8px',
226
- border: '1px solid #1f2937',
227
- background: '#111827',
228
- }}>
229
- <p style={{ fontSize: '13px', color: '#9ca3af', lineHeight: '1.6' }}>
230
- <strong style={{ color: '#d1d5db' }}>Don't have a server URL?</strong>
231
- <br />
232
- Ask your administrator for the sync server address. For local development,
233
- use <code style={{ color: '#818cf8' }}>ws://localhost:3001/kora-sync</code>.
234
- </p>
235
- </div>
236
- </div>
237
- </div>
238
- )
268
+ {/* Help text */}
269
+ <div
270
+ style={{
271
+ marginTop: '48px',
272
+ padding: '16px',
273
+ borderRadius: '8px',
274
+ border: '1px solid #1f2937',
275
+ background: '#111827',
276
+ }}
277
+ >
278
+ <p style={{ fontSize: '13px', color: '#9ca3af', lineHeight: '1.6' }}>
279
+ <strong style={{ color: '#d1d5db' }}>Don't have a server URL?</strong>
280
+ <br />
281
+ Ask your administrator for the sync server address. For local development, use{' '}
282
+ <code style={{ color: '#818cf8' }}>ws://localhost:3001/kora-sync</code>.
283
+ </p>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ )
239
288
  }
@@ -0,0 +1,45 @@
1
+ import { type AuthClient, createKoraAuth } from '@korajs/auth'
2
+
3
+ export function createDesktopAuthClient(syncUrl: string | null): AuthClient {
4
+ return createKoraAuth({
5
+ serverUrl: getAuthServerUrl(syncUrl),
6
+ })
7
+ }
8
+
9
+ export function getAuthServerUrl(syncUrl: string | null): string {
10
+ if (import.meta.env.VITE_AUTH_URL) {
11
+ return import.meta.env.VITE_AUTH_URL
12
+ }
13
+ if (!syncUrl) {
14
+ return 'http://localhost:3001'
15
+ }
16
+
17
+ const url = new URL(syncUrl)
18
+ url.protocol = url.protocol === 'wss:' ? 'https:' : 'http:'
19
+ url.pathname = url.pathname.replace(/\/kora-sync\/?$/, '') || '/'
20
+ url.search = ''
21
+ url.hash = ''
22
+ return url.toString().replace(/\/+$/, '')
23
+ }
24
+
25
+ export async function openOAuthSignIn(authClient: AuthClient, provider = 'google'): Promise<void> {
26
+ const { url } = await authClient.getOAuthAuthorizationUrl(provider)
27
+ window.open(url, '_blank', 'noopener,noreferrer')
28
+ }
29
+
30
+ export async function completeOAuthCallbackFromLocation(authClient: AuthClient): Promise<void> {
31
+ const url = new URL(window.location.href)
32
+ const code = url.searchParams.get('code')
33
+ const state = url.searchParams.get('state')
34
+ const provider = url.searchParams.get('provider') || 'google'
35
+
36
+ if (!code || !state) {
37
+ return
38
+ }
39
+
40
+ await authClient.completeOAuthSignIn(provider, { code, state })
41
+ url.searchParams.delete('code')
42
+ url.searchParams.delete('state')
43
+ url.searchParams.delete('provider')
44
+ window.history.replaceState({}, document.title, `${url.pathname}${url.search}${url.hash}`)
45
+ }
@@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client'
3
3
  import { AppShell } from './AppShell'
4
4
 
5
5
  createRoot(document.getElementById('root')!).render(
6
- <StrictMode>
7
- <AppShell />
8
- </StrictMode>,
6
+ <StrictMode>
7
+ <AppShell />
8
+ </StrictMode>,
9
9
  )
@@ -0,0 +1,25 @@
1
+ import type { CollectionAccessor } from 'korajs'
2
+
3
+ export interface CreateTodoInput {
4
+ title: string
5
+ }
6
+
7
+ export interface UpdateTodoStatusInput {
8
+ completed: boolean
9
+ }
10
+
11
+ export function createTodo(todos: CollectionAccessor, data: CreateTodoInput) {
12
+ return todos.insert({ title: data.title })
13
+ }
14
+
15
+ export function updateTodoStatus(
16
+ todos: CollectionAccessor,
17
+ id: string,
18
+ data: UpdateTodoStatusInput,
19
+ ) {
20
+ return todos.update(id, { completed: data.completed })
21
+ }
22
+
23
+ export function deleteTodo(todos: CollectionAccessor, id: string) {
24
+ return todos.delete(id)
25
+ }
@@ -0,0 +1,5 @@
1
+ import type { CollectionAccessor } from 'korajs'
2
+
3
+ export function orderedTodos(todos: CollectionAccessor) {
4
+ return todos.where({}).orderBy('createdAt', 'desc')
5
+ }
@@ -0,0 +1,10 @@
1
+ import { t } from '@korajs/core'
2
+
3
+ export const todos = {
4
+ fields: {
5
+ title: t.string(),
6
+ completed: t.boolean().default(false),
7
+ createdAt: t.timestamp().auto(),
8
+ },
9
+ indexes: ['completed', 'createdAt'],
10
+ }
@@ -0,0 +1,31 @@
1
+ import { useCollection, useMutation, useQuery } from '@korajs/react'
2
+ import {
3
+ type CreateTodoInput,
4
+ type UpdateTodoStatusInput,
5
+ createTodo,
6
+ deleteTodo,
7
+ updateTodoStatus,
8
+ } from './todo.mutations'
9
+ import { orderedTodos } from './todo.queries'
10
+
11
+ export function useTodos() {
12
+ const todos = useCollection('todos')
13
+ const allTodos = useQuery(orderedTodos(todos))
14
+ const addTodo = useMutation((data: CreateTodoInput) => createTodo(todos, data))
15
+ const toggleTodo = useMutation((id: string, data: UpdateTodoStatusInput) =>
16
+ updateTodoStatus(todos, id, data),
17
+ )
18
+ const removeTodo = useMutation((id: string) => deleteTodo(todos, id))
19
+
20
+ const activeTodos = allTodos.filter((todo) => !todo.completed)
21
+ const completedTodos = allTodos.filter((todo) => !!todo.completed)
22
+
23
+ return {
24
+ allTodos,
25
+ activeTodos,
26
+ completedTodos,
27
+ addTodo,
28
+ toggleTodo,
29
+ deleteTodo: removeTodo,
30
+ }
31
+ }
@@ -1,15 +1,9 @@
1
- import { defineSchema, t } from '@korajs/core'
1
+ import { defineSchema } from '@korajs/core'
2
+ import { todos } from './modules/todos/todo.schema'
2
3
 
3
4
  export default defineSchema({
4
- version: 1,
5
- collections: {
6
- todos: {
7
- fields: {
8
- title: t.string(),
9
- completed: t.boolean().default(false),
10
- createdAt: t.timestamp().auto(),
11
- },
12
- indexes: ['completed'],
13
- },
14
- },
5
+ version: 1,
6
+ collections: {
7
+ todos,
8
+ },
15
9
  })