includio-cms 0.0.68 → 0.1.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/CHANGELOG.md +181 -0
- package/ROADMAP.md +92 -0
- package/dist/admin/client/account/lang.d.ts +0 -5
- package/dist/admin/client/account/lang.js +2 -12
- package/dist/admin/client/account/preferences-section.svelte +27 -24
- package/dist/admin/client/account/profile-section.svelte +11 -15
- package/dist/admin/client/account/security-section.svelte +0 -11
- package/dist/admin/client/account/sessions-section.svelte +2 -1
- package/dist/admin/client/collection/collection-entries.svelte +168 -43
- package/dist/admin/client/collection/collection-view.svelte.d.ts +5 -0
- package/dist/admin/client/collection/collection-view.svelte.js +22 -5
- package/dist/admin/client/collection/collection.svelte +2 -2
- package/dist/admin/client/collection/data-table.svelte +15 -3
- package/dist/admin/client/collection/data-table.svelte.d.ts +3 -0
- package/dist/admin/client/entry/entry.svelte +11 -7
- package/dist/admin/client/entry/header/status-badge.svelte +6 -3
- package/dist/admin/client/entry/header/version-history-sheet.svelte +6 -3
- package/dist/admin/client/form/form-submissions.svelte +3 -26
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +2 -12
- package/dist/admin/components/dashboard/recent-activity.svelte +2 -12
- package/dist/admin/components/fields/array-field.svelte +126 -71
- package/dist/admin/components/fields/relation-field.svelte +6 -10
- package/dist/admin/components/layout/header-actions.svelte +4 -3
- package/dist/admin/components/layout/nav-search.svelte +43 -31
- package/dist/admin/components/media/media-library.svelte +17 -6
- package/dist/admin/remote/entry.remote.d.ts +10 -1
- package/dist/admin/remote/entry.remote.js +16 -4
- package/dist/admin/state/interface-language.svelte.d.ts +4 -7
- package/dist/admin/state/interface-language.svelte.js +19 -18
- package/dist/admin/utils/arrayMove.d.ts +5 -0
- package/dist/admin/utils/arrayMove.js +12 -0
- package/dist/admin/utils/formatDate.d.ts +1 -0
- package/dist/admin/utils/formatDate.js +5 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/core/server/entries/operations/get.d.ts +2 -0
- package/dist/core/server/entries/operations/get.js +6 -0
- package/dist/db-postgres/index.js +26 -1
- package/dist/sveltekit/components/hybrid-context.d.ts +4 -0
- package/dist/sveltekit/components/hybrid-context.js +9 -0
- package/dist/sveltekit/components/hybrid-target.svelte +4 -1
- package/dist/sveltekit/components/image.svelte +5 -2
- package/dist/sveltekit/components/preview.svelte +3 -0
- package/dist/sveltekit/components/video.svelte +4 -1
- package/dist/sveltekit/index.d.ts +1 -0
- package/dist/sveltekit/index.js +1 -0
- package/dist/types/adapters/db.d.ts +3 -1
- package/dist/types/entries.d.ts +10 -2
- package/dist/types/languages.d.ts +2 -1
- package/dist/updates/0.0.69/index.d.ts +2 -0
- package/dist/updates/0.0.69/index.js +12 -0
- package/dist/updates/0.1.0/index.d.ts +2 -0
- package/dist/updates/0.1.0/index.js +25 -0
- package/dist/updates/index.js +3 -1
- package/package.json +7 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to includio-cms are documented here.
|
|
4
|
+
Generated from `src/lib/updates/` — do not edit manually.
|
|
5
|
+
|
|
6
|
+
## 0.1.0 — 2026-02-17
|
|
7
|
+
|
|
8
|
+
Stabilization — pagination, language switcher, and more
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Server-side pagination for collection entries (limit/offset/orderBy)
|
|
12
|
+
- Persistent page index and active tab across navigation
|
|
13
|
+
- countEntries adapter method for total count queries
|
|
14
|
+
- Automatic fallback to client-side pagination for search and name sorting
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Language switcher now reactive globally — all UI updates instantly without reload
|
|
18
|
+
- Removed hardcoded en/pl — locale list derived from Paraglide config
|
|
19
|
+
- Browser language auto-detected as default interface language
|
|
20
|
+
- Deduplicated formatRelativeDate across dashboard and form components
|
|
21
|
+
- Account profile: fixed name input not responding to typing
|
|
22
|
+
- Account preferences: added aria-pressed to theme/language toggles
|
|
23
|
+
- Removed placeholder 2FA stub from security section
|
|
24
|
+
- Media tag counts now display correct values — TagSidebar and BulkActionBar receive all files
|
|
25
|
+
- Hybrid target: data-hybrid-path no longer rendered on public pages — only in preview iframe
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
- getRawEntries remote now returns { entries: RawEntry[], total: number } instead of RawEntry[]
|
|
29
|
+
|
|
30
|
+
## 0.0.69 — 2026-02-17
|
|
31
|
+
|
|
32
|
+
DnD array reordering, nav-search batch fetch
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Drag-and-drop reordering in array fields with arrayMove utility
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Nav-search: batch-fetch data before rendering Command dialog — fixes missing data on open
|
|
39
|
+
|
|
40
|
+
## 0.0.68 — 2026-02-11
|
|
41
|
+
|
|
42
|
+
Fix EXIF/blur/focal bugs, expand type exports, add utility functions
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- Export Entry, EntryType, CMSConfig, CollectionConfig, SingleConfig, FormConfig, FormSubmission, Language, Localized, MediaFileType, ImageStyle, MediaTag from includio-cms/types
|
|
46
|
+
- Add getLink() utility for resolving URL field data to strings
|
|
47
|
+
- Add isImageFieldData() / isVideoFieldData() type guards for media fields
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- Blur placeholder (LQIP) now removed after image loads — fixes visible blur behind transparent PNGs
|
|
51
|
+
- Focal point change now awaits style regeneration before returning — fixes stale/missing styles after save
|
|
52
|
+
- Explicit .rotate() in sharp pipeline — fixes upside-down WebP/JPEG when source has EXIF orientation
|
|
53
|
+
- Use oriented dimensions (post-EXIF) for focal crop calculation — fixes wrong crop on rotated images
|
|
54
|
+
- Codegen: media field type now generates ImageFieldData | VideoFieldData instead of any
|
|
55
|
+
- Codegen: hyphenated collection slugs now produce correct PascalCase (article-category → ArticleCategory)
|
|
56
|
+
- Codegen: generated interfaces now include id and slug fields
|
|
57
|
+
- Preview deep merge now preserves resolved references without url (e.g. category relations)
|
|
58
|
+
|
|
59
|
+
## 0.0.67 — 2026-02-11
|
|
60
|
+
|
|
61
|
+
Image quality, LQIP, focal point, responsive srcset, auto-downscale
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
- Image styles: configurable quality parameter for format output
|
|
65
|
+
- LQIP: auto-generated blur placeholder (base64 WebP) on image upload
|
|
66
|
+
- LQIP: lazy backfill for existing images without blur placeholder
|
|
67
|
+
- Image component: LQIP blur placeholder via background-image on img element
|
|
68
|
+
- Focal point picker in media file details
|
|
69
|
+
- Smart crop: styles with crop=true use focal point to calculate crop region
|
|
70
|
+
- Style cache invalidation on focal point change
|
|
71
|
+
- Default WebP/AVIF styles auto-generate 640/1024/1920w srcset variants
|
|
72
|
+
- Custom styles support srcset[] and sizes config
|
|
73
|
+
- Image component renders srcset + sizes on <source> elements
|
|
74
|
+
- Image component: sizes prop to override style-level sizes on <source> elements
|
|
75
|
+
- Skips srcset widths larger than original image
|
|
76
|
+
- CMSConfig: media.maxOriginalWidth / maxOriginalHeight settings
|
|
77
|
+
- Auto-downscale oversized images on upload (fit: inside)
|
|
78
|
+
- SVG and non-image files are not affected
|
|
79
|
+
- Auto-format expansion: styles without explicit format auto-expand to AVIF/WebP/original
|
|
80
|
+
- Eager background generation of default image styles on upload/replace/focal-point change
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
- Case-insensitive SVG extension check in upload/downscale/LQIP
|
|
84
|
+
- Quality value clamped to 1-100 in generateImageStyle
|
|
85
|
+
- Silent errors replaced with console.warn for LQIP/downscale/file cleanup
|
|
86
|
+
- Remove hardcoded sizes: 100vw from default image styles
|
|
87
|
+
- Lazy load skeleton + error handling for hybrid layout imports
|
|
88
|
+
- AVIF <source> rendered before WebP for correct browser priority
|
|
89
|
+
- Cache-busting suffix in generated style filenames prevents stale CDN/browser cache
|
|
90
|
+
|
|
91
|
+
### Breaking
|
|
92
|
+
- getImageStyles() now returns { styles, blurDataUrl } instead of plain styles record
|
|
93
|
+
|
|
94
|
+
### Migration
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
ALTER TABLE image_styles ADD COLUMN IF NOT EXISTS quality INTEGER;
|
|
98
|
+
ALTER TABLE media_file ADD COLUMN IF NOT EXISTS blur_data_url TEXT;
|
|
99
|
+
ALTER TABLE media_file ADD COLUMN IF NOT EXISTS focal_x REAL;
|
|
100
|
+
ALTER TABLE media_file ADD COLUMN IF NOT EXISTS focal_y REAL;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 0.0.66 — 2025-02-10
|
|
104
|
+
|
|
105
|
+
Fix URL field validation for linked entries
|
|
106
|
+
|
|
107
|
+
### Fixed
|
|
108
|
+
- URL field: linked entries no longer require manual URL per language to save
|
|
109
|
+
|
|
110
|
+
## 0.0.65 — 2025-02-09
|
|
111
|
+
|
|
112
|
+
Move publish logic to entry table, replace folder-based media with tags
|
|
113
|
+
|
|
114
|
+
### Added
|
|
115
|
+
- Account settings page with session management
|
|
116
|
+
- Redesigned sidebar with collapsible groups
|
|
117
|
+
- Tag-based media library replacing folder structure
|
|
118
|
+
- Unified UI components and styling
|
|
119
|
+
|
|
120
|
+
### Breaking
|
|
121
|
+
- Media folders replaced with tags — existing folder assignments will be lost
|
|
122
|
+
|
|
123
|
+
### Migration
|
|
124
|
+
|
|
125
|
+
```sql
|
|
126
|
+
-- Move publish logic from entry_version to entry
|
|
127
|
+
|
|
128
|
+
ALTER TABLE entry ADD COLUMN published_at TIMESTAMP;
|
|
129
|
+
ALTER TABLE entry ADD COLUMN published_version_id UUID
|
|
130
|
+
REFERENCES entry_version(id) ON DELETE SET NULL;
|
|
131
|
+
ALTER TABLE entry ADD COLUMN published_by TEXT;
|
|
132
|
+
|
|
133
|
+
UPDATE entry e SET
|
|
134
|
+
published_version_id = latest.id,
|
|
135
|
+
published_at = first_pub.first_published_at,
|
|
136
|
+
published_by = latest.published_by
|
|
137
|
+
FROM (
|
|
138
|
+
SELECT DISTINCT ON (entry_id) id, entry_id, published_by
|
|
139
|
+
FROM entry_version
|
|
140
|
+
WHERE published_at IS NOT NULL AND published_at <= NOW()
|
|
141
|
+
ORDER BY entry_id, version_number DESC
|
|
142
|
+
) latest
|
|
143
|
+
JOIN (
|
|
144
|
+
SELECT entry_id, MIN(published_at) as first_published_at
|
|
145
|
+
FROM entry_version
|
|
146
|
+
WHERE published_at IS NOT NULL AND published_at <= NOW()
|
|
147
|
+
GROUP BY entry_id
|
|
148
|
+
) first_pub ON first_pub.entry_id = latest.entry_id
|
|
149
|
+
WHERE e.id = latest.entry_id;
|
|
150
|
+
|
|
151
|
+
UPDATE entry e SET
|
|
152
|
+
published_version_id = COALESCE(e.published_version_id, sub.id),
|
|
153
|
+
published_at = CASE WHEN e.published_at IS NULL THEN sub.published_at ELSE e.published_at END,
|
|
154
|
+
published_by = COALESCE(e.published_by, sub.published_by)
|
|
155
|
+
FROM (
|
|
156
|
+
SELECT DISTINCT ON (entry_id) id, entry_id, published_at, published_by
|
|
157
|
+
FROM entry_version
|
|
158
|
+
WHERE published_at IS NOT NULL AND published_at > NOW()
|
|
159
|
+
ORDER BY entry_id, published_at ASC
|
|
160
|
+
) sub
|
|
161
|
+
WHERE e.id = sub.entry_id AND e.published_version_id IS NULL;
|
|
162
|
+
|
|
163
|
+
-- Replace folder-based media with tag-based media
|
|
164
|
+
|
|
165
|
+
CREATE TABLE IF NOT EXISTS media_tag (
|
|
166
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
167
|
+
name TEXT NOT NULL UNIQUE,
|
|
168
|
+
color TEXT NOT NULL DEFAULT '#3b82f6',
|
|
169
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
CREATE TABLE IF NOT EXISTS media_file_tag (
|
|
173
|
+
file_id UUID NOT NULL REFERENCES media_file(id) ON DELETE CASCADE,
|
|
174
|
+
tag_id UUID NOT NULL REFERENCES media_tag(id) ON DELETE CASCADE,
|
|
175
|
+
PRIMARY KEY (file_id, tag_id)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
ALTER TABLE media_file DROP COLUMN IF EXISTS folder_id;
|
|
179
|
+
|
|
180
|
+
DROP TABLE IF EXISTS media_folder;
|
|
181
|
+
```
|
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
> `- [ ]` planned | `- [~]` in-progress | `- [x]` done
|
|
4
|
+
> `[feature]` `[fix]` `[breaking]` `[chore]`
|
|
5
|
+
> `[P0]` critical | `[P1]` important | `[P2]` nice-to-have
|
|
6
|
+
> `<!-- files: path/to/file.ts -->` optionally linked files
|
|
7
|
+
>
|
|
8
|
+
> **Versioning:** 0.0.69 is the last `0.0.x` release. From 0.1.0 onward: `0.MINOR.0` = features/changes, `0.MINOR.PATCH` = fixes.
|
|
9
|
+
|
|
10
|
+
## 0.0.69 _(last 0.0.x)_
|
|
11
|
+
|
|
12
|
+
- [x] `[feature]` `[P1]` DnD reordering in array fields + arrayMove utility <!-- files: src/cms/fields/array-field -->
|
|
13
|
+
- [x] `[fix]` `[P1]` Nav-search: batch-fetch data before rendering Command dialog <!-- files: src/cms/nav-search -->
|
|
14
|
+
|
|
15
|
+
## 0.1.0 — Stabilization
|
|
16
|
+
|
|
17
|
+
- [x] `[fix]` `[P0]` Collection table pagination — server-side, persistent page state, archived tab <!-- files: src/lib/admin/client/collection/collection-entries.svelte, src/lib/admin/client/collection/table-pagination.svelte -->
|
|
18
|
+
- [x] `[fix]` `[P0]` Language switcher — reactive globally without reload, remove hardcoded en/pl <!-- files: src/lib/admin/state/interface-language.svelte.ts, src/lib/admin/components/layout/header-actions.svelte -->
|
|
19
|
+
- [x] `[fix]` `[P0]` User account section — fix name input, aria-pressed on prefs, clean up 2FA stub (email change deferred to 0.1.2, avatar upload skipped) <!-- files: src/lib/admin/client/account/ -->
|
|
20
|
+
- [x] `[fix]` `[P0]` Media tag counts always 0 — pass actual files to TagSidebar <!-- files: src/lib/admin/components/media/media-library.svelte, src/lib/admin/components/media/tag-sidebar.svelte -->
|
|
21
|
+
- [x] `[fix]` `[P0]` Hybrid target — don't render `data-hybrid-path` for non-logged-in users <!-- files: src/lib/sveltekit/components/hybrid-target.svelte -->
|
|
22
|
+
|
|
23
|
+
## 0.1.1 — Input integrity
|
|
24
|
+
|
|
25
|
+
- [ ] `[fix]` `[P1]` Input constraints UI — HTML maxlength, character counter, pattern feedback <!-- files: src/lib/admin/components/fields/text-field.svelte -->
|
|
26
|
+
- [ ] `[fix]` `[P1]` Array field maxItems — disable Add button when max reached <!-- files: src/lib/admin/components/fields/array-field.svelte -->
|
|
27
|
+
- [ ] `[feature]` `[P1]` Array field fixed length — fixed item count, no add/remove, reorder only
|
|
28
|
+
- [ ] `[feature]` `[P1]` Field constraint info display — show constraints before validation error (WCAG/ATAG)
|
|
29
|
+
|
|
30
|
+
## 0.1.2 — User management & RBAC
|
|
31
|
+
|
|
32
|
+
### Phase 1 — Core
|
|
33
|
+
|
|
34
|
+
- [ ] `[feature]` `[P0]` RBAC middleware — `requireRole()`, role check w `requireAuth()` <!-- files: src/lib/admin/remote/middleware/auth.ts -->
|
|
35
|
+
- [ ] `[feature]` `[P0]` Admin users page — list, search, pagination via `authClient.admin.listUsers` <!-- files: src/lib/admin/client/users/ -->
|
|
36
|
+
- [ ] `[feature]` `[P0]` Create user — dialog z email/password/name/role via `authClient.admin.createUser` <!-- files: src/lib/admin/client/users/create-user-dialog.svelte -->
|
|
37
|
+
- [ ] `[feature]` `[P0]` Edit user — name, email, role via `adminUpdateUser` + `setRole` <!-- files: src/lib/admin/client/users/edit-user-dialog.svelte -->
|
|
38
|
+
- [ ] `[feature]` `[P0]` Delete user — confirmation dialog via `removeUser` <!-- files: src/lib/admin/client/users/ -->
|
|
39
|
+
- [ ] `[feature]` `[P0]` Route/sidebar gating — ukryj Users nav + chroń `/admin/users` dla non-admin <!-- files: src/lib/admin/components/layout/nav-main.svelte, src/lib/sveltekit/server/handle.ts -->
|
|
40
|
+
- [ ] `[feature]` `[P0]` First user bootstrap — pierwszy utworzony user auto-gets role `admin` <!-- files: src/lib/server/auth.ts -->
|
|
41
|
+
|
|
42
|
+
### Phase 2 — Extended
|
|
43
|
+
|
|
44
|
+
- [ ] `[feature]` `[P1]` Ban/unban UI — reason + expiry, `banUser`/`unbanUser` <!-- files: src/lib/admin/client/users/ban-user-dialog.svelte -->
|
|
45
|
+
- [ ] `[feature]` `[P1]` Admin session mgmt — list/revoke sesji innych userów <!-- files: src/lib/admin/client/users/user-sessions-sheet.svelte -->
|
|
46
|
+
- [ ] `[feature]` `[P1]` Impersonation UI — impersonate/stop bar <!-- files: src/lib/admin/client/users/impersonation-bar.svelte -->
|
|
47
|
+
- [ ] `[feature]` `[P2]` Email invitation system — invite link generation (custom, nie w better-auth) <!-- files: src/lib/core/server/auth/invite.ts -->
|
|
48
|
+
|
|
49
|
+
## 0.2.0 — Plugin system
|
|
50
|
+
|
|
51
|
+
- [ ] `[feature]` `[P0]` Wire plugin hooks into CRUD operations (before/afterCreate, Update, Delete) <!-- files: src/lib/types/plugins.ts, src/lib/core/server/entries/operations/ -->
|
|
52
|
+
- [ ] `[feature]` `[P0]` Plugin registration API — public surface for external plugins
|
|
53
|
+
- [ ] `[chore]` `[P1]` Plugin system documentation
|
|
54
|
+
|
|
55
|
+
## 0.2.1 — CMS API improvements
|
|
56
|
+
|
|
57
|
+
- [ ] `[feature]` `[P1]` Server-side pagination API (formalize 0.1.0 fix)
|
|
58
|
+
- [ ] `[feature]` `[P1]` Improved type generation <!-- files: src/lib/core/server/generator/ -->
|
|
59
|
+
- [ ] `[feature]` `[P1]` Proper filtering API (SQL-level, not JS post-query)
|
|
60
|
+
|
|
61
|
+
## 0.3.0 — Admin experience
|
|
62
|
+
|
|
63
|
+
- [ ] `[feature]` `[P1]` Admin overlay — built-in admin bar on site (quick edit, preview, audit)
|
|
64
|
+
- [ ] `[feature]` `[P1]` Media gallery virtualization/pagination <!-- files: src/lib/admin/components/media/files-list.svelte -->
|
|
65
|
+
- [ ] `[feature]` `[P2]` Image styles UI — preview generated variants in editor
|
|
66
|
+
|
|
67
|
+
## 0.3.1 — SEO module
|
|
68
|
+
|
|
69
|
+
- [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
|
|
70
|
+
- [ ] `[feature]` `[P1]` Global SEO settings
|
|
71
|
+
- [ ] `[feature]` `[P1]` Dedicated frontend SEO components <!-- files: src/lib/sveltekit/components/seo.svelte -->
|
|
72
|
+
- [ ] `[feature]` `[P2]` Sitemap generation
|
|
73
|
+
|
|
74
|
+
## 0.4.0 — WCAG/ATAG compliance
|
|
75
|
+
|
|
76
|
+
- [ ] `[chore]` `[P0]` Full WCAG/ATAG audit
|
|
77
|
+
- [ ] `[feature]` `[P0]` Accessibility rework based on audit findings
|
|
78
|
+
|
|
79
|
+
## Security hardening
|
|
80
|
+
|
|
81
|
+
- [ ] `[feature]` `[P1]` `sanitizeHTML` utility — general HTML sanitization outside richtext (text fields, SEO fields)
|
|
82
|
+
- [ ] `[feature]` `[P1]` CSP headers — Content-Security-Policy middleware
|
|
83
|
+
- [ ] `[feature]` `[P1]` CSRF protection — tokens for mutating operations
|
|
84
|
+
- [ ] `[feature]` `[P1]` Rate limiting — API/auth endpoints
|
|
85
|
+
- [ ] `[chore]` `[P1]` Security audit — review `{@html}` usage, innerHTML, injection vectors
|
|
86
|
+
- [ ] `[chore]` `[P1]` Input sanitization audit — review all fields for XSS
|
|
87
|
+
|
|
88
|
+
## Backlog
|
|
89
|
+
|
|
90
|
+
- [ ] `[feature]` `[P2]` Alternative richtext editor — Word-like mode, single richtext field instead of blocks
|
|
91
|
+
- [ ] `[chore]` `[P2]` Caching/performance layer (scope TBD)
|
|
92
|
+
- [ ] `[feature]` `[P2]` API/CLI for configuration (setup DX for less technical users)
|
|
@@ -23,9 +23,6 @@ export declare const accountLang: Record<string, {
|
|
|
23
23
|
confirmPassword: string;
|
|
24
24
|
updatePassword: string;
|
|
25
25
|
passwordUpdated: string;
|
|
26
|
-
twoFactor: string;
|
|
27
|
-
twoFactorDesc: string;
|
|
28
|
-
comingSoon: string;
|
|
29
26
|
deleteAccount: string;
|
|
30
27
|
deleteAccountDesc: string;
|
|
31
28
|
deleteAccountButton: string;
|
|
@@ -42,8 +39,6 @@ export declare const accountLang: Record<string, {
|
|
|
42
39
|
themeDark: string;
|
|
43
40
|
themeSystem: string;
|
|
44
41
|
language: string;
|
|
45
|
-
languageEn: string;
|
|
46
|
-
languagePl: string;
|
|
47
42
|
};
|
|
48
43
|
sessions: {
|
|
49
44
|
title: string;
|
|
@@ -24,9 +24,6 @@ export const accountLang = {
|
|
|
24
24
|
confirmPassword: 'Potwierdź hasło',
|
|
25
25
|
updatePassword: 'Zmień hasło',
|
|
26
26
|
passwordUpdated: 'Hasło zostało zmienione',
|
|
27
|
-
twoFactor: 'Uwierzytelnianie dwuskładnikowe',
|
|
28
|
-
twoFactorDesc: 'Dodatkowa warstwa zabezpieczeń dla Twojego konta',
|
|
29
|
-
comingSoon: 'Wkrótce',
|
|
30
27
|
deleteAccount: 'Usuń konto',
|
|
31
28
|
deleteAccountDesc: 'Trwale usuń swoje konto i wszystkie dane',
|
|
32
29
|
deleteAccountButton: 'Usuń konto',
|
|
@@ -42,9 +39,7 @@ export const accountLang = {
|
|
|
42
39
|
themeLight: 'Jasny',
|
|
43
40
|
themeDark: 'Ciemny',
|
|
44
41
|
themeSystem: 'Systemowy',
|
|
45
|
-
language: 'Język'
|
|
46
|
-
languageEn: 'Angielski',
|
|
47
|
-
languagePl: 'Polski'
|
|
42
|
+
language: 'Język'
|
|
48
43
|
},
|
|
49
44
|
sessions: {
|
|
50
45
|
title: 'Aktywne sesje',
|
|
@@ -88,9 +83,6 @@ export const accountLang = {
|
|
|
88
83
|
confirmPassword: 'Confirm password',
|
|
89
84
|
updatePassword: 'Update password',
|
|
90
85
|
passwordUpdated: 'Password updated',
|
|
91
|
-
twoFactor: 'Two-factor authentication',
|
|
92
|
-
twoFactorDesc: 'Add an extra layer of security to your account',
|
|
93
|
-
comingSoon: 'Coming soon',
|
|
94
86
|
deleteAccount: 'Delete account',
|
|
95
87
|
deleteAccountDesc: 'Permanently delete your account and all data',
|
|
96
88
|
deleteAccountButton: 'Delete account',
|
|
@@ -106,9 +98,7 @@ export const accountLang = {
|
|
|
106
98
|
themeLight: 'Light',
|
|
107
99
|
themeDark: 'Dark',
|
|
108
100
|
themeSystem: 'System',
|
|
109
|
-
language: 'Language'
|
|
110
|
-
languageEn: 'English',
|
|
111
|
-
languagePl: 'Polish'
|
|
101
|
+
language: 'Language'
|
|
112
102
|
},
|
|
113
103
|
sessions: {
|
|
114
104
|
title: 'Active sessions',
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as Card from '../../../components/ui/card/index.js';
|
|
3
3
|
import { accountLang } from './lang.js';
|
|
4
|
-
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
4
|
+
import { useInterfaceLanguage, locales } from '../../state/interface-language.svelte.js';
|
|
5
5
|
import { userPrefersMode, setMode } from 'mode-watcher';
|
|
6
6
|
import Sun from '@tabler/icons-svelte/icons/sun';
|
|
7
7
|
import Moon from '@tabler/icons-svelte/icons/moon';
|
|
8
8
|
import DeviceDesktop from '@tabler/icons-svelte/icons/device-desktop';
|
|
9
|
+
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
9
10
|
|
|
10
11
|
const interfaceLanguage = useInterfaceLanguage();
|
|
11
12
|
|
|
@@ -15,9 +16,15 @@
|
|
|
15
16
|
setMode(theme);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function setLanguage(language:
|
|
19
|
+
function setLanguage(language: InterfaceLanguage) {
|
|
19
20
|
interfaceLanguage.current = language;
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
function getLanguageDisplayName(locale: string): string {
|
|
24
|
+
const name = new Intl.DisplayNames([interfaceLanguage.current], { type: 'language' }).of(locale);
|
|
25
|
+
if (!name) return locale.toUpperCase();
|
|
26
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
27
|
+
}
|
|
21
28
|
</script>
|
|
22
29
|
|
|
23
30
|
<div class="space-y-6">
|
|
@@ -26,9 +33,10 @@
|
|
|
26
33
|
<Card.Title>{lang.preferences.theme}</Card.Title>
|
|
27
34
|
</Card.Header>
|
|
28
35
|
<Card.Content>
|
|
29
|
-
<div class="flex gap-2">
|
|
36
|
+
<div class="flex gap-2" role="group" aria-label={lang.preferences.theme}>
|
|
30
37
|
<button
|
|
31
38
|
type="button"
|
|
39
|
+
aria-pressed={userPrefersMode.current === 'light'}
|
|
32
40
|
onclick={() => setTheme('light')}
|
|
33
41
|
class="inline-flex h-10 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none {userPrefersMode.current ===
|
|
34
42
|
'light'
|
|
@@ -40,6 +48,7 @@
|
|
|
40
48
|
</button>
|
|
41
49
|
<button
|
|
42
50
|
type="button"
|
|
51
|
+
aria-pressed={userPrefersMode.current === 'dark'}
|
|
43
52
|
onclick={() => setTheme('dark')}
|
|
44
53
|
class="inline-flex h-10 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none {userPrefersMode.current ===
|
|
45
54
|
'dark'
|
|
@@ -51,6 +60,7 @@
|
|
|
51
60
|
</button>
|
|
52
61
|
<button
|
|
53
62
|
type="button"
|
|
63
|
+
aria-pressed={userPrefersMode.current === 'system'}
|
|
54
64
|
onclick={() => setTheme('system')}
|
|
55
65
|
class="inline-flex h-10 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none {userPrefersMode.current ===
|
|
56
66
|
'system'
|
|
@@ -69,27 +79,20 @@
|
|
|
69
79
|
<Card.Title>{lang.preferences.language}</Card.Title>
|
|
70
80
|
</Card.Header>
|
|
71
81
|
<Card.Content>
|
|
72
|
-
<div class="flex gap-2">
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class="inline-flex h-10 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none {interfaceLanguage.current ===
|
|
87
|
-
'pl'
|
|
88
|
-
? 'border-[#2D4A77] bg-[#2D4A77]/10 text-[#2D4A77]'
|
|
89
|
-
: 'border-input hover:bg-accent'}"
|
|
90
|
-
>
|
|
91
|
-
{lang.preferences.languagePl}
|
|
92
|
-
</button>
|
|
82
|
+
<div class="flex gap-2" role="group" aria-label={lang.preferences.language}>
|
|
83
|
+
{#each locales as locale}
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
aria-pressed={interfaceLanguage.current === locale}
|
|
87
|
+
onclick={() => setLanguage(locale)}
|
|
88
|
+
class="inline-flex h-10 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none {interfaceLanguage.current ===
|
|
89
|
+
locale
|
|
90
|
+
? 'border-[#2D4A77] bg-[#2D4A77]/10 text-[#2D4A77]'
|
|
91
|
+
: 'border-input hover:bg-accent'}"
|
|
92
|
+
>
|
|
93
|
+
{getLanguageDisplayName(locale)}
|
|
94
|
+
</button>
|
|
95
|
+
{/each}
|
|
93
96
|
</div>
|
|
94
97
|
</Card.Content>
|
|
95
98
|
</Card.Root>
|
|
@@ -9,21 +9,27 @@
|
|
|
9
9
|
import { profileSchema } from './schema.js';
|
|
10
10
|
import { accountLang } from './lang.js';
|
|
11
11
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
12
|
+
import { toLocaleCode } from '../../utils/formatDate.js';
|
|
12
13
|
import { authClient } from '../../auth-client.js';
|
|
13
14
|
import { toast } from 'svelte-sonner';
|
|
15
|
+
import { untrack } from 'svelte';
|
|
14
16
|
|
|
15
17
|
let session = authClient.useSession();
|
|
16
18
|
const interfaceLanguage = useInterfaceLanguage();
|
|
17
19
|
|
|
18
20
|
$effect(() => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const sessionName = $session.data?.user.name;
|
|
22
|
+
if (sessionName) {
|
|
23
|
+
untrack(() => {
|
|
24
|
+
$formData.name = sessionName;
|
|
25
|
+
});
|
|
21
26
|
}
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
const form = superForm(defaults(zod4(profileSchema)), {
|
|
25
30
|
validators: zod4Client(profileSchema),
|
|
26
31
|
SPA: true,
|
|
32
|
+
resetForm: false,
|
|
27
33
|
onUpdate: async ({ form }) => {
|
|
28
34
|
if (form.valid) {
|
|
29
35
|
await authClient.updateUser({
|
|
@@ -48,7 +54,7 @@
|
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
function formatDate(date: Date | string): string {
|
|
51
|
-
return new Date(date).toLocaleDateString(interfaceLanguage.current
|
|
57
|
+
return new Date(date).toLocaleDateString(toLocaleCode(interfaceLanguage.current), {
|
|
52
58
|
year: 'numeric',
|
|
53
59
|
month: 'long'
|
|
54
60
|
});
|
|
@@ -79,12 +85,7 @@
|
|
|
79
85
|
<Form.Control>
|
|
80
86
|
{#snippet children({ props })}
|
|
81
87
|
<Form.Label>{lang.profile.name}</Form.Label>
|
|
82
|
-
<Input
|
|
83
|
-
{...props}
|
|
84
|
-
bind:value={$formData.name}
|
|
85
|
-
type="text"
|
|
86
|
-
class="mt-1"
|
|
87
|
-
/>
|
|
88
|
+
<Input {...props} bind:value={$formData.name} type="text" class="mt-1" />
|
|
88
89
|
{/snippet}
|
|
89
90
|
</Form.Control>
|
|
90
91
|
<Form.FieldErrors class="text-sm text-red-500" />
|
|
@@ -92,12 +93,7 @@
|
|
|
92
93
|
|
|
93
94
|
<div class="space-y-2">
|
|
94
95
|
<label class="text-sm font-medium">{lang.profile.email}</label>
|
|
95
|
-
<Input
|
|
96
|
-
type="email"
|
|
97
|
-
value={$session.data.user.email}
|
|
98
|
-
disabled
|
|
99
|
-
class="mt-1"
|
|
100
|
-
/>
|
|
96
|
+
<Input type="email" value={$session.data.user.email} disabled class="mt-1" />
|
|
101
97
|
</div>
|
|
102
98
|
|
|
103
99
|
<div class="flex items-center gap-4">
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import Button from '../../../components/ui/button/button.svelte';
|
|
5
5
|
import * as Card from '../../../components/ui/card/index.js';
|
|
6
6
|
import * as AlertDialog from '../../../components/ui/alert-dialog/index.js';
|
|
7
|
-
import { Badge } from '../../../components/ui/badge/index.js';
|
|
8
7
|
import { defaults, superForm } from 'sveltekit-superforms';
|
|
9
8
|
import { zod4, zod4Client } from 'sveltekit-superforms/adapters';
|
|
10
9
|
import { passwordSchema, deleteAccountSchema } from './schema.js';
|
|
@@ -124,16 +123,6 @@
|
|
|
124
123
|
</Card.Content>
|
|
125
124
|
</Card.Root>
|
|
126
125
|
|
|
127
|
-
<Card.Root>
|
|
128
|
-
<Card.Header>
|
|
129
|
-
<Card.Title>{lang.security.twoFactor}</Card.Title>
|
|
130
|
-
<Card.Description>{lang.security.twoFactorDesc}</Card.Description>
|
|
131
|
-
</Card.Header>
|
|
132
|
-
<Card.Content>
|
|
133
|
-
<Badge variant="outline">{lang.security.comingSoon}</Badge>
|
|
134
|
-
</Card.Content>
|
|
135
|
-
</Card.Root>
|
|
136
|
-
|
|
137
126
|
<Card.Root class="border-destructive/50">
|
|
138
127
|
<Card.Header>
|
|
139
128
|
<Card.Title class="text-destructive">{lang.security.deleteAccount}</Card.Title>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Badge } from '../../../components/ui/badge/index.js';
|
|
4
4
|
import { accountLang } from './lang.js';
|
|
5
5
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
6
|
+
import { toLocaleCode } from '../../utils/formatDate.js';
|
|
6
7
|
import { authClient } from '../../auth-client.js';
|
|
7
8
|
import { toast } from 'svelte-sonner';
|
|
8
9
|
import DeviceDesktop from '@tabler/icons-svelte/icons/device-desktop';
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
function formatDate(date: Date | string): string {
|
|
89
|
-
return new Date(date).toLocaleString(interfaceLanguage.current
|
|
90
|
+
return new Date(date).toLocaleString(toLocaleCode(interfaceLanguage.current), {
|
|
90
91
|
year: 'numeric',
|
|
91
92
|
month: 'short',
|
|
92
93
|
day: 'numeric',
|