Skip to content

Commit a3bd0b0

Browse files
Merge pull request #27 from hackclub/fix-shop-ordering
2 parents ba8b1eb + 9f7b792 commit a3bd0b0

File tree

3 files changed

+58
-18
lines changed

3 files changed

+58
-18
lines changed

app/controllers/pages_controller.rb

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,32 +109,61 @@ def shop
109109
end
110110

111111
# POST /shop/purchase
112-
# Purchases a shop item. Requires authentication.
112+
# Purchases a shop item or grant variant. Requires authentication.
113+
# Supports both database-backed items (item_id) and virtual grant variants (category + variant).
113114
def purchase
114115
require_auth or return
115116

116-
item = ShopItem.where(status: [ "active", "in stock", "stock", nil, "" ]).find_by(id: params[:item_id])
117+
# Try to find a database-backed item first
118+
item = ShopItem.where(status: [ "active", "in stock", "stock", nil, "" ]).find_by(id: params[:item_id]) if params[:item_id].present?
117119

118-
unless item
119-
redirect_to shop_path, flash: { error: "Item not found" }
120-
return
120+
if item
121+
# Database-backed item purchase
122+
cost = item.cost || 0
123+
name = item.name
124+
shop_item = item
125+
else
126+
# Virtual grant variant purchase (category + variant tier)
127+
category = params[:category].to_s.strip
128+
variant = params[:variant].to_s.strip.downcase
129+
quantity = [ params[:quantity].to_i, 1 ].max
130+
131+
valid_categories = %w[keyboard mouse monitor headphones webcam]
132+
bolts_map = { "standard" => 500, "quality" => 1100, "advanced" => 1700, "professional" => 2300 }
133+
grant_map = { "standard" => 50, "quality" => 110, "advanced" => 170, "professional" => 230 }
134+
135+
unless valid_categories.include?(category.downcase) && bolts_map.key?(variant)
136+
redirect_to shop_path, flash: { error: "Invalid item selection" }
137+
return
138+
end
139+
140+
cost = bolts_map[variant] * quantity
141+
grant_amount = grant_map[variant] * quantity
142+
name = "#{category.capitalize} #{variant.capitalize} Grant (#{quantity}x) - $#{grant_amount} HCB"
143+
144+
# Find or create a placeholder ShopItem for grant orders
145+
shop_item = ShopItem.find_or_create_by!(name: "Grant Order Placeholder") do |si|
146+
si.cost = 0
147+
si.status = "system"
148+
si.description = "System placeholder for grant-based orders"
149+
end
121150
end
122151

123-
if @current_user.balance < (item.cost || 0)
152+
if @current_user.balance < cost
124153
redirect_to shop_path, flash: { error: "Not enough bolts!" }
125154
return
126155
end
127156

128157
ActiveRecord::Base.transaction do
129-
@current_user.update!(balance: @current_user.balance - item.cost)
158+
@current_user.update!(balance: @current_user.balance - cost)
130159
@current_user.shop_orders.create!(
131-
shop_item: item,
132-
name: item.name,
160+
shop_item: shop_item,
161+
name: name,
133162
status: "pending"
134163
)
135164
end
136165

137-
redirect_to shop_path, flash: { success: "Purchased #{item.name}!" }
166+
redirect_to shop_path, flash: { success: "Purchased #{name}!" }
138167
rescue ActiveRecord::RecordInvalid => e
139168
redirect_to shop_path, flash: { error: "Purchase failed: #{e.message}" }
140169
end

app/views/pages/shop.html.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@
136136
</div>
137137
<%= form_with url: purchase_path, method: :post, local: true, id: "shop-modal-form" do %>
138138
<input type="hidden" name="item_id" id="shop-modal-item-id" value="">
139+
<input type="hidden" name="category" id="shop-modal-category" value="">
139140
<input type="hidden" name="variant" id="shop-modal-variant" value="">
141+
<input type="hidden" name="quantity" id="shop-modal-quantity" value="1">
140142
<button id="shop-modal-buy" type="submit" class="btn btn--primary btn--block">Purchase</button>
141143
<% end %>
142144
</div>

public/javascripts/application.js

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@
8989
const modalShortage = document.getElementById("shop-modal-shortage");
9090
const modalForm = document.getElementById("shop-modal-form");
9191
const modalItemId = document.getElementById("shop-modal-item-id");
92+
const modalCategory = document.getElementById("shop-modal-category");
9293
const modalVariant = document.getElementById("shop-modal-variant");
94+
const modalQuantity = document.getElementById("shop-modal-quantity");
9395
const modalBuy = document.getElementById("shop-modal-buy");
9496
const userBalance = Number(modal?.dataset.userBalance || 0);
9597

@@ -109,12 +111,16 @@
109111
const name = modal.dataset.itemName || "Item";
110112
const qty = Math.max(1, Number(modalQty?.value || 1));
111113
const totalBolts = baseBolts * qty;
114+
const totalGrant = grant * qty;
112115
if (modalPrice) modalPrice.textContent = String(totalBolts);
113116
if (modalDesc) {
114-
modalDesc.textContent = `This item provide a ${grant}$ HCB card grant to spend on a ${name}. Quantity: ${qty}.`;
117+
modalDesc.textContent = `This provides a $${totalGrant} HCB card grant to spend on a ${name}. Quantity: ${qty}.`;
115118
}
116-
// enable/disable buy based on computed total bolts
117-
const canBuy = Boolean(modalItemId?.value) && userBalance >= totalBolts;
119+
// Sync quantity to hidden form field
120+
if (modalQuantity) modalQuantity.value = String(qty);
121+
// Enable buy if user has enough balance (category/variant are always set for grant items)
122+
const hasValidSelection = Boolean(modalCategory?.value) || Boolean(modalItemId?.value);
123+
const canBuy = hasValidSelection && userBalance >= totalBolts;
118124
if (modalBuy) modalBuy.disabled = !canBuy;
119125
if (modalWarning) {
120126
const shortage = Math.max(0, totalBolts - userBalance);
@@ -129,16 +135,19 @@
129135
const display = data.display || (data.name ? `${data.name}_${variantLower}` : 'Item');
130136
modalTitle.textContent = display;
131137
modalImage.src = data.img || "/images/signin/hackclub.svg";
132-
// store computation inputs on modal dataset
138+
// Store computation inputs on modal dataset
133139
modal.dataset.baseBolts = String(Number(data.bolts || 0));
134140
modal.dataset.grant = String(Number(data.grant || 0));
135141
modal.dataset.itemName = data.name || "Item";
136-
// reset qty to 1 each open
142+
// Reset qty to 1 each open
137143
if (modalQty) modalQty.value = "1";
138-
// compute totals, description, and buy state
144+
// Set hidden form fields
145+
if (modalItemId) modalItemId.value = data.itemId || "";
146+
if (modalCategory) modalCategory.value = data.name || "";
147+
if (modalVariant) modalVariant.value = data.variant || "";
148+
if (modalQuantity) modalQuantity.value = "1";
149+
// Compute totals, description, and buy state
139150
updateModalTotals();
140-
modalItemId.value = data.itemId || "";
141-
modalVariant.value = data.variant || "";
142151
modal.classList.remove("modal--hidden");
143152
}
144153

0 commit comments

Comments
 (0)