kern 0.5.0 → 0.6.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +13 -2
  4. data/README.md +15 -13
  5. data/app/assets/builds/tailwind.css +1 -1
  6. data/app/controllers/kern/billing_profiles/subscriptions_controller.rb +37 -0
  7. data/app/controllers/kern/billing_profiles/subscriptions_controller.rb.tt +37 -0
  8. data/app/controllers/kern/settings/subscriptions_controller.rb +6 -0
  9. data/app/controllers/kern/signups_controller.rb +1 -1
  10. data/app/models/billing_profile/subscription.rb +16 -0
  11. data/app/models/billing_profile.rb +7 -0
  12. data/app/models/session.rb +1 -1
  13. data/app/models/signup.rb +1 -1
  14. data/app/models/user.rb +1 -1
  15. data/app/models/workspace/access.rb +3 -0
  16. data/app/models/workspace/access.rb.tt +10 -0
  17. data/app/models/workspace/billable.rb +16 -0
  18. data/app/models/workspace/feature_access.rb +28 -0
  19. data/app/models/workspace/members.rb +9 -7
  20. data/app/models/workspace/setup.rb +5 -3
  21. data/app/models/workspace.rb +2 -0
  22. data/app/views/components/_dialog.html.erb +36 -0
  23. data/app/views/kern/pages/welcome.html.erb +10 -6
  24. data/app/views/kern/settings/_cards.html.erb +11 -3
  25. data/app/views/kern/settings/subscriptions/_plan.html.erb +35 -0
  26. data/app/views/kern/settings/subscriptions/show.html.erb +11 -0
  27. data/app/views/kern/settings/users/show.html.erb +2 -2
  28. data/app/views/kern/signups/new.html.erb +2 -0
  29. data/app/views/layouts/kern/application.html.erb +3 -1
  30. data/app/views/layouts/kern/application.html.erb.tt +1 -0
  31. data/app/views/layouts/kern/auth.html.erb +1 -1
  32. data/app/webhooks/stripe/base.rb +31 -0
  33. data/app/webhooks/stripe/checkout_session_completed.rb +46 -0
  34. data/app/webhooks/stripe/customer_subscription_deleted.rb +20 -0
  35. data/app/webhooks/stripe/customer_subscription_updated.rb +33 -0
  36. data/bin/release +2 -3
  37. data/config/initializers/stripe.rb +5 -0
  38. data/config/initializers/stripe.rb.tt +3 -0
  39. data/config/routes.rb +4 -0
  40. data/db/migrate/20250101000007_create_billing_profiles.rb +14 -0
  41. data/db/migrate/20250101000008_create_billing_profile_subscriptions.rb +20 -0
  42. data/lib/generators/kern/feature/USAGE +1 -0
  43. data/lib/generators/kern/feature/feature_generator.rb +82 -19
  44. data/lib/generators/kern/install/install_generator.rb +43 -7
  45. data/lib/generators/kern/install/templates/POST_INSTALL +25 -0
  46. data/lib/generators/kern/install/templates/configurations/plans.yml +43 -0
  47. data/lib/generators/kern/install/templates/configurations/stripe.yml +11 -0
  48. data/lib/generators/kern/layouts/layouts_generator.rb +12 -0
  49. data/lib/kern/version.rb +1 -1
  50. metadata +25 -3
  51. data/lib/generators/kern/views/USAGE +0 -22
  52. data/lib/generators/kern/views/views_generator.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51b072139e6f1ddd51d9f6ed4d54b6b86766cbebe5c47ba442bdf3e6b7dc4682
4
- data.tar.gz: faa1c2937aff2ae45731f0f519d010a216215c5b1d1f61786aa53205872cb286
3
+ metadata.gz: 7556363f2a221d668d4e6accb7f593281689a209e93b12121450b332b7019cb7
4
+ data.tar.gz: 8e6764cb26603e1c426fcad5c70458159fd5342177036e8ba39144b4120fd8f0
5
5
  SHA512:
6
- metadata.gz: 0d3ebbe105034fd6f4fcedfeda2df9e5a177ffdc9e358a996a3bf28cff52b9a70506cee37bbc6a90749029e6d84102c2f9e9fbb4d3fb6c10be6cce56cba5596f
7
- data.tar.gz: 768a29e1c27f2d72caff3700ebd14dea167be3b4bc534b9a74a980180d8f04fe1ac99578e1fc80fa959e84e3cda49e5defd8b7c77995fa2d1d0b6418ebe05b60
6
+ metadata.gz: 53e33276427207bc33ea89af080844b3aa74d2f06bde0a36687b884d5be3361becfe4075dc9548477058a71c5e858fec178fa61d634c4a616810a4fd447ece33
7
+ data.tar.gz: a506a4aa52724b5a16d9708479e525165c9ca005d035e1cebfcc39af9bb9a0a5e0a5f01f00f6d43e93fcdf7d74a9bf7cd1393ff6dbeeb2db725ef578d5f93027
data/Gemfile CHANGED
@@ -10,6 +10,8 @@ gem "puma"
10
10
  gem "sqlite3"
11
11
  gem "tailwindcss-rails"
12
12
  gem "turbo-rails"
13
+ gem "stripe"
14
+ gem "rails_vault"
13
15
 
14
16
  group :development do
15
17
  gem "standard", "~> 1.50.0"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kern (0.5.0)
4
+ kern (0.6.0)
5
5
  rails (>= 8.0.0)
6
6
 
7
7
  GEM
@@ -194,6 +194,9 @@ GEM
194
194
  rails-html-sanitizer (1.6.2)
195
195
  loofah (~> 2.21)
196
196
  nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
197
+ rails_vault (0.1.0)
198
+ rails (>= 7.2.2)
199
+ store_attribute (>= 1.3)
197
200
  railties (8.1.1)
198
201
  actionpack (= 8.1.1)
199
202
  activesupport (= 8.1.1)
@@ -252,7 +255,10 @@ GEM
252
255
  standard-performance (1.9.0)
253
256
  lint_roller (~> 1.1)
254
257
  rubocop-performance (~> 1.26.0)
258
+ store_attribute (2.0.1)
259
+ activerecord (>= 6.1)
255
260
  stringio (3.1.9)
261
+ stripe (18.0.1)
256
262
  tailwindcss-rails (4.4.0)
257
263
  railties (>= 7.0.0)
258
264
  tailwindcss-ruby (~> 4.0)
@@ -300,9 +306,11 @@ DEPENDENCIES
300
306
  propshaft
301
307
  puma
302
308
  rails (~> 8.1.0)
309
+ rails_vault
303
310
  rake (~> 13.3.0)
304
311
  sqlite3
305
312
  standard (~> 1.50.0)
313
+ stripe
306
314
  tailwindcss-rails
307
315
  turbo-rails
308
316
 
@@ -337,7 +345,7 @@ CHECKSUMS
337
345
  io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb
338
346
  irb (1.15.3) sha256=4349edff1efa7ff7bfd34cb9df74a133a588ba88c2718098b3b4468b81184aaa
339
347
  json (2.17.1) sha256=e0e4824541336a44915436f53e7ea74c687314fb8f88080fa1456f6a34ead92e
340
- kern (0.5.0)
348
+ kern (0.6.0)
341
349
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
342
350
  lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
343
351
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
@@ -375,6 +383,7 @@ CHECKSUMS
375
383
  rails (8.1.1) sha256=877509b7aef309239978685883097d2c03e21383a50a3f78882cf9b3b5c136f7
376
384
  rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
377
385
  rails-html-sanitizer (1.6.2) sha256=35fce2ca8242da8775c83b6ba9c1bcaad6751d9eb73c1abaa8403475ab89a560
386
+ rails_vault (0.1.0) sha256=ac98a27a1ef563735948ba5278ec2da8d2cebfab843bea8ee27dfdcbbe79dda5
378
387
  railties (8.1.1) sha256=fb0c7038b147bea41bf6697fa443ff1c5c47d3bb1eedd9ecf1bceeb90efcb868
379
388
  rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
380
389
  rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
@@ -397,7 +406,9 @@ CHECKSUMS
397
406
  standard (1.50.0) sha256=b6c67f61fd6cedeec90ee338c6e913d9ccc4c467660ad1575da8aa6ba10f4aec
398
407
  standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
399
408
  standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
409
+ store_attribute (2.0.1) sha256=643655e4800655b58379e8b01bd524f5586093a9d88698483ac8762cda25b5ab
400
410
  stringio (3.1.9) sha256=c111af13d3a73eab96a3bc2655ecf93788d13d28cb8e25c1dcbff89ace885121
411
+ stripe (18.0.1) sha256=9be1f8e286b9046591496d09b83c48b3c5256b9864d67076baac3985d0cc4f22
401
412
  tailwindcss-rails (4.4.0) sha256=efa2961351a52acebe616e645a81a30bb4f27fde46cc06ce7688d1cd1131e916
402
413
  tailwindcss-ruby (4.1.16) sha256=d30e4713152bb89ebe1fddb5b5f9b31d7755bf5576453e601eb58b19174c48c0
403
414
  tailwindcss-ruby (4.1.16-aarch64-linux-gnu) sha256=916554206b47b4218ba9130ee369687a6546fba0224b7168f118e4f955b2f97e
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kern
2
2
 
3
- Rails engine that handles the SaaS foundation: authentication, team invitations, subscription/billing, and common partials. Skip the boilerplate and start shipping the actual product. Launch faster.
3
+ Rails engine that handles the SaaS foundation: authentication, team invitations, subscription/billing, and common partials. Skip the boilerplate and start shipping the actual product.
4
4
 
5
5
 
6
6
  <table>
@@ -15,6 +15,11 @@ Rails engine that handles the SaaS foundation: authentication, team invitations,
15
15
  <img src="https://raw.githubusercontent.com/Rails-Designer/kern/HEAD/.github/kern__dashboard.jpg" alt="Dashboard" width="400">
16
16
  </a>
17
17
  </td>
18
+ <td>
19
+ <a href="https://raw.githubusercontent.com/Rails-Designer/kern/HEAD/.github/kern__subscriptions.jpg" target="_blank">
20
+ <img src="https://raw.githubusercontent.com/Rails-Designer/kern/HEAD/.github/kern__subscriptions.jpg" alt="Subscriptions" width="400">
21
+ </a>
22
+ </td>
18
23
  </tr>
19
24
  </table>
20
25
 
@@ -72,10 +77,7 @@ Kern works out of the box after installation. All features are available immedia
72
77
 
73
78
  ### Generators
74
79
 
75
- If you want to customize any available view or feature, you can run:
76
-
77
- - `bin/rails generate kern:views`;
78
- - `bin/rails generate kern:feature`.
80
+ If you want to customize any available feature (e.g. sign up, log in, etc.), you can run: `bin/rails generate kern:feature`.
79
81
 
80
82
  Append `--help` to see the generator's help.
81
83
 
@@ -105,12 +107,12 @@ Update user's email and password.
105
107
 
106
108
  ### Multi-tenancy
107
109
 
108
- By default a `Workspace` record is created upon sign up. Each user is associated to it using a `Member` join table. Next to that: each `Member` can have multiple `Role`'s you can use to determine if they are authorized.
110
+ By default a `Workspace` record is created upon sign up. Each `User` is associated to it using a `Member` join table. Each `Member` can have multiple `Role`'s you can use to authorize if they should have access (`Current.member.has_role? :owner`).
109
111
 
110
112
 
111
113
  ### Billing & subscriptions using Stripe
112
114
 
113
- Following [Rails Designer's Stripe Billing set up](https://railsdesigner.com/saas/billing-with-stripe/), offer subscriptions right away.
115
+ Following [Rails Designer's Stripe Billing set up](https://railsdesigner.com/saas/billing-with-stripe/), offer subscriptions right away. Just update `config/configurations/plans.yml` and add your Stripe API key and Signing secret to your credentials.
114
116
 
115
117
 
116
118
  ### Team invitations
@@ -118,7 +120,7 @@ Following [Rails Designer's Stripe Billing set up](https://railsdesigner.com/saa
118
120
  Add more users to your `Workspace`, update roles or remove them again.
119
121
 
120
122
 
121
- ### Form Builder
123
+ ### Form builder
122
124
 
123
125
  Pulled from [Rails Designer's Components](https://railsdesigner.com/components/), the included Form Builder lets you quicly build beautiful forms. Including a magic `input` helper that works like this:
124
126
 
@@ -131,13 +133,13 @@ Pulled from [Rails Designer's Components](https://railsdesigner.com/components/)
131
133
  ```
132
134
 
133
135
 
134
- ### Component Helper
136
+ ### Component helper
135
137
 
136
138
  Some basic components are included like:
137
139
 
138
140
  - container; keep all your app's content in check
139
141
  - heading; have consistent headings for each screen
140
- - enhanced dialog element (works with Turbo Frames and can be shown as a `modal` or a `slider`)
142
+ - enhanced dialog element (works with Turbo Frames and, includes `centered` or a `drawer` variants)
141
143
  - flash messages (notifiy your users from the bottom-right)
142
144
 
143
145
  These are built using vanilla Rails, as [described in this article]() (`component` helper included).
@@ -155,17 +157,17 @@ When using Turbo Stream responses, use `turbo_stream.notify "Updated"` to notifi
155
157
 
156
158
  ## Rails Icons support
157
159
 
158
- The application/dashboard layout and the Form Builder have support for [Rails Icons](https://github.com/Rails-Desiger/rails_icons).
160
+ The application/dashboard layout and the Form Builder have optional support for [Rails Icons](https://github.com/Rails-Desiger/rails_icons). Run `bin/rails generate rails_icons:install --libraries=phosphor` to enable them.
159
161
 
160
162
 
161
163
  ### Sluggable concern
162
164
 
163
- If you do not want to expose the record's primary key, use the `Sluggable` concern. Include it in the model, make sure it has a (unique) `slug` column, and look up records now using `ModelName.sluggable.find(params[:id])`.
165
+ If you do not want to expose the record's primary key, use the `Sluggable` concern. Include it in the model, make sure it has a (unique) `slug` column, and look up records using `ModelName.sluggable.find(params[:id])`.
164
166
 
165
167
 
166
168
  ### Configuration
167
169
 
168
- Little syntactic sugar on top of Rails' `config_for` as explained [in this article](https://railsdesigner.com/saas/configuration/). Keep configuration together in, e.g. `config/configuration/urls.yml` and call each value like `Config::Urls.docs`.^
170
+ Little syntactic sugar on top of Rails' `config_for` as explained [in this article](https://railsdesigner.com/saas/configuration/). Keep configuration together in, e.g. `config/configuration/urls.yml` and call each value like `Config::Urls.docs`.
169
171
 
170
172
 
171
173
  ## Contributing
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-emerald-300:oklch(84.5% .143 164.978);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-800:oklch(45.9% .187 3.815);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:var(--color-slate-50);--color-gray-100:var(--color-slate-100);--color-gray-200:var(--color-slate-200);--color-gray-300:var(--color-slate-300);--color-gray-400:var(--color-slate-400);--color-gray-500:var(--color-slate-500);--color-gray-600:var(--color-slate-600);--color-gray-700:var(--color-slate-700);--color-gray-800:var(--color-slate-800);--color-gray-900:var(--color-slate-900);--color-gray-950:var(--color-slate-950);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-50:var(--color-blue-50);--color-brand-300:var(--color-blue-300);--color-brand-400:var(--color-blue-400);--color-brand-500:var(--color-blue-500);--color-brand-600:var(--color-blue-600);--color-brand-700:var(--color-blue-700);--color-brand-800:var(--color-blue-800);--color-brand-950:var(--color-blue-950)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}a:not([class]){text-decoration-line:underline}a:not([class]):hover{text-decoration-line:none}}@layer components{[class^=btn-]{align-items:center;column-gap:calc(var(--spacing)*1);width:100%;padding-inline:calc(var(--spacing)*3.5);padding-block:calc(var(--spacing)*1.25);display:flex}@media (min-width:48rem){[class^=btn-]{width:auto}}[class^=btn-]{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:.0125em;letter-spacing:.0125em;border-style:var(--tw-border-style);border-radius:var(--radius-sm);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:transparent;cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-width:1px;border-color:#0000}[class^=btn-] [data-slot=icon],[class^=btn-] svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.btn-secondary{color:var(--color-gray-800);border-color:var(--color-white);--tw-ring-color:var(--color-gray-300)}.btn-secondary [data-slot=icon],.btn-secondary svg{color:var(--color-gray-600)}.btn-secondary:hover{--tw-ring-color:var(--color-gray-300)}.btn-primary{background-color:var(--color-brand-600);color:var(--color-white)}.btn-primary [data-slot=icon],.btn-primary svg{color:var(--color-white)}.btn-primary:hover{background-color:var(--color-brand-700)}.btn-block{width:100%;display:block}.buttons{margin-top:calc(var(--spacing)*4);justify-content:flex-end;display:flex}}@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.-top-\[400px\]{top:-400px}.top-0{top:calc(var(--spacing)*0)}.-right-\[300px\]{right:-300px}.right-4{right:calc(var(--spacing)*4)}.-bottom-\[450px\]{bottom:-450px}.bottom-4{bottom:calc(var(--spacing)*4)}.-left-\[350px\]{left:-350px}.col-span-12{grid-column:span 12/span 12}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-7{margin-top:calc(var(--spacing)*7)}.mt-8{margin-top:calc(var(--spacing)*8)}.ml-0{margin-left:calc(var(--spacing)*0)}.ml-1{margin-left:calc(var(--spacing)*1)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-4\.5{width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-\[800px\]{width:800px;height:800px}.h-8{height:calc(var(--spacing)*8)}.h-dvh{height:100dvh}.h-screen{height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-32{rotate:32deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-x-1{column-gap:calc(var(--spacing)*1)}.gap-x-1\.5{column-gap:calc(var(--spacing)*1.5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.gap-y-0\.5{row-gap:calc(var(--spacing)*.5)}.gap-y-5{row-gap:calc(var(--spacing)*5)}.overflow-clip{overflow:clip}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200\/50{border-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.border-gray-200\/50{border-color:color-mix(in oklab,var(--color-gray-200)50%,transparent)}}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-200\/40{background-color:#e2e8f066}@supports (color:color-mix(in lab, red, red)){.bg-gray-200\/40{background-color:color-mix(in oklab,var(--color-gray-200)40%,transparent)}}.bg-white{background-color:var(--color-white)}.bg-white\/50{background-color:#ffffff80}@supports (color:color-mix(in lab, red, red)){.bg-white\/50{background-color:color-mix(in oklab,var(--color-white)50%,transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-900\/80{--tw-gradient-from:#0f172bcc}@supports (color:color-mix(in lab, red, red)){.from-gray-900\/80{--tw-gradient-from:color-mix(in oklab,var(--color-gray-900)80%,transparent)}}.from-gray-900\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.fill-brand-400{fill:var(--color-brand-400)}.fill-brand-600\/60{fill:#155dfc99}@supports (color:color-mix(in lab, red, red)){.fill-brand-600\/60{fill:color-mix(in oklab,var(--color-brand-600)60%,transparent)}}.p-2{padding:calc(var(--spacing)*2)}.px-0\.5{padding-inline:calc(var(--spacing)*.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-5{padding-block:calc(var(--spacing)*5)}.pt-4{padding-top:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.text-center{text-align:center}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.text-brand-500{color:var(--color-brand-500)}.text-brand-600{color:var(--color-brand-600)}.text-current{color:currentColor}.text-cyan-400{color:var(--color-cyan-400)}.text-emerald-300{color:var(--color-emerald-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-red-400{color:var(--color-red-400)}.text-red-600{color:var(--color-red-600)}.text-slate-800{color:var(--color-slate-800)}.text-white{color:var(--color-white)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-brand-500{accent-color:var(--color-brand-500)}.opacity-100{opacity:1}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-gray-200\/50{--tw-ring-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/50{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)50%,transparent)}}.ring-gray-200\/60{--tw-ring-color:#e2e8f099}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/60{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)60%,transparent)}}.ring-gray-200\/80{--tw-ring-color:#e2e8f0cc}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/80{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)80%,transparent)}}.ring-gray-700\/90{--tw-ring-color:#314158e6}@supports (color:color-mix(in lab, red, red)){.ring-gray-700\/90{--tw-ring-color:color-mix(in oklab,var(--color-gray-700)90%,transparent)}}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.delay-75{transition-delay:75ms}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}:is(.\*\:inline-block>*){display:inline-block}@media (hover:hover){.group-hover\/link\:fill-brand-700:is(:where(.group\/link):hover *){fill:var(--color-brand-700)}.group-hover\/link\:text-brand-700:is(:where(.group\/link):hover *){color:var(--color-brand-700)}.group-hover\/link\:text-brand-950:is(:where(.group\/link):hover *){color:var(--color-brand-950)}.group-hover\/link\:text-gray-950:is(:where(.group\/link):hover *){color:var(--color-gray-950)}}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.selection\:bg-pink-200\/40 ::selection{background-color:#fccee866}@supports (color:color-mix(in lab, red, red)){.selection\:bg-pink-200\/40 ::selection{background-color:color-mix(in oklab,var(--color-pink-200)40%,transparent)}}.selection\:bg-pink-200\/40::selection{background-color:#fccee866}@supports (color:color-mix(in lab, red, red)){.selection\:bg-pink-200\/40::selection{background-color:color-mix(in oklab,var(--color-pink-200)40%,transparent)}}.selection\:text-pink-800\/70 ::selection{color:#a2004cb3}@supports (color:color-mix(in lab, red, red)){.selection\:text-pink-800\/70 ::selection{color:color-mix(in oklab,var(--color-pink-800)70%,transparent)}}.selection\:text-pink-800\/70::selection{color:#a2004cb3}@supports (color:color-mix(in lab, red, red)){.selection\:text-pink-800\/70::selection{color:color-mix(in oklab,var(--color-pink-800)70%,transparent)}}.file\:mr-2::file-selector-button{margin-right:calc(var(--spacing)*2)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-gray-100::file-selector-button{background-color:var(--color-gray-100)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-1::file-selector-button{padding-block:calc(var(--spacing)*1)}.file\:text-xs::file-selector-button{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-gray-700::file-selector-button{color:var(--color-gray-700)}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:bg-brand-50:hover{background-color:var(--color-brand-50)}.hover\:bg-gray-200\/40:hover{background-color:#e2e8f066}@supports (color:color-mix(in lab, red, red)){.hover\:bg-gray-200\/40:hover{background-color:color-mix(in oklab,var(--color-gray-200)40%,transparent)}}.hover\:text-brand-800:hover{color:var(--color-brand-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-lg\/5:hover{--tw-shadow-alpha:5%;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,oklab(0% 0 0/.05)),0 4px 6px -4px var(--tw-shadow-color,oklab(0% 0 0/.05));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-gray-300:hover{--tw-ring-color:var(--color-gray-300)}.hover\:file\:bg-gray-200:hover::file-selector-button{background-color:var(--color-gray-200)}}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:ring:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-brand-300:focus{--tw-ring-color:var(--color-brand-300)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}@media (hover:hover){.disabled\:hover\:ring-gray-200\/80:disabled:hover{--tw-ring-color:#e2e8f0cc}@supports (color:color-mix(in lab, red, red)){.disabled\:hover\:ring-gray-200\/80:disabled:hover{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)80%,transparent)}}}@media (min-width:48rem){.md\:col-span-4{grid-column:span 4/span 4}.md\:mx-4{margin-inline:calc(var(--spacing)*4)}.md\:rounded-md{border-radius:var(--radius-md)}.md\:rounded-xl{border-radius:var(--radius-xl)}.md\:px-4{padding-inline:calc(var(--spacing)*4)}.md\:pb-4{padding-bottom:calc(var(--spacing)*4)}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media (min-width:64rem){.lg\:not-sr-only{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:w-60{width:calc(var(--spacing)*60)}.lg\:px-6{padding-inline:calc(var(--spacing)*6)}}@starting-style{.starting\:ml-3\.5{margin-left:calc(var(--spacing)*3.5)}}@starting-style{.starting\:translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}}@starting-style{.starting\:scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}}@starting-style{.starting\:opacity-0{opacity:0}}.\[\&\>input\]\:mt-\[0\.15em\]>input{margin-top:.15em}.\[\&\[data-slot\=\'field\'\]\+\[data-slot\=\'field\'\]\]\:mt-4[data-slot=field]+[data-slot=field]{margin-top:calc(var(--spacing)*4)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-900:oklch(39.6% .141 25.723);--color-emerald-300:oklch(84.5% .143 164.978);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-800:oklch(45.9% .187 3.815);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:var(--color-slate-50);--color-gray-100:var(--color-slate-100);--color-gray-200:var(--color-slate-200);--color-gray-300:var(--color-slate-300);--color-gray-400:var(--color-slate-400);--color-gray-500:var(--color-slate-500);--color-gray-600:var(--color-slate-600);--color-gray-700:var(--color-slate-700);--color-gray-800:var(--color-slate-800);--color-gray-900:var(--color-slate-900);--color-gray-950:var(--color-slate-950);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-50:var(--color-blue-50);--color-brand-300:var(--color-blue-300);--color-brand-400:var(--color-blue-400);--color-brand-500:var(--color-blue-500);--color-brand-600:var(--color-blue-600);--color-brand-700:var(--color-blue-700);--color-brand-800:var(--color-blue-800);--color-brand-950:var(--color-blue-950)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}a:not([class]){text-decoration-line:underline}a:not([class]):hover{text-decoration-line:none}}@layer components{[class^=btn-]{align-items:center;column-gap:calc(var(--spacing)*1);width:100%;padding-inline:calc(var(--spacing)*3.5);padding-block:calc(var(--spacing)*1.25);display:flex}@media (min-width:48rem){[class^=btn-]{width:auto}}[class^=btn-]{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:.0125em;letter-spacing:.0125em;border-style:var(--tw-border-style);border-radius:var(--radius-sm);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:transparent;cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-width:1px;border-color:#0000}[class^=btn-] [data-slot=icon],[class^=btn-] svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.btn-secondary{color:var(--color-gray-800);border-color:var(--color-white);--tw-ring-color:var(--color-gray-300)}.btn-secondary [data-slot=icon],.btn-secondary svg{color:var(--color-gray-600)}.btn-secondary:hover{--tw-ring-color:var(--color-gray-300)}.btn-primary{background-color:var(--color-brand-600);color:var(--color-white)}.btn-primary [data-slot=icon],.btn-primary svg{color:var(--color-white)}.btn-primary:hover{background-color:var(--color-brand-700)}.btn-block{width:100%;display:block}.buttons{margin-top:calc(var(--spacing)*4);justify-content:flex-end;display:flex}}@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.-top-\[400px\]{top:-400px}.top-0{top:calc(var(--spacing)*0)}.-right-\[300px\]{right:-300px}.right-4{right:calc(var(--spacing)*4)}.-bottom-\[450px\]{bottom:-450px}.bottom-4{bottom:calc(var(--spacing)*4)}.-left-\[350px\]{left:-350px}.col-span-12{grid-column:span 12/span 12}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.my-20{margin-block:calc(var(--spacing)*20)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-7{margin-top:calc(var(--spacing)*7)}.mt-8{margin-top:calc(var(--spacing)*8)}.ml-0{margin-left:calc(var(--spacing)*0)}.ml-1{margin-left:calc(var(--spacing)*1)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-4\.5{width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-\[800px\]{width:800px;height:800px}.h-8{height:calc(var(--spacing)*8)}.h-dvh{height:100dvh}.h-screen{height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow-1{flex-grow:1}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-0{--tw-translate-y:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-32{rotate:32deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-4{gap:calc(var(--spacing)*4)}.gap-8{gap:calc(var(--spacing)*8)}.gap-x-1{column-gap:calc(var(--spacing)*1)}.gap-x-1\.5{column-gap:calc(var(--spacing)*1.5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.gap-y-0\.5{row-gap:calc(var(--spacing)*.5)}.gap-y-5{row-gap:calc(var(--spacing)*5)}.overflow-clip{overflow:clip}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200\/50{border-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.border-gray-200\/50{border-color:color-mix(in oklab,var(--color-gray-200)50%,transparent)}}.border-red-100{border-color:var(--color-red-100)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-200\/40{background-color:#e2e8f066}@supports (color:color-mix(in lab, red, red)){.bg-gray-200\/40{background-color:color-mix(in oklab,var(--color-gray-200)40%,transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-white{background-color:var(--color-white)}.bg-white\/50{background-color:#ffffff80}@supports (color:color-mix(in lab, red, red)){.bg-white\/50{background-color:color-mix(in oklab,var(--color-white)50%,transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-900\/80{--tw-gradient-from:#0f172bcc}@supports (color:color-mix(in lab, red, red)){.from-gray-900\/80{--tw-gradient-from:color-mix(in oklab,var(--color-gray-900)80%,transparent)}}.from-gray-900\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.fill-brand-400{fill:var(--color-brand-400)}.fill-brand-600\/60{fill:#155dfc99}@supports (color:color-mix(in lab, red, red)){.fill-brand-600\/60{fill:color-mix(in oklab,var(--color-brand-600)60%,transparent)}}.p-2{padding:calc(var(--spacing)*2)}.px-0\.5{padding-inline:calc(var(--spacing)*.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.pt-4{padding-top:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.text-center{text-align:center}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.text-brand-500{color:var(--color-brand-500)}.text-brand-600{color:var(--color-brand-600)}.text-current{color:currentColor}.text-cyan-400{color:var(--color-cyan-400)}.text-emerald-300{color:var(--color-emerald-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-red-400{color:var(--color-red-400)}.text-red-600{color:var(--color-red-600)}.text-red-900{color:var(--color-red-900)}.text-slate-800{color:var(--color-slate-800)}.text-white{color:var(--color-white)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-brand-500{accent-color:var(--color-brand-500)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-gray-200\/50{--tw-ring-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/50{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)50%,transparent)}}.ring-gray-200\/60{--tw-ring-color:#e2e8f099}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/60{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)60%,transparent)}}.ring-gray-200\/80{--tw-ring-color:#e2e8f0cc}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/80{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)80%,transparent)}}.ring-gray-700\/90{--tw-ring-color:#314158e6}@supports (color:color-mix(in lab, red, red)){.ring-gray-700\/90{--tw-ring-color:color-mix(in oklab,var(--color-gray-700)90%,transparent)}}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-discrete{transition-behavior:allow-discrete}.delay-75{transition-delay:75ms}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}:is(.\*\:inline-block>*){display:inline-block}@media (hover:hover){.group-hover\/link\:fill-brand-700:is(:where(.group\/link):hover *){fill:var(--color-brand-700)}.group-hover\/link\:text-brand-700:is(:where(.group\/link):hover *){color:var(--color-brand-700)}.group-hover\/link\:text-brand-950:is(:where(.group\/link):hover *){color:var(--color-brand-950)}.group-hover\/link\:text-gray-950:is(:where(.group\/link):hover *){color:var(--color-gray-950)}}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.selection\:bg-blue-200\/40 ::selection{background-color:#bedbff66}@supports (color:color-mix(in lab, red, red)){.selection\:bg-blue-200\/40 ::selection{background-color:color-mix(in oklab,var(--color-blue-200)40%,transparent)}}.selection\:bg-blue-200\/40::selection{background-color:#bedbff66}@supports (color:color-mix(in lab, red, red)){.selection\:bg-blue-200\/40::selection{background-color:color-mix(in oklab,var(--color-blue-200)40%,transparent)}}.selection\:bg-pink-200\/40 ::selection{background-color:#fccee866}@supports (color:color-mix(in lab, red, red)){.selection\:bg-pink-200\/40 ::selection{background-color:color-mix(in oklab,var(--color-pink-200)40%,transparent)}}.selection\:bg-pink-200\/40::selection{background-color:#fccee866}@supports (color:color-mix(in lab, red, red)){.selection\:bg-pink-200\/40::selection{background-color:color-mix(in oklab,var(--color-pink-200)40%,transparent)}}.selection\:text-blue-800\/70 ::selection{color:#193cb8b3}@supports (color:color-mix(in lab, red, red)){.selection\:text-blue-800\/70 ::selection{color:color-mix(in oklab,var(--color-blue-800)70%,transparent)}}.selection\:text-blue-800\/70::selection{color:#193cb8b3}@supports (color:color-mix(in lab, red, red)){.selection\:text-blue-800\/70::selection{color:color-mix(in oklab,var(--color-blue-800)70%,transparent)}}.selection\:text-pink-800\/70 ::selection{color:#a2004cb3}@supports (color:color-mix(in lab, red, red)){.selection\:text-pink-800\/70 ::selection{color:color-mix(in oklab,var(--color-pink-800)70%,transparent)}}.selection\:text-pink-800\/70::selection{color:#a2004cb3}@supports (color:color-mix(in lab, red, red)){.selection\:text-pink-800\/70::selection{color:color-mix(in oklab,var(--color-pink-800)70%,transparent)}}.file\:mr-2::file-selector-button{margin-right:calc(var(--spacing)*2)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-gray-100::file-selector-button{background-color:var(--color-gray-100)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-1::file-selector-button{padding-block:calc(var(--spacing)*1)}.file\:text-xs::file-selector-button{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-gray-700::file-selector-button{color:var(--color-gray-700)}.open\:block:is([open],:popover-open,:open){display:block}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:bg-brand-50:hover{background-color:var(--color-brand-50)}.hover\:bg-gray-200\/40:hover{background-color:#e2e8f066}@supports (color:color-mix(in lab, red, red)){.hover\:bg-gray-200\/40:hover{background-color:color-mix(in oklab,var(--color-gray-200)40%,transparent)}}.hover\:text-brand-800:hover{color:var(--color-brand-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-gray-300:hover{--tw-ring-color:var(--color-gray-300)}.hover\:file\:bg-gray-200:hover::file-selector-button{background-color:var(--color-gray-200)}}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:ring:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-brand-300:focus{--tw-ring-color:var(--color-brand-300)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}@media (hover:hover){.disabled\:hover\:ring-gray-200\/80:disabled:hover{--tw-ring-color:#e2e8f0cc}@supports (color:color-mix(in lab, red, red)){.disabled\:hover\:ring-gray-200\/80:disabled:hover{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)80%,transparent)}}}@media (min-width:48rem){.md\:col-span-4{grid-column:span 4/span 4}.md\:mx-4{margin-inline:calc(var(--spacing)*4)}.md\:gap-8{gap:calc(var(--spacing)*8)}.md\:rounded-md{border-radius:var(--radius-md)}.md\:rounded-xl{border-radius:var(--radius-xl)}.md\:px-4{padding-inline:calc(var(--spacing)*4)}.md\:pb-4{padding-bottom:calc(var(--spacing)*4)}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media (min-width:64rem){.lg\:not-sr-only{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:w-60{width:calc(var(--spacing)*60)}.lg\:px-6{padding-inline:calc(var(--spacing)*6)}}@starting-style{.starting\:ml-3\.5{margin-left:calc(var(--spacing)*3.5)}}@starting-style{.starting\:translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}}@starting-style{.starting\:scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}}@starting-style{.starting\:opacity-0{opacity:0}}.\[\&_code\]\:font-bold code{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&\:not\(\[open\]\)\[variant\=centered\]\]\:scale-95:not([open])[variant=centered]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.\[\&\:not\(\[open\]\)\[variant\=drawer\]\]\:translate-x-full:not([open])[variant=drawer]{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>input\]\:mt-\[0\.15em\]>input{margin-top:.15em}.\[\&\[data-slot\=\'field\'\]\+\[data-slot\=\'field\'\]\]\:mt-4[data-slot=field]+[data-slot=field]{margin-top:calc(var(--spacing)*4)}.\[\&\[variant\=centered\]\]\:m-auto[variant=centered]{margin:auto}.\[\&\[variant\=centered\]\]\:rounded-lg[variant=centered]{border-radius:var(--radius-lg)}.\[\&\[variant\=centered\]\]\:backdrop\:bg-gray-950\/10[variant=centered]::backdrop{background-color:#0206181a}@supports (color:color-mix(in lab, red, red)){.\[\&\[variant\=centered\]\]\:backdrop\:bg-gray-950\/10[variant=centered]::backdrop{background-color:color-mix(in oklab,var(--color-gray-950)10%,transparent)}}.\[\&\[variant\=centered\]\]\:backdrop\:backdrop-blur-\[2px\][variant=centered]::backdrop{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}@media not all and (min-width:40rem){.\[\&\[variant\=centered\]\]\:max-sm\:mb-0[variant=centered]{margin-bottom:calc(var(--spacing)*0)}.\[\&\[variant\=centered\]\]\:max-sm\:rounded-b-none[variant=centered]{border-bottom-right-radius:0;border-bottom-left-radius:0}}@media (min-width:40rem){.\[\&\[variant\=centered\]\]\:sm\:my-auto[variant=centered]{margin-block:auto}}@starting-style{.\[\&\[variant\=centered\]\]\:starting\:scale-95[variant=centered]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}}@media not all and (min-width:40rem){@starting-style{.\[\&\[variant\=centered\]\]\:max-sm\:starting\:translate-y-full[variant=centered]{--tw-translate-y:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}}@starting-style{.\[\&\[variant\=centered\]\]\:max-sm\:starting\:scale-100[variant=centered]{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}}}.\[\&\[variant\=drawer\]\]\:m-0[variant=drawer]{margin:calc(var(--spacing)*0)}.\[\&\[variant\=drawer\]\]\:ml-auto[variant=drawer]{margin-left:auto}.\[\&\[variant\=drawer\]\]\:h-screen[variant=drawer]{height:100vh}.\[\&\[variant\=drawer\]\]\:max-h-none[variant=drawer]{max-height:none}.\[\&\[variant\=drawer\]\]\:max-w-sm[variant=drawer]{max-width:var(--container-sm)}.\[\&\[variant\=drawer\]\]\:rounded-l-lg[variant=drawer]{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}@starting-style{.\[\&\[variant\=drawer\]\]\:starting\:translate-x-full[variant=drawer]{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -0,0 +1,37 @@
1
+ module Kern
2
+ class BillingProfiles::SubscriptionsController < ApplicationController
3
+ def create
4
+ session = Stripe::Checkout::Session.create({
5
+ success_url: main_app.root_url,
6
+ cancel_url: settings_subscriptions_url,
7
+ client_reference_id: Current.workspace.slug,
8
+ customer_email: Current.user.email,
9
+ mode: "subscription",
10
+
11
+ subscription_data: {
12
+ trial_period_days: 7
13
+ },
14
+
15
+ line_items: [{
16
+ quantity: 1,
17
+ price: price_id
18
+ }]
19
+ })
20
+
21
+ redirect_to session.url, status: 303, allow_other_host: true
22
+ end
23
+
24
+ def edit
25
+ session = Stripe::BillingPortal::Session.create({
26
+ customer: Current.workspace.billing_profile.external_id,
27
+ return_url: root_url
28
+ })
29
+
30
+ redirect_to session.url, status: 303, allow_other_host: true
31
+ end
32
+
33
+ private
34
+
35
+ def price_id = params[:price_id] || Config::Stripe.default_price_id
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module Kern
2
+ class BillingProfiles::SubscriptionsController < ApplicationController
3
+ def create
4
+ session = Stripe::Checkout::Session.create({
5
+ success_url: root_url,
6
+ cancel_url: settings_subscriptions_url,
7
+ client_reference_id: Current.workspace.slug,
8
+ customer_email: Current.user.email,
9
+ mode: "subscription",
10
+
11
+ subscription_data: {
12
+ trial_period_days: 7
13
+ },
14
+
15
+ line_items: [{
16
+ quantity: 1,
17
+ price: price_id
18
+ }]
19
+ })
20
+
21
+ redirect_to session.url, status: 303, allow_other_host: true
22
+ end
23
+
24
+ def edit
25
+ session = Stripe::BillingPortal::Session.create({
26
+ customer: Current.workspace.billing_profile.external_id,
27
+ return_url: root_url
28
+ })
29
+
30
+ redirect_to session.url, status: 303, allow_other_host: true
31
+ end
32
+
33
+ private
34
+
35
+ def price_id = params[:price_id] || Config::Stripe.default_price_id
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ module Kern
2
+ class Settings::SubscriptionsController < ApplicationController
3
+ def show
4
+ end
5
+ end
6
+ end
@@ -16,7 +16,7 @@ module Kern
16
16
  if @signup.save
17
17
  start_new_session_for @signup.user
18
18
 
19
- redirect_to main_app.root_path
19
+ redirect_to root_url
20
20
  else
21
21
  render :new, status: :unprocessable_entity
22
22
  end
@@ -0,0 +1,16 @@
1
+ module BillingProfile
2
+ class Subscription < ApplicationRecord
3
+ include Sluggable
4
+
5
+ belongs_to :billing_profile
6
+
7
+ enum :status, %w[incomplete active trialing canceled incomplete_expired past_due unpaid free].index_by(&:itself), default: "incomplete"
8
+
9
+ store_attribute :metadata, :interval, :string
10
+ store_attribute :metadata, :product_id, :string
11
+ store_attribute :metadata, :price_id, :string
12
+ store_attribute :metadata, :quantity, :integer, default: 0
13
+
14
+ validates :quantity, numericality: {only_integer: true, greater_than_or_equal_to: 0}
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ class BillingProfile < ApplicationRecord
2
+ include Sluggable
3
+
4
+ belongs_to :workspace
5
+
6
+ has_many :subscriptions, dependent: :destroy
7
+ end
@@ -1,5 +1,5 @@
1
1
  class Session < ApplicationRecord
2
2
  belongs_to :user
3
3
 
4
- # encrypts :user_agent, :ip
4
+ encrypts :user_agent, :ip
5
5
  end
data/app/models/signup.rb CHANGED
@@ -27,7 +27,7 @@ class Signup < ApplicationForm
27
27
  def create_user = User.create!(email_address: email_address, password: password)
28
28
 
29
29
  # def send_welcome_email_to(user)
30
- # Logic to send email
30
+ # logic to send email; suggest to use https://github.com/Rails-Designer/courrier/
31
31
  # end
32
32
 
33
33
  def email_is_unique?
data/app/models/user.rb CHANGED
@@ -10,5 +10,5 @@ class User < ApplicationRecord
10
10
 
11
11
  validates :password, length: 8..128
12
12
 
13
- # encrypts :email, deterministic: true, downcase: true
13
+ encrypts :email, deterministic: true, downcase: true
14
14
  end
@@ -0,0 +1,3 @@
1
+ class Workspace::Access < RailsVault::Base
2
+ vault_attribute :member_count, :integer, default: Config::Plans.dig(:default, :features, :member_count)
3
+ end
@@ -0,0 +1,10 @@
1
+ class Workspace::Access < RailsVault::Base
2
+ # This class is using [Rails Vault](https://github.com/Rails-Designer/rails_vault/)
3
+ #
4
+ # You can define each access as follows:
5
+ #
6
+ # vault_attribute :member_count, :integer, default: Config::Plans.dig(:default, :features, :member_count)
7
+ # vault_attribute :email_notifications, :boolean, default: Config::Plans.dig(:default, :features, :email_notifications)
8
+
9
+ # For more details how this works https://railsdesigner.com/saas/saas-feature-access/
10
+ end
@@ -0,0 +1,16 @@
1
+ class Workspace
2
+ module Billable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :billing_profile, dependent: :destroy
7
+ end
8
+ delegate :subscriptions, to: :billing_profile
9
+
10
+ def subscribed?
11
+ return false unless billing_profile.present?
12
+
13
+ subscriptions.exists?(status: %w[active free trialing])
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ module Workspace::FeatureAccess
2
+ extend ActiveSupport::Concern
3
+
4
+ class PlanNotFoundError < StandardError; end
5
+
6
+ included do
7
+ vault :access
8
+ end
9
+
10
+ def add_access(product_id)
11
+ plan = plans[product_id.to_sym]
12
+
13
+ raise PlanNotFoundError, "Plan with product_id `#{product_id}` not found in `config/configurations/plans.yml`. Needs to be one of #{plans.keys.reject { it == :fallback }.join(", ")}." if plan.blank?
14
+
15
+ access.update plan[:features]
16
+ access
17
+ end
18
+
19
+ def reset_access!
20
+ Workspace::Access.where(resource: self).destroy_all
21
+
22
+ create_access
23
+ end
24
+
25
+ private
26
+
27
+ def plans = Config::Plans
28
+ end
@@ -1,10 +1,12 @@
1
- module Workspace::Members
2
- extend ActiveSupport::Concern
1
+ class Workspace
2
+ module Members
3
+ extend ActiveSupport::Concern
3
4
 
4
- included do
5
- has_many :members, dependent: :destroy
6
- has_many :users, through: :members
7
- end
5
+ included do
6
+ has_many :members, dependent: :destroy
7
+ has_many :users, through: :members
8
+ end
8
9
 
9
- def add_member(to:, role: nil) = Member::Setup.new(workspace: self, user: to, role: role).save
10
+ def add_member(to:, role: nil) = Member::Setup.new(workspace: self, user: to, role: role).save
11
+ end
10
12
  end
@@ -9,13 +9,15 @@ class Workspace::Setup
9
9
  ActiveRecord::Base.transaction do
10
10
  create_workspace.tap do |workspace|
11
11
  workspace.add_member to: @user, role: :owner
12
+
13
+ add_feature_access_to workspace
12
14
  end
13
15
  end
14
16
  end
15
17
 
16
18
  private
17
19
 
18
- def create_workspace
19
- Workspace.create(name: "My Workspace")
20
- end
20
+ def create_workspace = Workspace.create(name: "My Workspace")
21
+
22
+ def add_feature_access_to(workspace) = workspace.create_access
21
23
  end
@@ -1,6 +1,8 @@
1
1
  class Workspace < ApplicationRecord
2
2
  include Sluggable
3
3
 
4
+ include Billable
5
+ include FeatureAccess
4
6
  include Members
5
7
 
6
8
  validates :name, presence: true
@@ -0,0 +1,36 @@
1
+ <dialog
2
+ id="overlay"
3
+ closedby="any"
4
+ variant="centered"
5
+ class="
6
+ hidden
7
+ px-3 py-4 max-w-md w-full
8
+ opacity-100 scale-100 translate-x-0 translate-y-0
9
+ shadow-2xl
10
+ transition-discrete
11
+ transition-all duration-300
12
+ open:block
13
+ opacity-0
14
+ [&:not([open])[variant=drawer]]:translate-x-full
15
+ [&:not([open])[variant=centered]]:scale-95
16
+ starting:opacity-0
17
+
18
+ /* Modal-specific */
19
+ [&[variant=centered]]:m-auto
20
+ [&[variant=centered]]:rounded-lg
21
+ [&[variant=centered]]:starting:scale-95
22
+ [&[variant=centered]]:backdrop:bg-gray-950/10 [&[variant=centered]]:backdrop:backdrop-blur-[2px]
23
+ [&[variant=centered]]:max-sm:mb-0 [&[variant=centered]]:max-sm:rounded-b-none
24
+ [&[variant=centered]]:max-sm:starting:translate-y-full
25
+ [&[variant=centered]]:max-sm:starting:scale-100
26
+ [&[variant=centered]]:sm:my-auto
27
+
28
+ /* Drawer-specific */
29
+ [&[variant=drawer]]:m-0 [&[variant=drawer]]:ml-auto
30
+ [&[variant=drawer]]:h-screen [&[variant=drawer]]:max-h-none [&[variant=drawer]]:max-w-sm
31
+ [&[variant=drawer]]:rounded-l-lg
32
+ [&[variant=drawer]]:starting:translate-x-full
33
+ "
34
+ >
35
+ <%= tag.turbo_frame id: :modal %>
36
+ </dialog>