Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
ef55b2c
Add shop tables
Jul 18, 2025
1de346f
Start fleshing out ShinyShop with data structures similar to ShinyPages
Jul 18, 2025
cef27e3
Change test data set-up stage to be more reliable
Jul 24, 2025
934fb24
Fix let in spec - product not page
Jul 24, 2025
15ae651
Remove default_page/section stuff
Jul 24, 2025
3c4b7e3
Remove currently unneeded test data
Jul 24, 2025
5465994
Expand product model to reference section/template/elements
Jul 24, 2025
9b6b41e
Expand product factory to connect template
Jul 24, 2025
6686f6a
Add product to test fixtures
Jul 24, 2025
47c8fa2
Add bare bones template for viewing shop products
Jul 24, 2025
f653f99
Merge branch 'main' into feature/shop-product-descriptions
Jul 24, 2025
1456b24
Clean up old files, if any
Jul 24, 2025
8cadf04
Merge branch 'main' into feature/shop-product-descriptions
Jul 25, 2025
b84dcf5
Fix new rubocop complaint
Jul 25, 2025
5abff2d
Fix comment banner date in shop migrations
Jul 28, 2025
28fcc80
Continue adding templates (etc) to shop
Jul 28, 2025
cd91764
Fix Page -> Shop error
Jul 28, 2025
45dca2d
Add more locale strings for shop
Jul 28, 2025
d096377
Fix missed bits of Pages -> Shop code conversion
Jul 28, 2025
4ba7b3c
Specify template ID when creating product
Aug 4, 2025
ba436b3
Split long line
Aug 4, 2025
0b9afd5
Merge branch 'main' into feature/shop-product-descriptions
Aug 6, 2025
7a13e83
Add routing for templates and sections
Aug 13, 2025
adcc844
Add seed data for templates and sections
Aug 13, 2025
7dd79d3
Add factories for shop sections and templates
Aug 13, 2025
e10a5f9
Remove unwanted capability
Aug 13, 2025
23a36b0
Update product spec in line with recent changes to data
Aug 13, 2025
02f2712
Add pundit policies for shop sections and templates
Aug 13, 2025
d8eade5
Remove commented out pry calls
Aug 13, 2025
1e8626d
Add locale string for search templates
Aug 13, 2025
76dff89
Adding views and tests for shop sections
Aug 13, 2025
06be6b3
Add locale strings for new element form inputs
Aug 13, 2025
3d0ff77
Add menu options for template CRUD
Aug 19, 2025
7a2321b
Add menu item for shop templates
Aug 19, 2025
c3268bc
Remove superfluous column from list of products
Aug 21, 2025
6fc4e32
Merge branch 'main' into feature/shop-product-descriptions
Aug 21, 2025
8294f41
WIP adding new index page for products and sections
Aug 26, 2025
f9c1ea6
Add views partials for adding product elements
Aug 26, 2025
0306002
Add in some more code to handle top-level products/sections
Aug 26, 2025
00f54c8
Fixed indented display of products and sections in categories
Aug 27, 2025
8b71171
Merge branch 'main' into feature/shop-product-descriptions
Aug 27, 2025
58b4c92
Merge branch 'main' into feature/shop-product-descriptions
Aug 27, 2025
4150613
Add WIP on shop product templates
Aug 28, 2025
80093a6
Add locale string for model name
Sep 5, 2025
350e4eb
Tidy up copypasta errors
Sep 5, 2025
a211b36
Add title string for product template admin
Sep 5, 2025
037ae33
Add pundit policy line for product template search feature
Sep 5, 2025
86ca171
Remove unused locale string
Sep 5, 2025
bee1dd3
Merge branch 'main' into feature/shop-product-descriptions
Sep 5, 2025
f24c3d7
Merge branch 'main' into feature/shop-product-descriptions
Sep 8, 2025
826bde2
Merge branch 'main' into feature/shop-product-descriptions
Sep 9, 2025
ff6586a
Merging the two versions of the products+sections page
Sep 15, 2025
992c5d4
Persist section ID for shop products
Sep 16, 2025
e550f80
Split long line
Sep 16, 2025
a4df24f
Fiddle with CSS to get faux and real buttons aligned
Oct 7, 2025
ddfcdbb
Merge branch 'main' into feature/shop-product-descriptions
Oct 7, 2025
80f7d8a
Remove superfluous CSS snippet, per CodeFactor
Oct 7, 2025
7954523
Merge branch 'main' into feature/shop-product-descriptions
Oct 7, 2025
a8a9b6c
Merge branch 'main' into feature/shop-product-descriptions
Oct 7, 2025
8482480
Merge branch 'main' into feature/shop-product-descriptions
Oct 9, 2025
f3194fb
Merge branch 'main' into feature/shop-product-descriptions
Oct 13, 2025
c7daa2c
Change elements admin pages from CKEditor to Trix
Oct 17, 2025
b0a00b6
Merge branch 'main' into feature/shop-product-descriptions
Oct 17, 2025
78f4db3
Add tests for shop template element admin controller
Oct 20, 2025
176ad59
Remove product element routing and tests
Oct 20, 2025
c5c4e94
Remove i18n strings for removed shop elements features
Oct 20, 2025
9d2e742
Merge branch 'main' into feature/shop-product-descriptions
Oct 20, 2025
635e6b4
Merge branch 'main' into feature/shop-product-descriptions
Oct 21, 2025
59ccda8
Add routing for shop sections on main site
Oct 23, 2025
71159a0
Merge branch 'main' into feature/shop-product-descriptions
Oct 23, 2025
a7714f6
Comment out binding.pry
Oct 23, 2025
874a8e6
Whitespace
Oct 23, 2025
6b56d56
Merge branch 'main' into feature/shop-product-descriptions
Oct 27, 2025
91f55a0
Update /products and /products/section test to expect consistency
Oct 27, 2025
2c0dd46
Merge branch 'main' into feature/shop-product-descriptions
Oct 29, 2025
52c0829
Merge branch 'main' into feature/shop-product-descriptions
Oct 30, 2025
86c19c5
WIP in Shop
Oct 31, 2025
6c51a62
Switch a stray `pages` to `products` in Shop section code
Oct 31, 2025
70f67fe
Remove commented out binding.pry
Nov 3, 2025
f6f4ceb
Adjust tests looking for section name
Nov 4, 2025
1e6fa88
Bring in some more of the Page/Product stuff
Nov 13, 2025
8aa1691
Render subsection index if appropriate
Nov 18, 2025
6f38626
Merge branch 'main' into feature/shop-product-descriptions
Nov 20, 2025
8c49e67
Test gets correct model name now, instead of generic Page
Nov 20, 2025
20df016
Handle 404 in non-HTML requests
Nov 20, 2025
1a79f99
Fix shop route helper
Nov 20, 2025
039bca4
XML test to look for empty page instead of Product Not Found message
Nov 20, 2025
19f0482
Shop product_or_section_path fixes
Nov 24, 2025
cfe3d9d
Merge branch 'main' into feature/shop-product-descriptions
Nov 27, 2025
0e7b538
Add test for same slug in different sections
Dec 1, 2025
73fedee
Merge branch 'main' into feature/shop-product-descriptions
Dec 1, 2025
285818b
Handle products in sections
Dec 2, 2025
8398e04
Handle products in sections
Dec 2, 2025
7e1e8a0
WIP
Dec 2, 2025
d31aad7
Merge branch 'main' into feature/shop-product-descriptions
Dec 3, 2025
88daf61
Stringify model name
Dec 4, 2025
152d4df
Stringify model name
Dec 4, 2025
900475c
Recurse through path to product
Dec 10, 2025
ecb58bf
Refine test for right slug. wrong path
Dec 10, 2025
bb788f0
Fix dupe test name
Dec 10, 2025
da6c965
Split multiline string
Dec 10, 2025
68f6409
Render shop sections only if in right section
Dec 11, 2025
0e99443
WIP/TODO
Dec 15, 2025
6093a7a
Merge branch 'main' into feature/shop-product-descriptions
Jan 7, 2026
89eef25
Merge branch 'main' into feature/shop-product-descriptions
Jan 12, 2026
92567bb
More WIP on the Shopreset
Jan 12, 2026
541c4f6
Check for correct error string
Jan 12, 2026
0857b63
Fix erroneous error messages
Jan 15, 2026
51f9fbd
Clean up handling of product/section 404s
Jan 15, 2026
2601b41
Simplify find_section method a lot
Jan 15, 2026
40ce78a
Start simplifying show method
Jan 20, 2026
43336e2
Continue evolving find_section stuff
Jan 20, 2026
38615dd
Continuing evolution of the find_section and show methods
Jan 20, 2026
5f3a0a8
Merge branch 'main' into feature/shop-product-descriptions
Jan 26, 2026
83264db
Add shop plugin to report
Jan 27, 2026
5c4ce33
Remove a load of probably unused code
Jan 27, 2026
70f4086
Generate a standard coverage report if not running CI
Jan 29, 2026
2514225
Make more product info available in menus
Jan 29, 2026
37d8746
Remove test string from HTML
Jan 29, 2026
edb41d1
Introduce Shop frontend menu
Feb 3, 2026
e3bba36
Remove superfluous method
Feb 5, 2026
bf2a430
WIP on golfing the find_section method
Feb 5, 2026
13c7c4b
Reduce LOC in find_section method
Feb 5, 2026
b7e4565
Add subsection data to exercise subsection code
Feb 10, 2026
05c8fd5
Merge branch 'main' into feature/shop-product-descriptions
Feb 17, 2026
10845da
Test for presence of subsection name
Feb 17, 2026
dab2a29
Merge branch 'main' into feature/shop-product-descriptions
Feb 18, 2026
9f39118
Merge branch 'main' into feature/shop-product-descriptions
Feb 24, 2026
abc1e3a
Remove currently unused helper methods
Mar 3, 2026
5731330
Remove TODO comments to appease CodeFactor
Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubycritic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ paths:
- plugins/ShinyPages/app
- plugins/ShinyProfiles/app
- plugins/ShinySearch/app
- plugins/ShinyShop/app
- plugins/ShinySEO/app
72 changes: 72 additions & 0 deletions db/migrate/20250715133806_add_shop_tables.shiny_shop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

# This migration comes from shiny_shop (originally 20250715130616)
# ShinyShop plugin for ShinyCMS ~ https://shinycms.org
#
# Copyright 2009-2025 Denny de la Haye ~ https://denny.me
#
# ShinyCMS is free software; you can redistribute it and/or modify it under the terms of the GPL (version 2 or later)

class AddShopTables < ActiveRecord::Migration[8.0]
def change
create_table :shiny_shop_templates, force: :cascade do |t|
t.string :name, null: false
t.text :description
t.string :filename, null: false

t.timestamps
t.datetime :deleted_at, index: true
end

create_table :shiny_shop_template_elements, force: :cascade do |t|
t.string :name, null: false
t.string :content
t.string :element_type, default: 'short_text', null: false
t.integer :position

t.belongs_to :template, foreign_key: { to_table: :shiny_shop_templates }, null: false

t.timestamps
t.datetime :deleted_at, index: true
end

create_table :shiny_shop_sections, force: :cascade do |t|
t.string :internal_name, null: false
t.string :public_name
t.string :slug, null: false
t.text :description
t.integer :position
t.boolean :show_in_menus, default: true, null: false
t.boolean :show_on_site, default: true, null: false

t.belongs_to :section, foreign_key: { to_table: :shiny_shop_sections }

t.timestamps
t.datetime :deleted_at, index: true

t.index [:section_id, :slug], name: :index_shop_sections_on_section_id_and_slug, unique: true
end

change_table :shiny_shop_products, force: :cascade do |t|
t.boolean :show_in_menus, default: true, null: false

t.belongs_to :section, foreign_key: { to_table: :shiny_shop_sections }
t.belongs_to :template, foreign_key: { to_table: :shiny_shop_templates }

t.index [:section_id, :slug], name: :index_products_on_section_id_and_slug, unique: true
t.datetime :deleted_at, index: true
end

create_table :shiny_shop_product_elements, force: :cascade do |t|
t.string :name, null: false
t.string :content
t.string :element_type, default: 'short_text', null: false
t.integer :position

t.belongs_to :product, foreign_key: { to_table: :shiny_shop_products }, null: false

t.timestamps
t.datetime :deleted_at, index: true
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

# This migration comes from shiny_shop (originally 20251202131048)
# ShinyShop plugin for ShinyCMS ~ https://shinycms.org
#
# Copyright 2009-2025 Denny de la Haye ~ https://denny.me
#
# ShinyCMS is free software; you can redistribute it and/or modify it under the terms of the GPL (version 2 or later)

class RemoveProductSlugUniqueIndex < ActiveRecord::Migration[8.0]
def change
remove_index :shop_products, :slug, if_exists: true
end
end
69 changes: 67 additions & 2 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# less error prone than running all of your migrations from scratch. Old migrations may fail
# to apply correctly if those migrations use external dependencies or application code.

ActiveRecord::Schema[8.0].define(version: 2025_07_07_134039) do
ActiveRecord::Schema[8.0].define(version: 2025_12_02_133022) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "pg_stat_statements"
Expand Down Expand Up @@ -469,6 +469,19 @@
t.index ["user_id"], name: "index_shiny_profiles_profiles_on_user_id", unique: true
end

create_table "shiny_shop_product_elements", force: :cascade do |t|
t.string "name", null: false
t.string "content"
t.string "element_type", default: "short_text", null: false
t.integer "position"
t.bigint "product_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "deleted_at", precision: nil
t.index ["deleted_at"], name: "index_shiny_shop_product_elements_on_deleted_at"
t.index ["product_id"], name: "index_shiny_shop_product_elements_on_product_id"
end

create_table "shiny_shop_products", force: :cascade do |t|
t.string "internal_name", null: false
t.string "public_name"
Expand All @@ -482,9 +495,56 @@
t.integer "price"
t.boolean "active", default: false, null: false
t.string "stripe_price_id"
t.index ["slug"], name: "index_shiny_shop_products_on_slug", unique: true
t.boolean "show_in_menus", default: true, null: false
t.bigint "section_id"
t.bigint "template_id"
t.datetime "deleted_at", precision: nil
t.index ["deleted_at"], name: "index_shiny_shop_products_on_deleted_at"
t.index ["section_id", "slug"], name: "index_products_on_section_id_and_slug", unique: true
t.index ["section_id"], name: "index_shiny_shop_products_on_section_id"
t.index ["stripe_id"], name: "index_shiny_shop_products_on_stripe_id", unique: true
t.index ["stripe_price_id"], name: "index_shiny_shop_products_on_stripe_price_id", unique: true
t.index ["template_id"], name: "index_shiny_shop_products_on_template_id"
end

create_table "shiny_shop_sections", force: :cascade do |t|
t.string "internal_name", null: false
t.string "public_name"
t.string "slug", null: false
t.text "description"
t.integer "position"
t.boolean "show_in_menus", default: true, null: false
t.boolean "show_on_site", default: true, null: false
t.bigint "section_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "deleted_at", precision: nil
t.index ["deleted_at"], name: "index_shiny_shop_sections_on_deleted_at"
t.index ["section_id", "slug"], name: "index_shop_sections_on_section_id_and_slug", unique: true
t.index ["section_id"], name: "index_shiny_shop_sections_on_section_id"
end

create_table "shiny_shop_template_elements", force: :cascade do |t|
t.string "name", null: false
t.string "content"
t.string "element_type", default: "short_text", null: false
t.integer "position"
t.bigint "template_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "deleted_at", precision: nil
t.index ["deleted_at"], name: "index_shiny_shop_template_elements_on_deleted_at"
t.index ["template_id"], name: "index_shiny_shop_template_elements_on_template_id"
end

create_table "shiny_shop_templates", force: :cascade do |t|
t.string "name", null: false
t.text "description"
t.string "filename", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "deleted_at", precision: nil
t.index ["deleted_at"], name: "index_shiny_shop_templates_on_deleted_at"
end

create_table "shinycms_anonymous_authors", force: :cascade do |t|
Expand Down Expand Up @@ -746,6 +806,11 @@
add_foreign_key "shiny_pages_template_elements", "shiny_pages_templates", column: "template_id"
add_foreign_key "shiny_profiles_links", "shiny_profiles_profiles", column: "profile_id"
add_foreign_key "shiny_profiles_profiles", "shinycms_users", column: "user_id"
add_foreign_key "shiny_shop_product_elements", "shiny_shop_products", column: "product_id"
add_foreign_key "shiny_shop_products", "shiny_shop_sections", column: "section_id"
add_foreign_key "shiny_shop_products", "shiny_shop_templates", column: "template_id"
add_foreign_key "shiny_shop_sections", "shiny_shop_sections", column: "section_id"
add_foreign_key "shiny_shop_template_elements", "shiny_shop_templates", column: "template_id"
add_foreign_key "shinycms_capabilities", "shinycms_capability_categories", column: "category_id"
add_foreign_key "shinycms_comments", "shinycms_comments", column: "parent_id"
add_foreign_key "shinycms_comments", "shinycms_discussions", column: "discussion_id"
Expand Down
1 change: 1 addition & 0 deletions plugins/ShinyCMS/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ Rails/RenderInline:
Exclude:
- ../../app/controllers/application_controller.rb
- ../ShinyPages/app/controllers/shiny_pages/pages_controller.rb
- ../ShinyShop/app/controllers/shiny_shop/products_controller.rb

# Configurable user model
Rails/ReflectionClassName:
Expand Down
14 changes: 14 additions & 0 deletions plugins/ShinyCMS/app/assets/stylesheets/shinycms/admin_area.scss
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ main p {
padding: 0.5em 1em;
}

.button_to {
display: inline;
}


// ========== ( drag-to-sort ) ==========

Expand Down Expand Up @@ -302,6 +306,16 @@ form .ui-sortable-handle {
text-decoration: none;
}

.table-striped .actions button {
background: $input-bg;
border: 1px solid $input-border;
border-radius: 0.2em;
color: $button-text;
height: 1.9em;
padding: 0.2em 0.5em 0.4em;
text-decoration: none;
}

.table-striped .actions a:hover {
background: $input-border;
color: $input-bg;
Expand Down
10 changes: 9 additions & 1 deletion plugins/ShinyCMS/app/controllers/shinycms/errors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ class ErrorsController < ApplicationController
include ShinyCMS::MainSiteControllerBase

def not_found
render status: :not_found
respond_to do |format|
format.html do
@resource_type = request.env[ 'action_dispatch.exception' ]&.model&.demodulize || 'Page'
render status: :not_found
end
format.any do
head :not_found
end
end
end

def internal_server_error
Expand Down
6 changes: 2 additions & 4 deletions plugins/ShinyCMS/app/views/shinycms/errors/not_found.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<% resource_type ||= 'page' %>

<% @page_title = t( '.title', resource_type: resource_type.capitalize ) %>
<% @page_title = t( '.title', resource_type: @resource_type.capitalize ) %>

<section>
<header>
Expand All @@ -10,7 +8,7 @@
</header>

<p>
<%= t( '.explanation', resource_type: resource_type.downcase ) %>
<%= t( '.explanation', resource_type: @resource_type.downcase ) %>
</p>
</section>

Expand Down
5 changes: 5 additions & 0 deletions plugins/ShinyCMS/app/views/shinycms/menu/_menu.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
<% if feature_enabled? :tags %>
<%= render partial: 'menu/tags' %>
<% end %>

<% if plugin_loaded? :ShinyShop %>
<%= render partial: 'shiny_shop/menu/products',
locals: { items: shop_top_level_menu_items } %>
<% end %>
</ul>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<% @page_title = @product.name %>
<h1>
<%= @product.name %>
</h1>
<p>
<%= image_tag url_for( an_image ), class: 'classy' if an_image&.attached? %>
</p>
<h2>
<%= a_heading %>
</h2>

<%= simple_format some_text %>

<%= sanitize html_text %>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
expect( models ).to be_an Enumerable

expect( models.first ).to be ShinyAccess::Group
expect( models.last ).to be ShinyProfiles::Profile
expect( models.last ).to be ShinyShop::TemplateElement
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@

expect( response ).to have_http_status :not_found
expect( response.body ).to have_css 'h2', text: I18n.t(
'shinycms.errors.not_found.title', resource_type: 'Page'
'shinycms.errors.not_found.title', resource_type: 'Discussion'
)
end
end
Expand Down
9 changes: 7 additions & 2 deletions plugins/ShinyCMS/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@

RSpec.configure do |config|
config.before( :suite ) do
# Copy TEST theme templates into place
FileUtils.cp_r 'plugins/ShinyCMS/spec/fixtures/TEST', 'themes/TEST' unless Dir.exist? 'themes/TEST'
# Copy fresh TEST theme templates into place
FileUtils.rm_r 'themes/TEST' if Dir.exist? 'themes/TEST'
FileUtils.cp_r 'plugins/ShinyCMS/spec/fixtures/TEST', 'themes/TEST'
end

# This setting specifies which spec files to load when `rspec` is run without
Expand Down Expand Up @@ -62,6 +63,10 @@
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
[ SimpleCov::Formatter::Codecov, SimpleCov::Formatter::JSONFormatter ]
)
else
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
[ SimpleCov::Formatter::HTMLFormatter ]
)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def create

def edit
authorize @template
# binding.pry
end

def update
Expand Down Expand Up @@ -86,7 +85,6 @@ def stash_new_template
end

def stash_template
# binding.pry
@template = Template.find( params[:id] )
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<% return unless @current_user.can? :list, :products %>
<% return unless @current_user.can?( :list, :products ) ||
@current_user.can?( :list, :product_sections ) ||
@current_user.can?( :list, :product_templates ) %>

<%= component 'admin/menu/section',
text: t( 'shiny_shop.admin.products.menu' ), icon: 'rss' do %>
Expand All @@ -10,4 +12,16 @@
text: t( 'shiny_shop.admin.products.new.title' ), icon: 'pencil',
if: @current_user.can?( :add, :products ) %>

<%= component 'admin/menu/item', link: shiny_shop.new_section_path,
text: t( 'shiny_shop.admin.sections.new.title' ), icon: 'playlist-add',
if: @current_user.can?( :add, :product_sections ) %>

<%= component 'admin/menu/item', link: shiny_shop.templates_path,
text: t( 'shiny_shop.admin.templates.index.title' ), icon: 'library',
if: @current_user.can?( :list, :product_templates ) %>

<%= component 'admin/menu/item', link: shiny_shop.new_template_path,
text: t( 'shiny_shop.admin.templates.new.title' ), icon: 'library-add',
if: @current_user.can?( :add, :product_templates ) %>

<% end %>
Loading