# CC Soccer D11 - Session Handoff
**Date:** March 7, 2026
**Session:** Player discount system + duplicate cart messages fix
**Branch:** `main`

## Last Updated
2026-03-07

## Current State
- All core functionality working
- Player discount system fully implemented (admin UI + checkout integration)
- Discount applies before credits during checkout; 100% discount orders complete at $0
- Duplicate "added to cart" status messages fixed

---

## ⚠️ Andrew: What You Need To Do

```bash
cd ~/public_html/test_ccsoccer_site
git pull
PATH=/opt/cpanel/ea-php83/root/usr/bin:$PATH /opt/cpanel/ea-php83/root/usr/bin/php vendor/drush/drush/drush.php -r web cr
```

**Schema changes:** No — `field_discount_percent` already exists on user entity from earlier migration/install hooks.

---

## What Was Done This Session

### 1. Player Discount Column on Admin Pages

**Problem:** The old site had a "Discount" column on the Players page allowing admins to assign 0-100% discounts to board members and helpful players. The new site was missing this column even though `field_discount_percent` already existed on the User entity.

**Solution:** Added an inline-editable Discount column to both player admin pages, following the existing Skill Level AJAX pattern:
- Season Players page (`/admin/ccsoccer/season/{id}/players`) — `SeasonController::players()`
- All Players page (`/admin/ccsoccer/players`) — `PlayerAdminController::listPlayers()`
- New AJAX save endpoint `PlayerSkillController::saveDiscount()` at route `ccsoccer.player_discount_save`
- JS behavior `Drupal.behaviors.playerDiscount` in `player-skill.js`
- CSS styles for discount input in `player-skill.css`

**Usage:** Admin enters a number 0-100 in the Discount column. 100 = board members (free), 50 = helpful players, 0 = default (no discount).

### 2. Discount Applied During Checkout

**Problem:** The discount field value needed to actually reduce prices during Commerce checkout.

**Solution:** Created `DiscountOrderProcessor` implementing `OrderProcessorInterface`:
- Runs automatically during order refresh (priority 100, before credits)
- Reads customer's `field_discount_percent`, calculates percentage of subtotal
- Adds a locked promotion `Adjustment` with source_id `player_discount`
- Label shows "Player Discount (X%)" in order summary
- Updated `CreditsPane` to subtract discount before calculating max credits available
- Added discount info display in the credits checkout pane

**Order of operations:** Subtotal → Player Discount → Credits → Payment

### 3. Fix: 100% Discount Users Couldn't Proceed Past Credits Step

**Problem:** Users with 100% discount who also had credits could not click "Continue to Review" — the button appeared to do nothing.

**Root cause:** With 100% discount, `getOrderSubtotalCents()` returned 0. The `credits_amount` field had `#min => 0.01` and `#max => 0`, creating an impossible HTML5 constraint. The browser's native form validation silently blocked submission even though the field was hidden via Drupal's `#states` API.

**Fix:** Added early return in `CreditsPane::buildPaneForm()` when post-discount amount is $0. Sets credits to 'keep' action and skips the credits form entirely.

### 4. Fix: Duplicate "Added to Cart" Status Messages

**Problem:** When adding items to cart, two status messages appeared (e.g., "Coed Winter 2026 added to your cart" AND "Added Coed Winter 2026 to your cart").

**Root cause:** Commerce's built-in `CartEventSubscriber` automatically displays an "added to cart" message whenever `CartManager::addEntity()` is called. Custom ccsoccer code was also adding its own message in the same flow.

**Fix:** Removed redundant custom messages from 6 locations across 5 files. Commerce's default message is now the only one shown.

---

## Previous Session (March 4, 2026)

### Home Page League/Tournament Cards — Images Added

Added `picture` field to Tournament entity, moved cover images to theme folder, created update hooks 9056 + 9057 to install field and attach images, updated `homePage()` to dynamically load Tournament entity.

---

## Files Modified This Session

### New Files
| File | Purpose |
|------|---------|
| `web/modules/custom/ccsoccer/src/Commerce/DiscountOrderProcessor.php` | Commerce OrderProcessor — applies player discount as locked promotion adjustment during order refresh |

### Modified Files
| File | Changes |
|------|---------|
| `web/modules/custom/ccsoccer/ccsoccer.services.yml` | Added `ccsoccer.discount_order_processor` service (priority 100, tagged `commerce_order.order_processor`) |
| `web/modules/custom/ccsoccer/ccsoccer.routing.yml` | Added route `ccsoccer.player_discount_save` at `/admin/ccsoccer/player/discount/save` |
| `web/modules/custom/ccsoccer/src/Controller/PlayerSkillController.php` | Added `saveDiscount()` AJAX endpoint — validates 0-100, saves `field_discount_percent` on user entity |
| `web/modules/custom/ccsoccer/src/Controller/SeasonController.php` | Added Discount column (header + inline input) and drupalSettings URL to `players()` method |
| `web/modules/custom/ccsoccer/src/Controller/PlayerAdminController.php` | Added Discount column (header + inline input) and drupalSettings URL to `listPlayers()` method |
| `web/modules/custom/ccsoccer/js/player-skill.js` | Added `Drupal.behaviors.playerDiscount` — AJAX save on blur/Enter, client-side validation, toast notifications |
| `web/modules/custom/ccsoccer/css/player-skill.css` | Added discount input styles, success/error states, spinner hiding, save status indicator |
| `web/modules/custom/ccsoccer/src/Plugin/Commerce/CheckoutPane/CreditsPane.php` | Early return for $0 post-discount orders; `getOrderSubtotalCents()` subtracts discount; `getPlayerDiscountInfo()` helper; discount info display |
| `web/modules/custom/ccsoccer/src/Controller/RegistrationController.php` | Removed duplicate "Added to cart" messages for season and tournament (Commerce handles this) |
| `web/modules/custom/ccsoccer/src/Controller/ContentController.php` | Removed duplicate "added to cart" message for jersey purchase |
| `web/modules/custom/ccsoccer/src/Plugin/Commerce/CheckoutPane/PlayerInfoPane.php` | Removed duplicate "Jersey added to your order" message |
| `web/modules/custom/ccsoccer/src/Plugin/Commerce/CheckoutPane/JerseySelectionPane.php` | Removed duplicate "Jersey added to your cart" message |
| `web/modules/custom/ccsoccer/src/Form/RegistrationSelectForm.php` | Removed duplicate "Added to cart" messages for season/tournament selection form |

---

## Remaining Work

### Recently Completed
- [x] Player discount column on both admin player pages (inline AJAX editing)
- [x] Discount applied during Commerce checkout (before credits)
- [x] 100% discount checkout flow working ($0 orders complete successfully)
- [x] Duplicate "added to cart" status messages fixed

### Content
- [ ] Add description to 2026 Summer Cup tournament entity (`/admin/ccsoccer/tournaments`)

### Inner Page Styling
- [ ] My Orders page
- [ ] My Registrations page
- [ ] Credits page
- [ ] Purchase Jerseys page
- [ ] User profile / edit form
- [ ] Group management page

### Forms
- [ ] Registration form inputs, buttons, visual styling
- [ ] User edit form styling

### Navigation / Mobile
- [ ] Re-add Tournament Schedule to main nav

### Notifications
- [ ] "Don't send to already registered" logic
- [ ] Automated reminders (6/4/2/1 week intervals)

### Deployment Prep
- [ ] Self-host Inter font
- [ ] Re-enable CSS/JS aggregation
- [ ] Final mobile/browser testing
- [ ] Remove IP whitelist block from production `.htaccess`
- [ ] Decide canonical domain (www vs non-www) and uncomment the appropriate redirect rule in `.htaccess`
- [ ] Confirm HTTPS redirect is handled at host level (InMotion), or add RewriteCond to `.htaccess`

### Small Items
- [ ] Breadcrumbs: custom builder for full trails on custom routes
- [ ] Game status: only show ON/CANCELLED after 3pm
- [ ] Contact page — does /contact exist? Footer links to it
- [ ] Social links — Facebook/Instagram URLs are placeholder (#)
- [ ] Hero width mismatch (doesn't go full bleed)
- [ ] Password reset flow for migrated users

---

## Test Server .htaccess (IP Whitelist)

The test server `.htaccess` has an IP whitelist block that is **not in git**.
It's protected from being overwritten by pulls via `skip-worktree`.

If the whitelist is ever lost again, paste this near the top of `web/.htaccess`
(before the `<FilesMatch>` block), then re-run the skip-worktree command:

```apache
# IP Whitelist - Test server only (DO NOT commit to git)
# Note: inline comments after IPs cause 500 errors - keep IPs on one line, no comments
# Note: <RequireAny> container causes 500 on InMotion shared hosting - use single line
# Caleb/Layne: 68.249.41.9 | Andrew: 35.151.50.130 | Dave: 99.8.107.54 | Haley: 97.84.70.141
Require ip 68.249.41.9 35.151.50.130 99.8.107.54 97.84.70.141

# Prevent search engine indexing - Test server only
<IfModule mod_headers.c>
  Header set X-Robots-Tag "noindex, nofollow, noarchive"
</IfModule>
```

```bash
# Run once after editing - prevents git pull from overwriting
git update-index --skip-worktree web/.htaccess

# Verify (should show lowercase 's' prefix)
git ls-files -v web/.htaccess

# To temporarily undo (e.g. to pull a legit .htaccess change)
git update-index --no-skip-worktree web/.htaccess
```

---

## Server Quick Reference
```bash
cd ~/public_html/test_ccsoccer_site
git pull
PATH=/opt/cpanel/ea-php83/root/usr/bin:$PATH /opt/cpanel/ea-php83/root/usr/bin/php vendor/drush/drush/drush.php -r web updb -y
PATH=/opt/cpanel/ea-php83/root/usr/bin:$PATH /opt/cpanel/ea-php83/root/usr/bin/php vendor/drush/drush/drush.php -r web cr
```

## Git Workflow
- Always `git pull` before `git push` — Andrew may have pushed changes
- `main` is the primary branch
- `settings.local.php` is NOT in git — never commit it, manage per-environment manually
- **Before running `drush cex`:** check diff to avoid reverting config changes from other contributors
