@glw907/cairn-cms 0.36.0 → 0.37.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
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are recorded here, most recent first.
|
|
4
4
|
|
|
5
|
+
## 0.37.0
|
|
6
|
+
|
|
7
|
+
The magic-link sign-in confirmation is now a branded panel in place of the flat success bar. After an
|
|
8
|
+
editor requests a link, the page shows a mail icon in a soft success tile, a "Check your email"
|
|
9
|
+
heading, and the ten-minute expiry note, all in the admin's Warm Stone styling. Below a divider it
|
|
10
|
+
adds guidance for the link that never arrives: check the spam folder first, then confirm the address
|
|
11
|
+
matches the one the site owner added. This covers the common fat-finger case, where a mistyped address
|
|
12
|
+
gets the same neutral confirmation and no email. A "Use a different email" action returns to the form
|
|
13
|
+
so the address gets corrected without a reload. The confirmation copy stays identical whether or not
|
|
14
|
+
the email is on the allowlist, so the page still never leaks membership.
|
|
15
|
+
|
|
16
|
+
The change is internal to the `LoginPage` component and needs no action.
|
|
17
|
+
|
|
5
18
|
## 0.36.0
|
|
6
19
|
|
|
7
20
|
cairn now emits structured diagnostic events. The engine had three bare `console.error` calls and no
|
|
@@ -7,6 +7,7 @@ the allowlist, so the page never leaks membership (spec §7.1).
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import './cairn-admin.css';
|
|
9
9
|
import { onMount } from 'svelte';
|
|
10
|
+
import MailCheckIcon from '@lucide/svelte/icons/mail-check';
|
|
10
11
|
import CairnLogo from './CairnLogo.svelte';
|
|
11
12
|
import CsrfField from './CsrfField.svelte';
|
|
12
13
|
import { cairnFaviconHref } from './cairn-favicon.js';
|
|
@@ -22,6 +23,9 @@ the allowlist, so the page never leaks membership (spec §7.1).
|
|
|
22
23
|
let { data, form }: Props = $props();
|
|
23
24
|
|
|
24
25
|
let rootEl = $state<HTMLElement>();
|
|
26
|
+
// Lets a mistyped address go back to the form without a reload, even though the server still
|
|
27
|
+
// reports `sent`. The success copy never reveals whether the email was on the allowlist.
|
|
28
|
+
let dismissed = $state(false);
|
|
25
29
|
onMount(() => {
|
|
26
30
|
if (rootEl) warnIfChromeWrapped(rootEl);
|
|
27
31
|
});
|
|
@@ -44,13 +48,35 @@ the allowlist, so the page never leaks membership (spec §7.1).
|
|
|
44
48
|
</div>
|
|
45
49
|
|
|
46
50
|
<h1 class="text-lg font-semibold">Sign in to {data.siteName}</h1>
|
|
47
|
-
<p class="mt-1 mb-5 text-sm text-[var(--color-muted)]">Enter your email. We'll send a one-time sign-in link.</p>
|
|
48
51
|
|
|
49
|
-
{#if form?.sent}
|
|
50
|
-
<div role="status" class="
|
|
51
|
-
|
|
52
|
+
{#if form?.sent && !dismissed}
|
|
53
|
+
<div role="status" class="mt-5 flex flex-col items-center text-center">
|
|
54
|
+
<div
|
|
55
|
+
class="mb-4 flex h-11 w-11 items-center justify-center rounded-xl text-[var(--color-success)]"
|
|
56
|
+
style="background-color: color-mix(in oklch, var(--color-success) 16%, transparent);"
|
|
57
|
+
>
|
|
58
|
+
<MailCheckIcon class="h-6 w-6" />
|
|
59
|
+
</div>
|
|
60
|
+
<h2 class="text-lg font-semibold">Check your email</h2>
|
|
61
|
+
<p class="mt-1 text-sm text-[var(--color-muted)]">
|
|
62
|
+
We sent a sign-in link to your inbox. Open it within 10 minutes to finish signing in.
|
|
63
|
+
</p>
|
|
64
|
+
<div class="mt-5 w-full border-t border-[var(--cairn-card-border)] pt-4 text-left">
|
|
65
|
+
<p class="text-sm text-[var(--color-muted)]">
|
|
66
|
+
No link after a minute or two? Check your spam folder first. If it still hasn't arrived,
|
|
67
|
+
double-check the address. It has to match the one your site owner added.
|
|
68
|
+
</p>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class="btn btn-ghost btn-sm mt-3 -ml-2 text-primary"
|
|
72
|
+
onclick={() => (dismissed = true)}
|
|
73
|
+
>
|
|
74
|
+
Use a different email
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
52
77
|
</div>
|
|
53
78
|
{:else}
|
|
79
|
+
<p class="mt-1 mb-5 text-sm text-[var(--color-muted)]">Enter your email. We'll send a one-time sign-in link.</p>
|
|
54
80
|
{#if data.error}
|
|
55
81
|
<div role="alert" class="alert alert-error mb-3 text-sm">That link expired. Request a new one below.</div>
|
|
56
82
|
{/if}
|
|
@@ -3337,6 +3337,10 @@
|
|
|
3337
3337
|
margin-top: calc(var(--spacing) * 4);
|
|
3338
3338
|
}
|
|
3339
3339
|
|
|
3340
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .mt-5 {
|
|
3341
|
+
margin-top: calc(var(--spacing) * 5);
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3340
3344
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .mt-6 {
|
|
3341
3345
|
margin-top: calc(var(--spacing) * 6);
|
|
3342
3346
|
}
|
|
@@ -3394,6 +3398,10 @@
|
|
|
3394
3398
|
margin-bottom: calc(var(--spacing) * 6);
|
|
3395
3399
|
}
|
|
3396
3400
|
|
|
3401
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .-ml-2 {
|
|
3402
|
+
margin-left: calc(var(--spacing) * -2);
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3397
3405
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .ml-1 {
|
|
3398
3406
|
margin-left: calc(var(--spacing) * 1);
|
|
3399
3407
|
}
|
|
@@ -3786,6 +3794,10 @@
|
|
|
3786
3794
|
height: calc(var(--spacing) * 5);
|
|
3787
3795
|
}
|
|
3788
3796
|
|
|
3797
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-6 {
|
|
3798
|
+
height: calc(var(--spacing) * 6);
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3789
3801
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-7 {
|
|
3790
3802
|
height: calc(var(--spacing) * 7);
|
|
3791
3803
|
}
|
|
@@ -3794,6 +3806,10 @@
|
|
|
3794
3806
|
height: calc(var(--spacing) * 8);
|
|
3795
3807
|
}
|
|
3796
3808
|
|
|
3809
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-11 {
|
|
3810
|
+
height: calc(var(--spacing) * 11);
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3797
3813
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-12 {
|
|
3798
3814
|
height: calc(var(--spacing) * 12);
|
|
3799
3815
|
}
|
|
@@ -3852,6 +3868,10 @@
|
|
|
3852
3868
|
width: calc(var(--spacing) * 5);
|
|
3853
3869
|
}
|
|
3854
3870
|
|
|
3871
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-6 {
|
|
3872
|
+
width: calc(var(--spacing) * 6);
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3855
3875
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-7 {
|
|
3856
3876
|
width: calc(var(--spacing) * 7);
|
|
3857
3877
|
}
|
|
@@ -3864,6 +3884,10 @@
|
|
|
3864
3884
|
width: calc(var(--spacing) * 9);
|
|
3865
3885
|
}
|
|
3866
3886
|
|
|
3887
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-11 {
|
|
3888
|
+
width: calc(var(--spacing) * 11);
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3867
3891
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-12 {
|
|
3868
3892
|
width: calc(var(--spacing) * 12);
|
|
3869
3893
|
}
|
|
@@ -4420,6 +4444,10 @@
|
|
|
4420
4444
|
padding-top: calc(var(--spacing) * 3);
|
|
4421
4445
|
}
|
|
4422
4446
|
|
|
4447
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pt-4 {
|
|
4448
|
+
padding-top: calc(var(--spacing) * 4);
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4423
4451
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pr-3 {
|
|
4424
4452
|
padding-right: calc(var(--spacing) * 3);
|
|
4425
4453
|
}
|
|
@@ -4432,6 +4460,10 @@
|
|
|
4432
4460
|
text-align: center;
|
|
4433
4461
|
}
|
|
4434
4462
|
|
|
4463
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-left {
|
|
4464
|
+
text-align: left;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4435
4467
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-right {
|
|
4436
4468
|
text-align: right;
|
|
4437
4469
|
}
|
|
@@ -4561,6 +4593,10 @@
|
|
|
4561
4593
|
color: var(--color-subtle);
|
|
4562
4594
|
}
|
|
4563
4595
|
|
|
4596
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[var\(--color-success\)\] {
|
|
4597
|
+
color: var(--color-success);
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4564
4600
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-base-content {
|
|
4565
4601
|
color: var(--color-base-content);
|
|
4566
4602
|
}
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ the allowlist, so the page never leaks membership (spec §7.1).
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import './cairn-admin.css';
|
|
9
9
|
import { onMount } from 'svelte';
|
|
10
|
+
import MailCheckIcon from '@lucide/svelte/icons/mail-check';
|
|
10
11
|
import CairnLogo from './CairnLogo.svelte';
|
|
11
12
|
import CsrfField from './CsrfField.svelte';
|
|
12
13
|
import { cairnFaviconHref } from './cairn-favicon.js';
|
|
@@ -22,6 +23,9 @@ the allowlist, so the page never leaks membership (spec §7.1).
|
|
|
22
23
|
let { data, form }: Props = $props();
|
|
23
24
|
|
|
24
25
|
let rootEl = $state<HTMLElement>();
|
|
26
|
+
// Lets a mistyped address go back to the form without a reload, even though the server still
|
|
27
|
+
// reports `sent`. The success copy never reveals whether the email was on the allowlist.
|
|
28
|
+
let dismissed = $state(false);
|
|
25
29
|
onMount(() => {
|
|
26
30
|
if (rootEl) warnIfChromeWrapped(rootEl);
|
|
27
31
|
});
|
|
@@ -44,13 +48,35 @@ the allowlist, so the page never leaks membership (spec §7.1).
|
|
|
44
48
|
</div>
|
|
45
49
|
|
|
46
50
|
<h1 class="text-lg font-semibold">Sign in to {data.siteName}</h1>
|
|
47
|
-
<p class="mt-1 mb-5 text-sm text-[var(--color-muted)]">Enter your email. We'll send a one-time sign-in link.</p>
|
|
48
51
|
|
|
49
|
-
{#if form?.sent}
|
|
50
|
-
<div role="status" class="
|
|
51
|
-
|
|
52
|
+
{#if form?.sent && !dismissed}
|
|
53
|
+
<div role="status" class="mt-5 flex flex-col items-center text-center">
|
|
54
|
+
<div
|
|
55
|
+
class="mb-4 flex h-11 w-11 items-center justify-center rounded-xl text-[var(--color-success)]"
|
|
56
|
+
style="background-color: color-mix(in oklch, var(--color-success) 16%, transparent);"
|
|
57
|
+
>
|
|
58
|
+
<MailCheckIcon class="h-6 w-6" />
|
|
59
|
+
</div>
|
|
60
|
+
<h2 class="text-lg font-semibold">Check your email</h2>
|
|
61
|
+
<p class="mt-1 text-sm text-[var(--color-muted)]">
|
|
62
|
+
We sent a sign-in link to your inbox. Open it within 10 minutes to finish signing in.
|
|
63
|
+
</p>
|
|
64
|
+
<div class="mt-5 w-full border-t border-[var(--cairn-card-border)] pt-4 text-left">
|
|
65
|
+
<p class="text-sm text-[var(--color-muted)]">
|
|
66
|
+
No link after a minute or two? Check your spam folder first. If it still hasn't arrived,
|
|
67
|
+
double-check the address. It has to match the one your site owner added.
|
|
68
|
+
</p>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class="btn btn-ghost btn-sm mt-3 -ml-2 text-primary"
|
|
72
|
+
onclick={() => (dismissed = true)}
|
|
73
|
+
>
|
|
74
|
+
Use a different email
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
52
77
|
</div>
|
|
53
78
|
{:else}
|
|
79
|
+
<p class="mt-1 mb-5 text-sm text-[var(--color-muted)]">Enter your email. We'll send a one-time sign-in link.</p>
|
|
54
80
|
{#if data.error}
|
|
55
81
|
<div role="alert" class="alert alert-error mb-3 text-sm">That link expired. Request a new one below.</div>
|
|
56
82
|
{/if}
|