poetry-jumpstart_pro 0.1.12

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 (92) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +119 -0
  5. data/lib/generators/poetry_jumpstart_pro/install/files/poetry_jumpstart_pro_helper.rb +41 -0
  6. data/lib/generators/poetry_jumpstart_pro/install/install_generator.rb +196 -0
  7. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/account/passwords/edit.html.erb +46 -0
  8. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/account_invitations/show.html.erb +29 -0
  9. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/account_users/_form.html.erb +20 -0
  10. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/account_users/edit.html.erb +34 -0
  11. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/_form.html.erb +26 -0
  12. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/account_invitations/edit.html.erb +49 -0
  13. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/account_invitations/new.html.erb +37 -0
  14. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/edit.html.erb +51 -0
  15. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/index.html.erb +56 -0
  16. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/new.html.erb +18 -0
  17. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/show.html.erb +102 -0
  18. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/accounts/transfers/_form.html.erb +30 -0
  19. data/lib/generators/poetry_jumpstart_pro/install/templates/accounts/application/_account_navbar.html.erb +40 -0
  20. data/lib/generators/poetry_jumpstart_pro/install/templates/announcements/announcements/_announcement.html.erb +28 -0
  21. data/lib/generators/poetry_jumpstart_pro/install/templates/announcements/announcements/index.html.erb +50 -0
  22. data/lib/generators/poetry_jumpstart_pro/install/templates/announcements/announcements/show.html.erb +32 -0
  23. data/lib/generators/poetry_jumpstart_pro/install/templates/api_tokens/api_tokens/_form.html.erb +39 -0
  24. data/lib/generators/poetry_jumpstart_pro/install/templates/api_tokens/api_tokens/edit.html.erb +16 -0
  25. data/lib/generators/poetry_jumpstart_pro/install/templates/api_tokens/api_tokens/index.html.erb +48 -0
  26. data/lib/generators/poetry_jumpstart_pro/install/templates/api_tokens/api_tokens/new.html.erb +15 -0
  27. data/lib/generators/poetry_jumpstart_pro/install/templates/api_tokens/api_tokens/show.html.erb +69 -0
  28. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/application/_error_messages.html.erb +17 -0
  29. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/passwords/edit.html.erb +23 -0
  30. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/passwords/new.html.erb +26 -0
  31. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/registrations/edit.html.erb +103 -0
  32. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/registrations/new.html.erb +55 -0
  33. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/sessions/new.html.erb +39 -0
  34. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/sessions/otps/new.html.erb +24 -0
  35. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/shared/_oauth.html.erb +22 -0
  36. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/sudo/new.html.erb +31 -0
  37. data/lib/generators/poetry_jumpstart_pro/install/templates/auth/users/two_factor/verify.html.erb +53 -0
  38. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/_charges.html.erb +45 -0
  39. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/_email.html.erb +15 -0
  40. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/_info.html.erb +30 -0
  41. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/show.html.erb +26 -0
  42. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/_plan.html.erb +33 -0
  43. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/_subscription.html.erb +74 -0
  44. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/_summary.html.erb +21 -0
  45. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/cancels/show.html.erb +46 -0
  46. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/edit.html.erb +65 -0
  47. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/paddle_billing/edit.html.erb +17 -0
  48. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/payment_methods/forms/_fake_processor.html.erb +6 -0
  49. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/payment_methods/new.html.erb +26 -0
  50. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/resumes/show.html.erb +29 -0
  51. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/billing/subscriptions/upcomings/show.html.erb +67 -0
  52. data/lib/generators/poetry_jumpstart_pro/install/templates/billing/pricing/show.html.erb +61 -0
  53. data/lib/generators/poetry_jumpstart_pro/install/templates/checkouts/checkouts/_testimonial.html.erb +15 -0
  54. data/lib/generators/poetry_jumpstart_pro/install/templates/checkouts/checkouts/show.html.erb +125 -0
  55. data/lib/generators/poetry_jumpstart_pro/install/templates/dashboard/dashboard/show.html.erb +60 -0
  56. data/lib/generators/poetry_jumpstart_pro/install/templates/errors/errors/404.html.erb +12 -0
  57. data/lib/generators/poetry_jumpstart_pro/install/templates/errors/errors/500.html.erb +12 -0
  58. data/lib/generators/poetry_jumpstart_pro/install/templates/errors/errors/show.html.erb +16 -0
  59. data/lib/generators/poetry_jumpstart_pro/install/templates/madmin/madmin/dashboard/show.html.erb +40 -0
  60. data/lib/generators/poetry_jumpstart_pro/install/templates/madmin/madmin/fields/array_field/_form.html.erb +29 -0
  61. data/lib/generators/poetry_jumpstart_pro/install/templates/madmin/madmin/fields/array_field/_index.html.erb +13 -0
  62. data/lib/generators/poetry_jumpstart_pro/install/templates/madmin/madmin/fields/array_field/_show.html.erb +13 -0
  63. data/lib/generators/poetry_jumpstart_pro/install/templates/notifications/notifications/_notification.html.erb +12 -0
  64. data/lib/generators/poetry_jumpstart_pro/install/templates/notifications/notifications/index.html.erb +31 -0
  65. data/lib/generators/poetry_jumpstart_pro/install/templates/notifications/notifications/nav.html.erb +17 -0
  66. data/lib/generators/poetry_jumpstart_pro/install/templates/public/public/about.html.erb +32 -0
  67. data/lib/generators/poetry_jumpstart_pro/install/templates/public/public/index.html.erb +71 -0
  68. data/lib/generators/poetry_jumpstart_pro/install/templates/public/public/privacy.html.erb +37 -0
  69. data/lib/generators/poetry_jumpstart_pro/install/templates/public/public/terms.html.erb +37 -0
  70. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_account_menu.html.erb +41 -0
  71. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_dev_menu.html.erb +13 -0
  72. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_flash.html.erb +37 -0
  73. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_footer.html.erb +18 -0
  74. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_left_nav.html.erb +11 -0
  75. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_navbar.html+native.erb +36 -0
  76. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_navbar.html.erb +32 -0
  77. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_notifications.html.erb +26 -0
  78. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_right_nav.html.erb +6 -0
  79. data/lib/generators/poetry_jumpstart_pro/install/templates/shell/application/_user_menu.html.erb +34 -0
  80. data/lib/generators/poetry_jumpstart_pro/install/templates/users/users/_user.html.erb +9 -0
  81. data/lib/generators/poetry_jumpstart_pro/install/templates/users/users/agreements/show.html.erb +33 -0
  82. data/lib/generators/poetry_jumpstart_pro/install/templates/users/users/connected_accounts/_connected_account.html.erb +25 -0
  83. data/lib/generators/poetry_jumpstart_pro/install/templates/users/users/connected_accounts/index.html.erb +34 -0
  84. data/lib/generators/poetry_jumpstart_pro/install/templates/users/users/referrals/index.html.erb +49 -0
  85. data/lib/generators/poetry_jumpstart_pro/install/templates/users/users/two_factor/backup_codes.html.erb +32 -0
  86. data/lib/poetry/jumpstart_pro/css_scanner.rb +121 -0
  87. data/lib/poetry/jumpstart_pro/engine.rb +15 -0
  88. data/lib/poetry/jumpstart_pro/stylesheets.rb +155 -0
  89. data/lib/poetry/jumpstart_pro/test_edits.rb +51 -0
  90. data/lib/poetry/jumpstart_pro/version.rb +8 -0
  91. data/lib/poetry/jumpstart_pro.rb +43 -0
  92. metadata +194 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1c9e81c6db2c0850dbeb6a11e2d85879803a326ac4d6f558efa04580f2e96943
4
+ data.tar.gz: ff13db73aa4ff09caee3fa89a932f68a56a84898e11d16df2b76d1527ec4739e
5
+ SHA512:
6
+ metadata.gz: 49d3be69c1e975012e5ebd8e141f7b0e376e3da8e136038b5a0b6e345dc81a39a6641b1217d9306af3717263c6dcb7cb538da799973009a071784d24174d2171
7
+ data.tar.gz: deb4930e9b3063b62e0111acc4cd24371d9079769cb73ce9f36145c05798d26e660c5c6648fe86222adc67044215f2620df116273cb553f4f736001a5ae497f5
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.12] - 2026-10-07
8
+
9
+ ### Added
10
+
11
+ - First release. `bin/rails g poetry_jumpstart_pro:install` re-skins a Jumpstart Pro app on Rails 8 authentication with Poetry: seventy-nine view recreations in thirteen categories (auth, shell, accounts, users, api_tokens, notifications, announcements, checkouts, public, dashboard, errors, billing, madmin) install as `app/views` overrides, which Rails resolves ahead of the Jumpstart engine's originals. The checkout processor forms, the Braintree and PayPal payment method forms, the agreement content partials and the mention list stay Jumpstart's.
12
+ - The generator runs `poetry:install` when the app has no Poetry yet; reconciles Jumpstart's stylesheets with Poetry's (the forms plugin's class strategy, the primary and background tokens, the bare `a`, `ul` and `ol` rules, and heading, `code` and `kbd` rules that skip Poetry's components); installs `app/helpers/poetry_jumpstart_pro_helper.rb`, the pagination and toast adapters; adds the app stylesheet to Madmin; and updates the selectors in Jumpstart's own sign in, two-factor and pagination tests.
13
+ - Jumpstart's untouched development-boot copies of its default views are replaced without a prompt, and an edited copy prompts like any generator conflict. Every step is idempotent, so a re-run after an upgrade is safe.
14
+ - Options: `--theme`, `--skip-poetry-install`, `--skip-stylesheets` and `--skip-tests`, beside the usual generator flags.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Matt Solt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # poetry-jumpstart_pro
2
+
3
+ Re-skin a [Jumpstart Pro](https://jumpstartrails.com) app with
4
+ [Poetry](https://github.com/roboruby/poetry) components: accessible,
5
+ themeable from one design source, and agent-legible.
6
+
7
+ > **License note.** Jumpstart Pro is commercial. This gem ships **only
8
+ > Poetry-native recreations** of the screens (created with the Jumpstart Pro
9
+ > author's explicit permission), never Jumpstart's source. It installs them as
10
+ > **host `app/views/` overrides**, which Rails resolves ahead of the Jumpstart
11
+ > engine's originals, so your licensed Jumpstart source is never touched.
12
+
13
+ ## Requirements
14
+
15
+ - Jumpstart Pro on Rails 8 authentication (the `users/*` sign in screens).
16
+ - Poetry at the same version as this gem (`gem "poetry"`; the family releases in lockstep), Ruby 3.4 or later.
17
+
18
+ ## Install
19
+
20
+ ```ruby
21
+ # Gemfile (in your Jumpstart Pro app)
22
+ gem "poetry"
23
+ gem "poetry-jumpstart_pro", group: :development
24
+ ```
25
+
26
+ ```bash
27
+ bundle install
28
+ bin/rails g poetry_jumpstart_pro:install # every category; runs poetry:install if needed
29
+ bin/rails tailwindcss:build
30
+ bin/rails poetry:check
31
+ bin/rails test && bin/rails test:system
32
+ ```
33
+
34
+ Pass category names to install a subset (`bin/rails g
35
+ poetry_jumpstart_pro:install auth shell`). Every installed file is yours to
36
+ edit; delete an override to fall back to Jumpstart's view. Re-running is
37
+ safe: identical files are skipped and every edit below is idempotent.
38
+
39
+ ## What the installer does
40
+
41
+ 1. **Reconciles the stylesheets.** Jumpstart's CSS declares a few things
42
+ globally that would win over Poetry's tokens and components. The forms
43
+ plugin moves to the class strategy, Jumpstart's `--color-primary` mapping
44
+ and `--background` declarations give way to Poetry's tokens, the bare
45
+ `a`, `ul` and `ol` rules leave `components/base.css`, and the bare heading,
46
+ `code` and `kbd` rules in `components/typography.css` skip Poetry's
47
+ components (elements with `data-slot`).
48
+ 2. **Installs Poetry** with `bin/rails g poetry:install` when the app has no
49
+ Poetry tokens yet (`--theme` picks the theme).
50
+ 3. **Copies the views** for each category into `app/views/`. In development
51
+ Jumpstart copies a few default views into `app/views/` on boot (the
52
+ layout, `_head`, the left and right nav, the dashboard, the landing and
53
+ about pages, the agreements); an untouched copy is replaced without a
54
+ prompt, an edited one prompts like any Rails generator conflict.
55
+ 4. **Adds `app/helpers/poetry_jumpstart_pro_helper.rb`**:
56
+ `poetry_pagination_nav(@page)` draws Jumpstart's pagination page as
57
+ `poetry_pagination`, and `poetry_toast_variant` maps Jumpstart's toast
58
+ icons onto Poetry's toast variants.
59
+ 5. **Styles the admin** (madmin category): `Madmin.stylesheets << "tailwind"`
60
+ in `config/initializers/madmin.rb`.
61
+ 6. **Updates Jumpstart's own tests** that assert on replaced markup: the sign
62
+ in helpers click `[name="commit"]` (Poetry's submit is a button), and the
63
+ pagination test reads Poetry's `nav[aria-label=pagination]`.
64
+
65
+ The layouts stay Jumpstart's. The page's one `poetry_toaster` lives in the
66
+ flash partial, which every Jumpstart layout renders once.
67
+
68
+ Options: `--skip-stylesheets`, `--skip-tests`, `--skip-poetry-install`,
69
+ `--theme NAME`, plus the usual generator flags (`--force`, `--skip`,
70
+ `--pretend`).
71
+
72
+ ## Categories
73
+
74
+ | Category | Screens |
75
+ |----------|---------|
76
+ | `auth` | Sign in, two-factor challenge, sign up, profile, password reset, sudo, OAuth buttons, form errors |
77
+ | `shell` | Navbar and its Hotwire Native variant, left and right nav, account, user and dev menus, notifications, flash and toaster, footer |
78
+ | `accounts` | Accounts index, show, new, edit and form, members, invitations, transfer, account password, the settings navigation |
79
+ | `users` | Mention chip, agreements, connected accounts, referrals, two-factor backup codes |
80
+ | `api_tokens` | API tokens index, show, new, edit and form |
81
+ | `notifications` | Notifications index, the navbar panel and the row |
82
+ | `announcements` | Announcements index, show and the row |
83
+ | `checkouts` | The checkout page and its testimonial |
84
+ | `public` | Landing, about, privacy, terms |
85
+ | `dashboard` | The signed-in home |
86
+ | `errors` | 404, 500 and the generic error page |
87
+ | `billing` | Billing overview, email, info, charges, subscriptions (plan, summary, change, cancel, resume, upcoming, payment method), pricing |
88
+ | `madmin` | Admin dashboard and the array field |
89
+
90
+ Seventy-nine templates in all. Some views stay Jumpstart's because they
91
+ cannot be recreated without copying: the checkout processor forms, the
92
+ Braintree and PayPal payment method forms, the agreement content partials
93
+ and the mention list.
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ bundle install
99
+ bundle exec rake
100
+ ```
101
+
102
+ The suite validates the generator, the stylesheet and test edits, and the
103
+ templates. Point it at your own licensed Jumpstart Pro checkout to add the
104
+ drift guard (every template shadows a view the engine ships), the license
105
+ guard (no template is a near copy of its original) and the translation check
106
+ (every key without a default exists in Jumpstart's English locale):
107
+
108
+ ```bash
109
+ JUMPSTART_PRO_PATH=/path/to/jumpstart-pro-rails bundle exec rake
110
+ ```
111
+
112
+ Those checks skip cleanly when the variable is unset. The Jumpstart repo is
113
+ never copied into or committed to this project.
114
+
115
+ ## License
116
+
117
+ Available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
118
+ The license covers the Poetry recreations this gem ships; it does not
119
+ contain (and grants no rights to) Jumpstart Pro's own source.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Installed by `bin/rails g poetry_jumpstart_pro:install` - this file is
4
+ # yours: edit freely.
5
+ #
6
+ # The two adapters the Poetry views lean on: Jumpstart's pagination page
7
+ # drawn as poetry_pagination, and Jumpstart's toast icons mapped onto
8
+ # Poetry's toast variants.
9
+ module PoetryJumpstartProHelper
10
+ # Jumpstart toasts name an icon (alert, notice, success); Poetry toasts
11
+ # carry the intent as a variant, which also picks the icon.
12
+ TOAST_VARIANTS = {
13
+ "alert" => :destructive,
14
+ "notice" => :info,
15
+ "success" => :success
16
+ }.freeze
17
+
18
+ # The Poetry pagination for a Jumpstart Pagination::Page, the @page a
19
+ # controller's paginate call sets. Renders nothing at a single page and
20
+ # keeps every other query param, through Jumpstart's pagination_path.
21
+ # Any other keywords (the siblings: and edges: window, attributes for the
22
+ # nav) pass through to poetry_pagination.
23
+ #
24
+ # <%= poetry_pagination_nav(@page) %>
25
+ #
26
+ # @param page [Pagination::Page] the page object
27
+ def poetry_pagination_nav(page, **)
28
+ return "".html_safe unless page.multiple_pages?
29
+
30
+ poetry_pagination(current: page.number, total: page.total_pages,
31
+ path: ->(number) { pagination_path(page, number) }, **)
32
+ end
33
+
34
+ # The Poetry toast variant for a Jumpstart toast's icon_name.
35
+ #
36
+ # @param icon_name [String, Symbol, nil] the toast's icon name
37
+ # @return [Symbol] the Poetry toast variant
38
+ def poetry_toast_variant(icon_name)
39
+ TOAST_VARIANTS.fetch(icon_name.to_s, :default)
40
+ end
41
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "poetry/jumpstart_pro"
5
+
6
+ module PoetryJumpstartPro
7
+ # `rails g poetry_jumpstart_pro:install [categories...]`
8
+ #
9
+ # Re-skins a Jumpstart Pro app with Poetry. It copies Poetry recreations of
10
+ # Jumpstart's views into the host's app/views/, where Rails resolves them
11
+ # ahead of the Jumpstart engine's originals, then wires what those views
12
+ # lean on:
13
+ #
14
+ # - the stylesheet reconciliation (Poetry::JumpstartPro::Stylesheets),
15
+ # so Jumpstart's global CSS stops overriding Poetry's components
16
+ # - Poetry itself (`poetry:install`) when the app does not have it yet
17
+ # - app/helpers/poetry_jumpstart_pro_helper.rb, the pagination and toast
18
+ # adapters
19
+ # - Poetry's styles on the Madmin pages, with the madmin category
20
+ # - the selectors of Jumpstart's own tests that assert on the replaced
21
+ # markup (Poetry::JumpstartPro::TestEdits)
22
+ #
23
+ # Views are verbatim copies (copy_file, never template), so the ERB is the
24
+ # exact runtime view. In development Jumpstart copies a few default views
25
+ # into app/views on boot (the layout, _head, the left and right nav, the
26
+ # dashboard, the landing and about pages, the agreements); a copy still
27
+ # identical to the engine's view is replaced without asking, and one the
28
+ # app has edited prompts like any Rails generator conflict. Every step is
29
+ # idempotent, so re-running after an upgrade is safe.
30
+ #
31
+ # @example Install every category
32
+ # rails g poetry_jumpstart_pro:install
33
+ # @example Install the sign in screens and the app shell only
34
+ # rails g poetry_jumpstart_pro:install auth shell
35
+ class InstallGenerator < Rails::Generators::Base
36
+ source_root File.expand_path("templates", __dir__)
37
+
38
+ # The host helper the views call, copied once and then the host's own.
39
+ HELPER = File.expand_path("files/poetry_jumpstart_pro_helper.rb", __dir__)
40
+
41
+ # The file whose presence means `poetry:install` has run.
42
+ POETRY_TOKENS = "app/assets/tailwind/poetry/tokens.css"
43
+ # The Tailwind entry, which imports the tokens once the install finished.
44
+ TAILWIND_ENTRY = "app/assets/tailwind/application.css"
45
+ # Where a Jumpstart Pro app keeps the engine's own views.
46
+ ENGINE_VIEWS = "lib/jumpstart/app/views"
47
+
48
+ # Madmin's initializer and the line that adds the app stylesheet to it.
49
+ MADMIN_INITIALIZER = "config/initializers/madmin.rb"
50
+ # Appended to the Madmin initializer for the madmin category.
51
+ MADMIN_STYLESHEET = %(Madmin.stylesheets << "tailwind")
52
+
53
+ argument :categories, type: :array, default: Poetry::JumpstartPro::CATEGORIES,
54
+ banner: "category ..."
55
+
56
+ class_option :theme, type: :string, default: nil,
57
+ desc: "The Poetry theme for `poetry:install` when Poetry is not installed yet"
58
+ class_option :skip_poetry_install, type: :boolean, default: false,
59
+ desc: "Do not run `poetry:install`, even when Poetry is missing"
60
+ class_option :skip_stylesheets, type: :boolean, default: false,
61
+ desc: "Leave Jumpstart's stylesheets untouched"
62
+ class_option :skip_tests, type: :boolean, default: false,
63
+ desc: "Leave the selectors in Jumpstart's own tests untouched"
64
+
65
+ desc "Re-skin a Jumpstart Pro app's views with Poetry components"
66
+
67
+ # Step: reconciles Jumpstart's stylesheets with Poetry's, before
68
+ # `poetry:install` so its token report starts from the reconciled files
69
+ # (Thor task).
70
+ # @api private
71
+ def reconcile_stylesheets
72
+ return if installed.empty? || options[:skip_stylesheets]
73
+
74
+ Poetry::JumpstartPro::Stylesheets::FILES.each do |relative, edit|
75
+ path = File.join(destination_root, relative)
76
+ next say_status(:missing, relative, :yellow) unless File.exist?(path)
77
+
78
+ before = File.read(path)
79
+ after = Poetry::JumpstartPro::Stylesheets.public_send(edit, before)
80
+ next say_status(:identical, relative, :blue) if after == before
81
+
82
+ File.write(path, after) unless options[:pretend]
83
+ say_status :reconcile, relative, :green
84
+ end
85
+ end
86
+
87
+ # Step: runs `poetry:install` when the app has no Poetry yet (Thor task).
88
+ # @api private
89
+ def install_poetry
90
+ return if poetry_installed?
91
+
92
+ theme = options[:theme] ? ["--theme", options[:theme]] : []
93
+ return generate("poetry:install", *theme) unless options[:skip_poetry_install]
94
+
95
+ say_status :poetry, "not installed - run `bin/rails g poetry:install` before using these views", :yellow
96
+ end
97
+
98
+ # Step: copies each requested category's view overrides (Thor task).
99
+ # @api private
100
+ def install_categories
101
+ categories.each do |category|
102
+ unless Poetry::JumpstartPro::CATEGORIES.include?(category)
103
+ say_status :skip, "unknown category #{category.inspect} " \
104
+ "(available: #{Poetry::JumpstartPro::CATEGORIES.join(", ")})", :yellow
105
+ next
106
+ end
107
+ copy_category(category)
108
+ end
109
+ end
110
+
111
+ # Step: copies the pagination and toast adapters the views call (Thor task).
112
+ # @api private
113
+ def install_helper
114
+ return if installed.empty?
115
+
116
+ copy_file HELPER, "app/helpers/poetry_jumpstart_pro_helper.rb"
117
+ end
118
+
119
+ # Step: adds the app stylesheet to Madmin's pages, for the madmin
120
+ # category (Thor task).
121
+ # @api private
122
+ def style_madmin
123
+ return unless installed.include?("madmin")
124
+
125
+ path = File.join(destination_root, MADMIN_INITIALIZER)
126
+ return say_status(:missing, MADMIN_INITIALIZER, :yellow) unless File.exist?(path)
127
+ return say_status(:identical, MADMIN_INITIALIZER, :blue) if File.read(path).include?(MADMIN_STYLESHEET)
128
+
129
+ append_to_file MADMIN_INITIALIZER,
130
+ "\n# Poetry's styles on the admin pages poetry-jumpstart_pro re-skinned.\n#{MADMIN_STYLESHEET}\n"
131
+ end
132
+
133
+ # Step: updates the selectors in Jumpstart's own tests that assert on the
134
+ # markup the installed views replace (Thor task).
135
+ # @api private
136
+ def update_tests
137
+ return if options[:skip_tests]
138
+
139
+ Poetry::JumpstartPro::TestEdits.for(installed).each do |edit|
140
+ path = File.join(destination_root, edit.file)
141
+ next unless File.exist?(path) && edit.applies_to?(File.read(path))
142
+
143
+ gsub_file edit.file, edit.pattern, edit.replacement
144
+ end
145
+ end
146
+
147
+ # Step: prints the post-install checklist (Thor task).
148
+ # @api private
149
+ def next_steps
150
+ return if installed.empty?
151
+
152
+ say ""
153
+ say "poetry-jumpstart_pro installed #{installed.join(", ")} as overrides in app/views/.", :green
154
+ say " 1. Rebuild the CSS: bin/rails tailwindcss:build"
155
+ say " 2. Check the views: bin/rails poetry:check"
156
+ say " 3. Run your tests: bin/rails test && bin/rails test:system"
157
+ say "Every installed file is yours to edit. Delete an override to fall back to Jumpstart's view."
158
+ end
159
+
160
+ private
161
+
162
+ # The requested categories this gem knows.
163
+ def installed
164
+ categories & Poetry::JumpstartPro::CATEGORIES
165
+ end
166
+
167
+ # Whether `poetry:install` has run to the end in the host: the tokens
168
+ # are vendored and the Tailwind entry imports them.
169
+ def poetry_installed?
170
+ entry = File.join(destination_root, TAILWIND_ENTRY)
171
+ File.exist?(File.join(destination_root, POETRY_TOKENS)) &&
172
+ File.exist?(entry) && File.read(entry).include?("poetry/tokens.css")
173
+ end
174
+
175
+ # Whether the host's copy of a view is Jumpstart's default, unedited: the
176
+ # file the engine copies in on a development boot.
177
+ def jumpstart_default?(view)
178
+ host = File.join(destination_root, view)
179
+ original = File.join(destination_root, ENGINE_VIEWS, view.delete_prefix("app/views/"))
180
+ File.exist?(host) && File.exist?(original) && File.binread(host) == File.binread(original)
181
+ end
182
+
183
+ # Mirror templates/<category>/**/* onto app/views/**/*, stripping the
184
+ # category prefix (the template tree already mirrors the view tree).
185
+ def copy_category(category)
186
+ base = File.join(self.class.source_root, category)
187
+ Dir.glob("#{base}/**/*").select { |path| File.file?(path) }.sort.each do |source|
188
+ relative = source.delete_prefix("#{base}/")
189
+ view = File.join("app/views", relative)
190
+ # Only ever force: an explicit false would override a --force given
191
+ # on the command line, since Thor merges this over the options.
192
+ copy_file File.join(category, relative), view, **(jumpstart_default?(view) ? { force: true } : {})
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,46 @@
1
+ <%# Poetry recreation of Jumpstart's account/passwords/edit: the password
2
+ change form on the Poetry builder (password fields never round-trip),
3
+ then two-factor authentication - enabling navigates to the backup codes,
4
+ disabling confirms in an AlertDialog that submits the DELETE form. %>
5
+ <% content_for :title, t(".title") %>
6
+
7
+ <header class="flex flex-col gap-4">
8
+ <h1 class="text-2xl font-semibold tracking-tight"><%= t(".title") %></h1>
9
+ <%= poetry_separator %>
10
+ </header>
11
+
12
+ <%= form_with(model: current_user, url: account_password_path, method: :put, builder: Poetry::Ui::FormBuilder, class: "mt-6 max-w-md space-y-6") do |form| %>
13
+ <%= render "application/error_messages", resource: current_user %>
14
+
15
+ <%= form.group do %>
16
+ <%= form.password_field :password_challenge, autocomplete: "current-password", hint: t("users.registrations.edit.we_need_your_current_password_to_confirm_your_changes") %>
17
+ <%= form.password_field :password, autocomplete: "new-password", hint: t("users.shared.minimum_password_length", count: User::MINIMUM_PASSWORD_LENGTH) %>
18
+ <%= form.password_field :password_confirmation, autocomplete: "new-password" %>
19
+ <% end %>
20
+
21
+ <div>
22
+ <%= form.submit t(".update") %>
23
+ </div>
24
+ <% end %>
25
+
26
+ <%= poetry_separator(class: "my-10") %>
27
+
28
+ <section class="space-y-4" aria-labelledby="two-factor-title">
29
+ <div class="space-y-1">
30
+ <h2 id="two-factor-title" class="text-lg font-semibold"><%= t(".two_factor.title") %></h2>
31
+ <p class="text-sm text-muted-foreground"><%= t(".two_factor.description") %></p>
32
+ </div>
33
+
34
+ <% if current_user.otp_required_for_login? %>
35
+ <%= form_with(url: user_two_factor_path, method: :delete, id: "disable-two-factor-form", class: "hidden") {} %>
36
+ <%= poetry_alert_dialog do |dialog| %>
37
+ <% dialog.with_trigger(variant: :outline, class: "text-destructive") { t(".two_factor.disable") } %>
38
+ <% dialog.with_title { t(".two_factor.disable") } %>
39
+ <% dialog.with_description { t("are_you_sure") } %>
40
+ <% dialog.with_cancel { t("cancel") } %>
41
+ <% dialog.with_action(variant: :destructive, submit: "disable-two-factor-form") { t(".two_factor.disable") } %>
42
+ <% end %>
43
+ <% else %>
44
+ <%= poetry_button(href: backup_codes_user_two_factor_path, variant: :outline) { t(".two_factor.enable") } %>
45
+ <% end %>
46
+ </section>
@@ -0,0 +1,29 @@
1
+ <%# Poetry recreation of Jumpstart's account_invitations/show (accept or
2
+ decline an invitation): a centered card; Accept submits the PUT form,
3
+ Decline confirms in an AlertDialog that submits the DELETE form. %>
4
+ <% content_for :title, t(".title", account: @account.name) %>
5
+
6
+ <div class="mx-auto w-full max-w-2xl px-4 py-8 lg:px-6">
7
+ <%= poetry_card(title_tag: :h1, class: "mx-auto max-w-md text-center") do |card| %>
8
+ <% card.with_title { t(".title", account: @account.name) } %>
9
+ <% card.with_description { t(".invited_you_to_join_html", inviter: @account_invitation.invited_by.name, account: @account_invitation.account.name) } %>
10
+
11
+ <div class="flex justify-center">
12
+ <%= account_avatar @account, size: "size-16" %>
13
+ </div>
14
+
15
+ <% card.with_footer(class: "flex flex-wrap justify-center gap-3") do %>
16
+ <%= form_with(url: account_invitation_path(@account_invitation.token), method: :put) do %>
17
+ <%= poetry_button(type: :submit) { t(".accept") } %>
18
+ <% end %>
19
+ <%= form_with(url: account_invitation_path(@account_invitation.token), method: :delete, id: "decline-invitation-form", class: "hidden") {} %>
20
+ <%= poetry_alert_dialog do |dialog| %>
21
+ <% dialog.with_trigger(variant: :outline) { t(".decline") } %>
22
+ <% dialog.with_title { t(".decline") } %>
23
+ <% dialog.with_description { t("are_you_sure") } %>
24
+ <% dialog.with_cancel { t("cancel") } %>
25
+ <% dialog.with_action(variant: :destructive, submit: "decline-invitation-form") { t(".decline") } %>
26
+ <% end %>
27
+ <% end %>
28
+ <% end %>
29
+ </div>
@@ -0,0 +1,20 @@
1
+ <%# Poetry recreation of Jumpstart's account_users/_form: one checkbox per
2
+ role, each the builder's horizontal Field with its visible label; the
3
+ owner's admin box stays disabled (the same name/disabled contract the
4
+ integration tests assert). %>
5
+ <%= form_with(model: [account_user.account, account_user], builder: Poetry::Ui::FormBuilder, class: "max-w-md space-y-6") do |form| %>
6
+ <%= render "application/error_messages", resource: form.object %>
7
+
8
+ <%= form.fieldset(legend: t("accounts.roles"), legend_variant: :label) do %>
9
+ <%= poetry_field_group(variant: :choices) do %>
10
+ <% AccountUser::ROLES.each do |role| %>
11
+ <%= form.check_box role, label: account_user_role_name(role), disabled: (role == :admin && account_user.account_owner?) %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <div class="flex flex-wrap items-center gap-3">
17
+ <%= form.submit %>
18
+ <%= poetry_button(href: account_path(account_user.account), variant: :ghost) { t("cancel") } %>
19
+ </div>
20
+ <% end %>
@@ -0,0 +1,34 @@
1
+ <%# Poetry recreation of Jumpstart's account_users/edit: a member's roles,
2
+ with removal as an AlertDialog that submits the DELETE form it sits in.
3
+ The title key interpolates the member (the engine left %{user} unfilled). %>
4
+ <% user_name = @account_user.user.name || @account_user.user.email %>
5
+ <% content_for :title, t(".title", user: user_name) %>
6
+
7
+ <header class="flex flex-col gap-4">
8
+ <%= poetry_breadcrumb do |crumb| %>
9
+ <% crumb.with_item(t("accounts.index.title"), href: accounts_path) %>
10
+ <% crumb.with_item(@account.name, href: account_path(@account)) %>
11
+ <% crumb.with_item(user_name) %>
12
+ <% end %>
13
+ <div class="flex flex-wrap items-start justify-between gap-4">
14
+ <div class="space-y-1">
15
+ <h1 class="text-2xl font-semibold tracking-tight"><%= t(".title", user: user_name) %></h1>
16
+ <p class="text-sm text-muted-foreground"><%= t(".description", user: user_name) %></p>
17
+ </div>
18
+ <% unless @account_user.account_owner? %>
19
+ <%= form_with(url: account_account_user_path(@account, @account_user), method: :delete, id: "remove-member-form", class: "hidden") {} %>
20
+ <%= poetry_alert_dialog do |dialog| %>
21
+ <% dialog.with_trigger(variant: :outline, class: "text-destructive") { t("delete") } %>
22
+ <% dialog.with_title { t("are_you_sure") } %>
23
+ <% dialog.with_description { t(".remove_description", default: "%{user} will lose access to %{account}.", user: user_name, account: @account.name) } %>
24
+ <% dialog.with_cancel { t("cancel") } %>
25
+ <% dialog.with_action(variant: :destructive, submit: "remove-member-form") { t("delete") } %>
26
+ <% end %>
27
+ <% end %>
28
+ </div>
29
+ <%= poetry_separator %>
30
+ </header>
31
+
32
+ <div class="mt-6">
33
+ <%= render "account_users/form", account_user: @account_user %>
34
+ </div>
@@ -0,0 +1,26 @@
1
+ <%# Poetry recreation of Jumpstart's accounts/_form on the Poetry form
2
+ builder: labels, values, errors and aria-required derive from the
3
+ model; the avatar upload only exists once the account is persisted. %>
4
+ <%= form_with(model: account, builder: Poetry::Ui::FormBuilder, class: "max-w-md space-y-6") do |form| %>
5
+ <%= render "application/error_messages", resource: form.object %>
6
+
7
+ <%= form.group do %>
8
+ <%= form.input :name, label: t("helpers.label.account.name"), placeholder: t("helpers.placeholder.account.name"), autofocus: true %>
9
+ <%= form.input :domain, placeholder: t("helpers.placeholder.account.domain") if Jumpstart::Multitenancy.domain? %>
10
+ <%= form.input :subdomain, placeholder: t("helpers.placeholder.account.subdomain") if Jumpstart::Multitenancy.subdomain? %>
11
+
12
+ <% if form.object.persisted? %>
13
+ <div class="flex items-center gap-4">
14
+ <%= account_avatar form.object, size: "size-12" %>
15
+ <div class="min-w-0 flex-1">
16
+ <%= form.file_input :avatar, accept: "image/png, image/jpeg, image/heic, image/heif, image/webp" %>
17
+ </div>
18
+ </div>
19
+ <% end %>
20
+ <% end %>
21
+
22
+ <div class="flex flex-wrap items-center gap-3">
23
+ <%= form.submit %>
24
+ <%= poetry_button(href: (form.object.persisted? ? account_path(form.object) : accounts_path), variant: :ghost) { t("cancel") } %>
25
+ </div>
26
+ <% end %>
@@ -0,0 +1,49 @@
1
+ <%# Poetry recreation of Jumpstart's accounts/account_invitations/edit: the
2
+ invitation form plus a guarded delete (AlertDialog submitting the DELETE
3
+ form it sits in). %>
4
+ <% content_for :title, t(".title") %>
5
+
6
+ <header class="flex flex-col gap-4">
7
+ <%= poetry_breadcrumb do |crumb| %>
8
+ <% crumb.with_item(t("accounts.index.title"), href: accounts_path) %>
9
+ <% crumb.with_item(@account.name, href: account_path(@account)) %>
10
+ <% crumb.with_item(t(".title")) %>
11
+ <% end %>
12
+ <div class="flex flex-wrap items-start justify-between gap-4">
13
+ <div class="space-y-1">
14
+ <h1 class="text-2xl font-semibold tracking-tight"><%= t(".title") %></h1>
15
+ <p class="text-sm text-muted-foreground"><%= t(".description") %></p>
16
+ </div>
17
+ <%= form_with(url: account_account_invitation_path(@account, @account_invitation), method: :delete, id: "destroy-invitation-form", class: "hidden") {} %>
18
+ <%= poetry_alert_dialog do |dialog| %>
19
+ <% dialog.with_trigger(variant: :outline, class: "text-destructive") { t(".destroy") } %>
20
+ <% dialog.with_title { t(".destroy") } %>
21
+ <% dialog.with_description { t(".destroy_description", default: "The invitation sent to %{email} will stop working.", email: @account_invitation.email) } %>
22
+ <% dialog.with_cancel { t("cancel") } %>
23
+ <% dialog.with_action(variant: :destructive, submit: "destroy-invitation-form") { t(".destroy") } %>
24
+ <% end %>
25
+ </div>
26
+ <%= poetry_separator %>
27
+ </header>
28
+
29
+ <%= form_with(model: [@account, @account_invitation], builder: Poetry::Ui::FormBuilder, class: "mt-6 max-w-md space-y-6") do |form| %>
30
+ <%= render "application/error_messages", resource: form.object %>
31
+
32
+ <%= form.group do %>
33
+ <%= form.input :name, placeholder: t("helpers.placeholder.account_invitation.name") %>
34
+ <%= form.input :email, placeholder: t("helpers.placeholder.account_invitation.email") %>
35
+
36
+ <%= form.fieldset(legend: t("accounts.roles"), legend_variant: :label) do %>
37
+ <%= poetry_field_group(variant: :choices) do %>
38
+ <% AccountUser::ROLES.each do |role| %>
39
+ <%= form.check_box role, label: account_user_role_name(role) %>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>
43
+ <% end %>
44
+
45
+ <div class="flex flex-wrap items-center gap-3">
46
+ <%= form.submit t(".update") %>
47
+ <%= poetry_button(href: account_path(@account), variant: :ghost) { t("cancel") } %>
48
+ </div>
49
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <%# Poetry recreation of Jumpstart's accounts/account_invitations/new. %>
2
+ <% content_for :title, t(".title") %>
3
+
4
+ <header class="flex flex-col gap-4">
5
+ <%= poetry_breadcrumb do |crumb| %>
6
+ <% crumb.with_item(t("accounts.index.title"), href: accounts_path) %>
7
+ <% crumb.with_item(@account.name, href: account_path(@account)) %>
8
+ <% crumb.with_item(t(".title")) %>
9
+ <% end %>
10
+ <div class="space-y-1">
11
+ <h1 class="text-2xl font-semibold tracking-tight"><%= t(".title") %></h1>
12
+ <p class="text-sm text-muted-foreground"><%= t(".description") %></p>
13
+ </div>
14
+ <%= poetry_separator %>
15
+ </header>
16
+
17
+ <%= form_with(model: @account_invitation, url: account_account_invitations_path(@account), builder: Poetry::Ui::FormBuilder, class: "mt-6 max-w-md space-y-6") do |form| %>
18
+ <%= render "application/error_messages", resource: form.object %>
19
+
20
+ <%= form.group do %>
21
+ <%= form.input :name, placeholder: t("helpers.placeholder.account_invitation.name"), autofocus: true %>
22
+ <%= form.input :email, placeholder: t("helpers.placeholder.account_invitation.email") %>
23
+
24
+ <%= form.fieldset(legend: t("accounts.roles"), legend_variant: :label) do %>
25
+ <%= poetry_field_group(variant: :choices) do %>
26
+ <% AccountUser::ROLES.each do |role| %>
27
+ <%= form.check_box role, label: account_user_role_name(role) %>
28
+ <% end %>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
32
+
33
+ <div class="flex flex-wrap items-center gap-3">
34
+ <%= form.submit t(".send") %>
35
+ <%= poetry_button(href: account_path(@account), variant: :ghost) { t("cancel") } %>
36
+ </div>
37
+ <% end %>