@allbluecn/web-app 0.11.0 → 0.11.2

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 (101) hide show
  1. package/package.json +6 -3
  2. package/public/favicon-dark.svg +1 -0
  3. package/public/favicon.ico +0 -0
  4. package/public/favicon.svg +1 -0
  5. package/public/logo/allblue-logo-dark.png +0 -0
  6. package/public/logo/allblue-logo-light.png +0 -0
  7. package/src/__tests__/integration/login-email-acceptance.test.ts +202 -0
  8. package/src/__tests__/integration/login-email-p2-acceptance.test.ts +142 -0
  9. package/src/components/auth/icon-input.tsx +9 -1
  10. package/src/components/auth/password-input.tsx +9 -1
  11. package/src/components/changelog/changelog-page.tsx +44 -1
  12. package/src/components/forms/app-field.tsx +3 -1
  13. package/src/components/knowledge/knowledge-toolbar.tsx +17 -21
  14. package/src/components/layout/sidebar-03/app-sidebar.tsx +4 -15
  15. package/src/components/notifications/settings-notifications.tsx +41 -1
  16. package/src/components/settings/personal-team/account/settings-profile.tsx +68 -1
  17. package/src/lib/__tests__/edition-desktop.test.ts +23 -0
  18. package/src/lib/app-version.ts +1 -1
  19. package/src/lib/auth/auth.config.ts +123 -99
  20. package/src/lib/changelog/sdk-versions.ts +4 -4
  21. package/src/lib/edition-desktop.ts +22 -0
  22. package/src/lib/session.ts +2 -2
  23. package/src/routeTree.gen.ts +114 -0
  24. package/src/routes/__root.tsx +20 -3
  25. package/src/routes/__tests__/-auth-schemas.test.ts +62 -0
  26. package/src/routes/__tests__/-edition-routing.test.tsx +14 -1
  27. package/src/routes/__tests__/route-tree-structure.test.ts +1 -0
  28. package/src/routes/_login/changelog/confirm/route.lazy.tsx +72 -0
  29. package/src/routes/_login/changelog/confirm/route.tsx +28 -0
  30. package/src/routes/_login/email-unsubscribe/route.lazy.tsx +76 -0
  31. package/src/routes/_login/email-unsubscribe/route.tsx +28 -0
  32. package/src/routes/_login/forgot-password/route.lazy.tsx +2 -1
  33. package/src/routes/_login/login/route.lazy.tsx +4 -2
  34. package/src/routes/_login/register/route.lazy.tsx +28 -17
  35. package/src/routes/_login/register/route.tsx +8 -1
  36. package/src/routes/_login/reset-password/route.lazy.tsx +4 -2
  37. package/src/routes/_login/schemas/-auth.ts +30 -7
  38. package/src/routes/_login/verify-email/route.lazy.tsx +82 -0
  39. package/src/routes/_login/verify-email/route.tsx +28 -0
  40. package/src/routes/_nav/route.tsx +5 -0
  41. package/src/routes/_public/index/route.tsx +2 -1
  42. package/src/routes/api/auth/desktop-callback.ts +2 -2
  43. package/src/routes/api/billing-webhook.ts +4 -0
  44. package/src/routes/api/cron/changelog-release.ts +100 -0
  45. package/src/routes/api/cron/subscription-reminders.ts +110 -0
  46. package/src/routes/api.auth.$.ts +3 -5
  47. package/src/server/__tests__/auth-registration.test.ts +60 -0
  48. package/src/server/auth-registration.ts +40 -0
  49. package/src/server/billing-email.ts +39 -0
  50. package/src/server/email/__tests__/deliverability.test.ts +68 -0
  51. package/src/server/email/__tests__/index.test.ts +32 -0
  52. package/src/server/email/__tests__/outbox.test.tsx +129 -0
  53. package/src/server/email/__tests__/product-templates.test.tsx +106 -0
  54. package/src/server/email/__tests__/registry.test.tsx +50 -0
  55. package/src/server/email/deliverability.ts +57 -0
  56. package/src/server/email/drivers/console.ts +11 -0
  57. package/src/server/email/drivers/resend.ts +31 -0
  58. package/src/server/email/drivers/smtp.ts +27 -0
  59. package/src/server/email/index.ts +56 -0
  60. package/src/server/email/outbox.ts +141 -0
  61. package/src/server/email/preferences.ts +20 -0
  62. package/src/server/email/registry.ts +38 -0
  63. package/src/server/email/sender.ts +34 -17
  64. package/src/server/email/templates/_base/button.tsx +12 -0
  65. package/src/server/email/templates/_base/email-layout.tsx +117 -0
  66. package/src/server/email/templates/_base/theme-fonts.tsx +43 -0
  67. package/src/server/email/templates/_base/theme.ts +22 -0
  68. package/src/server/email/templates/auth/reset-password.tsx +58 -0
  69. package/src/server/email/templates/auth/verify-email.tsx +59 -0
  70. package/src/server/email/templates/auth/welcome.tsx +49 -0
  71. package/src/server/email/templates/billing/payment-receipt.tsx +51 -0
  72. package/src/server/email/templates/billing/subscription-canceled.tsx +51 -0
  73. package/src/server/email/templates/billing/subscription-ending.tsx +51 -0
  74. package/src/server/email/templates/notification/mirror.tsx +50 -0
  75. package/src/server/email/templates/product/changelog-confirm.tsx +53 -0
  76. package/src/server/email/templates/product/changelog-release.tsx +65 -0
  77. package/src/server/email/templates/team/apply-notify.tsx +53 -0
  78. package/src/server/email/templates/team/approved.tsx +53 -0
  79. package/src/server/email/templates/team/invite.tsx +57 -0
  80. package/src/server/middlewares/security-headers.ts +11 -2
  81. package/src/server/notifications/__tests__/email-mirror.test.ts +193 -0
  82. package/src/server/notifications/__tests__/verify-reminder.test.ts +94 -0
  83. package/src/server/notifications/notification-service.ts +71 -1
  84. package/src/server/notifications/verify-reminder.ts +55 -0
  85. package/src/server/oauth-config.ts +59 -0
  86. package/src/server/serverFns/__tests__/auth-server-schemas.test.ts +168 -0
  87. package/src/server/serverFns/__tests__/email-verification.test.ts +149 -0
  88. package/src/server/serverFns/__tests__/team-invitation.test.ts +2 -2
  89. package/src/server/serverFns/auth/email-verification.ts +132 -0
  90. package/src/server/serverFns/auth/forgot-password.ts +22 -2
  91. package/src/server/serverFns/auth/providers.ts +4 -3
  92. package/src/server/serverFns/auth/register.ts +63 -19
  93. package/src/server/serverFns/auth/registration-mode.ts +23 -0
  94. package/src/server/serverFns/auth/reset-password.ts +1 -1
  95. package/src/server/serverFns/auth/{invitation.ts → verify-reminder.ts} +9 -26
  96. package/src/server/serverFns/changelog/__tests__/subscribe.test.ts +119 -0
  97. package/src/server/serverFns/changelog/subscribe.ts +89 -0
  98. package/src/server/serverFns/email-unsubscribe.ts +66 -0
  99. package/src/server/serverFns/notifications.ts +10 -0
  100. package/src/server/serverFns/team.ts +49 -11
  101. package/vite.shared.ts +87 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbluecn/web-app",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -80,6 +80,8 @@
80
80
  "@radix-ui/react-popover": "^1.1.15",
81
81
  "@radix-ui/react-slot": "^1.2.4",
82
82
  "@radix-ui/react-visually-hidden": "^1.2.11",
83
+ "@react-email/components": "^1.0.12",
84
+ "@react-email/render": "^2.1.0",
83
85
  "@tanstack/ai": "^0.52.0",
84
86
  "@tanstack/ai-anthropic": "^0.18.3",
85
87
  "@tanstack/ai-gemini": "^0.26.4",
@@ -164,6 +166,7 @@
164
166
  "nanoid": "^5.1.11",
165
167
  "next-themes": "^0.4.6",
166
168
  "nitro": "3.0.260903-beta",
169
+ "nodemailer": "^10.0.3",
167
170
  "ogl": "^1.0.11",
168
171
  "pdfjs-dist": "^6.3.289",
169
172
  "pinyin-pro": "^3.29.3",
@@ -185,8 +188,8 @@
185
188
  "three": "^0.180.0",
186
189
  "tw-animate-css": "^1.4.0",
187
190
  "zod": "^4.4.3",
188
- "@allbluecn/database": "0.6.0",
189
- "@allbluecn/kernel": "0.6.1"
191
+ "@allbluecn/database": "0.8.0",
192
+ "@allbluecn/kernel": "0.8.0"
190
193
  },
191
194
  "devDependencies": {
192
195
  "@babel/parser": "^8.0.4",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-49.835 -77.17 1014 1014"><path d="M912.82 421.43C912.92 423.04 913.03 424.64 913.13 426.24C913.41 436.02 913.68 445.8 913.96 455.57C913.82 457.16 913.67 458.75 913.53 460.34C913.39 464.09 913.25 467.85 913.11 471.61C913.01 475.69 912.9 479.76 912.8 483.84C905.03 614.78 836.48 739.86 721.87 806.58C713.75 811.31 705.22 814.88 696.81 818.82C681.84 825.84 666.92 832.57 651.26 838.16C619.75 849.42 586.6 854.7 553.54 858.44C552.2 858.57 550.85 858.7 549.51 858.83C545.18 858.97 540.85 859.11 536.52 859.25C533.01 859.29 529.5 859.33 525.98 859.38C520.6 859.19 515.22 859 509.83 858.81C507.65 858.71 505.47 858.6 503.29 858.49C501.39 858.34 499.48 858.18 497.58 858.02C496.24 857.91 494.9 857.8 493.57 857.69C461.02 854.33 429.45 847.25 399.28 834.59C396.56 833.45 393.57 832.92 390.9 831.7C389.25 830.95 388.09 829.59 386.42 828.83C375.89 824.08 364.9 820.07 354.95 814.1C348.49 810.22 340.67 802.9 334.06 800.22C327.67 797.62 320.09 797.29 313.41 795.58C302.7 792.84 291.97 788.87 281.95 784.16C218.81 754.5 165.31 704.82 137.47 640.13C125.41 612.09 117.59 582.91 112.22 552.9C105.08 513.03 104.34 471.16 86.98 433.78C80.1 418.98 71.48 405.92 59 395.26C43.31 381.86 27.91 375.15 13.87 358.51C8.99 352.73 4.81 346.43 2.3 339.24C1.43 336.74 1.16 333.94 0.14 331.58C0.14 326.94 0.14 322.31 0.14 317.67C1.35 314.89 1.52 311.38 2.36 308.37C4.36 301.27 6.96 294.29 10.08 287.6C22.74 260.44 38.53 234.64 55.66 210.06C139.44 89.85 287.99 10.93 433.59 2.96C435.46 2.86 437.32 2.77 439.19 2.68C470.99 -0.23 503.07 3.72 534.54 7.84C556.37 10.7 577.66 15.99 598.74 22.39C618.17 28.3 637.25 34.9 655.82 43.23C745.71 83.53 819.5 157.02 863.49 244.8C879.78 277.29 893.81 312.84 901.66 348.38C907 372.56 910.26 396.87 912.82 421.43ZM493.57 182.93C494.93 182.82 496.3 182.72 497.66 182.61C505.74 182.61 513.82 182.61 521.9 182.61C524.07 182.72 526.24 182.82 528.41 182.92C561.81 185.44 594.93 192.49 625.58 206.35C635.18 210.69 644.79 215.62 653.81 221.07C658.57 223.94 663.03 228.33 668.22 230.26C673.63 226.68 683.25 209.59 686.8 203.36C703.34 174.33 713.07 141.46 709.77 107.79C709.63 106.48 709.5 105.18 709.37 103.87C709.14 100.94 709.21 96.23 707.38 93.76C704.78 90.25 698.39 87.71 694.72 85.35C685.43 79.4 676.09 73.48 666.3 68.38C624.09 46.36 577.59 31.74 530.61 24.81C522.06 23.54 513.55 22.01 504.89 21.63C503.54 21.51 502.2 21.38 500.86 21.25C493.3 20.83 485.75 20.42 478.2 20C476.56 19.89 474.93 19.78 473.3 19.68C450.82 17.58 427.93 20.22 405.63 22.9C371.88 26.95 341.56 33.48 309.14 43.67C296.56 47.62 283.89 52.16 271.81 57.45C268.64 58.83 261.75 60.48 260.51 63.62C264.92 66.44 271.7 66.57 276.75 68.08C286.8 71.09 296.71 75.83 305.56 81.45C336.54 101.12 361.81 126.41 378.02 159.73C382.31 168.53 386.57 177.57 389.71 186.86C391.66 192.64 392.43 199.06 394.6 204.64C398.04 204.62 401.67 202.32 404.95 201.13C413.17 198.15 421.55 195.3 430 193.03C450.85 187.43 472.07 184.53 493.57 182.93ZM187.04 542.19C186.91 540.33 186.78 538.46 186.65 536.59C186.84 532.9 186.2 529.26 186.27 525.58C186.54 511.2 187.22 496.43 189.24 482.19C199.47 410.09 228.1 344.53 276.74 290.11C281.56 284.72 286.16 278.95 291.42 273.99C309.99 256.47 329.78 240.3 351.93 227.51C359.31 223.25 366.61 218.8 374.29 215.08C378.39 213.1 384.07 211.94 386.99 208.33C387.19 198.94 377.68 178.18 373.32 169.26C355.91 133.67 330.18 103.03 294.94 83.94C284.52 78.3 273.61 73.83 262.29 70.33C258.28 69.09 252.68 66.98 248.55 68.23C244.02 69.6 239.34 72.89 235.11 75.04C221.9 81.75 209.46 89.66 197.15 97.86C189.2 103.16 180.88 107.88 173.15 113.55C122.61 150.58 78.65 197.8 47.79 252.38C40.47 265.32 33.02 277.98 27.12 291.68C22.72 301.89 16.43 318.01 18.24 329.1C21.06 346.46 41.16 360.45 54.63 369.63C72.08 381.53 88.02 396.33 98.55 414.87C122.95 457.88 122.14 510.27 131.72 557.48C135.8 577.54 139.77 597.71 146.98 616.95C164.32 663.26 195.1 705.41 234.93 734.76C247.38 743.94 259.84 752.27 273.72 759.2C277.82 761.24 284.79 766.61 289.13 765.47C287.16 761.37 278.25 754.14 274.66 750.17C263.65 737.97 252.77 725.18 243.61 711.49C207.97 658.28 192.06 605.7 187.04 542.19ZM718.15 117.52C718.18 120.49 718.2 123.46 718.22 126.43C717.46 136.86 716.88 147.28 714.61 157.54C710.82 174.7 702.85 191 694.8 206.49C691.82 212.22 688.37 217.72 684.34 222.76C681.34 226.51 676.68 230.85 675.33 235.44C680.4 240.92 687.28 245.04 692.79 250.16C704.7 261.25 715.61 273.36 725.75 286.09C749.66 316.1 767.03 353.54 774.82 391.04C777.72 404.95 778.9 418.75 780.67 432.78C780.72 438.18 780.77 443.59 780.83 448.99C780.62 451.46 780.42 453.92 780.22 456.39C780.1 458.52 779.98 460.64 779.87 462.77C779.99 464.5 779.95 464.53 781.34 465.43C784.02 465.54 786.7 465.66 789.38 465.77C801.15 466.66 813.28 466.98 825.04 465.79C826.96 465.68 828.88 465.57 830.79 465.47C850.7 465.45 874.53 460.05 892.99 452.64C895.33 445.14 893.36 435.28 893.03 427.51C892.24 408.85 890.32 389.91 886.51 371.61C875.06 316.67 855.45 263.3 825.31 215.75C812.61 195.72 799.1 175.14 782.34 158.19C767.47 143.15 752.77 128.48 736.46 114.95C732.34 111.54 721.34 101.22 716.84 100.67C717.28 106.29 717.72 111.9 718.15 117.52ZM514.64 364.49C519.5 364.49 524.36 364.48 529.22 364.48C563.64 368.65 596.33 384.31 611.49 417.37C616.99 429.35 619.78 441.71 621.78 454.66C621.9 456.05 622.03 457.44 622.16 458.83C622.17 460.94 622.18 463.05 622.18 465.17C622.04 466.8 621.9 468.43 621.76 470.06C618.66 503.22 595.23 538.24 560.43 543.86C552.87 545.08 544.33 545.22 536.87 543.15C509.15 535.43 492.16 506.74 509.02 480.29C512.66 474.57 519.84 467.6 527.17 471.71C528.62 472.52 530.07 473.94 530.56 475.58C531.59 479.06 528.57 481.57 526.76 484.19C523.83 488.43 521.98 492.45 521.68 497.65C521.68 499.83 521.67 502.01 521.67 504.19C523.6 527.88 550.02 530.06 567.56 522.56C587.94 513.84 600.08 489.72 601.61 468.48C601.62 464.39 601.62 460.29 601.62 456.2C601.57 450.19 600.01 444.32 598.49 438.54C594.42 423.05 585.53 407.69 572.6 397.9C550.9 381.46 518.84 377.57 493.2 385.06C450.3 397.59 419.32 435.92 407.95 477.96C403.48 494.49 401.61 512.55 402.5 529.63C403.14 541.91 406.11 554.6 409.85 566.28C432.39 636.77 500.5 676.66 572.99 670.86C574.32 670.73 575.66 670.61 576.99 670.48C585.6 670.28 594.5 667.86 602.79 665.69C619.35 661.34 634.41 655.75 649.35 647.3C718.36 608.26 757.1 538.55 759.19 459.93C759.4 452.24 760.69 444.53 759.98 436.83C759.86 433.88 759.75 430.92 759.64 427.96C759.49 425.25 759.34 422.53 759.2 419.81C759.08 418.45 758.96 417.09 758.84 415.73C757.72 395.27 751.43 375.82 744.23 356.82C719.23 290.85 664.95 240.51 599.81 215.16C592.66 212.38 585.11 210.77 577.73 208.75C562.15 204.48 545.42 201.03 529.18 200.74C527.3 200.61 525.43 200.47 523.55 200.34C508.74 198.48 493.06 200 478.27 201.41C432.35 205.78 387.73 222.74 349.49 248.57C337.91 256.39 326.64 265.14 316.44 274.71C313.21 277.74 308.64 280.6 306.1 284.06C309.51 288.45 323.4 292.81 329.01 296.26C346.82 307.23 364.64 318.41 380.18 332.61C395 346.15 408.61 361.07 421.19 376.68C424.81 381.17 427.82 386.38 430.99 391.21C432.51 393.54 433.34 396.53 435.62 397.97C438.8 396.11 441.43 392.85 444.37 390.49C448.2 387.42 452.75 384.46 457.07 382.13C476.8 371.52 492.29 366.73 514.64 364.49ZM206.07 543.81C206.18 545.13 206.3 546.45 206.42 547.77C206.56 549.42 206.7 551.08 206.84 552.73C206.97 554.09 207.1 555.46 207.22 556.82C208.75 587.42 217.32 618.74 229.86 646.59C233.67 655.06 237.48 668.35 244.3 674.7C248.5 674.4 253.61 670.6 257.46 668.8C267.12 664.28 277.07 660.52 286.69 655.88C304.27 647.41 321.86 638.41 338.62 628.38C346.33 623.76 353.75 618.59 361.45 613.92C373.55 606.58 386.27 599.78 397.53 591.17C396.7 584.09 392.15 576.86 390.09 569.96C386.6 558.27 385.08 546.09 383.12 534.09C383.01 532.7 382.91 531.32 382.8 529.93C382.8 525.39 382.8 520.85 382.8 516.31C382.92 514.13 383.03 511.95 383.15 509.77C383.3 507.85 383.44 505.92 383.59 503.99C383.72 502.68 383.86 501.36 383.99 500.05C387.62 476.41 393.83 452.71 406.6 432.14C410.26 426.24 414.34 420.51 418.96 415.31C422.09 411.79 426.6 408.12 428.75 404.01C423.15 392.59 414.55 382.49 406.58 372.65C386.28 347.58 361.41 325.17 333.7 308.56C325.97 303.92 307.5 291.43 299.46 290.31C293.08 294.95 288.07 302.54 283.01 308.55C272.5 321.03 262.6 333.83 254.46 348.01C227.76 394.48 212.55 439.71 207.31 493.16C205.65 510.12 204.84 526.78 206.07 543.81ZM816.94 474.28C810.72 474.33 804.51 474.38 798.29 474.42C794.01 474.23 789.72 474.04 785.44 473.85C783.65 473.78 781.85 473.71 780.06 473.65C779.98 473.8 779.9 473.96 779.82 474.11C778.27 480.1 778.41 486.95 777.39 493.16C775.21 506.55 771.82 519.48 767.88 532.43C764.89 542.25 760.29 551.72 755.84 560.94C739.05 595.71 716.36 625.15 684.87 647.99C672.14 657.21 658.79 665.87 644.45 672.36C640.55 674.12 636.43 675.95 632.35 677.25C629.55 678.14 626.58 678.11 624.02 679.56C624.13 683.59 626.94 686.68 628.68 690.3C631.87 696.92 634.91 703.67 637.71 710.45C646.43 731.52 653.6 753.15 658.57 775.43C660.45 783.85 661.15 792.48 662.45 800.99C662.98 804.48 662.8 808.75 664.98 811.45C672.92 809.52 679.75 804.41 687.05 800.92C704.15 792.76 720.13 783.37 735.63 772.49C806.65 722.63 859.72 644.07 880.43 559.79C886.19 536.35 891.08 512.51 891.99 488.3C892.24 481.78 894.47 467.13 892.62 461.55C888.62 461.6 884.42 464.05 880.53 465.2C875.2 466.79 869.75 467.99 864.32 469.2C848.64 472.68 832.81 472.65 816.94 474.28ZM517.98 839.83C521.68 839.84 525.39 839.86 529.1 839.87C570.52 840.69 615.92 830.57 654.44 815.48C656.78 805.45 652.44 794.04 650.83 784.11C646.7 758.8 638.19 733.8 628.28 710.2C625.69 704.03 622.06 698.44 619.35 692.36C617.98 689.3 617.09 685.12 614.73 682.76C605.25 683.65 595.84 686.76 586.36 688.03C574.03 689.68 561.94 689.08 549.6 689.43C547.67 689.29 545.74 689.15 543.81 689.01C501.53 683.39 462.28 669.63 431.63 638.56C422.63 629.44 415.77 619.45 408.57 608.93C406.16 605.41 404.3 601.27 401.58 598.04C394.71 599.37 378.41 613 371.47 617.46C347.08 633.15 322.05 647.46 296.12 660.41C286.68 665.13 276.85 669.6 267.03 673.45C261.1 675.77 253.05 677.55 248.4 682.08C249.64 688.81 255.93 696.23 259.72 701.88C270.21 717.54 282.56 731.83 295.93 745.1C345.45 794.22 410.48 826.76 479.4 837C492.01 838.87 505.24 839.34 517.98 839.83Z" fill="#ffffff" fill-rule="evenodd" stroke="#ffffff" stroke-width="70" stroke-linejoin="round"/><path d="M439.19 2.68C437.32 2.77 435.46 2.86 433.59 2.96C433.73 2.78 433.86 2.61 434 2.43C435.73 2.51 437.46 2.6 439.19 2.68ZM473.3 19.68C474.93 19.78 476.56 19.89 478.2 20C476.7 20.09 475.2 20.17 473.71 20.26C473.57 20.07 473.44 19.87 473.3 19.68ZM500.86 21.25C502.2 21.38 503.54 21.51 504.89 21.63C503.68 21.72 502.47 21.8 501.26 21.88C501.13 21.67 500.99 21.46 500.86 21.25ZM709.37 103.87C709.5 105.18 709.63 106.48 709.77 107.79C709.56 107.66 709.35 107.52 709.15 107.38C709.22 106.21 709.29 105.04 709.37 103.87ZM718.15 117.52C718.39 117.65 718.63 117.79 718.87 117.92C718.87 120.62 718.87 123.32 718.87 126.03C718.65 126.16 718.44 126.3 718.22 126.43C718.2 123.46 718.18 120.49 718.15 117.52ZM497.66 182.61C496.3 182.72 494.93 182.82 493.57 182.93C493.7 182.73 493.84 182.54 493.97 182.35C495.2 182.44 496.43 182.53 497.66 182.61ZM521.9 182.61C523.94 182.53 525.97 182.44 528.01 182.35C528.14 182.54 528.28 182.73 528.41 182.92C526.24 182.82 524.07 182.72 521.9 182.61ZM523.55 200.34C525.43 200.47 527.3 200.61 529.18 200.74C527.44 200.82 525.7 200.91 523.96 200.99C523.82 200.77 523.69 200.56 523.55 200.34ZM529.22 364.48C524.36 364.48 519.5 364.49 514.64 364.49C514.77 364.29 514.91 364.09 515.04 363.89C519.63 363.89 524.23 363.89 528.82 363.89C528.95 364.09 529.09 364.28 529.22 364.48ZM758.84 415.73C758.96 417.09 759.08 418.45 759.2 419.81C758.99 419.68 758.79 419.54 758.58 419.41C758.67 418.18 758.75 416.96 758.84 415.73ZM912.82 421.43C913.01 421.57 913.19 421.71 913.38 421.84C913.3 423.31 913.21 424.77 913.13 426.24C913.03 424.64 912.92 423.04 912.82 421.43ZM759.64 427.96C759.75 430.92 759.86 433.88 759.98 436.83C759.78 436.7 759.59 436.56 759.39 436.43C759.47 433.61 759.56 430.78 759.64 427.96ZM780.67 432.78C780.72 438.18 780.77 443.59 780.83 448.99C780.77 443.59 780.72 438.18 780.67 432.78ZM621.78 454.66C621.99 454.8 622.21 454.93 622.43 455.07C622.34 456.32 622.25 457.57 622.16 458.83C622.03 457.44 621.9 456.05 621.78 454.66ZM913.96 455.57C914.04 457.03 914.11 458.48 914.19 459.93C913.97 460.07 913.75 460.2 913.53 460.34C913.67 458.75 913.82 457.16 913.96 455.57ZM601.62 456.2C601.62 460.29 601.62 464.39 601.61 468.48C601.62 464.39 601.62 460.29 601.62 456.2ZM780.22 456.39C780.3 458.38 780.38 460.37 780.47 462.36C780.27 462.5 780.07 462.63 779.87 462.77C779.98 460.64 780.1 458.52 780.22 456.39ZM622.18 465.17C622.26 466.66 622.35 468.16 622.43 469.66C622.2 469.79 621.98 469.93 621.76 470.06C621.9 468.43 622.04 466.8 622.18 465.17ZM781.34 465.43C783.89 465.35 786.43 465.28 788.97 465.2C789.11 465.39 789.24 465.58 789.38 465.77C786.7 465.66 784.02 465.54 781.34 465.43ZM830.79 465.47C828.88 465.57 826.96 465.68 825.04 465.79C825.17 465.59 825.31 465.4 825.45 465.2C827.23 465.29 829.01 465.38 830.79 465.47ZM913.11 471.61C913.2 475.55 913.29 479.49 913.38 483.43C913.19 483.57 912.99 483.7 912.8 483.84C912.9 479.76 913.01 475.69 913.11 471.61ZM785.44 473.85C783.57 473.94 781.69 474.02 779.82 474.11C779.9 473.96 779.98 473.8 780.06 473.65C781.85 473.71 783.65 473.78 785.44 473.85ZM798.29 474.42C804.51 474.38 810.72 474.33 816.94 474.28C814.3 475.59 801.18 475.53 798.29 474.42ZM521.68 497.65C521.68 499.83 521.67 502.01 521.67 504.19C521.67 502.01 521.68 499.83 521.68 497.65ZM383.99 500.05C383.86 501.36 383.72 502.68 383.59 503.99C383.5 502.81 383.42 501.63 383.34 500.45C383.56 500.32 383.78 500.18 383.99 500.05ZM383.15 509.77C383.03 511.95 382.92 514.13 382.8 516.31C382.71 514.27 382.62 512.22 382.53 510.18C382.74 510.04 382.95 509.91 383.15 509.77ZM382.8 529.93C382.91 531.32 383.01 532.7 383.12 534.09C382.92 533.95 382.73 533.82 382.53 533.68C382.62 532.43 382.71 531.18 382.8 529.93ZM186.65 536.59C186.78 538.46 186.91 540.33 187.04 542.19C186.82 542.06 186.61 541.92 186.4 541.79C186.49 540.06 186.57 538.33 186.65 536.59ZM206.07 543.81C206.27 543.95 206.47 544.08 206.67 544.22C206.58 545.4 206.5 546.59 206.42 547.77C206.3 546.45 206.18 545.13 206.07 543.81ZM206.84 552.73C207.05 552.86 207.26 553 207.48 553.13C207.39 554.36 207.31 555.59 207.22 556.82C207.1 555.46 206.97 554.09 206.84 552.73ZM576.99 670.48C575.66 670.61 574.32 670.73 572.99 670.86C573.12 670.66 573.26 670.45 573.39 670.24C574.59 670.32 575.79 670.4 576.99 670.48ZM543.81 689.01C545.74 689.15 547.67 689.29 549.6 689.43C547.81 689.52 546.01 689.6 544.22 689.69C544.08 689.47 543.95 689.24 543.81 689.01ZM517.98 839.83C521.68 839.84 525.39 839.86 529.1 839.87C525.39 839.86 521.68 839.84 517.98 839.83ZM493.57 857.69C494.9 857.8 496.24 857.91 497.58 858.02C496.37 858.1 495.17 858.19 493.97 858.27C493.84 858.08 493.7 857.88 493.57 857.69ZM503.29 858.49C505.47 858.6 507.65 858.71 509.83 858.81C507.79 858.9 505.74 858.99 503.7 859.08C503.56 858.88 503.43 858.69 503.29 858.49ZM553.54 858.44C553.4 858.66 553.27 858.87 553.13 859.08C551.93 858.99 550.72 858.91 549.51 858.83C550.85 858.7 552.2 858.57 553.54 858.44ZM525.98 859.38C529.5 859.33 533.01 859.29 536.52 859.25C536.38 859.46 536.25 859.67 536.11 859.89C532.87 859.89 529.63 859.89 526.39 859.89C526.25 859.72 526.12 859.55 525.98 859.38Z" fill="#ffffff" fill-rule="evenodd" stroke="#ffffff" stroke-width="70" stroke-linejoin="round"/></svg>
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-49.835 -77.17 1014 1014"><path d="M912.82 421.43C912.92 423.04 913.03 424.64 913.13 426.24C913.41 436.02 913.68 445.8 913.96 455.57C913.82 457.16 913.67 458.75 913.53 460.34C913.39 464.09 913.25 467.85 913.11 471.61C913.01 475.69 912.9 479.76 912.8 483.84C905.03 614.78 836.48 739.86 721.87 806.58C713.75 811.31 705.22 814.88 696.81 818.82C681.84 825.84 666.92 832.57 651.26 838.16C619.75 849.42 586.6 854.7 553.54 858.44C552.2 858.57 550.85 858.7 549.51 858.83C545.18 858.97 540.85 859.11 536.52 859.25C533.01 859.29 529.5 859.33 525.98 859.38C520.6 859.19 515.22 859 509.83 858.81C507.65 858.71 505.47 858.6 503.29 858.49C501.39 858.34 499.48 858.18 497.58 858.02C496.24 857.91 494.9 857.8 493.57 857.69C461.02 854.33 429.45 847.25 399.28 834.59C396.56 833.45 393.57 832.92 390.9 831.7C389.25 830.95 388.09 829.59 386.42 828.83C375.89 824.08 364.9 820.07 354.95 814.1C348.49 810.22 340.67 802.9 334.06 800.22C327.67 797.62 320.09 797.29 313.41 795.58C302.7 792.84 291.97 788.87 281.95 784.16C218.81 754.5 165.31 704.82 137.47 640.13C125.41 612.09 117.59 582.91 112.22 552.9C105.08 513.03 104.34 471.16 86.98 433.78C80.1 418.98 71.48 405.92 59 395.26C43.31 381.86 27.91 375.15 13.87 358.51C8.99 352.73 4.81 346.43 2.3 339.24C1.43 336.74 1.16 333.94 0.14 331.58C0.14 326.94 0.14 322.31 0.14 317.67C1.35 314.89 1.52 311.38 2.36 308.37C4.36 301.27 6.96 294.29 10.08 287.6C22.74 260.44 38.53 234.64 55.66 210.06C139.44 89.85 287.99 10.93 433.59 2.96C435.46 2.86 437.32 2.77 439.19 2.68C470.99 -0.23 503.07 3.72 534.54 7.84C556.37 10.7 577.66 15.99 598.74 22.39C618.17 28.3 637.25 34.9 655.82 43.23C745.71 83.53 819.5 157.02 863.49 244.8C879.78 277.29 893.81 312.84 901.66 348.38C907 372.56 910.26 396.87 912.82 421.43ZM493.57 182.93C494.93 182.82 496.3 182.72 497.66 182.61C505.74 182.61 513.82 182.61 521.9 182.61C524.07 182.72 526.24 182.82 528.41 182.92C561.81 185.44 594.93 192.49 625.58 206.35C635.18 210.69 644.79 215.62 653.81 221.07C658.57 223.94 663.03 228.33 668.22 230.26C673.63 226.68 683.25 209.59 686.8 203.36C703.34 174.33 713.07 141.46 709.77 107.79C709.63 106.48 709.5 105.18 709.37 103.87C709.14 100.94 709.21 96.23 707.38 93.76C704.78 90.25 698.39 87.71 694.72 85.35C685.43 79.4 676.09 73.48 666.3 68.38C624.09 46.36 577.59 31.74 530.61 24.81C522.06 23.54 513.55 22.01 504.89 21.63C503.54 21.51 502.2 21.38 500.86 21.25C493.3 20.83 485.75 20.42 478.2 20C476.56 19.89 474.93 19.78 473.3 19.68C450.82 17.58 427.93 20.22 405.63 22.9C371.88 26.95 341.56 33.48 309.14 43.67C296.56 47.62 283.89 52.16 271.81 57.45C268.64 58.83 261.75 60.48 260.51 63.62C264.92 66.44 271.7 66.57 276.75 68.08C286.8 71.09 296.71 75.83 305.56 81.45C336.54 101.12 361.81 126.41 378.02 159.73C382.31 168.53 386.57 177.57 389.71 186.86C391.66 192.64 392.43 199.06 394.6 204.64C398.04 204.62 401.67 202.32 404.95 201.13C413.17 198.15 421.55 195.3 430 193.03C450.85 187.43 472.07 184.53 493.57 182.93ZM187.04 542.19C186.91 540.33 186.78 538.46 186.65 536.59C186.84 532.9 186.2 529.26 186.27 525.58C186.54 511.2 187.22 496.43 189.24 482.19C199.47 410.09 228.1 344.53 276.74 290.11C281.56 284.72 286.16 278.95 291.42 273.99C309.99 256.47 329.78 240.3 351.93 227.51C359.31 223.25 366.61 218.8 374.29 215.08C378.39 213.1 384.07 211.94 386.99 208.33C387.19 198.94 377.68 178.18 373.32 169.26C355.91 133.67 330.18 103.03 294.94 83.94C284.52 78.3 273.61 73.83 262.29 70.33C258.28 69.09 252.68 66.98 248.55 68.23C244.02 69.6 239.34 72.89 235.11 75.04C221.9 81.75 209.46 89.66 197.15 97.86C189.2 103.16 180.88 107.88 173.15 113.55C122.61 150.58 78.65 197.8 47.79 252.38C40.47 265.32 33.02 277.98 27.12 291.68C22.72 301.89 16.43 318.01 18.24 329.1C21.06 346.46 41.16 360.45 54.63 369.63C72.08 381.53 88.02 396.33 98.55 414.87C122.95 457.88 122.14 510.27 131.72 557.48C135.8 577.54 139.77 597.71 146.98 616.95C164.32 663.26 195.1 705.41 234.93 734.76C247.38 743.94 259.84 752.27 273.72 759.2C277.82 761.24 284.79 766.61 289.13 765.47C287.16 761.37 278.25 754.14 274.66 750.17C263.65 737.97 252.77 725.18 243.61 711.49C207.97 658.28 192.06 605.7 187.04 542.19ZM718.15 117.52C718.18 120.49 718.2 123.46 718.22 126.43C717.46 136.86 716.88 147.28 714.61 157.54C710.82 174.7 702.85 191 694.8 206.49C691.82 212.22 688.37 217.72 684.34 222.76C681.34 226.51 676.68 230.85 675.33 235.44C680.4 240.92 687.28 245.04 692.79 250.16C704.7 261.25 715.61 273.36 725.75 286.09C749.66 316.1 767.03 353.54 774.82 391.04C777.72 404.95 778.9 418.75 780.67 432.78C780.72 438.18 780.77 443.59 780.83 448.99C780.62 451.46 780.42 453.92 780.22 456.39C780.1 458.52 779.98 460.64 779.87 462.77C779.99 464.5 779.95 464.53 781.34 465.43C784.02 465.54 786.7 465.66 789.38 465.77C801.15 466.66 813.28 466.98 825.04 465.79C826.96 465.68 828.88 465.57 830.79 465.47C850.7 465.45 874.53 460.05 892.99 452.64C895.33 445.14 893.36 435.28 893.03 427.51C892.24 408.85 890.32 389.91 886.51 371.61C875.06 316.67 855.45 263.3 825.31 215.75C812.61 195.72 799.1 175.14 782.34 158.19C767.47 143.15 752.77 128.48 736.46 114.95C732.34 111.54 721.34 101.22 716.84 100.67C717.28 106.29 717.72 111.9 718.15 117.52ZM514.64 364.49C519.5 364.49 524.36 364.48 529.22 364.48C563.64 368.65 596.33 384.31 611.49 417.37C616.99 429.35 619.78 441.71 621.78 454.66C621.9 456.05 622.03 457.44 622.16 458.83C622.17 460.94 622.18 463.05 622.18 465.17C622.04 466.8 621.9 468.43 621.76 470.06C618.66 503.22 595.23 538.24 560.43 543.86C552.87 545.08 544.33 545.22 536.87 543.15C509.15 535.43 492.16 506.74 509.02 480.29C512.66 474.57 519.84 467.6 527.17 471.71C528.62 472.52 530.07 473.94 530.56 475.58C531.59 479.06 528.57 481.57 526.76 484.19C523.83 488.43 521.98 492.45 521.68 497.65C521.68 499.83 521.67 502.01 521.67 504.19C523.6 527.88 550.02 530.06 567.56 522.56C587.94 513.84 600.08 489.72 601.61 468.48C601.62 464.39 601.62 460.29 601.62 456.2C601.57 450.19 600.01 444.32 598.49 438.54C594.42 423.05 585.53 407.69 572.6 397.9C550.9 381.46 518.84 377.57 493.2 385.06C450.3 397.59 419.32 435.92 407.95 477.96C403.48 494.49 401.61 512.55 402.5 529.63C403.14 541.91 406.11 554.6 409.85 566.28C432.39 636.77 500.5 676.66 572.99 670.86C574.32 670.73 575.66 670.61 576.99 670.48C585.6 670.28 594.5 667.86 602.79 665.69C619.35 661.34 634.41 655.75 649.35 647.3C718.36 608.26 757.1 538.55 759.19 459.93C759.4 452.24 760.69 444.53 759.98 436.83C759.86 433.88 759.75 430.92 759.64 427.96C759.49 425.25 759.34 422.53 759.2 419.81C759.08 418.45 758.96 417.09 758.84 415.73C757.72 395.27 751.43 375.82 744.23 356.82C719.23 290.85 664.95 240.51 599.81 215.16C592.66 212.38 585.11 210.77 577.73 208.75C562.15 204.48 545.42 201.03 529.18 200.74C527.3 200.61 525.43 200.47 523.55 200.34C508.74 198.48 493.06 200 478.27 201.41C432.35 205.78 387.73 222.74 349.49 248.57C337.91 256.39 326.64 265.14 316.44 274.71C313.21 277.74 308.64 280.6 306.1 284.06C309.51 288.45 323.4 292.81 329.01 296.26C346.82 307.23 364.64 318.41 380.18 332.61C395 346.15 408.61 361.07 421.19 376.68C424.81 381.17 427.82 386.38 430.99 391.21C432.51 393.54 433.34 396.53 435.62 397.97C438.8 396.11 441.43 392.85 444.37 390.49C448.2 387.42 452.75 384.46 457.07 382.13C476.8 371.52 492.29 366.73 514.64 364.49ZM206.07 543.81C206.18 545.13 206.3 546.45 206.42 547.77C206.56 549.42 206.7 551.08 206.84 552.73C206.97 554.09 207.1 555.46 207.22 556.82C208.75 587.42 217.32 618.74 229.86 646.59C233.67 655.06 237.48 668.35 244.3 674.7C248.5 674.4 253.61 670.6 257.46 668.8C267.12 664.28 277.07 660.52 286.69 655.88C304.27 647.41 321.86 638.41 338.62 628.38C346.33 623.76 353.75 618.59 361.45 613.92C373.55 606.58 386.27 599.78 397.53 591.17C396.7 584.09 392.15 576.86 390.09 569.96C386.6 558.27 385.08 546.09 383.12 534.09C383.01 532.7 382.91 531.32 382.8 529.93C382.8 525.39 382.8 520.85 382.8 516.31C382.92 514.13 383.03 511.95 383.15 509.77C383.3 507.85 383.44 505.92 383.59 503.99C383.72 502.68 383.86 501.36 383.99 500.05C387.62 476.41 393.83 452.71 406.6 432.14C410.26 426.24 414.34 420.51 418.96 415.31C422.09 411.79 426.6 408.12 428.75 404.01C423.15 392.59 414.55 382.49 406.58 372.65C386.28 347.58 361.41 325.17 333.7 308.56C325.97 303.92 307.5 291.43 299.46 290.31C293.08 294.95 288.07 302.54 283.01 308.55C272.5 321.03 262.6 333.83 254.46 348.01C227.76 394.48 212.55 439.71 207.31 493.16C205.65 510.12 204.84 526.78 206.07 543.81ZM816.94 474.28C810.72 474.33 804.51 474.38 798.29 474.42C794.01 474.23 789.72 474.04 785.44 473.85C783.65 473.78 781.85 473.71 780.06 473.65C779.98 473.8 779.9 473.96 779.82 474.11C778.27 480.1 778.41 486.95 777.39 493.16C775.21 506.55 771.82 519.48 767.88 532.43C764.89 542.25 760.29 551.72 755.84 560.94C739.05 595.71 716.36 625.15 684.87 647.99C672.14 657.21 658.79 665.87 644.45 672.36C640.55 674.12 636.43 675.95 632.35 677.25C629.55 678.14 626.58 678.11 624.02 679.56C624.13 683.59 626.94 686.68 628.68 690.3C631.87 696.92 634.91 703.67 637.71 710.45C646.43 731.52 653.6 753.15 658.57 775.43C660.45 783.85 661.15 792.48 662.45 800.99C662.98 804.48 662.8 808.75 664.98 811.45C672.92 809.52 679.75 804.41 687.05 800.92C704.15 792.76 720.13 783.37 735.63 772.49C806.65 722.63 859.72 644.07 880.43 559.79C886.19 536.35 891.08 512.51 891.99 488.3C892.24 481.78 894.47 467.13 892.62 461.55C888.62 461.6 884.42 464.05 880.53 465.2C875.2 466.79 869.75 467.99 864.32 469.2C848.64 472.68 832.81 472.65 816.94 474.28ZM517.98 839.83C521.68 839.84 525.39 839.86 529.1 839.87C570.52 840.69 615.92 830.57 654.44 815.48C656.78 805.45 652.44 794.04 650.83 784.11C646.7 758.8 638.19 733.8 628.28 710.2C625.69 704.03 622.06 698.44 619.35 692.36C617.98 689.3 617.09 685.12 614.73 682.76C605.25 683.65 595.84 686.76 586.36 688.03C574.03 689.68 561.94 689.08 549.6 689.43C547.67 689.29 545.74 689.15 543.81 689.01C501.53 683.39 462.28 669.63 431.63 638.56C422.63 629.44 415.77 619.45 408.57 608.93C406.16 605.41 404.3 601.27 401.58 598.04C394.71 599.37 378.41 613 371.47 617.46C347.08 633.15 322.05 647.46 296.12 660.41C286.68 665.13 276.85 669.6 267.03 673.45C261.1 675.77 253.05 677.55 248.4 682.08C249.64 688.81 255.93 696.23 259.72 701.88C270.21 717.54 282.56 731.83 295.93 745.1C345.45 794.22 410.48 826.76 479.4 837C492.01 838.87 505.24 839.34 517.98 839.83Z" fill="#1a1a1a" fill-rule="evenodd" stroke="#1a1a1a" stroke-width="70" stroke-linejoin="round"/><path d="M439.19 2.68C437.32 2.77 435.46 2.86 433.59 2.96C433.73 2.78 433.86 2.61 434 2.43C435.73 2.51 437.46 2.6 439.19 2.68ZM473.3 19.68C474.93 19.78 476.56 19.89 478.2 20C476.7 20.09 475.2 20.17 473.71 20.26C473.57 20.07 473.44 19.87 473.3 19.68ZM500.86 21.25C502.2 21.38 503.54 21.51 504.89 21.63C503.68 21.72 502.47 21.8 501.26 21.88C501.13 21.67 500.99 21.46 500.86 21.25ZM709.37 103.87C709.5 105.18 709.63 106.48 709.77 107.79C709.56 107.66 709.35 107.52 709.15 107.38C709.22 106.21 709.29 105.04 709.37 103.87ZM718.15 117.52C718.39 117.65 718.63 117.79 718.87 117.92C718.87 120.62 718.87 123.32 718.87 126.03C718.65 126.16 718.44 126.3 718.22 126.43C718.2 123.46 718.18 120.49 718.15 117.52ZM497.66 182.61C496.3 182.72 494.93 182.82 493.57 182.93C493.7 182.73 493.84 182.54 493.97 182.35C495.2 182.44 496.43 182.53 497.66 182.61ZM521.9 182.61C523.94 182.53 525.97 182.44 528.01 182.35C528.14 182.54 528.28 182.73 528.41 182.92C526.24 182.82 524.07 182.72 521.9 182.61ZM523.55 200.34C525.43 200.47 527.3 200.61 529.18 200.74C527.44 200.82 525.7 200.91 523.96 200.99C523.82 200.77 523.69 200.56 523.55 200.34ZM529.22 364.48C524.36 364.48 519.5 364.49 514.64 364.49C514.77 364.29 514.91 364.09 515.04 363.89C519.63 363.89 524.23 363.89 528.82 363.89C528.95 364.09 529.09 364.28 529.22 364.48ZM758.84 415.73C758.96 417.09 759.08 418.45 759.2 419.81C758.99 419.68 758.79 419.54 758.58 419.41C758.67 418.18 758.75 416.96 758.84 415.73ZM912.82 421.43C913.01 421.57 913.19 421.71 913.38 421.84C913.3 423.31 913.21 424.77 913.13 426.24C913.03 424.64 912.92 423.04 912.82 421.43ZM759.64 427.96C759.75 430.92 759.86 433.88 759.98 436.83C759.78 436.7 759.59 436.56 759.39 436.43C759.47 433.61 759.56 430.78 759.64 427.96ZM780.67 432.78C780.72 438.18 780.77 443.59 780.83 448.99C780.77 443.59 780.72 438.18 780.67 432.78ZM621.78 454.66C621.99 454.8 622.21 454.93 622.43 455.07C622.34 456.32 622.25 457.57 622.16 458.83C622.03 457.44 621.9 456.05 621.78 454.66ZM913.96 455.57C914.04 457.03 914.11 458.48 914.19 459.93C913.97 460.07 913.75 460.2 913.53 460.34C913.67 458.75 913.82 457.16 913.96 455.57ZM601.62 456.2C601.62 460.29 601.62 464.39 601.61 468.48C601.62 464.39 601.62 460.29 601.62 456.2ZM780.22 456.39C780.3 458.38 780.38 460.37 780.47 462.36C780.27 462.5 780.07 462.63 779.87 462.77C779.98 460.64 780.1 458.52 780.22 456.39ZM622.18 465.17C622.26 466.66 622.35 468.16 622.43 469.66C622.2 469.79 621.98 469.93 621.76 470.06C621.9 468.43 622.04 466.8 622.18 465.17ZM781.34 465.43C783.89 465.35 786.43 465.28 788.97 465.2C789.11 465.39 789.24 465.58 789.38 465.77C786.7 465.66 784.02 465.54 781.34 465.43ZM830.79 465.47C828.88 465.57 826.96 465.68 825.04 465.79C825.17 465.59 825.31 465.4 825.45 465.2C827.23 465.29 829.01 465.38 830.79 465.47ZM913.11 471.61C913.2 475.55 913.29 479.49 913.38 483.43C913.19 483.57 912.99 483.7 912.8 483.84C912.9 479.76 913.01 475.69 913.11 471.61ZM785.44 473.85C783.57 473.94 781.69 474.02 779.82 474.11C779.9 473.96 779.98 473.8 780.06 473.65C781.85 473.71 783.65 473.78 785.44 473.85ZM798.29 474.42C804.51 474.38 810.72 474.33 816.94 474.28C814.3 475.59 801.18 475.53 798.29 474.42ZM521.68 497.65C521.68 499.83 521.67 502.01 521.67 504.19C521.67 502.01 521.68 499.83 521.68 497.65ZM383.99 500.05C383.86 501.36 383.72 502.68 383.59 503.99C383.5 502.81 383.42 501.63 383.34 500.45C383.56 500.32 383.78 500.18 383.99 500.05ZM383.15 509.77C383.03 511.95 382.92 514.13 382.8 516.31C382.71 514.27 382.62 512.22 382.53 510.18C382.74 510.04 382.95 509.91 383.15 509.77ZM382.8 529.93C382.91 531.32 383.01 532.7 383.12 534.09C382.92 533.95 382.73 533.82 382.53 533.68C382.62 532.43 382.71 531.18 382.8 529.93ZM186.65 536.59C186.78 538.46 186.91 540.33 187.04 542.19C186.82 542.06 186.61 541.92 186.4 541.79C186.49 540.06 186.57 538.33 186.65 536.59ZM206.07 543.81C206.27 543.95 206.47 544.08 206.67 544.22C206.58 545.4 206.5 546.59 206.42 547.77C206.3 546.45 206.18 545.13 206.07 543.81ZM206.84 552.73C207.05 552.86 207.26 553 207.48 553.13C207.39 554.36 207.31 555.59 207.22 556.82C207.1 555.46 206.97 554.09 206.84 552.73ZM576.99 670.48C575.66 670.61 574.32 670.73 572.99 670.86C573.12 670.66 573.26 670.45 573.39 670.24C574.59 670.32 575.79 670.4 576.99 670.48ZM543.81 689.01C545.74 689.15 547.67 689.29 549.6 689.43C547.81 689.52 546.01 689.6 544.22 689.69C544.08 689.47 543.95 689.24 543.81 689.01ZM517.98 839.83C521.68 839.84 525.39 839.86 529.1 839.87C525.39 839.86 521.68 839.84 517.98 839.83ZM493.57 857.69C494.9 857.8 496.24 857.91 497.58 858.02C496.37 858.1 495.17 858.19 493.97 858.27C493.84 858.08 493.7 857.88 493.57 857.69ZM503.29 858.49C505.47 858.6 507.65 858.71 509.83 858.81C507.79 858.9 505.74 858.99 503.7 859.08C503.56 858.88 503.43 858.69 503.29 858.49ZM553.54 858.44C553.4 858.66 553.27 858.87 553.13 859.08C551.93 858.99 550.72 858.91 549.51 858.83C550.85 858.7 552.2 858.57 553.54 858.44ZM525.98 859.38C529.5 859.33 533.01 859.29 536.52 859.25C536.38 859.46 536.25 859.67 536.11 859.89C532.87 859.89 529.63 859.89 526.39 859.89C526.25 859.72 526.12 859.55 525.98 859.38Z" fill="#1a1a1a" fill-rule="evenodd" stroke="#1a1a1a" stroke-width="70" stroke-linejoin="round"/></svg>
@@ -0,0 +1,202 @@
1
+ // 登录邮箱业务 P1-P3 验收(真实 DB,console driver)
2
+ // 覆盖:邀请码注册(有效/无效/用尽)、邮箱验证 token 确认、忘记密码发件、
3
+ // open 模式注册、guest 短路、EmailLog 全链路落库
4
+ // serverFn 走真实 handler:mock createServerFn RPC 壳(validator.parse + handler 直调),
5
+ // @tanstack/react-start/server 的 getRequest 返回 null(session 相关 serverFn 本脚本不覆盖)
6
+ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'
7
+
8
+ vi.mock('@tanstack/react-start', () => ({
9
+ createServerFn: (config: any) => {
10
+ const api: any = {
11
+ _validator: undefined,
12
+ validator(schema: any) {
13
+ this._validator = schema
14
+ return this
15
+ },
16
+ handler(fn: Function) {
17
+ const wrapped = async (args: any) => {
18
+ let data = args?.data
19
+ if (this._validator && data !== undefined) {
20
+ data = this._validator.parse ? this._validator.parse(data) : data
21
+ }
22
+ return fn({ data })
23
+ }
24
+ return wrapped
25
+ },
26
+ }
27
+ return api
28
+ },
29
+ }))
30
+
31
+ vi.mock('@tanstack/react-start/server', () => ({
32
+ getRequest: vi.fn(() => null),
33
+ }))
34
+
35
+ vi.stubEnv('EMAIL_DRIVER', 'console')
36
+
37
+ import { prisma } from '@allbluecn/database'
38
+ import { registerFn } from '@/server/serverFns/auth/register'
39
+ import { confirmEmailVerificationFn } from '@/server/serverFns/auth/email-verification'
40
+ import { forgotPasswordFn } from '@/server/serverFns/auth/forgot-password'
41
+ import { sendTemplateEmail } from '@/server/email'
42
+
43
+ const RUN = `acc${Date.now().toString(36)}`
44
+ // 实现层 consumeInvitationCode 会 trim().toUpperCase() 归一化,造码须用大写
45
+ const CODE_OK = `${RUN}OK`.toUpperCase()
46
+ const CODE_ONCE = `${RUN}1X`.toUpperCase()
47
+ const EMAIL1 = `${run_email(RUN)}@example.com`
48
+ const EMAIL_OPEN = `${RUN}open@example.com`
49
+
50
+ function run_email(r: string) { return `user_${r}` }
51
+
52
+ let createdUserIds: string[] = []
53
+ let createdCodeIds: string[] = []
54
+
55
+ beforeAll(async () => {
56
+ // 清理旧同前缀数据 + 造码
57
+ await prisma.invitationCode.deleteMany({ where: { code: { startsWith: RUN } } })
58
+ await prisma.user.deleteMany({ where: { email: { contains: RUN } } })
59
+ await prisma.emailLog.deleteMany({ where: { to: { contains: RUN } } })
60
+ const mk = (code: string, maxUses: number) =>
61
+ prisma.invitationCode.create({
62
+ data: { code, maxUses, usedCount: 0, expiresAt: new Date(Date.now() + 86400_000) },
63
+ })
64
+ const a = await mk(CODE_OK, 2)
65
+ const b = await mk(CODE_ONCE, 1)
66
+ createdCodeIds.push(a.id, b.id)
67
+ // 确保 invite 模式(默认即 invite:无记录)
68
+ await prisma.systemSetting.deleteMany({ where: { key: 'auth.registrationMode' } })
69
+ })
70
+
71
+ afterAll(async () => {
72
+ // 清理验收数据
73
+ await prisma.invitationUse.deleteMany({ where: { codeId: { in: createdCodeIds } } })
74
+ await prisma.invitationCode.deleteMany({ where: { id: { in: createdCodeIds } } })
75
+ if (createdUserIds.length) {
76
+ await prisma.emailVerificationToken.deleteMany({ where: { userId: { in: createdUserIds } } })
77
+ await prisma.emailLog.deleteMany({ where: { userId: { in: createdUserIds } } })
78
+ await prisma.notification.deleteMany({ where: { userId: { in: createdUserIds } } })
79
+ await prisma.user.deleteMany({ where: { id: { in: createdUserIds } } })
80
+ }
81
+ await prisma.emailLog.deleteMany({ where: { to: { contains: RUN } } })
82
+ await prisma.systemSetting.deleteMany({ where: { key: 'auth.registrationMode' } })
83
+ })
84
+
85
+ describe('验收 1:invite 模式注册全链路(有效码)', () => {
86
+ it('注册成功 + 邀请码消耗 + 使用记录 + 验证邮件 EmailLog', async () => {
87
+ const res = await registerFn({
88
+ data: { username: `u_${RUN}`, email: EMAIL1, password: 'Abcdef12', invitationCode: CODE_OK },
89
+ })
90
+ expect(res.email).toBe(EMAIL1)
91
+ createdUserIds.push(res.id)
92
+
93
+ const code = await prisma.invitationCode.findUnique({ where: { code: CODE_OK } })
94
+ expect(code?.usedCount).toBe(1)
95
+
96
+ const use = await prisma.invitationUse.findFirst({ where: { codeId: code!.id } })
97
+ expect(use?.userId).toBe(res.id)
98
+
99
+ const log = await prisma.emailLog.findFirst({
100
+ where: { to: EMAIL1, templateKey: 'auth.verify-email' },
101
+ })
102
+ expect(log?.status).toBe('SENT')
103
+ expect(log?.category).toBe('auth')
104
+ expect(log?.provider).toBe('console')
105
+ })
106
+
107
+ it('弱密码被 serverFn validator 拒绝', async () => {
108
+ await expect(
109
+ registerFn({ data: { username: `u2_${RUN}`, email: `u2${RUN}@example.com`, password: '12345678', invitationCode: CODE_OK } }),
110
+ ).rejects.toThrow()
111
+ })
112
+
113
+ it('无效码拒绝且不创建用户', async () => {
114
+ await expect(
115
+ registerFn({ data: { username: `u3_${RUN}`, email: `u3${RUN}@example.com`, password: 'Abcdef12', invitationCode: 'NOPE-NOPE' } }),
116
+ ).rejects.toThrow(/邀请码/)
117
+ const u = await prisma.user.findUnique({ where: { email: `u3${RUN}@example.com` } })
118
+ expect(u).toBeNull()
119
+ })
120
+ })
121
+
122
+ describe('验收 2:邀请码次数用尽', () => {
123
+ it('maxUses=1 的码第二次注册被拒', async () => {
124
+ const r1 = await registerFn({
125
+ data: { username: `u4_${RUN}`, email: `u4${RUN}@example.com`, password: 'Abcdef12', invitationCode: CODE_ONCE },
126
+ })
127
+ createdUserIds.push(r1.id)
128
+ await expect(
129
+ registerFn({ data: { username: `u5_${RUN}`, email: `u5${RUN}@example.com`, password: 'Abcdef12', invitationCode: CODE_ONCE } }),
130
+ ).rejects.toThrow(/用尽|过期|禁用/)
131
+ })
132
+ })
133
+
134
+ describe('验收 3:邮箱验证 token 确认 + welcome 邮件', () => {
135
+ it('真实 token 确认成功 → emailVerified 写入 + auth.welcome EmailLog', async () => {
136
+ const uid = createdUserIds[0]
137
+ const token = await prisma.emailVerificationToken.create({
138
+ data: { token: `tok_${RUN}`, userId: uid, expiresAt: new Date(Date.now() + 86400_000) },
139
+ })
140
+ const res = await confirmEmailVerificationFn({ data: { token: token.token } })
141
+ expect(res.ok).toBe(true)
142
+
143
+ const u = await prisma.user.findUnique({ where: { id: uid }, select: { emailVerified: true } })
144
+ expect(u?.emailVerified).toBeTruthy()
145
+
146
+ const welcome = await prisma.emailLog.findFirst({
147
+ where: { to: EMAIL1, templateKey: 'auth.welcome' },
148
+ })
149
+ expect(welcome?.status).toBe('SENT')
150
+ })
151
+
152
+ it('伪造 token 确认失败', async () => {
153
+ const res = await confirmEmailVerificationFn({ data: { token: `bad_${RUN}` } })
154
+ expect(res.ok).toBe(false)
155
+ })
156
+ })
157
+
158
+ describe('验收 4:忘记密码发件 + reset 邮件 EmailLog', () => {
159
+ it('固定响应 + 邮件日志', async () => {
160
+ const res = await forgotPasswordFn({ data: { email: EMAIL1 } })
161
+ expect(res.message).toBeTruthy()
162
+ const log = await prisma.emailLog.findFirst({
163
+ where: { to: EMAIL1, templateKey: 'auth.reset-password' },
164
+ })
165
+ expect(log?.status).toBe('SENT')
166
+ })
167
+
168
+ it('不存在的邮箱:响应一致但无邮件(防枚举)', async () => {
169
+ const ghost = `ghost${RUN}@example.com`
170
+ const res = await forgotPasswordFn({ data: { email: ghost } })
171
+ expect(res.message).toBeTruthy()
172
+ const log = await prisma.emailLog.findFirst({ where: { to: ghost } })
173
+ expect(log).toBeNull()
174
+ })
175
+ })
176
+
177
+ describe('验收 5:open 模式注册(无码)', () => {
178
+ it('切换 open 后无邀请码注册成功', async () => {
179
+ await prisma.systemSetting.create({ data: { key: 'auth.registrationMode', value: 'open' } })
180
+ const res = await registerFn({
181
+ data: { username: `u6_${RUN}`, email: EMAIL_OPEN, password: 'Abcdef12', invitationCode: '' },
182
+ })
183
+ createdUserIds.push(res.id)
184
+ expect(res.email).toBe(EMAIL_OPEN)
185
+ })
186
+ })
187
+
188
+ describe('验收 6:guest 短路', () => {
189
+ it('guest@localhost 收件人直接 skipped,不写 EmailLog', async () => {
190
+ const res = await sendTemplateEmail({
191
+ key: 'auth.verify-email',
192
+ to: 'guest@localhost',
193
+ locale: 'zh',
194
+ userId: 'local-guest',
195
+ vars: { url: 'https://example.com/verify?token=x', username: 'guest' },
196
+ })
197
+ expect(res.skipped).toBe(true)
198
+ expect(res.skipReason).toBe('guest')
199
+ const log = await prisma.emailLog.findFirst({ where: { to: 'guest@localhost' } })
200
+ expect(log).toBeNull()
201
+ })
202
+ })
@@ -0,0 +1,142 @@
1
+ // 计划 2 集成验收:changelog double opt-in 订阅全链路 + 退订双机制
2
+ // 真实 DB + console driver,覆盖:订阅→确认邮件→确认页→发布批次→退订(DB token + HMAC 双机制)
3
+ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'
4
+
5
+ vi.mock('@tanstack/react-start', () => ({
6
+ createServerFn: (config: any) => {
7
+ const api: any = {
8
+ _validator: undefined,
9
+ validator(schema: any) { this._validator = schema; return this },
10
+ handler(fn: Function) {
11
+ return async (args: any) => {
12
+ let data = args?.data
13
+ if (this._validator && data !== undefined) {
14
+ data = this._validator.parse ? this._validator.parse(data) : data
15
+ }
16
+ return fn({ data })
17
+ }
18
+ },
19
+ }
20
+ return api
21
+ },
22
+ }))
23
+ vi.mock('@tanstack/react-start/server', () => ({ getRequest: vi.fn(() => null) }))
24
+ // 消除本地 env 依赖(计划 1 验收遗留的 stub 模式,commit 11f5e1b4)
25
+ vi.stubEnv('EMAIL_DRIVER', 'console')
26
+
27
+ import { prisma } from '@allbluecn/database'
28
+ import { subscribeChangelogFn, confirmChangelogSubscriptionFn } from '@/server/serverFns/changelog/subscribe'
29
+ import { unsubscribeByEmailTokenFn } from '@/server/serverFns/email-unsubscribe'
30
+ import { sendTemplateEmail } from '@/server/email'
31
+ import { signUnsubscribeToken } from '@/server/email/deliverability'
32
+ import { generateEmailToken } from '@/server/email/outbox'
33
+
34
+ const RUN = `p2acc${Date.now().toString(36)}`
35
+ const EMAIL = `${RUN}@example.com`
36
+ const EMAIL2 = `${RUN}b@example.com`
37
+
38
+ let confirmToken = ''
39
+ let hmacToken = ''
40
+
41
+ beforeAll(async () => {
42
+ await prisma.emailSubscriber.deleteMany({ where: { email: { contains: RUN } } })
43
+ await prisma.emailLog.deleteMany({ where: { to: { contains: RUN } } })
44
+ })
45
+
46
+ afterAll(async () => {
47
+ await prisma.emailSubscriber.deleteMany({ where: { email: { contains: RUN } } })
48
+ await prisma.emailLog.deleteMany({ where: { to: { contains: RUN } } })
49
+ })
50
+
51
+ describe('验收 1:changelog 订阅 double opt-in', () => {
52
+ it('订阅 → pending + 确认邮件 EmailLog + confirmToken 生成', async () => {
53
+ const res = await subscribeChangelogFn({ data: { email: EMAIL, locale: 'zh' } })
54
+ expect(res.status).toBe('pending')
55
+ const sub = await prisma.emailSubscriber.findUnique({ where: { email: EMAIL }, select: { confirmToken: true, confirmedAt: true } })
56
+ expect(sub?.confirmedAt).toBeNull()
57
+ confirmToken = sub!.confirmToken
58
+ expect(confirmToken).toBeTruthy()
59
+ const log = await prisma.emailLog.findFirst({ where: { to: EMAIL, templateKey: 'product.changelog-confirm' } })
60
+ expect(log?.status).toBe('SENT')
61
+ })
62
+
63
+ it('确认链接 → confirmedAt 写入', async () => {
64
+ const res = await confirmChangelogSubscriptionFn({ data: { token: confirmToken } })
65
+ expect(res.ok).toBe(true)
66
+ const sub = await prisma.emailSubscriber.findUnique({ where: { email: EMAIL }, select: { confirmedAt: true } })
67
+ expect(sub?.confirmedAt).toBeTruthy()
68
+ })
69
+
70
+ it('重复订阅已确认用户 → already', async () => {
71
+ const res = await subscribeChangelogFn({ data: { email: EMAIL } })
72
+ expect(res.status).toBe('already')
73
+ })
74
+
75
+ it('伪造 token 确认失败', async () => {
76
+ const res = await confirmChangelogSubscriptionFn({ data: { token: `fake-${RUN}` } })
77
+ expect(res.ok).toBe(false)
78
+ })
79
+ })
80
+
81
+ describe('验收 2:退订双机制', () => {
82
+ it('机制 2(订阅者 DB token)→ 退订即时生效', async () => {
83
+ const res = await unsubscribeByEmailTokenFn({ data: { token: confirmToken } })
84
+ expect(res.ok).toBe(true)
85
+ const sub = await prisma.emailSubscriber.findUnique({ where: { email: EMAIL }, select: { unsubscribedAt: true } })
86
+ expect(sub?.unsubscribedAt).toBeTruthy()
87
+ })
88
+
89
+ it('机制 2 重复退订幂等返回 ok', async () => {
90
+ const res = await unsubscribeByEmailTokenFn({ data: { token: confirmToken } })
91
+ expect(res.ok).toBe(true)
92
+ })
93
+
94
+ it('机制 1(注册用户 HMAC token)→ EmailPreference unsubscribedAll', async () => {
95
+ // 造一个真实用户 + HMAC token
96
+ const user = await prisma.user.create({
97
+ data: {
98
+ username: `u_${RUN}`,
99
+ email: EMAIL2,
100
+ password: '$2a$12$dummyhashplaceholderdummyhashplaceholderdummyhashplaceholder',
101
+ status: 'ACTIVE',
102
+ },
103
+ })
104
+ hmacToken = signUnsubscribeToken({ userId: user.id, email: EMAIL2, category: 'notification' })
105
+ const res = await unsubscribeByEmailTokenFn({ data: { token: hmacToken } })
106
+ expect(res.ok).toBe(true)
107
+ const pref = await prisma.emailPreference.findUnique({ where: { userId: user.id } })
108
+ expect(pref?.unsubscribedAll).toBe(true)
109
+ await prisma.user.delete({ where: { id: user.id } })
110
+ })
111
+
112
+ it('无效 token → ok=false', async () => {
113
+ const res = await unsubscribeByEmailTokenFn({ data: { token: `nope-${RUN}-${Date.now()}` } })
114
+ expect(res.ok).toBe(false)
115
+ })
116
+ })
117
+
118
+ describe('验收 3:退订后不再发送(outbox 退订检查)', () => {
119
+ it('已退订用户镜像邮件被跳过(skipReason=unsubscribed)', async () => {
120
+ // 用验收 2 的用户(已 unsubscribedAll)——需重建用户(已删)
121
+ const user = await prisma.user.create({
122
+ data: {
123
+ username: `u2_${RUN}`,
124
+ email: EMAIL2,
125
+ password: '$2a$12$dummyhashplaceholderdummyhashplaceholderdummyhashplaceholder',
126
+ status: 'ACTIVE',
127
+ emailVerified: new Date(),
128
+ },
129
+ })
130
+ // EmailPreference 已存在(验收 2 创建,unsubscribedAll=true,因 user 删除级联删了)→ 重建
131
+ await prisma.emailPreference.create({ data: { userId: user.id, unsubscribedAll: true, unsubscribedCategories: [] } })
132
+ const res = await sendTemplateEmail({
133
+ key: 'notification.mirror',
134
+ to: EMAIL2,
135
+ locale: 'zh',
136
+ userId: user.id,
137
+ vars: { title: 'test' },
138
+ })
139
+ expect(res.skipped).toBe(true)
140
+ await prisma.user.delete({ where: { id: user.id } })
141
+ })
142
+ })
@@ -22,9 +22,10 @@ import type { LucideIcon } from 'lucide-react'
22
22
  interface IconInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
23
23
  icon: LucideIcon
24
24
  label: string
25
+ error?: string | null
25
26
  }
26
27
 
27
- export function IconInput({ icon: Icon, label, id, className, ...props }: IconInputProps) {
28
+ export function IconInput({ icon: Icon, label, id, className, error, ...props }: IconInputProps) {
28
29
  const inputId = id || label.toLowerCase()
29
30
 
30
31
  return (
@@ -34,12 +35,19 @@ export function IconInput({ icon: Icon, label, id, className, ...props }: IconIn
34
35
  <Input
35
36
  id={inputId}
36
37
  className={`peer ps-9 ${className || ''}`}
38
+ aria-invalid={!!error}
39
+ aria-describedby={error ? `${inputId}-error` : undefined}
37
40
  {...props}
38
41
  />
39
42
  <div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground">
40
43
  <Icon size={16} />
41
44
  </div>
42
45
  </div>
46
+ {error ? (
47
+ <p id={`${inputId}-error`} className="text-xs text-destructive mt-1">
48
+ {error}
49
+ </p>
50
+ ) : null}
43
51
  </div>
44
52
  )
45
53
  }
@@ -23,9 +23,10 @@ import { m } from "@/paraglide/messages.js";
23
23
 
24
24
  interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
25
25
  label: string
26
+ error?: string | null
26
27
  }
27
28
 
28
- export function PasswordInput({ label, id, className, ...props }: PasswordInputProps) {
29
+ export function PasswordInput({ label, id, className, error, ...props }: PasswordInputProps) {
29
30
  const inputId = id || label.toLowerCase()
30
31
  const [isVisible, setIsVisible] = useState(false)
31
32
 
@@ -37,6 +38,8 @@ export function PasswordInput({ label, id, className, ...props }: PasswordInputP
37
38
  id={inputId}
38
39
  className={`ps-9 pe-9 ${className || ''}`}
39
40
  type={isVisible ? 'text' : 'password'}
41
+ aria-invalid={!!error}
42
+ aria-describedby={error ? `${inputId}-error` : undefined}
40
43
  {...props}
41
44
  />
42
45
  <div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground">
@@ -61,6 +64,11 @@ export function PasswordInput({ label, id, className, ...props }: PasswordInputP
61
64
  )}
62
65
  </button>
63
66
  </div>
67
+ {error ? (
68
+ <p id={`${inputId}-error`} className="text-xs text-destructive mt-1">
69
+ {error}
70
+ </p>
71
+ ) : null}
64
72
  </div>
65
73
  )
66
74
  }
@@ -23,6 +23,8 @@ import { Route } from '@/routes/_nav/changelog/route'
23
23
  import type { DeveloperLogEntry } from '@allbluecn/kernel'
24
24
  import { SDK_GROUPS } from '@/lib/changelog/sdk-versions'
25
25
  import { APP_FULL_VERSION, APP_UPDATE_DATE_DISPLAY } from '@/lib/app-version'
26
+ import { subscribeChangelogFn } from '@/server/serverFns/changelog/subscribe'
27
+ import { toast } from '@/components/ui/sonner'
26
28
  import { m } from "@/paraglide/messages.js";
27
29
 
28
30
  const TAG_KEYS = ['all', 'feat', 'fix', 'improvement', 'design', 'refactor', 'breaking', 'docs', 'security', 'perf']
@@ -51,10 +53,39 @@ const REQUIRES_ACTION_TAGS = new Set(['breaking'])
51
53
  function ChangelogSidebar() {
52
54
  const [activeGroup, setActiveGroup] = useState<string | null>(null)
53
55
  const [isOverflowing, setIsOverflowing] = useState(false)
56
+ const [email, setEmail] = useState('')
57
+ const [subscribing, setSubscribing] = useState(false)
54
58
  const outerRef = useRef<HTMLDivElement>(null)
55
59
  const isOverflowingRef = useRef(false)
56
60
  const expandedHeightRef = useRef(0)
57
61
 
62
+ const handleSubscribe = async () => {
63
+ const trimmed = email.trim()
64
+ if (!trimmed || subscribing) return
65
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
66
+ toast.error(m.common_changelog_subscribeInvalidEmail())
67
+ return
68
+ }
69
+ setSubscribing(true)
70
+ try {
71
+ const result = await subscribeChangelogFn({ data: { email: trimmed } })
72
+ if (result.status === 'already') {
73
+ toast.info(m.common_changelog_subscribeAlready())
74
+ } else {
75
+ toast.success(m.common_changelog_subscribeCheckEmail())
76
+ }
77
+ setEmail('')
78
+ } catch (err) {
79
+ toast.error(
80
+ err instanceof Error && /email/i.test(err.message)
81
+ ? m.common_changelog_subscribeInvalidEmail()
82
+ : m.common_changelog_subscribeFailed(),
83
+ )
84
+ } finally {
85
+ setSubscribing(false)
86
+ }
87
+ }
88
+
58
89
  const checkOverflow = useCallback(() => {
59
90
  if (!outerRef.current) return
60
91
 
@@ -146,10 +177,22 @@ function ChangelogSidebar() {
146
177
  <div className="mt-3 flex gap-1.5">
147
178
  <input
148
179
  type="email"
180
+ value={email}
181
+ onChange={(e) => setEmail(e.target.value)}
182
+ onKeyDown={(e) => {
183
+ if (e.key === 'Enter') {
184
+ e.preventDefault()
185
+ handleSubscribe()
186
+ }
187
+ }}
149
188
  placeholder={m.common_changelog_subscribe_placeholder()}
150
189
  className="flex-1 min-w-0 h-8 rounded-md border border-border bg-background px-3 text-xs placeholder:text-muted-foreground/70 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-shadow"
151
190
  />
152
- <button className="h-8 px-3 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors shrink-0">
191
+ <button
192
+ onClick={handleSubscribe}
193
+ disabled={subscribing || !email.trim()}
194
+ className="h-8 px-3 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors shrink-0 disabled:opacity-50 disabled:pointer-events-none"
195
+ >
153
196
  {m.common_changelog_subscribe_submit()}
154
197
  </button>
155
198
  </div>
@@ -37,6 +37,7 @@ export interface AppFieldProps<TValue> {
37
37
  description?: string
38
38
  validators?: AppFieldValidators<TValue>
39
39
  children?: (field: AnyFieldApi) => ReactNode
40
+ renderError?: boolean
40
41
  }
41
42
 
42
43
  export function AppField<TValue>({
@@ -46,6 +47,7 @@ export function AppField<TValue>({
46
47
  description,
47
48
  validators,
48
49
  children,
50
+ renderError = true,
49
51
  }: AppFieldProps<TValue>) {
50
52
  return (
51
53
  <Field>
@@ -57,7 +59,7 @@ export function AppField<TValue>({
57
59
  children={(field) => (
58
60
  <>
59
61
  {children ? children(field) : <DefaultInput field={field} />}
60
- <AppFieldError field={field} />
62
+ {renderError && <AppFieldError field={field} />}
61
63
  </>
62
64
  )}
63
65
  />