0e8698f538
- Register BillingSettingsContent in SETTINGS_SECTIONS so the settings MODAL renders Abonnemang (it was falling back to Företag — billing was only a standalone page, never a registered section). Page is now a thin wrapper over the same component. - Add GET /api/billing/status (isPaying / configured / trialEndsAt) so the client section gets state without server-only reads. - Redesign for conversion: trial days-left urgency banner, reactive monthly/yearly price with a 'Spara 2 mån' badge, full-width price-bearing CTA, Stripe trust line, design-system-compliant chrome (flat Card, no shadow/rounded-xl, on-scale spacing, serif headline). Trialing companies now see the upgrade path (not the manage button). The reported 'peach band' was not reproduced in code — no peach/salmon color exists in the app CSS and the only bottom drag-handle is in a md:hidden mobile sheet; most likely a macOS screenshot/desktop artifact. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { requireAuth } from '@/lib/auth/require-auth'
|
|
import { requireCompanyId } from '@/lib/company/context'
|
|
import { isStripeConfigured } from '@/lib/stripe/client'
|
|
|
|
/**
|
|
* Billing status for the client-rendered billing section (which lives inside the
|
|
* settings modal Dialog and can't read the DB server-side). Returns whether the
|
|
* company is paying, whether Stripe checkout is configured, and the trial expiry
|
|
* (for the days-left urgency banner). Read-only.
|
|
*/
|
|
export async function GET() {
|
|
const { user, supabase, error } = await requireAuth()
|
|
if (error) return error
|
|
|
|
let companyId: string | null = null
|
|
try {
|
|
companyId = await requireCompanyId(supabase, user.id)
|
|
} catch {
|
|
companyId = null
|
|
}
|
|
|
|
let isPaying = false
|
|
let trialEndsAt: string | null = null
|
|
if (companyId) {
|
|
const { data: sub } = await supabase
|
|
.from('company_subscriptions')
|
|
.select('status')
|
|
.eq('company_id', companyId)
|
|
.maybeSingle()
|
|
const status = (sub as { status: string | null } | null)?.status ?? null
|
|
// Paying = a real subscription. Deliberately excludes 'trialing' so a
|
|
// trialing company still sees the upgrade path (not the manage button).
|
|
isPaying = status === 'active' || status === 'past_due'
|
|
|
|
const { data: trial } = await supabase
|
|
.from('capability_grants')
|
|
.select('expires_at')
|
|
.eq('company_id', companyId)
|
|
.eq('source', 'trial')
|
|
.order('expires_at', { ascending: false })
|
|
.limit(1)
|
|
.maybeSingle()
|
|
trialEndsAt = (trial as { expires_at: string | null } | null)?.expires_at ?? null
|
|
}
|
|
|
|
return NextResponse.json({ isPaying, configured: isStripeConfigured(), trialEndsAt })
|
|
}
|