# CC Soccer D11 - Session Handoff
**Date:** March 20, 2026
**Session:** Teams nav + account menu reorder
**Branch:** `main`

## Last Updated
2026-03-20 (session 6 — Teams nav + account menu)

## Current State

### Completed this session (March 20, 2026)

#### Teams — nav + access
- Route changed from `_permission: 'view reports'` (board-only) to `_access: 'TRUE'` (public route)
- Anonymous users hitting `/teams` get redirected to login with `?destination=/teams` so they land there after logging in
- Controller `teamsPage()` handles the redirect: `$this->redirect('user.login', [], ['query' => $destination])`
- Teams link now appears in main nav header for all users (was suppressed by Drupal's access check at render time)
- Teams link added to footer Quick Links (hardcoded in `page.html.twig`, always visible)
- Files: `ccsoccer.routing.yml`, `src/Controller/ContentController.php`, `themes/ccsoccer_theme/templates/page.html.twig`

#### Account menu — reorder + cleanup
- Removed `My Credits` from account menu entirely
- New order: My Profile (1), My Registrations (2), My Orders (3), My Team (4), My Schedule (5), My Cart (6), Purchase Jerseys (7), [separator], My Tournament Team (20), My Tournament Schedule (21), [separator], Log Out (99)
- Tournament items pushed to weight 20/21 to create a logical bottom section
- Log Out pushed to weight 99 via `hook_menu_links_discovered_alter()` so it always renders last (core default was 10, which put it before tournament items)
- Tournament section gets a visual `border-top` separator in CSS; adjacent sibling rule removes duplicate border when both tournament items appear together
- File: `ccsoccer.links.menu.yml`, `ccsoccer.module`, `themes/ccsoccer_theme/css/navigation.css`

#### League defaults → Season form
- Added `minimum_age` and `max_players` fields to League entity (`League.php`)
- Removed `setDefaultValue()` and `setRequired()` from both fields on Season — they stay blank until league populates them
- `SeasonForm::buildForm()` embeds all league field values into `drupalSettings.ccsoccer.leagueDefaults` (new seasons only)
- `season-form.js` Drupal behavior listens on `select[name="league"]` and both date inputs; populates fields on league change, calculates `price_per_game × weeks` when both dates are present
- Update hooks: 9058 (minimum_age DB column), 9059 (max_players DB column), 9060 (registers both with Drupal's entity field manager via `installFieldStorageDefinition` — required for saves to work)
- Files: `src/Entity/League.php`, `src/Entity/Season.php`, `ccsoccer.install`, `src/Form/SeasonForm.php`, `js/season-form.js`, `ccsoccer.libraries.yml`
- Deploy: requires `drush updb -y` (runs 9058–9060) then `drush cr`
- After deploy: edit each league to set `minimum_age` and `max_players` values
- Additional fixes after initial testing:
  - Price calc was undercounting by 1 week (ceil treated end date as exclusive); fixed to `floor + 1` so both start and end game dates count
  - Date change not recalculating on edit form — `isNew()` guard removed from drupalSettings/library attach so JS loads on both new and edit forms
  - JS file was being written to Claude's environment instead of local disk on initial creation — fixed via `Filesystem:write_file`

#### Footer cleanup
- Social column (Follow Us / Facebook / Instagram) removed from markup
- Footer reflowed from 4-column to 3-column grid (`1.5fr 1fr 1fr`)
- Email corrected: `info@ccsoccer.com` → `ccsoccer@ccsoccer.com` in footer template
- `system.site.yml` system mail: `admin@example.com` → `ccsoccer@ccsoccer.com`
- `contact.form.feedback.yml` recipient: `admin@example.com` → `ccsoccer@ccsoccer.com`
- Note: config changes require `drush cim` on deploy, not just `drush cr`
- Files: `page.html.twig`, `layout.css`, `config/sync/system.site.yml`, `config/sync/contact.form.feedback.yml`

#### Account menu mobile — Log out separator
- Mobile block had `border-top: none` on `li:last-child` explicitly removing the separator that works fine on desktop
- Fixed to match desktop: `border-top: 1px solid var(--border-color)` with matching margin/padding
- File: `themes/ccsoccer_theme/css/navigation.css`

---

### Completed previous session (March 18, 2026)

#### Mobile fixes — 5 issues

**1. Manage Group — Pending invites table overflow**
- Both `.roster-table` and `.invitations-table` now collapse to labeled card rows at ≤640px
- `data-label` attributes added to all `<td>` elements in `ccsoccer-group-manage.html.twig`
- Files: `css/group-management.css`, `templates/ccsoccer-group-manage.html.twig`

**2. Order detail — long product name pushes wide**
- Added `overflow-wrap: break-word` + `word-break: break-word` + `min-width: 0` to `.order-information table.cols-4 td.views-field-purchased-entity`
- File: `themes/ccsoccer_theme/css/user-pages.css`

**3. Cart — line details push wide**
- Same word-break fix on `.view-commerce-cart-form .views-form td.views-field-purchased-entity`
- File: `css/checkout.css`

**4. Register button invisible on mobile when on /register page**
- `.is-active` class (same specificity, later in source) was winning with `background: none`
- Fix: re-assert `background: var(--color-primary) !important` for both normal and `.is-active` states in mobile block
- File: `themes/ccsoccer_theme/css/navigation.css`

**5. My Account not tappable on mobile**
- `pointer-events: none` was left on the first account menu item from desktop dropdown logic
- Removed it in the mobile block
- File: `themes/ccsoccer_theme/css/navigation.css`

#### My Account link → /my-account
- Added override at top of existing `ccsoccer_menu_links_discovered_alter()` to redirect `user.account_menu_link` from `/user/{uid}` to `ccsoccer.my_account` (`/my-account`)
- File: `ccsoccer.module`

#### Order detail — Back to My Orders button
- `ccsoccer_preprocess_page()` injects `back_to_orders_url` variable on `entity.commerce_order.user_view` route
- `page.html.twig` renders a `button--primary-soft` back link before `page.content` when variable is set
- Files: `ccsoccer.module`, `themes/ccsoccer_theme/templates/page.html.twig`, `themes/ccsoccer_theme/css/user-pages.css`

#### Cart — hide Quantity and Total columns on mobile
- CSS selector couldn't match because Commerce uses Drupal Views to render the cart table; the `.views-form` wrapper we were targeting doesn't exist in the final rendered DOM (Views builds it differently at runtime). Order detail page works fine because it's a plain PHP template with no Views layer.
- Handled in `cart.js` instead: anchors `once` on the `<table>` element itself, then hides `th`/`td` for `views-field-edit-quantity` and `views-field-total-price__number`
- Threshold set to `window.innerWidth <= 768` — Safari was reporting a width just over 600px, causing the 600px threshold to miss
- Total column hidden because it duplicates Price; the subtotals block below already shows totals
- File: `web/modules/custom/ccsoccer/js/cart.js`

---

### Completed previous session (March 16, 2026 — Andrew: security hardening)

#### Available Registrations page (`/register`) — four fixes

**1. Page title / section heading text**
- Route title: `Register for Season` → `Available Registrations`
- Section heading: `Available Seasons` → `Leagues`
- Section heading: `Available Tournaments` → `Tournaments`

**2. Cache tags — new seasons/tournaments now show without manual cache clear**
- Root cause: cache tags were built only from already-loaded entities, so a brand-new season had no tag to trigger invalidation
- Fix: added `entity_list:season` and `entity_list:tournament` to `$build['#cache']['tags']`
- Drupal automatically invalidates these list tags whenever any entity of that type is created/updated/deleted

**3. Empty state message**
- When no seasons or tournaments are visible to the current user, shows: "There are no registrations available at this time. Check back soon!"
- Filter-aware: when a filter pill (Coed / Men's / Tournaments) is active, empty state checks only the filtered available arrays — having registrations in other categories doesn't suppress the message

**4. Admin eligibility bypass in `userMeetsSeasonRequirements()`**
- Root cause: admins/board members without a fully completed profile (e.g. gender not set) were having men's 35+ seasons silently hidden as `ineligible`
- Fix: users with `administer ccsoccer` or `manage seasons` permission now skip age/gender checks entirely
- Regular players are still subject to normal eligibility filtering

---

## Files Changed This Session (March 20)
- `web/modules/custom/ccsoccer/ccsoccer.routing.yml` (Teams route: board-only → public)
- `web/modules/custom/ccsoccer/ccsoccer.links.menu.yml` (account menu reorder, My Credits removed, Log Out weight)
- `web/modules/custom/ccsoccer/ccsoccer.module` (Log Out weight 99 in hook_menu_links_discovered_alter)
- `web/modules/custom/ccsoccer/src/Controller/ContentController.php` (teamsPage: anon → login redirect)
- `web/themes/custom/ccsoccer_theme/css/navigation.css` (tournament section separator + mobile log out fix)
- `web/themes/custom/ccsoccer_theme/css/layout.css` (footer 4-col → 3-col grid, remove social selectors)
- `web/themes/custom/ccsoccer_theme/templates/page.html.twig` (Teams in footer, remove social, fix email)
- `config/sync/system.site.yml` (mail: ccsoccer@ccsoccer.com)
- `config/sync/contact.form.feedback.yml` (recipient: ccsoccer@ccsoccer.com)
- `web/modules/custom/ccsoccer/src/Entity/League.php` (minimum_age + max_players fields added)
- `web/modules/custom/ccsoccer/src/Entity/Season.php` (removed setDefaultValue/setRequired from minimum_age + max_players)
- `web/modules/custom/ccsoccer/ccsoccer.install` (update hooks 9058, 9059, 9060)
- `web/modules/custom/ccsoccer/src/Form/SeasonForm.php` (drupalSettings league defaults, removed AJAX approach)
- `web/modules/custom/ccsoccer/js/season-form.js` (new — client-side league defaults behavior)
- `web/modules/custom/ccsoccer/ccsoccer.libraries.yml` (season-form library added)

## Files Changed Previous Session (March 18)
- `web/modules/custom/ccsoccer/ccsoccer.module` (menu link override, preprocess_page back button)
- `web/modules/custom/ccsoccer/css/group-management.css` (mobile card layout)
- `web/modules/custom/ccsoccer/css/checkout.css` (word-break on cart item column)
- `web/modules/custom/ccsoccer/js/cart.js` (hide quantity + total columns on mobile via JS)
- `web/modules/custom/ccsoccer/templates/ccsoccer-group-manage.html.twig` (data-label attributes)
- `web/themes/custom/ccsoccer_theme/css/navigation.css` (register button active fix, pointer-events fix)
- `web/themes/custom/ccsoccer_theme/css/user-pages.css` (word-break on order item column, page-back-nav)
- `web/themes/custom/ccsoccer_theme/templates/page.html.twig` (back to orders button)

---

## Deploy Notes
Standard deploy — no new PHP classes, no composer changes needed:
```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
```

---

## Remaining Work

### Page cleanup in progress
- [ ] My Profile edit page — field/layout tweaks (jersey size review, width/layout)
- [ ] Address book page — CSS styling
- [ ] Payment methods page — CSS styling
- [ ] Credits (Admin) page — dark table header needs token-based styling
- [ ] Order detail page — further polish (time format, quantity shows 1.00 not 1)
- [ ] Purchase Jerseys — node edit (remove duplicate h2, add jersey set description + photo)
- [ ] Product catalog exploration — assess Commerce out-of-box before customizing

### Width/layout pass (batch fix)
Pages that are full-width and should be ~60% constrained:
- [ ] User edit form
- [ ] Address book
- [ ] Payment methods
- [ ] Order detail (partially done — customer-information/order-information constrained)

### Button methodology pass (after all pages on custom theme)
- Formalize 3-tier system: primary red / primary-soft navigation / white informational
- Audit all button usages across templates and CSS files

### Content
- [ ] Add description to 2026 Summer Cup tournament entity

### Inner Page Styling
- [ ] Credits page (player)
- [ ] Purchase Jerseys page

### Forms
- [ ] Registration form inputs, buttons, visual 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)

### Security Hardening (Before Production)

**Completed (March 16, 2026):**
- [x] Add `X-CSRF-Token` headers to all AJAX POST requests in JS files (8 files: player-skill, season-players, notification-confirm, tournament-teams, roster-builder, tournament-roster-builder, schedule-builder, tournament-schedule-builder)
- [x] Add controller-level access checks to all AJAX endpoints that modify user data (8 controllers: PlayerAdminController, PlayerSkillController, TournamentTeamsController, RosterBuilderController, TournamentRosterBuilderController, ScheduleController, TournamentScheduleController, NotificationController)
- [x] Replace `innerHTML` with safe DOM methods in JS files handling user-controlled data (group-management, schedule-builder, tournament-roster-builder, tournament-schedule-builder)
- [x] Add rate limiting (Drupal flood control) to player-facing endpoints (GroupController: userSearch, invite, nudge)

**Still needed (code fixes — future sessions):**
- [ ] Remove inline JS event handler in `ReportController.php`; use Drupal behaviors *(Finding #8 — HIGH)*
- [ ] Sanitize "reason" text before storage in `TournamentDepositForfeitForm.php` and `TournamentDepositRefundForm.php` *(Finding #11 — MEDIUM)*
- [ ] Replace `$e->getMessage()` in JSON responses with generic error messages; log details server-side *(Finding #12 — MEDIUM)*
- [ ] Standardize HTML escaping in render arrays using `Html::escape()` *(Finding #15 — MEDIUM)*
- [ ] Validate entity access on `clone_from` parameter in `SeasonForm.php` and `TournamentForm.php` *(Finding #16 — LOW)*

**Production deployment (do at launch):**
- [ ] **Delete `ccsoccer-d11-migrated-200users.sql` from the repository** *(Finding #2 — do this now)*
- [ ] Remove/rotate Authorize.net API credentials from version control; move to environment variables or `settings.local.php` config overrides *(Finding #1)*
- [ ] Set a strong `hash_salt` value in production `settings.local.php` *(Finding #3)*
- [ ] Disable and uninstall the Devel module for production (`drush pm:uninstall devel devel_generate`) *(Finding #9)*
- [ ] Configure `trusted_host_patterns` in `settings.local.php` — do this now on test (`'^test\.ccsoccer\.com$'`), then again on production (`'^ccsoccer\.com$'`, `'^www\.ccsoccer\.com$'`) *(Finding #4)*
- [ ] Verify `development.services.yml` is NOT loaded in production *(Finding #10)*
- [ ] Add Content Security Policy headers (start with report-only mode) *(Finding #14)*
- [ ] See `CC_Soccer_Security_Assessment_2026_03_16.md` for full details on all 16 findings

### Deployment Prep
- [ ] Enable reCAPTCHA on registration form
- [ ] Self-host Inter font
- [ ] Enable CSS/JS aggregation on test + production before launch
- [ ] Final mobile/browser testing
- [ ] Remove IP whitelist block from production `.htaccess`
- [ ] Decide canonical domain (www vs non-www)
- [ ] Confirm HTTPS redirect handling

### Small Items
- [ ] Breadcrumbs
- [ ] Game status: only show ON/CANCELLED after 3pm
- [ ] Contact page
- [ ] Social links (placeholder #)
- [ ] Hero width (full bleed)
- [ ] Password reset flow for migrated users
- [ ] Fix contextual theme stale database reference
- [ ] Fix Commerce config import loop
- [ ] CSS consolidation pass (33 CSS files — replace hardcoded hex/px with tokens)

---

## Test Server .htaccess (IP Whitelist)

Not in git — protected via `skip-worktree`. If ever lost:

```apache
# IP Whitelist - Test server only (DO NOT commit to git)
# 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

<IfModule mod_headers.c>
  Header set X-Robots-Tag "noindex, nofollow, noarchive"
</IfModule>
```

```bash
git update-index --skip-worktree web/.htaccess
```

---

## Server Quick Reference
```bash
cd ~/public_html/test_ccsoccer_site
git pull

# If new PHP classes added:
PATH=/opt/cpanel/ea-php83/root/usr/bin:$PATH /opt/cpanel/ea-php83/root/usr/bin/php /opt/cpanel/composer/bin/composer dump-autoload

# If composer.lock changed:
PATH=/opt/cpanel/ea-php83/root/usr/bin:$PATH /opt/cpanel/ea-php83/root/usr/bin/php /opt/cpanel/composer/bin/composer install --ignore-platform-req=ext-intl

# Standard post-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
