Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'
cache: 'npm'

- name: Install dependencies
Expand All @@ -42,7 +42,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'
cache: 'npm'

- name: Install dependencies
Expand Down
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,59 @@ A GitHub Actions workflow runs on all PRs and pushes to the main branch:

The project uses the following environment variables:
- `NEXT_PUBLIC_HOTJAR_ID` - Hotjar tracking ID
- `NEXT_PUBLIC_SITE_URL` - Canonical site URL used for store checkout redirects (example: `https://pythonessprogrammer.com`)
- `STRIPE_SECRET_KEY` - Stripe secret key for paid product checkout sessions
- `STRIPE_WEBHOOK_SECRET` - Stripe webhook signing secret for `POST /api/store/webhook`
- `BEEHIIV_API_KEY` - Beehiiv API key for optional subscriber sync after free claims and paid purchases
- `BEEHIIV_PUBLICATION_ID` - Beehiiv publication ID (format: `pub_...`) used by subscription API calls
- `RESEND_API_KEY` - Resend API key for transactional purchase emails
- `STORE_EMAIL_FROM` - Optional sender for transactional emails (default: `store@pythonessprogrammer.com`)
- `STORE_DOWNLOAD_TOKEN_SECRET` - Optional HMAC secret for paid download gate tokens (falls back to `STRIPE_WEBHOOK_SECRET`)
- Additional environment variables can be added in `.env.local`

## Stripe webhook local testing (small CLI flow)

Use Stripe CLI to test paid fulfillment locally (webhook + gated download + transactional email send).

1. Start the app:

```bash
npm run dev
```

2. In a second terminal, authenticate Stripe CLI if needed:

```bash
stripe login
```

3. Start webhook forwarding to the local webhook route:

```bash
stripe listen --events checkout.session.completed --forward-to localhost:3000/api/store/webhook
```

4. Copy the webhook signing secret shown by Stripe CLI (`whsec_...`) into `.env.local`:

```bash
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxx
```

5. Trigger a real test checkout in the app:
- Open `http://localhost:3000/store`
- Buy the paid product with a Stripe test card (for example `4242 4242 4242 4242`)
- Confirm the redirect lands on `/store/success`

6. Verify fulfillment:
- Stripe CLI shows `checkout.session.completed` delivered
- webhook logs show `POST /api/store/webhook` succeeded
- buyer receives transactional email if `RESEND_API_KEY` is configured
- success-page download button and emailed link both pass through `/api/store/paid-download`

Notes:
- `stripe trigger checkout.session.completed` can test webhook wiring, but a real checkout is the best end-to-end test because it includes your exact metadata and redirect behavior.
- Free-product claims do not require Stripe and continue through `/api/store/free-claim`.

## Resources Page

The resources page (`/resources`) provides a comprehensive collection of free resources for digital wellness, automation, and neurodivergent-friendly tech solutions.
Expand Down
45 changes: 34 additions & 11 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});
import coreWebVitals from "eslint-config-next/core-web-vitals";
import typescript from "eslint-config-next/typescript";

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"coverage/**",
"build/**",
"public/**",
],
},
...coreWebVitals,
...typescript,
{
files: ["**/*.{ts,tsx}"],
rules: {
"@typescript-eslint/no-explicit-any": "warn",
},
},
{
files: [
"**/*.config.js",
"**/*.config.mjs",
"**/*.config.ts",
"jest.config.js",
"jest.setup.js",
"postcss.config.js",
"scripts/**/*.js",
],
rules: {
"@typescript-eslint/no-require-imports": "off",
},
},
];

export default eslintConfig;
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const createJestConfig = nextJest({
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
// Non-http(s) origins are "opaque"; localStorage throws in CI without a real URL.
testEnvironmentOptions: {
url: 'http://localhost/',
},
moduleNameMapper: {
// Handle module aliases
'^@/(.*)$': '<rootDir>/src/$1',
Expand Down
6 changes: 4 additions & 2 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[build]
command = "next build"
publish = "out"
# Overrides Netlify UI if it still points at `out` from the old static-export setup.
# Standard `next build` writes to `.next/`; there is no `out/` without `output: "export"`.
publish = ".next"

[build.environment]
NODE_VERSION = "18"
NODE_VERSION = "20"

[[plugins]]
package = "@netlify/plugin-nextjs"
Expand Down
3 changes: 2 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
// Ensures next-mdx-remote uses the app’s React/jsx-runtime (avoids “older version of React” during prerender with Turbopack).
transpilePackages: ['next-mdx-remote'],
reactStrictMode: true,
images: {
unoptimized: true,
Expand Down
Loading
Loading