@spree/docs 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/api-reference/store-api/errors.md +2 -2
  2. package/dist/api-reference/store-api/idempotency.md +1 -1
  3. package/dist/api-reference/store-api/localization.md +4 -4
  4. package/dist/api-reference/store-api/metadata.md +2 -2
  5. package/dist/api-reference/store.yaml +10599 -0
  6. package/dist/api-reference/storefront/authentication.md +2 -2
  7. package/dist/api-reference/tutorials/adyen-integration-guide-for-android.md +2 -2
  8. package/dist/api-reference/tutorials/adyen-integration-guide-for-ios.md +2 -2
  9. package/dist/api-reference/tutorials/quick-checkout-with-stripe.md +8 -8
  10. package/dist/api-reference/v2/introduction.md +2 -2
  11. package/dist/api-reference/webhooks-events.md +2 -2
  12. package/dist/developer/admin/admin.md +18 -18
  13. package/dist/developer/admin/components.md +1 -1
  14. package/dist/developer/admin/extending-ui.md +26 -26
  15. package/dist/developer/admin/helper-methods.md +2 -2
  16. package/dist/developer/admin/navigation.md +5 -5
  17. package/dist/developer/admin/tables.md +4 -4
  18. package/dist/developer/cli/quickstart.md +2 -2
  19. package/dist/developer/contributing/creating-an-extension.md +12 -12
  20. package/dist/developer/contributing/quickstart.md +1 -1
  21. package/dist/developer/core-concepts/addresses.md +11 -11
  22. package/dist/developer/core-concepts/adjustments.md +8 -8
  23. package/dist/developer/core-concepts/architecture.md +21 -21
  24. package/dist/developer/core-concepts/calculators.md +4 -4
  25. package/dist/developer/core-concepts/customers.md +9 -9
  26. package/dist/developer/core-concepts/events.md +5 -5
  27. package/dist/developer/core-concepts/inventory.md +5 -5
  28. package/dist/developer/core-concepts/markets.md +10 -10
  29. package/dist/developer/core-concepts/media.md +3 -3
  30. package/dist/developer/core-concepts/metafields.md +6 -6
  31. package/dist/developer/core-concepts/orders.md +14 -14
  32. package/dist/developer/core-concepts/payments.md +9 -9
  33. package/dist/developer/core-concepts/pricing.md +10 -10
  34. package/dist/developer/core-concepts/products.md +10 -10
  35. package/dist/developer/core-concepts/promotions.md +5 -5
  36. package/dist/developer/core-concepts/reports.md +2 -2
  37. package/dist/developer/core-concepts/search-filtering.md +7 -7
  38. package/dist/developer/core-concepts/shipments.md +13 -13
  39. package/dist/developer/core-concepts/slugs.md +3 -3
  40. package/dist/developer/core-concepts/staff-roles.md +7 -7
  41. package/dist/developer/core-concepts/store-credits-gift-cards.md +4 -4
  42. package/dist/developer/core-concepts/stores.md +15 -15
  43. package/dist/developer/core-concepts/taxes.md +11 -11
  44. package/dist/developer/core-concepts/translations.md +6 -6
  45. package/dist/developer/core-concepts/users.md +12 -12
  46. package/dist/developer/core-concepts/webhooks.md +8 -8
  47. package/dist/developer/create-spree-app/quickstart.md +5 -5
  48. package/dist/developer/customization/api.md +2 -2
  49. package/dist/developer/customization/authentication.md +2 -2
  50. package/dist/developer/customization/checkout.md +7 -7
  51. package/dist/developer/customization/decorators.md +24 -24
  52. package/dist/developer/customization/dependencies.md +1 -1
  53. package/dist/developer/customization/metadata.md +3 -3
  54. package/dist/developer/customization/permissions.md +1 -1
  55. package/dist/developer/customization/quickstart.md +9 -9
  56. package/dist/developer/customization/v4/checkout.md +3 -3
  57. package/dist/developer/customization/v4/deface.md +1 -1
  58. package/dist/developer/deployment/assets.md +1 -1
  59. package/dist/developer/deployment/aws.md +5 -5
  60. package/dist/developer/deployment/docker.md +2 -2
  61. package/dist/developer/deployment/environment_variables.md +1 -1
  62. package/dist/developer/deployment/render.md +5 -5
  63. package/dist/developer/getting-started/quickstart.md +2 -2
  64. package/dist/developer/how-to/custom-payment-method.md +6 -6
  65. package/dist/developer/how-to/custom-promotion.md +7 -7
  66. package/dist/developer/how-to/custom-report.md +3 -3
  67. package/dist/developer/how-to/custom-search-provider.md +7 -7
  68. package/dist/developer/multi-store/quickstart.md +1 -1
  69. package/dist/developer/multi-tenant/quickstart.md +1 -1
  70. package/dist/developer/sdk/authentication.md +1 -1
  71. package/dist/developer/sdk/configuration.md +1 -1
  72. package/dist/developer/sdk/store/markets.md +3 -3
  73. package/dist/developer/storefront/nextjs/customization.md +1 -1
  74. package/dist/developer/storefront/nextjs/quickstart.md +2 -2
  75. package/dist/developer/tutorial/admin.md +2 -2
  76. package/dist/developer/tutorial/extending-models.md +15 -15
  77. package/dist/developer/tutorial/file-uploads.md +1 -1
  78. package/dist/developer/tutorial/introduction.md +7 -7
  79. package/dist/developer/tutorial/rich-text.md +1 -1
  80. package/dist/developer/tutorial/testing.md +5 -61
  81. package/dist/developer/upgrades/3.7-to-4.0.md +1 -1
  82. package/dist/developer/upgrades/4.0-to-4.1.md +1 -1
  83. package/dist/developer/upgrades/4.10-to-5.0.md +1 -1
  84. package/dist/developer/upgrades/4.5-to-4.6.md +4 -4
  85. package/dist/developer/upgrades/4.8-to-4.9.md +1 -1
  86. package/dist/developer/upgrades/4.9-to-4.10.md +1 -1
  87. package/dist/developer/upgrades/5.0-to-5.1.md +1 -1
  88. package/dist/developer/upgrades/5.1-to-5.2.md +2 -2
  89. package/dist/developer/upgrades/5.2-to-5.3.md +2 -2
  90. package/dist/developer/upgrades/5.3-to-5.4.md +5 -5
  91. package/dist/developer/upgrades/quickstart.md +1 -1
  92. package/dist/integrations/integrations.md +10 -10
  93. package/dist/integrations/payments/adyen.md +1 -1
  94. package/dist/integrations/search/meilisearch.md +2 -2
  95. package/package.json +7 -2
  96. package/dist/developer/storefront/blocks.md +0 -285
  97. package/dist/developer/storefront/custom-css.md +0 -260
  98. package/dist/developer/storefront/custom-javascript.md +0 -166
  99. package/dist/developer/storefront/helper-methods.md +0 -1288
  100. package/dist/developer/storefront/links.md +0 -298
  101. package/dist/developer/storefront/pages.md +0 -163
  102. package/dist/developer/storefront/sections.md +0 -569
  103. package/dist/developer/storefront/storefront.md +0 -56
  104. package/dist/developer/storefront/themes.md +0 -161
  105. package/dist/developer/tutorial/page-builder.md +0 -487
  106. package/dist/developer/tutorial/seo.md +0 -332
  107. package/dist/developer/tutorial/storefront.md +0 -352
@@ -1,332 +0,0 @@
1
- ---
2
- title: SEO
3
- description: Learn how to optimize your Brands feature for search engines
4
- ---
5
-
6
- > **INFO:** This guide assumes you've completed the [Storefront](/developer/tutorial/storefront) tutorial. You should have working brand pages.
7
-
8
- This guide covers SEO best practices for your custom Spree features, including friendly URLs, meta tags, and Open Graph data.
9
-
10
- ## SEO-Friendly URLs
11
-
12
- Rather than using database IDs in URLs:
13
-
14
- ```
15
- https://example.com/brands/1
16
- ```
17
-
18
- Create human-readable URLs that help with SEO:
19
-
20
- ```
21
- https://example.com/brands/nike-sportswear
22
- ```
23
-
24
- ### Step 1: Add SEO Columns
25
-
26
- Add columns for slug and meta fields:
27
-
28
- ```bash
29
- bin/rails g migration AddSeoFieldsToSpreeBrands slug:string:uniq meta_title:string meta_description:text
30
- ```
31
-
32
- ```bash
33
- bin/rails db:migrate
34
- ```
35
-
36
- This adds:
37
- - `slug` - SEO-friendly URL with unique index
38
- - `meta_title` - Custom page title for search engines
39
- - `meta_description` - Description shown in search results
40
-
41
- ### Step 2: Add FriendlyId to the Model
42
-
43
- Add FriendlyId to generate SEO-friendly URLs automatically:
44
-
45
- ```ruby app/models/spree/brand.rb {3-4,9}
46
- module Spree
47
- class Brand < Spree::Base
48
- extend FriendlyId
49
- friendly_id :slug_candidates, use: %i[slugged]
50
-
51
- # ... other code ...
52
-
53
- validates :name, presence: true
54
- validates :slug, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
55
- end
56
- end
57
- ```
58
-
59
- The `slug_candidates` method is already defined in `Spree::Base`:
60
-
61
- ```ruby
62
- def slug_candidates
63
- [
64
- :name,
65
- [:name, :uuid_for_friendly_id]
66
- ]
67
- end
68
- ```
69
-
70
- This generates the slug from the name. If the name is taken, it appends a UUID.
71
-
72
- ### Step 3: Add SEO Fields to Admin
73
-
74
- Spree provides a reusable SEO partial that handles slug, meta title, and meta description with a live search preview. Update your admin form:
75
-
76
- ```erb app/views/spree/admin/brands/_form.html.erb {1-2,5-12,15,25-32}
77
- <div class="row" data-controller="slug-form seo-form">
78
- <div class="col-lg-8">
79
- <div class="card mb-6">
80
- <div class="card-body">
81
- <%= f.spree_text_field :name,
82
- required: true,
83
- data: {
84
- seo_form_target: 'sourceTitleInput',
85
- slug_form_target: 'name',
86
- action: 'slug-form#updateUrlFromName'
87
- } %>
88
- <%= f.spree_rich_text_area :description,
89
- data: { seo_form_target: 'sourceDescriptionInput' } %>
90
- </div>
91
- </div>
92
- </div>
93
-
94
- <div class="col-lg-4">
95
- <div class="card mb-6">
96
- <div class="card-header">
97
- <h5 class="card-title"><%= Spree.t(:logo) %></h5>
98
- </div>
99
- <div class="card-body">
100
- <%= f.spree_file_field :logo, width: 300, height: 300, crop: true %>
101
- </div>
102
- </div>
103
-
104
- <%= render 'spree/admin/shared/seo',
105
- f: f,
106
- title: @brand.name,
107
- meta_title: @brand.meta_title,
108
- description: @brand.description,
109
- slug: @brand.slug,
110
- slug_path: 'brands',
111
- placeholder: 'Add a name and description to see how this brand might appear in a search engine listing' %>
112
- </div>
113
- </div>
114
- ```
115
-
116
- The SEO partial provides:
117
- - **Live preview** of how the page will appear in search results
118
- - **Meta title** field with character count
119
- - **Meta description** field
120
- - **Slug** field with auto-generation from name
121
-
122
- The `seo-form` Stimulus controller syncs the preview with your form inputs in real-time.
123
-
124
- Don't forget to permit the SEO attributes in your controller:
125
-
126
- ```ruby app/controllers/spree/admin/brands_controller.rb {6-8}
127
- def permitted_resource_params
128
- params.require(:brand).permit(
129
- :name,
130
- :description,
131
- :logo,
132
- :slug,
133
- :meta_title,
134
- :meta_description)
135
- end
136
- ```
137
-
138
- ### Step 4: Use Slugs in Storefront
139
-
140
- Update your controller to use `friendly.find`:
141
-
142
- ```ruby app/controllers/spree/brands_controller.rb
143
- def load_brand
144
- @brand = Spree::Brand.friendly.find(params[:id])
145
- end
146
- ```
147
-
148
- The `friendly.find` method:
149
- - Finds by slug first (e.g., `/brands/nike-sportswear`)
150
- - Falls back to ID if no slug matches
151
- - Raises `ActiveRecord::RecordNotFound` if neither is found
152
-
153
- ### URL Helpers
154
-
155
- Always pass the model object to path helpers:
156
-
157
- ```erb
158
- <%= link_to brand.name, spree.brand_path(brand) %>
159
- <%# => /brands/nike-sportswear %>
160
- ```
161
-
162
- FriendlyId overrides `to_param` to return the slug automatically.
163
-
164
- ### Handling Slug Changes
165
-
166
- To redirect old URLs when slugs change, enable slug history:
167
-
168
- ```ruby app/models/spree/brand.rb
169
- extend FriendlyId
170
- friendly_id :slug_candidates, use: %i[slugged history]
171
- ```
172
-
173
- Old slugs will automatically resolve to the current slug.
174
-
175
- ## Meta Tags & Open Graph
176
-
177
- Spree automatically generates meta tags and Open Graph data. The system uses a helper called `object` that finds the main instance variable based on your controller name.
178
-
179
- For `BrandsController`, Spree looks for `@brand` and generates:
180
- - Meta description
181
- - Open Graph tags (og:title, og:description, og:image)
182
- - Twitter Card tags
183
-
184
- ### Page Title
185
-
186
- Override `accurate_title` in your controller to set the page `<title>`:
187
-
188
- ```ruby app/controllers/spree/brands_controller.rb
189
- private
190
-
191
- def accurate_title
192
- if @brand
193
- @brand.meta_title.presence || @brand.name
194
- else
195
- Spree.t(:brands)
196
- end
197
- end
198
- ```
199
-
200
- ### Meta Description
201
-
202
- Spree checks these sources in order:
203
-
204
- 1. `@page_description` instance variable (if set)
205
- 2. `@brand.meta_description` (if the model has this attribute)
206
- 3. `current_store.meta_description` (fallback)
207
-
208
- #### Using the SEO Partial
209
-
210
- If you followed [Step 1](#step-1-add-seo-columns) and [Step 3](#step-3-add-seo-fields-to-admin), your model already has `meta_description` and the admin form uses the SEO partial. Spree will automatically use `@brand.meta_description` for the page description.
211
-
212
- #### Manual Override
213
-
214
- For custom logic, set `@page_description` directly:
215
-
216
- ```ruby
217
- def show
218
- @page_description = "Shop #{@brand.name} - #{@brand.products.count} products available"
219
- end
220
- ```
221
-
222
- ### Social Sharing Image
223
-
224
- For Open Graph images, Spree checks:
225
-
226
- 1. `@page_image` instance variable (if set)
227
- 2. `@brand.image` (if the model responds to `image`)
228
- 3. `current_store.social_image` (fallback)
229
-
230
- If your Brand model has `logo` but not `image`, add an alias:
231
-
232
- ```ruby app/models/spree/brand.rb
233
- def image
234
- logo
235
- end
236
- ```
237
-
238
- Or set it manually:
239
-
240
- ```ruby
241
- def show
242
- @page_image = @brand.logo if @brand.logo.attached?
243
- end
244
- ```
245
-
246
- ## Complete SEO-Optimized Model
247
-
248
- Here's a complete Brand model with full SEO support:
249
-
250
- ```ruby app/models/spree/brand.rb
251
- module Spree
252
- class Brand < Spree::Base
253
- extend FriendlyId
254
- friendly_id :slug_candidates, use: %i[slugged]
255
-
256
- has_many :products, class_name: 'Spree::Product', dependent: :nullify
257
-
258
- has_one_attached :logo
259
- has_rich_text :description
260
-
261
- # Database columns: slug, meta_title, meta_description
262
-
263
- validates :name, presence: true
264
- validates :slug, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
265
-
266
- # SEO: Use logo as Open Graph image
267
- def image
268
- logo
269
- end
270
- end
271
- end
272
- ```
273
-
274
- ## Complete SEO-Optimized Controller
275
-
276
- ```ruby app/controllers/spree/brands_controller.rb
277
- module Spree
278
- class BrandsController < StoreController
279
- before_action :load_brand, only: [:show]
280
-
281
- def index
282
- @brands = Spree::Brand.order(:name)
283
- end
284
-
285
- def show
286
- @products = @brand.products
287
- .active(current_currency)
288
- .includes(storefront_products_includes)
289
- .page(params[:page])
290
- .per(12)
291
-
292
- # Optional: Custom page description
293
- # @page_description = "Shop #{@brand.name} products"
294
-
295
- # Optional: Custom social image
296
- # @page_image = @brand.logo if @brand.logo.attached?
297
- end
298
-
299
- private
300
-
301
- def load_brand
302
- @brand = Spree::Brand.friendly.find(params[:id])
303
- end
304
-
305
- def accurate_title
306
- if @brand
307
- @brand.meta_title.presence || @brand.name
308
- else
309
- Spree.t(:brands)
310
- end
311
- end
312
- end
313
- end
314
- ```
315
-
316
- ## SEO Checklist
317
-
318
- **Friendly URLs** - Use slugs instead of IDs (`/brands/nike` not `/brands/1`)
319
-
320
- **Page Titles** - Override `accurate_title` for descriptive titles
321
-
322
- **Meta Descriptions** - Add `meta_description` field or set `@page_description`
323
-
324
- **Social Images** - Provide `image` method or set `@page_image` for Open Graph
325
-
326
- **Slug History** - Enable FriendlyId history to handle URL changes gracefully
327
-
328
- ## Related Documentation
329
-
330
- - [Storefront Tutorial](/developer/tutorial/storefront) - Building storefront pages
331
- - [Model Tutorial](/developer/tutorial/model) - Creating the Brand model
332
- - [Admin Tutorial](/developer/tutorial/admin) - Building the admin interface
@@ -1,352 +0,0 @@
1
- ---
2
- title: Storefront
3
- description: Learn how to create storefront pages for your Brands feature
4
- ---
5
-
6
- In this tutorial, we'll create storefront pages to display brands to your customers. We'll start with a simple Rails approach using controllers and views, then in the next guide we'll connect it to Page Builder.
7
-
8
- > **INFO:** This guide assumes you've completed the [Model](/developer/tutorial/model), [Admin](/developer/tutorial/admin), [Rich Text](/developer/tutorial/rich-text), [File Uploads](/developer/tutorial/file-uploads), and [Extending Core Models](/developer/tutorial/extending-models) tutorials. You should have a working `Spree::Brand` model with products associated to brands.
9
-
10
- ## What We're Building
11
-
12
- By the end of this tutorial, you'll have:
13
-
14
- - A custom theme for your store
15
- - A brands listing page at `/brands`
16
- - Individual brand pages at `/brands/:id`
17
- - Styled views using Tailwind CSS
18
-
19
- ## Step 1: Create a Custom Theme
20
-
21
- Before adding custom views, create your own theme. This is important because:
22
-
23
- - **Upgrade safety** - Your customizations won't be overwritten when updating Spree
24
- - **Clean separation** - Your code stays separate from Spree's default theme
25
- - **Full control** - You get a complete copy of all templates to customize
26
-
27
- Run the theme generator:
28
-
29
- ```bash
30
- bin/rails g spree:storefront:theme MyStore
31
- ```
32
-
33
- This creates:
34
- - `app/views/themes/my_store/` - Copy of all storefront templates
35
- - `app/models/spree/themes/my_store.rb` - Theme configuration class
36
-
37
- The generator also updates your `config/initializers/spree.rb`:
38
-
39
- ```ruby config/initializers/spree.rb
40
- Spree.page_builder.themes << Spree::Themes::MyStore
41
- ```
42
-
43
- ### Activate Your Theme
44
-
45
- 1. Go to **Admin → Storefront → Themes**
46
- 2. Find your new theme in the "Add new theme" section
47
- 3. Click **Add** to activate it
48
-
49
- > **WARNING:** Always create a custom theme for production stores. Never modify the default theme directly - your changes will be lost during Spree upgrades.
50
-
51
- ## Step 2: Add Routes
52
-
53
- Now let's add routes for our brand pages. Create or update your routes file:
54
-
55
- ```ruby config/routes.rb
56
- Spree::Core::Engine.add_routes do
57
- scope '(:locale)', locale: /#{Spree.available_locales.join('|')}/, defaults: { locale: nil } do
58
- resources :brands, only: [:index, :show]
59
- end
60
- end
61
- ```
62
-
63
- > **INFO:** The `scope '(:locale)'` wrapper enables internationalization - URLs like `/fr/brands` will work automatically if you have French locale enabled.
64
-
65
- ## Step 3: Create the Controller
66
-
67
- Create a controller that inherits from `Spree::StoreController`. This base controller provides:
68
-
69
- - Access to `current_store`, `current_theme`, `current_currency`
70
- - User authentication helpers
71
- - Storefront layout and helpers
72
- - SEO and meta tag support
73
-
74
- ```ruby app/controllers/spree/brands_controller.rb
75
- module Spree
76
- class BrandsController < StoreController
77
- before_action :load_brand, only: [:show]
78
-
79
- def index
80
- @brands = Spree::Brand.order(:name)
81
- end
82
-
83
- def show
84
- @products = @brand.products.active(current_currency).includes(storefront_products_includes)
85
- end
86
-
87
- private
88
-
89
- def load_brand
90
- @brand = Spree::Brand.find(params[:id])
91
- end
92
-
93
- # Override to set page title for SEO
94
- def accurate_title
95
- if @brand
96
- @brand.name
97
- else
98
- Spree.t(:brands)
99
- end
100
- end
101
- end
102
- end
103
- ```
104
-
105
- ### Key Points
106
-
107
- - **Inherit from `Spree::StoreController`** - Not `ApplicationController`. This gives you access to all storefront functionality.
108
- - **Use `current_store`** - Always scope queries to the current store for multi-store support.
109
- - **Override `accurate_title`** - This sets the page `<title>` tag.
110
-
111
- > **TIP:** Want SEO-friendly URLs like `/brands/nike` instead of `/brands/123`? See the [SEO](/developer/tutorial/seo) tutorial to add slug support.
112
-
113
- ## Step 4: Create the Views
114
-
115
- Storefront views live in your theme directory. Create the brands views in your custom theme:
116
-
117
- ```bash
118
- mkdir -p app/views/themes/my_store/spree/brands
119
- ```
120
-
121
- Your theme's view structure:
122
-
123
- ```
124
- app/views/themes/my_store/spree/brands/
125
- ├── index.html.erb
126
- ├── show.html.erb
127
- └── _brand_card.html.erb
128
- ```
129
-
130
- ### Brands Listing Page
131
-
132
- ```erb app/views/themes/my_store/spree/brands/index.html.erb
133
- <div class="page-container py-8">
134
- <h1 class="text-2xl lg:text-3xl font-medium mb-8">
135
- <%= Spree.t(:brands) %>
136
- </h1>
137
-
138
- <% if @brands.any? %>
139
- <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
140
- <% @brands.each do |brand| %>
141
- <%= render 'brand_card', brand: brand %>
142
- <% end %>
143
- </div>
144
- <% else %>
145
- <p class="text-neutral-600">
146
- <%= Spree.t(:no_brands_found) %>
147
- </p>
148
- <% end %>
149
- </div>
150
- ```
151
-
152
- ### Brand Card Partial
153
-
154
- ```erb app/views/themes/my_store/spree/brands/_brand_card.html.erb
155
- <%= link_to spree.brand_path(brand),
156
- class: 'block group',
157
- data: { turbo_frame: '_top' } do %>
158
- <div class="aspect-square bg-accent rounded-lg overflow-hidden mb-3">
159
- <% if brand.logo.attached? %>
160
- <%= spree_image_tag brand.logo,
161
- width: 300,
162
- height: 300,
163
- class: 'w-full h-full object-contain p-6 group-hover:scale-105 transition-transform',
164
- alt: brand.name %>
165
- <% else %>
166
- <div class="w-full h-full flex items-center justify-center">
167
- <span class="text-4xl font-medium text-neutral-400">
168
- <%= brand.name.first.upcase %>
169
- </span>
170
- </div>
171
- <% end %>
172
- </div>
173
- <h2 class="font-medium group-hover:text-primary transition-colors">
174
- <%= brand.name %>
175
- </h2>
176
- <% end %>
177
- ```
178
-
179
- ### Single Brand Page
180
-
181
- ```erb app/views/themes/my_store/spree/brands/show.html.erb
182
- <div class="page-container py-8">
183
- <%# Brand Header %>
184
- <div class="flex flex-col md:flex-row gap-8 mb-12">
185
- <% if @brand.logo.attached? %>
186
- <div class="w-32 h-32 bg-accent rounded-lg flex-shrink-0">
187
- <%= spree_image_tag @brand.logo,
188
- width: 128,
189
- height: 128,
190
- class: 'w-full h-full object-contain p-4',
191
- alt: @brand.name %>
192
- </div>
193
- <% end %>
194
-
195
- <div>
196
- <h1 class="text-2xl lg:text-3xl font-medium mb-4">
197
- <%= @brand.name %>
198
- </h1>
199
-
200
- <% if @brand.description.present? %>
201
- <div class="prose max-w-none text-neutral-600">
202
- <%= @brand.description %>
203
- </div>
204
- <% end %>
205
- </div>
206
- </div>
207
-
208
- <%# Products Grid %>
209
- <% if @products.any? %>
210
- <h2 class="text-xl font-medium mb-6">
211
- <%= Spree.t(:products_by_brand, brand: @brand.name) %>
212
- </h2>
213
-
214
- <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
215
- <%= render 'spree/shared/products', products: @products %>
216
- </div>
217
- <% else %>
218
- <p class="text-neutral-600">
219
- <%= Spree.t(:no_products_found) %>
220
- </p>
221
- <% end %>
222
- </div>
223
- ```
224
-
225
- ## Step 5: Add Translations
226
-
227
- Add the necessary translations:
228
-
229
- ```yaml config/locales/en.yml
230
- en:
231
- spree:
232
- brands: Brands
233
- no_brands_found: No brands found.
234
- products_by_brand: "Products by %{brand}"
235
- ```
236
-
237
- ## Step 6: Add Navigation Link (Optional)
238
-
239
- To add a link to brands in your header navigation, you can use the Admin Dashboard:
240
-
241
- 1. Go to **Storefront → Theme Editor**
242
- 2. Click on **Header** section
243
- 3. Add a new navigation link pointing to `/brands`
244
-
245
- Or programmatically in your header partial:
246
-
247
- ```erb
248
- <%= link_to Spree.t(:brands), spree.brands_path, class: 'nav-link' %>
249
- ```
250
-
251
- ## Understanding the View Structure
252
-
253
- ### Theme Directory
254
-
255
- Views are organized by theme in `app/views/themes/{theme_name}/spree/`:
256
-
257
- ```
258
- app/views/themes/my_store/spree/
259
- ├── brands/ # Your brand views
260
- ├── products/ # Product views
261
- ├── shared/ # Shared partials
262
- └── page_sections/ # Page Builder sections
263
- ```
264
-
265
- > **INFO:** Spree looks for views in your active theme first. If a view isn't found, it falls back to the default theme. This means you only need to copy and customize the files you want to change.
266
-
267
- ### Key CSS Classes
268
-
269
- Spree's default theme uses these common patterns:
270
-
271
- | Class | Purpose |
272
- |-------|---------|
273
- | `page-container` | Centered container with max-width and padding |
274
- | `bg-accent` | Uses theme's accent background color |
275
- | `text-primary` | Uses theme's primary text color |
276
- | `btn-primary` | Primary button style |
277
- | `btn-secondary` | Secondary button style |
278
-
279
- ### Using Spree Helpers
280
-
281
- ```erb
282
- <%# Image helper with automatic optimization %>
283
- <%= spree_image_tag image, width: 400, height: 400 %>
284
-
285
- <%# URL helpers %>
286
- <%= spree.brands_path %>
287
- <%= spree.brand_path(brand) %>
288
-
289
- <%# Translation helper %>
290
- <%= Spree.t(:brands) %>
291
-
292
- <%# Price display %>
293
- <%= display_price(product.price_in(current_currency)) %>
294
- ```
295
-
296
- ## Testing Your Pages
297
-
298
- Start your Rails server and visit:
299
-
300
- - `http://localhost:3000/brands` - Brand listing
301
- - `http://localhost:3000/brands/1` - Single brand (using the brand's ID)
302
-
303
- > **TIP:** Want SEO-friendly URLs like `/brands/nike` instead of `/brands/1`? See the [SEO](/developer/tutorial/seo) tutorial to add slug support, meta tags, and Open Graph data.
304
-
305
- ## What's Next?
306
-
307
- You now have working brand pages using standard Rails MVC patterns. In the next guide, [Testing](/developer/tutorial/testing), we'll write automated tests for your feature to make sure it works as expected, also in the future when you make changes to your code.
308
-
309
- ## Complete Controller Example
310
-
311
- Here's the complete controller with all features:
312
-
313
- ```ruby app/controllers/spree/brands_controller.rb
314
- module Spree
315
- class BrandsController < StoreController
316
- before_action :load_brand, only: [:show]
317
-
318
- def index
319
- @brands = Spree::Brand.order(:name)
320
- .page(params[:page])
321
- .per(24)
322
- end
323
-
324
- def show
325
- @products = @brand.products.active(current_currency).includes(storefront_products_includes)
326
- @page_description = @brand.description&.to_plain_text&.truncate(160)
327
- end
328
-
329
- private
330
-
331
- def load_brand
332
- @brand = Spree::Brand.find(params[:id])
333
- end
334
-
335
- def accurate_title
336
- if @brand
337
- @brand.name
338
- else
339
- Spree.t(:brands)
340
- end
341
- end
342
- end
343
- end
344
- ```
345
-
346
- > **INFO:** To use SEO-friendly slugs like `/brands/nike` instead of `/brands/1`, follow the [SEO](/developer/tutorial/seo) tutorial.
347
-
348
- ## Related Documentation
349
-
350
- - [Storefront Overview](/developer/storefront/storefront) - Storefront architecture
351
- - [Custom CSS](/developer/storefront/custom-css) - Styling with Tailwind
352
- - [Helper Methods](/developer/storefront/helper-methods) - Available helpers