Compare commits
No commits in common. "main" and "fix/css-compilation-issues" have entirely different histories.
main
...
fix/css-co
|
|
@ -1,28 +0,0 @@
|
|||
name: Mirror to Gitea
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Mirror to Gitea
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_USERNAME: ${{ secrets.GITEA_USERNAME }}
|
||||
run: |
|
||||
REPO_NAME=$(basename $GITHUB_REPOSITORY)
|
||||
git remote add gitea https://$GITEA_USERNAME:$GITEA_TOKEN@gitea.jeffemmett.com/jeffemmett/$REPO_NAME.git || true
|
||||
git push gitea --all --force
|
||||
git push gitea --tags --force
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
# Deploying to Cloudflare Pages
|
||||
|
||||
This Next.js app is configured to run on Cloudflare Pages with full server-side functionality.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install Wrangler CLI globally:
|
||||
\`\`\`bash
|
||||
npm install -g wrangler
|
||||
\`\`\`
|
||||
|
||||
2. Login to Cloudflare:
|
||||
\`\`\`bash
|
||||
wrangler login
|
||||
\`\`\`
|
||||
|
||||
## Local Development
|
||||
|
||||
Run the app locally with Cloudflare Workers simulation:
|
||||
\`\`\`bash
|
||||
npm run preview
|
||||
\`\`\`
|
||||
|
||||
## Build for Cloudflare
|
||||
|
||||
Build the app for Cloudflare Pages:
|
||||
\`\`\`bash
|
||||
npm run pages:build
|
||||
\`\`\`
|
||||
|
||||
This creates a `.vercel/output/static` directory with your built app.
|
||||
|
||||
## Deploy to Cloudflare Pages
|
||||
|
||||
### Option 1: Command Line Deployment
|
||||
|
||||
\`\`\`bash
|
||||
npm run deploy
|
||||
\`\`\`
|
||||
|
||||
### Option 2: Cloudflare Dashboard
|
||||
|
||||
1. Go to [Cloudflare Pages](https://dash.cloudflare.com/pages)
|
||||
2. Create a new project
|
||||
3. Connect your Git repository
|
||||
4. Set build settings:
|
||||
- **Build command**: `npm run pages:build`
|
||||
- **Build output directory**: `.vercel/output/static`
|
||||
- **Root directory**: `/`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add these environment variables in Cloudflare Pages dashboard:
|
||||
|
||||
- `STRIPE_SECRET_KEY`
|
||||
- `STRIPE_PUBLISHABLE_KEY`
|
||||
- `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`
|
||||
- `STRIPE_WEBHOOK_SECRET`
|
||||
- `SENDGRID_API_KEY` - For sending notification emails (thank you emails after purchases)
|
||||
|
||||
## Important Notes
|
||||
|
||||
- API routes run as Cloudflare Workers
|
||||
- Stripe webhooks will need to be updated to your Cloudflare Pages URL
|
||||
- All server-side features (API routes, dynamic rendering) are fully supported
|
||||
- The app uses Edge Runtime for optimal performance on Cloudflare's network
|
||||
43
Dockerfile
43
Dockerfile
|
|
@ -1,43 +0,0 @@
|
|||
FROM node:20-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
|
|
@ -1,24 +1,8 @@
|
|||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
function getBaseUrl(request: NextRequest): string {
|
||||
// Use environment variable if set, otherwise construct from headers
|
||||
if (process.env.NEXT_PUBLIC_BASE_URL) {
|
||||
return process.env.NEXT_PUBLIC_BASE_URL
|
||||
}
|
||||
const host = request.headers.get("host") || request.headers.get("x-forwarded-host")
|
||||
const protocol = request.headers.get("x-forwarded-proto") || "https"
|
||||
if (host) {
|
||||
return `${protocol}://${host}`
|
||||
}
|
||||
return "https://alertbaytrumpeter.com"
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const baseUrl = getBaseUrl(request)
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
|
||||
|
||||
if (!stripeSecretKey) {
|
||||
|
|
@ -74,8 +58,8 @@ export async function POST(request: NextRequest) {
|
|||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${baseUrl}/cancel`,
|
||||
success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${request.nextUrl.origin}/cancel`,
|
||||
})
|
||||
|
||||
console.log("[v0] Subscription checkout session created successfully:", session.id)
|
||||
|
|
@ -103,8 +87,8 @@ export async function POST(request: NextRequest) {
|
|||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${baseUrl}/cancel`,
|
||||
success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${request.nextUrl.origin}/cancel`,
|
||||
})
|
||||
|
||||
console.log("[v0] Monthly subscription checkout session created successfully:", session.id)
|
||||
|
|
@ -154,8 +138,8 @@ export async function POST(request: NextRequest) {
|
|||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${baseUrl}/cancel`,
|
||||
success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${request.nextUrl.origin}/cancel`,
|
||||
})
|
||||
|
||||
console.log("[v0] Checkout session created successfully:", session.id)
|
||||
|
|
|
|||
|
|
@ -1,33 +1,12 @@
|
|||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
function getBaseUrl(request: NextRequest): string {
|
||||
if (process.env.NEXT_PUBLIC_BASE_URL) {
|
||||
return process.env.NEXT_PUBLIC_BASE_URL
|
||||
}
|
||||
const host = request.headers.get("host") || request.headers.get("x-forwarded-host")
|
||||
const protocol = request.headers.get("x-forwarded-proto") || "https"
|
||||
if (host) {
|
||||
return `${protocol}://${host}`
|
||||
}
|
||||
return "https://alertbaytrumpeter.com"
|
||||
}
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2024-06-20",
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const baseUrl = getBaseUrl(request)
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim()
|
||||
|
||||
if (!stripeSecretKey) {
|
||||
return NextResponse.json({ error: "Stripe configuration missing" }, { status: 500 })
|
||||
}
|
||||
|
||||
const stripe = new Stripe(stripeSecretKey, {
|
||||
apiVersion: "2024-06-20",
|
||||
})
|
||||
|
||||
const { session_id } = await request.json()
|
||||
|
||||
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id)
|
||||
|
|
@ -38,7 +17,7 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
customer: checkoutSession.customer as string,
|
||||
return_url: baseUrl,
|
||||
return_url: `${request.nextUrl.origin}`,
|
||||
})
|
||||
|
||||
return NextResponse.json({ url: portalSession.url })
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { priceId } = await request.json()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { type NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { name, email, subject, message } = await request.json()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim()
|
||||
|
|
|
|||
|
|
@ -1,101 +1,14 @@
|
|||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export const runtime = "edge"
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2024-06-20",
|
||||
})
|
||||
|
||||
async function sendThankYouEmail(
|
||||
customerEmail: string,
|
||||
customerName: string | null,
|
||||
amount: number,
|
||||
currency: string,
|
||||
productName: string
|
||||
) {
|
||||
const sendgridApiKey = process.env.SENDGRID_API_KEY?.trim()
|
||||
|
||||
if (!sendgridApiKey) {
|
||||
console.log("⚠️ SENDGRID_API_KEY not configured, skipping email")
|
||||
return
|
||||
}
|
||||
|
||||
const formattedAmount = new Intl.NumberFormat("en-CA", {
|
||||
style: "currency",
|
||||
currency: currency.toUpperCase(),
|
||||
}).format(amount / 100)
|
||||
|
||||
const name = customerName || "Valued Supporter"
|
||||
|
||||
try {
|
||||
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${sendgridApiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
personalizations: [
|
||||
{
|
||||
to: [{ email: customerEmail, name: name }],
|
||||
},
|
||||
],
|
||||
from: {
|
||||
email: "noreply@alertbaytrumpeter.com",
|
||||
name: "Alert Bay Trumpeter",
|
||||
},
|
||||
subject: "Thank You for Supporting the Alert Bay Trumpeter!",
|
||||
content: [
|
||||
{
|
||||
type: "text/html",
|
||||
value: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h1 style="color: #1e40af;">Thank You, ${name}!</h1>
|
||||
<p>Your generous support means the world to Jerry Higginson, the Alert Bay Trumpeter.</p>
|
||||
<div style="background-color: #f0f9ff; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<h2 style="color: #1e40af; margin-top: 0;">Subscription Details</h2>
|
||||
<p><strong>Amount:</strong> ${formattedAmount}/month</p>
|
||||
<p><strong>Plan:</strong> ${productName}</p>
|
||||
</div>
|
||||
<p>Your monthly contribution helps Jerry continue spreading smiles at sea by serenading cruise ship passengers with his trumpet.</p>
|
||||
<p>With over 1,000 nautical serenades since 1996, your support keeps the music playing!</p>
|
||||
<p style="margin-top: 30px;">
|
||||
With gratitude,<br>
|
||||
<strong>Jerry Higginson</strong><br>
|
||||
The Alert Bay Trumpeter
|
||||
</p>
|
||||
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 30px 0;">
|
||||
<p style="color: #6b7280; font-size: 12px;">
|
||||
If you have any questions, contact us at alertbaytrumpeter@icloud.com
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok || response.status === 202) {
|
||||
console.log(`✅ Thank you email sent to ${customerEmail}`)
|
||||
} else {
|
||||
const error = await response.text()
|
||||
console.error(`❌ Failed to send email: ${error}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error sending email:", error)
|
||||
}
|
||||
}
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim()
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET?.trim()
|
||||
|
||||
if (!stripeSecretKey || !webhookSecret) {
|
||||
return NextResponse.json({ error: "Stripe configuration missing" }, { status: 500 })
|
||||
}
|
||||
|
||||
const stripe = new Stripe(stripeSecretKey, {
|
||||
apiVersion: "2024-06-20",
|
||||
})
|
||||
|
||||
const body = await request.text()
|
||||
const signature = request.headers.get("stripe-signature")!
|
||||
|
||||
|
|
@ -113,45 +26,12 @@ export async function POST(request: NextRequest) {
|
|||
case "checkout.session.completed":
|
||||
const session = event.data.object as Stripe.Checkout.Session
|
||||
console.log(`💰 Payment successful for session: ${session.id}`)
|
||||
|
||||
// Send thank you email for subscriptions
|
||||
if (session.mode === "subscription" && session.customer_details?.email) {
|
||||
// Get subscription details
|
||||
const subscriptionId = session.subscription as string
|
||||
if (subscriptionId) {
|
||||
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
|
||||
const item = subscription.items.data[0]
|
||||
const amount = item.price.unit_amount || 0
|
||||
const currency = item.price.currency
|
||||
const productId = item.price.product as string
|
||||
const product = await stripe.products.retrieve(productId)
|
||||
|
||||
await sendThankYouEmail(
|
||||
session.customer_details.email,
|
||||
session.customer_details.name,
|
||||
amount,
|
||||
currency,
|
||||
product.name
|
||||
)
|
||||
}
|
||||
}
|
||||
// Handle successful payment here
|
||||
break
|
||||
|
||||
case "customer.subscription.created":
|
||||
const newSubscription = event.data.object as Stripe.Subscription
|
||||
console.log(`🎉 New subscription created: ${newSubscription.id}`)
|
||||
case "payment_intent.succeeded":
|
||||
const paymentIntent = event.data.object as Stripe.PaymentIntent
|
||||
console.log(`💰 Payment succeeded: ${paymentIntent.id}`)
|
||||
break
|
||||
|
||||
case "invoice.paid":
|
||||
const invoice = event.data.object as Stripe.Invoice
|
||||
console.log(`💵 Invoice paid: ${invoice.id}`)
|
||||
break
|
||||
|
||||
case "customer.subscription.deleted":
|
||||
const cancelledSubscription = event.data.object as Stripe.Subscription
|
||||
console.log(`😢 Subscription cancelled: ${cancelledSubscription.id}`)
|
||||
break
|
||||
|
||||
default:
|
||||
console.log(`Unhandled event type ${event.type}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
:root {
|
||||
--font-inter: var(--font-inter);
|
||||
|
|
|
|||
|
|
@ -21,33 +21,6 @@ export const metadata: Metadata = {
|
|||
description:
|
||||
"The official website of Jerry Higginson, the Alert Bay Trumpeter, entertaining cruise ship passengers since 1996",
|
||||
generator: "v0.app",
|
||||
icons: {
|
||||
icon: "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎺</text></svg>",
|
||||
},
|
||||
openGraph: {
|
||||
title: "Alert Bay Trumpeter - Jerry Higginson",
|
||||
description:
|
||||
"The official website of Jerry Higginson, the Alert Bay Trumpeter, entertaining cruise ship passengers since 1996",
|
||||
url: "https://alertbaytrumpeter.com",
|
||||
siteName: "Alert Bay Trumpeter",
|
||||
images: [
|
||||
{
|
||||
url: "/og-image.jpg",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "Jerry Higginson - Alert Bay Trumpeter",
|
||||
},
|
||||
],
|
||||
locale: "en_US",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Alert Bay Trumpeter - Jerry Higginson",
|
||||
description:
|
||||
"The official website of Jerry Higginson, the Alert Bay Trumpeter, entertaining cruise ship passengers since 1996",
|
||||
images: ["/og-image.jpg"],
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
|
|||
23
app/page.tsx
23
app/page.tsx
|
|
@ -50,14 +50,21 @@ export default function HomePage() {
|
|||
try {
|
||||
console.log("[v0] Starting donation process for product:", product.name)
|
||||
|
||||
// Always use subscription mode for products fetched from Stripe
|
||||
const requestBody = {
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
price: product.price,
|
||||
currency: product.currency,
|
||||
description: product.description,
|
||||
mode: "subscription",
|
||||
let requestBody: any
|
||||
|
||||
if (product.lookup_key) {
|
||||
// Use lookup_key for one-time donations (legacy sponsor tiers)
|
||||
requestBody = { lookup_key: product.lookup_key }
|
||||
} else {
|
||||
// Use product info for subscription checkouts
|
||||
requestBody = {
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
price: product.price,
|
||||
currency: product.currency,
|
||||
description: product.description,
|
||||
mode: "subscription",
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch("/api/create-checkout-session", {
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
project_name: "Alert Bay Trumpeter"
|
||||
default_status: "To Do"
|
||||
statuses: ["To Do", "In Progress", "Done"]
|
||||
labels: []
|
||||
milestones: []
|
||||
date_format: yyyy-mm-dd
|
||||
max_column_width: 20
|
||||
default_editor: "vim"
|
||||
auto_open_browser: true
|
||||
default_port: 6420
|
||||
remote_operations: true
|
||||
auto_commit: false
|
||||
bypass_git_hooks: false
|
||||
check_active_branches: true
|
||||
active_branch_days: 30
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
id: task-1
|
||||
title: Finalize SendGrid API connection for alertbaytrumpeter.com
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2025-12-06 14:37'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Complete the SendGrid integration for the Alert Bay Trumpeter website notification emails
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Create SendGrid account and generate API key
|
||||
- [ ] #2 Verify sender domain (alertbaytrumpeter.com) in SendGrid
|
||||
- [ ] #3 Add SENDGRID_API_KEY to Cloudflare Pages environment variables
|
||||
- [ ] #4 Test email delivery with a real subscription
|
||||
<!-- AC:END -->
|
||||
|
|
@ -9,13 +9,14 @@ const buttonVariants = cva(
|
|||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
default:
|
||||
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||
ghost:
|
||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
|
|
@ -25,8 +26,6 @@ const buttonVariants = cva(
|
|||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
|
|
@ -4,11 +4,8 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"deploy": "npm run pages:build && wrangler pages deploy",
|
||||
"dev": "next dev",
|
||||
"lint": "next lint",
|
||||
"pages:build": "npx @cloudflare/next-on-pages",
|
||||
"preview": "npm run pages:build && wrangler pages dev",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -47,21 +44,17 @@
|
|||
"recharts": "latest",
|
||||
"stripe": "latest",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "latest"
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/next-on-pages": "^1.13.5",
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-next": "15.2.4",
|
||||
"eslint-config-next": "15.1.3",
|
||||
"postcss": "^8.5",
|
||||
"tailwindcss": "^4.1.9",
|
||||
"typescript": "^5",
|
||||
"vercel": "^37.14.0",
|
||||
"wrangler": "^3.94.0"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
2921
pnpm-lock.yaml
2921
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 186 KiB |
|
|
@ -1,5 +1,4 @@
|
|||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
|
|
@ -75,8 +74,8 @@
|
|||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-sans: var(--font-inter);
|
||||
--font-serif: var(--font-playfair);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
name = "alert-bay-trumpeter"
|
||||
compatibility_date = "2024-12-01"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
pages_build_output_dir = ".vercel/output/static"
|
||||
|
||||
[env.production]
|
||||
vars = { NODE_ENV = "production" }
|
||||
Loading…
Reference in New Issue