@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="alert alert-success text-sm">
51
- Check your email for a sign-in link. It expires in 10 minutes.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glw907/cairn-cms",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -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="alert alert-success text-sm">
51
- Check your email for a sign-in link. It expires in 10 minutes.
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}