Three-tier sort on the Industry News feed:
1. Premium — articles pinned via adv.featured_story_slots (paid slots).
Sorted by position ascending. Highlighted with an amber left-border
gradient + ★ Featured ribbon.
2. Advertiser — articles whose title contains the company name of a
current active banner advertiser on this property, AND were published
in the last 48 hours. Highlighted with a blue left-border gradient
+ ◆ Advertiser ribbon. Multiple matches ordered by publish date.
3. General — everything else, newest first.
After 48h, advertiser stories naturally drop back into the general
tier. Premium slots fall off when their ends_at passes.
Detection is title-only with case-insensitive word-boundary match; the
longest matching company name wins so "Sony Pictures" beats "Sony"
when both advertise. Backed by the new adv.active_advertisers_by_
property view.
createAdminClient() now accepts an optional schema arg so we can hit
adv from BB without juggling clients.
API revalidate dropped 300 → 60s so the feed reflects campaign flips
within a minute.
- Add src/lib/supabase/admin.ts: server-only Supabase client constructed
with SUPABASE_SERVICE_ROLE_KEY. Uses persistSession=false. Throws if
the env var is missing.
- src/app/api/auth/wp-login/route.ts: switch the auth.admin.createUser
call (step 5, transparent migration on first login) to use the new
admin client. The anon-key client cannot call admin.createUser, which
would have made first-login attempts for legacy WP users fail at
step 5 with a permission error.
- src/app/login/page.tsx: replaced the old WP-style login form with a
server-side redirect to /client-login so the new client-login is the
canonical PR-firm/contributor entry point. Existing bookmarks for
/login keep working.
Coolify app env: SUPABASE_SERVICE_ROLE_KEY added (set out-of-band; not
NEXT_PUBLIC_-prefixed so it never ships to the client bundle).