|
4 | 4 | <%= render "shared/navbar" %> |
5 | 5 |
|
6 | 6 | <main class="container"> |
7 | | - <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;"> |
| 7 | + <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;"> |
8 | 8 | <h1 class="page-title" style="margin: 0;">Shop</h1> |
9 | 9 | <a href="/purchases" class="btn btn--secondary">Recent Purchases</a> |
10 | 10 | </div> |
11 | 11 |
|
12 | | - <div style="display: grid; grid-template-columns: 250px 1fr; gap: 2rem;"> |
13 | | - <aside class="shop-sidebar card"> |
14 | | - <h3 style="margin: 0 0 1rem 0; color: #1f2430; font-size: 1.1rem;">Categories</h3> |
15 | | - |
16 | | - <div style="display: flex; flex-direction: column; gap: 0.75rem;"> |
17 | | - <button class="shop-category-btn shop-category-btn--active" data-category="all"> |
18 | | - All Items |
19 | | - </button> |
20 | | - <button class="shop-category-btn" data-category="economic"> |
21 | | - Economic |
22 | | - </button> |
23 | | - <button class="shop-category-btn" data-category="standard"> |
24 | | - Standard |
25 | | - </button> |
26 | | - <button class="shop-category-btn" data-category="quality"> |
27 | | - Quality |
28 | | - </button> |
29 | | - <button class="shop-category-btn" data-category="advanced"> |
30 | | - Advanced |
31 | | - </button> |
32 | | - <button class="shop-category-btn" data-category="professional"> |
33 | | - Professional |
34 | | - </button> |
35 | | - </div> |
| 12 | + <div class="shop-layout"> |
| 13 | + <aside class="shop-categories card"> |
| 14 | + <h3 class="shop-heading">Category</h3> |
| 15 | + <ul class="shop-category-list"> |
| 16 | + <% categories = [ "Keyboard", "Mouse", "Monitor", "Headphones", "Webcam" ] %> |
| 17 | + <% categories.each_with_index do |cat, idx| %> |
| 18 | + <li> |
| 19 | + <button class="shop-category-item <%= 'is-active' if idx == 0 %>" data-target="cat-<%= cat.downcase %>"> |
| 20 | + <%= cat %> |
| 21 | + </button> |
| 22 | + </li> |
| 23 | + <% end %> |
| 24 | + </ul> |
36 | 25 | </aside> |
37 | 26 |
|
38 | | - <section> |
39 | | - <h2 style="margin: 0 0 1.5rem 0; color: #1f2430;">Items</h2> |
| 27 | + <section class="shop-items card"> |
| 28 | + <div class="shop-items__header"> |
| 29 | + <h2 class="shop-heading" style="margin: 0;">Items</h2> |
| 30 | + <div aria-hidden="true" class="shop-cart"> |
| 31 | + <img src="https://files.slack.com/files-pri/T09V59WQY1E-F0A793FGJ2V/bolt.png?pub_secret=7acc4044c5" alt="bolts" class="bolt-icon"> |
| 32 | + </div> |
| 33 | + </div> |
| 34 | + |
| 35 | + <% categories.each_with_index do |cat, idx| %> |
| 36 | + <% item = @shop_items.find { |i| i.name.to_s.downcase.strip == cat.downcase } %> |
| 37 | + <% base_cost = (item&.cost || 0).to_i %> |
| 38 | + <div id="cat-<%= cat.downcase %>" class="shop-item-detail <%= 'is-active' if idx == 0 %>"> |
| 39 | + <div class="shop-item-detail__variants"> |
| 40 | + <% variants = [ |
| 41 | + ['Standard grant', 'standard'], |
| 42 | + ['Quality grant', 'quality'], |
| 43 | + ['Advanced grant', 'advanced'], |
| 44 | + ['Professional grant', 'professional'] |
| 45 | + ] %> |
| 46 | + <% images_for_keyboard = [ |
| 47 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6SJ1AQAH/vecteezy_keyboard-3d-rendering-icon-illustration_28606613.png?pub_secret=9418a400cb', |
| 48 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6SHUUGN9/vecteezy_gaming-keyboard-with-anti-ghosting-technology-transparent_57571865.png?pub_secret=2fe417ad9b', |
| 49 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A7QABPARW/vecteezy_rgb-illuminated-white-gaming-keyboard-top-view_52855142.png?pub_secret=3d3c0544c6', |
| 50 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6EHQKAUF/vecteezy_white-rgb-mechanical-gaming-keyboard-with-cable_52855199.png?pub_secret=3e689005bb' |
| 51 | + ] if cat == 'Keyboard' %> |
| 52 | + <% images_for_mouse = [ |
| 53 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A7QDJR56C/vecteezy_computer-mouse-isolated-on-a-transparent-background_47833235.png?pub_secret=206f7975e4', |
| 54 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6PNNB8HH/vecteezy_ai-generated-computer-mouse-png-isolated-on-transparent_36113136.png?pub_secret=5328baa9cd', |
| 55 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6ZMVP5RA/vecteezy_ai-generated-computer-mouse-png-isolated-on-transparent_36112700.png?pub_secret=4a6941278c', |
| 56 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6ELUDWQP/vecteezy_ai-generated-computer-mouse-png-isolated-on-transparent_36112693.png?pub_secret=8a1b07b884' |
| 57 | + ] if cat == 'Mouse' %> |
| 58 | + <% images_for_monitor = [ |
| 59 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A793DR88Z/10740609.png?pub_secret=edffb80446', |
| 60 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6ZNZN99A/3d-monitor-curved-free-png.webp?pub_secret=9d8649f57c', |
| 61 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6W5FN41Y/vecteezy_3d-monitor-for-office_53739745.png?pub_secret=6a16537f48', |
| 62 | + 'https://files.slack.com/files-pri/T09V59WQY1E-F0A6EN1NVP1/vecteezy_sleek-black-computer-monitor-with-vibrant-red-abstract_56609224.png?pub_secret=5e30e2cd74' |
| 63 | + ] if cat == 'Monitor' %> |
| 64 | + <% variants.each_with_index do |(label_text, key), variant_idx| %> |
| 65 | + <% img_src = ( |
| 66 | + (cat == 'Keyboard' && images_for_keyboard && images_for_keyboard[variant_idx]) || |
| 67 | + (cat == 'Mouse' && images_for_mouse && images_for_mouse[variant_idx]) || |
| 68 | + (cat == 'Monitor' && images_for_monitor && images_for_monitor[variant_idx]) || |
| 69 | + '/images/signin/hackclub.svg' |
| 70 | + ) %> |
| 71 | + <% amount_map = { 'standard' => 50, 'quality' => 110, 'advanced' => 170, 'professional' => 230 } %> |
| 72 | + <% grant_amount = amount_map[key] || 0 %> |
| 73 | + <% bolts_map = { 'standard' => 500, 'quality' => 1100, 'advanced' => 1700, 'professional' => 2300 } %> |
| 74 | + <% base_bolts = bolts_map[key] || 0 %> |
| 75 | + <% price = base_bolts %> |
| 76 | + <% desc_text = "This item provide a #{grant_amount}$ HCB card grant to spend on a #{cat}" %> |
| 77 | + <div class="shop-variant"> |
| 78 | + <div class="shop-variant__thumb"> |
| 79 | + <img src="<%= img_src %>" alt="<%= label_text %>"> |
| 80 | + </div> |
40 | 81 |
|
41 | | - <% if @shop_items.any? %> |
42 | | - <div class="shop-grid"> |
43 | | - <% @shop_items.each do |item| %> |
44 | | - <div class="shop-card card" data-id="<%= item.id %>" data-cost="<%= item.cost %>" data-category="<%= item.status || 'standard' %>"> |
45 | | - <% if item.image_url.present? %> |
46 | | - <img class="shop-card__image" src="<%= item.image_url %>" alt="<%= item.name %>"> |
47 | | - <% else %> |
48 | | - <div class="shop-card__image shop-card__image--placeholder">🪛</div> |
49 | | - <% end %> |
50 | | - |
51 | | - <div class="shop-card__body"> |
52 | | - <h3 class="shop-card__title"><%= item.name.presence || "Unnamed Item" %></h3> |
53 | | - <p class="shop-card__description muted"><%= item.description.presence || "No description" %></p> |
54 | | - |
55 | | - <% item_cost = item.cost || 0 %> |
56 | | - <div class="shop-card__footer"> |
57 | | - <div> |
58 | | - <span class="shop-card__price"><%= item_cost.to_i %> 🪛</span> |
59 | | - <% if item.status.present? %> |
60 | | - <span class="pill" style="background: #e8f0e8; color: #4a7c59; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; margin-left: 0.5rem;"> |
61 | | - <%= item.status.capitalize %> |
62 | | - </span> |
63 | | - <% end %> |
64 | | - </div> |
65 | | - <%= form_with url: purchase_path, method: :post, local: true, class: "purchase-form" do %> |
66 | | - <input type="hidden" name="item_id" value="<%= item.id %>"> |
67 | | - <button type="submit" class="btn btn--primary" |
68 | | - <%= 'disabled' if @current_user.balance < item_cost %>> |
69 | | - <%= @current_user.balance >= item_cost ? 'Purchase' : 'Not enough screws' %> |
70 | | - </button> |
71 | | - <% end %> |
| 82 | + <div class="shop-variant__name"><%= label_text %></div> |
| 83 | + |
| 84 | + <div class="shop-variant__meta"> |
| 85 | + <button type="button" |
| 86 | + class="shop-variant__priceBox price-btn" |
| 87 | + data-item-id="<%= item&.id %>" |
| 88 | + data-name="<%= cat %>" |
| 89 | + data-variant="<%= key %>" |
| 90 | + data-display="<%= label_text %>" |
| 91 | + data-price="<%= price %>" |
| 92 | + data-grant="<%= grant_amount %>" |
| 93 | + data-bolts="<%= base_bolts %>" |
| 94 | + data-img="<%= img_src %>" |
| 95 | + data-desc="<%= desc_text.to_s.gsub('\"','"') %>"> |
| 96 | + <span class="shop-variant__price"><%= price %></span> |
| 97 | + <img src="https://files.slack.com/files-pri/T09V59WQY1E-F0A793FGJ2V/bolt.png?pub_secret=7acc4044c5" alt="bolts" class="bolt-icon"> |
| 98 | + </button> |
| 99 | + <span class="shop-variant__pill shop-variant__pill--<%= key %>"><%= label_text %></span> |
72 | 100 | </div> |
| 101 | + |
| 102 | + <!-- Purchase action moved to modal --> |
73 | 103 | </div> |
74 | | - </div> |
75 | | - <% end %> |
76 | | - </div> |
77 | | - <% else %> |
78 | | - <div class="card" style="text-align: center; padding: 40px;"> |
79 | | - <p class="muted">No items in the shop yet. Check back soon!</p> |
| 104 | + <% end %> |
| 105 | + </div> |
80 | 106 | </div> |
81 | 107 | <% end %> |
82 | 108 | </section> |
83 | 109 | </div> |
| 110 | + <div class="card" style="margin-top: 12px; background: #fff3cd; border: 1px solid #ffeeba; color: #856404; padding: 10px; text-align: center;"> |
| 111 | + Working progress! We're building the shop page, more items will be added soon! |
| 112 | + </div> |
84 | 113 | </main> |
85 | 114 |
|
86 | | -<style> |
87 | | - .shop-sidebar { |
88 | | - height: fit-content; |
89 | | - position: sticky; |
90 | | - top: 20px; |
91 | | - } |
92 | | - |
93 | | - .shop-category-btn { |
94 | | - background: #f5f5f3; |
95 | | - border: none; |
96 | | - color: #1f2430; |
97 | | - padding: 12px 16px; |
98 | | - border-radius: 8px; |
99 | | - cursor: pointer; |
100 | | - font-weight: 600; |
101 | | - font-size: 0.95rem; |
102 | | - transition: all 0.2s ease; |
103 | | - text-align: left; |
104 | | - } |
105 | | - |
106 | | - .shop-category-btn:hover { |
107 | | - background: #efefeb; |
108 | | - } |
109 | | - |
110 | | - .shop-category-btn--active { |
111 | | - background: #d6a161; |
112 | | - color: white; |
113 | | - } |
114 | | - |
115 | | - .shop-grid { |
116 | | - display: grid; |
117 | | - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |
118 | | - gap: 1.5rem; |
119 | | - } |
120 | | - |
121 | | - .shop-card { |
122 | | - padding: 0; |
123 | | - overflow: hidden; |
124 | | - } |
125 | | - |
126 | | - .shop-card__image { |
127 | | - width: 100%; |
128 | | - height: 200px; |
129 | | - object-fit: cover; |
130 | | - } |
131 | | - |
132 | | - .shop-card__image--placeholder { |
133 | | - width: 100%; |
134 | | - height: 200px; |
135 | | - display: flex; |
136 | | - align-items: center; |
137 | | - justify-content: center; |
138 | | - background: #f0f0f0; |
139 | | - font-size: 4rem; |
140 | | - } |
141 | | - |
142 | | - .shop-card__body { |
143 | | - padding: 1rem; |
144 | | - } |
145 | | - |
146 | | - .shop-card__title { |
147 | | - margin: 0 0 0.5rem 0; |
148 | | - font-size: 1rem; |
149 | | - color: #1f2430; |
150 | | - } |
151 | | - |
152 | | - .shop-card__description { |
153 | | - margin: 0 0 1rem 0; |
154 | | - font-size: 0.9rem; |
155 | | - line-height: 1.4; |
156 | | - } |
157 | | - |
158 | | - .shop-card__footer { |
159 | | - display: flex; |
160 | | - flex-direction: column; |
161 | | - gap: 0.75rem; |
162 | | - margin-top: 1rem; |
163 | | - padding-top: 1rem; |
164 | | - border-top: 1px solid #f0f0f0; |
165 | | - } |
166 | | - |
167 | | - .shop-card__price { |
168 | | - font-weight: 700; |
169 | | - color: #f25f2c; |
170 | | - font-size: 1.1rem; |
171 | | - } |
172 | | - |
173 | | - .purchase-form { |
174 | | - display: contents; |
175 | | - } |
176 | | - |
177 | | - @media (max-width: 768px) { |
178 | | - main.container { |
179 | | - display: block !important; |
180 | | - } |
181 | | - |
182 | | - .shop-sidebar { |
183 | | - display: grid; |
184 | | - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); |
185 | | - gap: 0.5rem; |
186 | | - position: static; |
187 | | - margin-bottom: 2rem; |
188 | | - } |
189 | | - } |
190 | | -</style> |
191 | | - |
192 | | -<script> |
193 | | - const categoryBtns = document.querySelectorAll('.shop-category-btn'); |
194 | | - const shopCards = document.querySelectorAll('.shop-card'); |
195 | | - |
196 | | - categoryBtns.forEach(btn => { |
197 | | - btn.addEventListener('click', () => { |
198 | | - categoryBtns.forEach(b => b.classList.remove('shop-category-btn--active')); |
199 | | - btn.classList.add('shop-category-btn--active'); |
200 | | - |
201 | | - const selectedCategory = btn.dataset.category; |
202 | | - |
203 | | - shopCards.forEach(card => { |
204 | | - const cardCategory = card.dataset.category; |
205 | | - if (selectedCategory === 'all' || cardCategory === selectedCategory) { |
206 | | - card.style.display = 'block'; |
207 | | - } else { |
208 | | - card.style.display = 'none'; |
209 | | - } |
210 | | - }); |
211 | | - }); |
212 | | - }); |
213 | | -</script> |
| 115 | +<!-- Item Modal --> |
| 116 | +<div id="shop-item-modal" class="modal modal--hidden" data-user-balance="<%= @current_user.balance.to_i %>"> |
| 117 | + <div class="modal__backdrop"></div> |
| 118 | + <div class="modal__content card"> |
| 119 | + <div class="modal__header"> |
| 120 | + <h3 id="shop-modal-title" class="modal__title">Item</h3> |
| 121 | + <button type="button" class="modal__close" aria-label="Close">×</button> |
| 122 | + </div> |
| 123 | + <div class="shop-modal__body"> |
| 124 | + <img id="shop-modal-image" class="shop-modal__image" src="/images/signin/hackclub.svg" alt="item"> |
| 125 | + <p id="shop-modal-desc" class="shop-modal__desc muted">Description</p> |
| 126 | + <div class="shop-modal__qty" style="display:flex;align-items:center;gap:8px;"> |
| 127 | + <label for="shop-modal-qty" class="muted">Quantity</label> |
| 128 | + <input id="shop-modal-qty" type="number" min="1" step="1" value="1" style="width:80px;"> |
| 129 | + </div> |
| 130 | + <div class="shop-modal__price"> |
| 131 | + <span id="shop-modal-price">0</span> |
| 132 | + <img src="https://files.slack.com/files-pri/T09V59WQY1E-F0A793FGJ2V/bolt.png?pub_secret=7acc4044c5" alt="bolts" class="bolt-icon"> |
| 133 | + </div> |
| 134 | + <div id="shop-modal-warning" style="display:none;background:#fdecea;border:1px solid #f5c2c7;color:#842029;padding:8px 12px;border-radius:8px;"> |
| 135 | + Not enough bolts to purchase. You need <strong><span id="shop-modal-shortage">0</span></strong> more. |
| 136 | + </div> |
| 137 | + <%= form_with url: purchase_path, method: :post, local: true, id: "shop-modal-form" do %> |
| 138 | + <input type="hidden" name="item_id" id="shop-modal-item-id" value=""> |
| 139 | + <input type="hidden" name="variant" id="shop-modal-variant" value=""> |
| 140 | + <button id="shop-modal-buy" type="submit" class="btn btn--primary btn--block">Purchase</button> |
| 141 | + <% end %> |
| 142 | + </div> |
| 143 | + </div> |
| 144 | + <style> |
| 145 | + .shop-modal__body { display: grid; gap: 12px; } |
| 146 | + .shop-modal__image { width: 100%; height: 180px; border-radius: 10px; object-fit: cover; background: var(--paper-alt); } |
| 147 | + .shop-modal__price { display: inline-flex; align-items: center; gap: 8px; background: #efd1a4; padding: 8px 12px; border-radius: 12px; width: fit-content; } |
| 148 | + </style> |
| 149 | +</div> |
0 commit comments