@stacksjs/defaults 0.75.231 → 0.75.233

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 (41) hide show
  1. package/ai/skills/stacks-native/CATALOG.md +4 -4
  2. package/ai/skills/stacks-storage/SKILL.md +43 -0
  3. package/ai/skills/stacks-testing/SKILL.md +38 -10
  4. package/functions/dashboard-form.test.ts +41 -0
  5. package/functions/dashboard-form.ts +30 -0
  6. package/functions/dashboard-list.test.ts +228 -0
  7. package/functions/dashboard-list.ts +257 -0
  8. package/ide/vscode/package.json +1 -1
  9. package/package.json +2 -2
  10. package/project/storage/framework/types/browser-composables.d.ts +56 -0
  11. package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +2 -7
  12. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +2 -4
  13. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +2 -4
  14. package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +2 -7
  15. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +2 -4
  16. package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +2 -7
  17. package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +2 -4
  18. package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +2 -7
  19. package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +2 -4
  20. package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +2 -4
  21. package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +2 -7
  22. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +2 -4
  23. package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +2 -7
  24. package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +2 -4
  25. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +2 -4
  26. package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +2 -4
  27. package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +2 -4
  28. package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +45 -92
  29. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +46 -92
  30. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +46 -92
  31. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +44 -90
  32. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +45 -91
  33. package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +2 -4
  34. package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +2 -7
  35. package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +2 -4
  36. package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +2 -4
  37. package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +2 -9
  38. package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +2 -9
  39. package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +2 -9
  40. package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +2 -9
  41. package/resources/plugins/preloader.ts +9 -1
@@ -1,4 +1,5 @@
1
1
  <script client>
2
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
2
3
  import type {
3
4
  ProductUnitOption,
4
5
  ProductUnitRecord,
@@ -52,10 +53,7 @@ function submit(): void {
52
53
  }
53
54
 
54
55
  function errorMessages(): string[] {
55
- return Object.entries(errors()).map(([field, message]) => {
56
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
57
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
58
- })
56
+ return formErrorMessages(errors())
59
57
  }
60
58
  </script>
61
59
 
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import type { ProductVariantOption, ProductVariantRecord } from '../../../../app/Actions/Dashboard/Commerce/product-variant-records'
3
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
3
4
 
4
5
  const open = useReactiveProp('open', false)
5
6
  const mode = useReactiveProp('mode', 'add')
@@ -49,10 +50,7 @@ function submit(): void {
49
50
  }
50
51
 
51
52
  function errorMessages(): string[] {
52
- return Object.entries(errors()).map(([field, message]) => {
53
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
54
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
55
- })
53
+ return formErrorMessages(errors())
56
54
  }
57
55
  </script>
58
56
 
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import { dashboardApi } from '../../../../../functions/dashboard-api'
3
+ import { useRecordsDashboard } from '../../../../../functions/dashboard-list'
3
4
  import { pushToast } from '../../../../../functions/toasts'
4
5
 
5
6
  type CourierStatus = 'active' | 'on_delivery' | 'on_break'
@@ -34,21 +35,48 @@ interface CourierPayload {
34
35
  userId: number | null
35
36
  }
36
37
 
37
- const records = state<CourierRecord[]>([])
38
+ function matchesQuery(records: CourierRecord[], query: string): CourierRecord[] {
39
+ return records.filter(record => [
40
+ record.name,
41
+ record.phone,
42
+ record.vehicle_number,
43
+ record.license,
44
+ record.status,
45
+ record.user?.name,
46
+ record.user?.email,
47
+ ].some(value => String(value || '').toLowerCase().includes(query)))
48
+ }
49
+
50
+ const {
51
+ records,
52
+ loading,
53
+ loadError,
54
+ search,
55
+ page,
56
+ perPage,
57
+ editorOpen,
58
+ editorMode,
59
+ editing,
60
+ saving,
61
+ editorError,
62
+ deleting,
63
+ deleteBusy,
64
+ deleteError,
65
+ filteredRecords,
66
+ paginatedRecords,
67
+ goToPage,
68
+ handleSearch,
69
+ openCreate,
70
+ openEdit,
71
+ closeEditor,
72
+ requestDelete,
73
+ closeDelete,
74
+ upsert,
75
+ remove,
76
+ clampPage,
77
+ } = useRecordsDashboard<CourierRecord>({ filter: matchesQuery })
78
+
38
79
  const users = state<UserOption[]>([])
39
- const loading = state(true)
40
- const loadError = state('')
41
- const search = state('')
42
- const page = state(1)
43
- const perPage = state(10)
44
- const editorOpen = state(false)
45
- const editorMode = state<'create' | 'edit'>('create')
46
- const editing = state<CourierRecord | null>(null)
47
- const saving = state(false)
48
- const editorError = state('')
49
- const deleting = state<CourierRecord | null>(null)
50
- const deleteBusy = state(false)
51
- const deleteError = state('')
52
80
 
53
81
  function userFor(id?: number | null): UserOption | undefined {
54
82
  return id ? users().find(user => user.id === id) : undefined
@@ -61,26 +89,6 @@ function withUser(record: CourierRecord): CourierRecord {
61
89
  }
62
90
  }
63
91
 
64
- function filteredRecords(): CourierRecord[] {
65
- const query = search().trim().toLowerCase()
66
- if (!query)
67
- return records()
68
- return records().filter(record => [
69
- record.name,
70
- record.phone,
71
- record.vehicle_number,
72
- record.license,
73
- record.status,
74
- record.user?.name,
75
- record.user?.email,
76
- ].some(value => String(value || '').toLowerCase().includes(query)))
77
- }
78
-
79
- function paginatedRecords(): CourierRecord[] {
80
- const start = (page() - 1) * perPage()
81
- return filteredRecords().slice(start, start + perPage())
82
- }
83
-
84
92
  function statusCount(status: CourierStatus): number {
85
93
  return records().filter(record => record.status === status).length
86
94
  }
@@ -106,9 +114,7 @@ async function loadCouriers(): Promise<void> {
106
114
  throw new TypeError('Courier resources were not returned in the expected shape.')
107
115
  users.set(userData.users)
108
116
  records.set(courierData.map(withUser))
109
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
110
- if (page() > lastPage)
111
- page.set(lastPage)
117
+ clampPage()
112
118
  }
113
119
  catch (error) {
114
120
  loadError.set(error instanceof Error ? error.message : String(error))
@@ -119,33 +125,6 @@ async function loadCouriers(): Promise<void> {
119
125
  }
120
126
  }
121
127
 
122
- function handleSearch(value: string): void {
123
- search.set(value)
124
- page.set(1)
125
- }
126
-
127
- function openCreate(): void {
128
- editing.set(null)
129
- editorMode.set('create')
130
- editorError.set('')
131
- editorOpen.set(true)
132
- }
133
-
134
- function openEdit(record: CourierRecord): void {
135
- editing.set(record)
136
- editorMode.set('edit')
137
- editorError.set('')
138
- editorOpen.set(true)
139
- }
140
-
141
- function closeEditor(): void {
142
- if (saving())
143
- return
144
- editorOpen.set(false)
145
- editing.set(null)
146
- editorError.set('')
147
- }
148
-
149
128
  function setEditorError(message: string): void {
150
129
  editorError.set(message)
151
130
  }
@@ -165,14 +144,7 @@ async function saveCourier(payload: CourierPayload): Promise<void> {
165
144
  },
166
145
  )
167
146
  const hydrated = withUser(updated)
168
- if (current) {
169
- records.set(records().map(record => record.id === current.id ? hydrated : record))
170
- }
171
- else {
172
- const nextRecords = [...records(), hydrated]
173
- records.set(nextRecords)
174
- page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
175
- }
147
+ upsert(hydrated, current)
176
148
  editorOpen.set(false)
177
149
  editing.set(null)
178
150
  pushToast('success', current ? 'Courier updated' : 'Courier added', { detail: hydrated.name })
@@ -185,18 +157,6 @@ async function saveCourier(payload: CourierPayload): Promise<void> {
185
157
  }
186
158
  }
187
159
 
188
- function requestDelete(record: CourierRecord): void {
189
- deleting.set(record)
190
- deleteError.set('')
191
- }
192
-
193
- function closeDelete(): void {
194
- if (deleteBusy())
195
- return
196
- deleting.set(null)
197
- deleteError.set('')
198
- }
199
-
200
160
  async function confirmDelete(): Promise<void> {
201
161
  const current = deleting()
202
162
  if (!current)
@@ -207,11 +167,8 @@ async function confirmDelete(): Promise<void> {
207
167
  await dashboardApi(`/api/dashboard/commerce/couriers/${current.id}`, {
208
168
  method: 'DELETE',
209
169
  })
210
- records.set(records().filter(record => record.id !== current.id))
170
+ remove(current)
211
171
  deleting.set(null)
212
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
213
- if (page() > lastPage)
214
- page.set(lastPage)
215
172
  pushToast('success', 'Courier deleted', { detail: current.name })
216
173
  }
217
174
  catch (error) {
@@ -222,10 +179,6 @@ async function confirmDelete(): Promise<void> {
222
179
  }
223
180
  }
224
181
 
225
- function goToPage(value: number): void {
226
- page.set(value)
227
- }
228
-
229
182
  onMount(() => {
230
183
  void loadCouriers()
231
184
  })
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import { dashboardApi } from '../../../../../functions/dashboard-api'
3
+ import { useRecordsDashboard } from '../../../../../functions/dashboard-list'
3
4
  import { pushToast } from '../../../../../functions/toasts'
4
5
 
5
6
  interface CourierRecord {
@@ -31,21 +32,48 @@ interface DeliveryRoutePayload {
31
32
  lastActive: number
32
33
  }
33
34
 
34
- const records = state<DeliveryRouteRecord[]>([])
35
+ function matchesQuery(records: DeliveryRouteRecord[], query: string): DeliveryRouteRecord[] {
36
+ return records.filter(record => [
37
+ record.id,
38
+ record.courier,
39
+ record.courier_record?.name,
40
+ record.vehicle,
41
+ record.stops,
42
+ record.delivery_time,
43
+ record.total_distance,
44
+ ].some(value => String(value || '').toLowerCase().includes(query)))
45
+ }
46
+
47
+ const {
48
+ records,
49
+ loading,
50
+ loadError,
51
+ search,
52
+ page,
53
+ perPage,
54
+ editorOpen,
55
+ editorMode,
56
+ editing,
57
+ saving,
58
+ editorError,
59
+ deleting,
60
+ deleteBusy,
61
+ deleteError,
62
+ filteredRecords,
63
+ paginatedRecords,
64
+ goToPage,
65
+ handleSearch,
66
+ openCreate,
67
+ openEdit,
68
+ closeEditor,
69
+ requestDelete,
70
+ closeDelete,
71
+ upsert,
72
+ remove,
73
+ clampPage,
74
+ } = useRecordsDashboard<DeliveryRouteRecord>({ filter: matchesQuery })
75
+
35
76
  const couriers = state<CourierRecord[]>([])
36
- const loading = state(true)
37
- const loadError = state('')
38
- const search = state('')
39
- const page = state(1)
40
- const perPage = state(10)
41
- const editorOpen = state(false)
42
- const editorMode = state<'create' | 'edit'>('create')
43
- const editing = state<DeliveryRouteRecord | null>(null)
44
- const saving = state(false)
45
- const editorError = state('')
46
- const deleting = state<DeliveryRouteRecord | null>(null)
47
- const deleteBusy = state(false)
48
- const deleteError = state('')
49
77
 
50
78
  function courierFor(id?: number | null): CourierRecord | undefined {
51
79
  return id ? couriers().find(courier => courier.id === id) : undefined
@@ -64,26 +92,6 @@ function routeTime(value: string | number): number {
64
92
  return new Date(String(value).replace(' ', 'T')).getTime()
65
93
  }
66
94
 
67
- function filteredRecords(): DeliveryRouteRecord[] {
68
- const query = search().trim().toLowerCase()
69
- if (!query)
70
- return records()
71
- return records().filter(record => [
72
- record.id,
73
- record.courier,
74
- record.courier_record?.name,
75
- record.vehicle,
76
- record.stops,
77
- record.delivery_time,
78
- record.total_distance,
79
- ].some(value => String(value || '').toLowerCase().includes(query)))
80
- }
81
-
82
- function paginatedRecords(): DeliveryRouteRecord[] {
83
- const start = (page() - 1) * perPage()
84
- return filteredRecords().slice(start, start + perPage())
85
- }
86
-
87
95
  function metricCards(): Array<{ label: string, value: string, detail: string }> {
88
96
  const totalStops = records().reduce((sum, record) => sum + record.stops, 0)
89
97
  const totalDistance = records().reduce((sum, record) => sum + record.total_distance, 0)
@@ -109,9 +117,7 @@ async function loadRoutes(): Promise<void> {
109
117
  throw new TypeError('Delivery route resources were not returned in the expected shape.')
110
118
  couriers.set(courierData)
111
119
  records.set(routeData.map(withCourier))
112
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
113
- if (page() > lastPage)
114
- page.set(lastPage)
120
+ clampPage()
115
121
  }
116
122
  catch (error) {
117
123
  loadError.set(error instanceof Error ? error.message : String(error))
@@ -122,33 +128,6 @@ async function loadRoutes(): Promise<void> {
122
128
  }
123
129
  }
124
130
 
125
- function handleSearch(value: string): void {
126
- search.set(value)
127
- page.set(1)
128
- }
129
-
130
- function openCreate(): void {
131
- editing.set(null)
132
- editorMode.set('create')
133
- editorError.set('')
134
- editorOpen.set(true)
135
- }
136
-
137
- function openEdit(record: DeliveryRouteRecord): void {
138
- editing.set(record)
139
- editorMode.set('edit')
140
- editorError.set('')
141
- editorOpen.set(true)
142
- }
143
-
144
- function closeEditor(): void {
145
- if (saving())
146
- return
147
- editorOpen.set(false)
148
- editing.set(null)
149
- editorError.set('')
150
- }
151
-
152
131
  function setEditorError(message: string): void {
153
132
  editorError.set(message)
154
133
  }
@@ -168,14 +147,7 @@ async function saveRoute(payload: DeliveryRoutePayload): Promise<void> {
168
147
  },
169
148
  )
170
149
  const hydrated = withCourier(updated)
171
- if (current) {
172
- records.set(records().map(record => record.id === current.id ? hydrated : record))
173
- }
174
- else {
175
- const nextRecords = [...records(), hydrated]
176
- records.set(nextRecords)
177
- page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
178
- }
150
+ upsert(hydrated, current)
179
151
  editorOpen.set(false)
180
152
  editing.set(null)
181
153
  pushToast('success', current ? 'Delivery route updated' : 'Delivery route added', { detail: `Route #${hydrated.id}` })
@@ -188,18 +160,6 @@ async function saveRoute(payload: DeliveryRoutePayload): Promise<void> {
188
160
  }
189
161
  }
190
162
 
191
- function requestDelete(record: DeliveryRouteRecord): void {
192
- deleting.set(record)
193
- deleteError.set('')
194
- }
195
-
196
- function closeDelete(): void {
197
- if (deleteBusy())
198
- return
199
- deleting.set(null)
200
- deleteError.set('')
201
- }
202
-
203
163
  async function confirmDelete(): Promise<void> {
204
164
  const current = deleting()
205
165
  if (!current)
@@ -210,11 +170,9 @@ async function confirmDelete(): Promise<void> {
210
170
  await dashboardApi(`/api/dashboard/commerce/delivery-routes/${current.id}`, {
211
171
  method: 'DELETE',
212
172
  })
213
- records.set(records().filter(record => record.id !== current.id))
173
+ remove(current)
214
174
  deleting.set(null)
215
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
216
- if (page() > lastPage)
217
- page.set(lastPage)
175
+ clampPage()
218
176
  pushToast('success', 'Delivery route deleted', { detail: `Route #${current.id}` })
219
177
  }
220
178
  catch (error) {
@@ -225,10 +183,6 @@ async function confirmDelete(): Promise<void> {
225
183
  }
226
184
  }
227
185
 
228
- function goToPage(value: number): void {
229
- page.set(value)
230
- }
231
-
232
186
  onMount(() => {
233
187
  void loadRoutes()
234
188
  })
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import { dashboardApi } from '../../../../../functions/dashboard-api'
3
+ import { useRecordsDashboard } from '../../../../../functions/dashboard-list'
3
4
  import { pushToast } from '../../../../../functions/toasts'
4
5
 
5
6
  type LicenseKeyStatus = 'active' | 'inactive' | 'unassigned'
@@ -42,21 +43,48 @@ interface LicenseKeyPayload {
42
43
  }
43
44
 
44
45
  const emptyOptions: LicenseKeyOptions = { customers: [], products: [], orders: [] }
45
- const records = state<LicenseKeyRecord[]>([])
46
+ function matchesQuery(records: LicenseKeyRecord[], query: string): LicenseKeyRecord[] {
47
+ return records.filter(record => [
48
+ record.key,
49
+ record.template,
50
+ record.status,
51
+ record.customer?.label,
52
+ record.customer?.detail,
53
+ record.product?.label,
54
+ record.order?.label,
55
+ ].some(value => String(value || '').toLowerCase().includes(query)))
56
+ }
57
+
58
+ const {
59
+ records,
60
+ loading,
61
+ loadError,
62
+ search,
63
+ page,
64
+ perPage,
65
+ editorOpen,
66
+ editorMode,
67
+ editing,
68
+ saving,
69
+ editorError,
70
+ deleting,
71
+ deleteBusy,
72
+ deleteError,
73
+ filteredRecords,
74
+ paginatedRecords,
75
+ goToPage,
76
+ handleSearch,
77
+ openCreate,
78
+ openEdit,
79
+ closeEditor,
80
+ requestDelete,
81
+ closeDelete,
82
+ upsert,
83
+ remove,
84
+ clampPage,
85
+ } = useRecordsDashboard<LicenseKeyRecord>({ filter: matchesQuery })
86
+
46
87
  const options = state<LicenseKeyOptions>(emptyOptions)
47
- const loading = state(true)
48
- const loadError = state('')
49
- const search = state('')
50
- const page = state(1)
51
- const perPage = state(10)
52
- const editorOpen = state(false)
53
- const editorMode = state<'create' | 'edit'>('create')
54
- const editing = state<LicenseKeyRecord | null>(null)
55
- const saving = state(false)
56
- const editorError = state('')
57
- const deleting = state<LicenseKeyRecord | null>(null)
58
- const deleteBusy = state(false)
59
- const deleteError = state('')
60
88
 
61
89
  function optionFor(items: RelatedSummary[], id?: number | null): RelatedSummary | null {
62
90
  return id ? items.find(item => item.id === id) || null : null
@@ -78,26 +106,6 @@ function dateTime(value: string): number {
78
106
  return new Date(value.replace(' ', 'T')).getTime()
79
107
  }
80
108
 
81
- function filteredRecords(): LicenseKeyRecord[] {
82
- const query = search().trim().toLowerCase()
83
- if (!query)
84
- return records()
85
- return records().filter(record => [
86
- record.key,
87
- record.template,
88
- record.status,
89
- record.customer?.label,
90
- record.customer?.detail,
91
- record.product?.label,
92
- record.order?.label,
93
- ].some(value => String(value || '').toLowerCase().includes(query)))
94
- }
95
-
96
- function paginatedRecords(): LicenseKeyRecord[] {
97
- const start = (page() - 1) * perPage()
98
- return filteredRecords().slice(start, start + perPage())
99
- }
100
-
101
109
  function metricCards(): Array<{ label: string, value: string, detail: string }> {
102
110
  const expiryCutoff = Date.now() + (30 * 24 * 60 * 60 * 1000)
103
111
  const expiring = records().filter((record) => {
@@ -124,9 +132,7 @@ async function loadKeys(): Promise<void> {
124
132
  throw new TypeError('License key resources were not returned in the expected shape.')
125
133
  options.set(optionData)
126
134
  records.set(keyData.map(withRelationships))
127
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
128
- if (page() > lastPage)
129
- page.set(lastPage)
135
+ clampPage()
130
136
  }
131
137
  catch (error) {
132
138
  loadError.set(error instanceof Error ? error.message : String(error))
@@ -137,33 +143,6 @@ async function loadKeys(): Promise<void> {
137
143
  }
138
144
  }
139
145
 
140
- function handleSearch(value: string): void {
141
- search.set(value)
142
- page.set(1)
143
- }
144
-
145
- function openCreate(): void {
146
- editing.set(null)
147
- editorMode.set('create')
148
- editorError.set('')
149
- editorOpen.set(true)
150
- }
151
-
152
- function openEdit(record: LicenseKeyRecord): void {
153
- editing.set(record)
154
- editorMode.set('edit')
155
- editorError.set('')
156
- editorOpen.set(true)
157
- }
158
-
159
- function closeEditor(): void {
160
- if (saving())
161
- return
162
- editorOpen.set(false)
163
- editing.set(null)
164
- editorError.set('')
165
- }
166
-
167
146
  function setEditorError(message: string): void {
168
147
  editorError.set(message)
169
148
  }
@@ -183,14 +162,7 @@ async function saveKey(payload: LicenseKeyPayload): Promise<void> {
183
162
  },
184
163
  )
185
164
  const hydrated = withRelationships(updated)
186
- if (current) {
187
- records.set(records().map(record => record.id === current.id ? hydrated : record))
188
- }
189
- else {
190
- const nextRecords = [...records(), hydrated]
191
- records.set(nextRecords)
192
- page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
193
- }
165
+ upsert(hydrated, current)
194
166
  editorOpen.set(false)
195
167
  editing.set(null)
196
168
  pushToast('success', current ? 'License key updated' : 'License key added', { detail: hydrated.key })
@@ -203,18 +175,6 @@ async function saveKey(payload: LicenseKeyPayload): Promise<void> {
203
175
  }
204
176
  }
205
177
 
206
- function requestDelete(record: LicenseKeyRecord): void {
207
- deleting.set(record)
208
- deleteError.set('')
209
- }
210
-
211
- function closeDelete(): void {
212
- if (deleteBusy())
213
- return
214
- deleting.set(null)
215
- deleteError.set('')
216
- }
217
-
218
178
  async function confirmDelete(): Promise<void> {
219
179
  const current = deleting()
220
180
  if (!current)
@@ -225,11 +185,9 @@ async function confirmDelete(): Promise<void> {
225
185
  await dashboardApi(`/api/dashboard/commerce/license-keys/${current.id}`, {
226
186
  method: 'DELETE',
227
187
  })
228
- records.set(records().filter(record => record.id !== current.id))
188
+ remove(current)
229
189
  deleting.set(null)
230
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
231
- if (page() > lastPage)
232
- page.set(lastPage)
190
+ clampPage()
233
191
  pushToast('success', 'License key deleted', { detail: current.key })
234
192
  }
235
193
  catch (error) {
@@ -240,10 +198,6 @@ async function confirmDelete(): Promise<void> {
240
198
  }
241
199
  }
242
200
 
243
- function goToPage(value: number): void {
244
- page.set(value)
245
- }
246
-
247
201
  onMount(() => {
248
202
  void loadKeys()
249
203
  })