Skip to content

Conversation

@pappensex
Copy link
Owner

Summary

  • rebuild the PI² layout with new landing hero, navigation/footer, and cookie consent banner
  • add dashboard/auth pages and legal markdown wrappers with content for impressum, privacy, AGB, cookies, and security
  • implement module API stubs and Stripe checkout/webhook skeleton plus updated docs and environment examples

Testing

  • npm run build

Codex Task

pappensex and others added 30 commits November 2, 2025 20:12
Added detailed README for YONI and Mutterschiff project, including architecture, quickstart guide, security baseline, and next steps.
Implement GitHub OAuth callback handling to exchange code for access token.
Implement GitHub webhook handler for pull requests.
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
… JS/JSX files

Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
…webhook handlers

Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Co-authored-by: pappensex <233804448+pappensex@users.noreply.github.com>
Copilot AI review requested due to automatic review settings November 22, 2025 14:43
@vercel
Copy link

vercel bot commented Nov 22, 2025

Deployment failed with the following error:

The `vercel.json` schema validation failed with the following message: should NOT have additional property `output`

Learn More: https://vercel.com/docs/concepts/projects/project-configuration

@vercel
Copy link

vercel bot commented Nov 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
transzendenz Error Error Nov 22, 2025 2:43pm
yoni-app Error Error Nov 22, 2025 2:43pm

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request represents a substantial rebuild that transforms the YONI mental health support application into PI² (PI squared), a clarity and productivity system. The changes replace the previous specialized therapeutic focus with a modular productivity platform featuring task management, automation flows, finance tracking, space organization, and energy management.

Key Changes:

  • Complete UI redesign with new landing page, navigation structure, and footer featuring clean, minimalist design
  • Implementation of five core modules (CORE, FLOW, MONEY, SPACE, ENERGY) with API route stubs
  • Addition of GDPR-compliant legal documentation (impressum, privacy policy, terms of service, cookie policy, security policy)
  • Stripe integration skeleton for subscription checkout and webhook handling
  • Removal of OpenAI chat functionality, GitHub OAuth, and blueprint/ritual management features

Reviewed changes

Copilot reviewed 43 out of 48 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
tailwind.config.js Updated color scheme from amethyst/purple theme to primary/gold/rose palette with new font families
lib/utils.ts Added className utility function for conditional styling
lib/types.ts Defined ModuleKey type and ModuleCard interface for the five core modules
lib/stripe.ts Created Stripe client singleton with conditional initialization
lib/db.ts Added database placeholder for future implementation
components/ui/button.tsx Implemented reusable Button component with primary/secondary variants and optional Link rendering
components/ui/card.tsx Created Card wrapper component for consistent card styling
components/layout/CookieBanner.tsx Added GDPR cookie consent banner with localStorage persistence
app/page.tsx Completely rebuilt landing page with hero section, module overview, and "First 100" call-to-action
app/layout.tsx Redesigned root layout with new header navigation, footer with legal links, and font configuration
app/globals.css Replaced extensive custom styling with Tailwind utility classes and simplified design tokens
app/dashboard/page.tsx New dashboard page showing module overview with placeholder content
app/auth/page.tsx Added authentication page placeholder for future auth integration
app/legal/*/page.tsx Created five legal document pages (impressum, datenschutz, agb, cookies, security) rendering markdown content
legal/*.md Added German legal documentation files with GDPR-compliant content
app/api/checkout/route.ts Simplified Stripe checkout to handle subscription mode with priceId parameter
app/api/stripe/webhook/route.ts Streamlined webhook handler for checkout.session.completed events
app/api/*/route.ts Added stub API routes for tasks, flows, finance, energy, and space modules
README.md Updated documentation to reflect PI² branding and simplified tech stack
CHANGELOG.md Initialized changelog with v0.9.0 entry
.env.example Updated environment variables for Stripe and database configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3 to +4
if (!process.env.STRIPE_SECRET_KEY) {
console.warn("STRIPE_SECRET_KEY is not set.");
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.warn for missing STRIPE_SECRET_KEY will execute on every module import. This could lead to console spam, especially in development environments with hot reloading. Consider either:

  1. Moving this warning to where stripe is actually used (e.g., in the API routes)
  2. Adding a guard to only warn once
  3. Using a proper logging library with log levels

Example fix:

let warnedAboutStripe = false;
if (!process.env.STRIPE_SECRET_KEY && !warnedAboutStripe) {
  console.warn("STRIPE_SECRET_KEY is not set.");
  warnedAboutStripe = true;
}

Copilot uses AI. Check for mistakes.
Comment on lines 9 to 22
export async function POST(req: NextRequest) {
try {
const body = await req.json();
if (!stripe) {
return NextResponse.json({ error: "Stripe not configured" }, { status: 500 });
}

const session = await stripe.checkout.sessions.create({
mode: "payment",
const { priceId } = await req.json();

line_items: [
{
price_data: {
currency: "eur",
product_data: {
name: "YONI Support Access",
},
unit_amount: 1111, // €11,11
},
quantity: 1,
},
],
if (!priceId) {
return NextResponse.json({ error: "Missing priceId" }, { status: 400 });
}

success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
metadata: body?.metadata || {},
});
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?checkout=success`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/?checkout=cancel`,
});

return NextResponse.json({ url: session.url });
} catch (err: any) {
console.error("❌ Error creating checkout session:", err);
return new NextResponse(`Error: ${err.message}`, { status: 400 });
}
return NextResponse.json({ url: session.url });
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for the Stripe checkout session creation. If stripe.checkout.sessions.create() throws an error (network issues, invalid price ID, API errors), the API will return a 500 error with an unhandled exception rather than a useful error message.

Add try-catch error handling:

export async function POST(req: NextRequest) {
  if (!stripe) {
    return NextResponse.json({ error: "Stripe not configured" }, { status: 500 });
  }

  try {
    const { priceId } = await req.json();
    
    if (!priceId) {
      return NextResponse.json({ error: "Missing priceId" }, { status: 400 });
    }

    const session = await stripe.checkout.sessions.create({
      mode: "subscription",
      line_items: [{ price: priceId, quantity: 1 }],
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?checkout=success`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/?checkout=cancel`,
    });

    return NextResponse.json({ url: session.url });
  } catch (error) {
    console.error("Checkout session creation failed:", error);
    return NextResponse.json(
      { error: "Failed to create checkout session" },
      { status: 500 }
    );
  }
}

Copilot uses AI. Check for mistakes.
<p className="text-gray-600">Sichere dir Zugang zu PI². Auth wird später angebunden.</p>
</div>
<div className="space-y-4 text-center">
<Button className="w-full justify-center">Login mit E-Mail (Platzhalter)</Button>
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The placeholder text mentions "Auth wird später angebunden" (Auth will be connected later), but the button text says "Login mit E-Mail (Platzhalter)" which translates to "Login with Email (Placeholder)".

For consistency and clarity, either use German throughout or English throughout. The codebase uses English for code/comments but German for user-facing text. However, "Platzhalter" in the button text might confuse users - consider removing it or making it less prominent:

  • "Login mit E-Mail" (without Platzhalter)
  • Or move the placeholder note to the description only
Suggested change
<Button className="w-full justify-center">Login mit E-Mail (Platzhalter)</Button>
<Button className="w-full justify-center">Login mit E-Mail</Button>

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +13
Geschäftskonto:
N26 Business – IBAN: DE18100110012091876561
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The IBAN (DE18100110012091876561) is exposed in the impressum. While IBANs are not as sensitive as credit card numbers and are designed to be shared for receiving payments, publicly displaying a bank account number on a public repository could lead to:

  1. Unwanted/spam transactions
  2. Potential security concerns
  3. Privacy issues

Consider:

  • Removing the specific IBAN and replacing it with a placeholder or generic contact instruction
  • Or moving this to an environment variable/config that's not committed to the repository
  • Or simply removing line 12-13 if bank details aren't required in the impressum

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
export function cn(...classes: Array<string | false | null | undefined>) {
return classes.filter(Boolean).join(" ");
}
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The cn utility function doesn't handle the case where classes might include falsy values other than false, null, or undefined. The TypeScript type annotation suggests it should handle these cases, but the implementation could be more robust.

Consider using a more battle-tested solution like clsx or classnames library, or improve the implementation to handle edge cases:

export function cn(...classes: Array<string | false | null | undefined>) {
  return classes.filter(Boolean).join(" ").trim();
}

Or better yet, use the popular clsx library which is well-tested and handles many edge cases.

Copilot uses AI. Check for mistakes.
<div className="container-wrapper py-12">
<Card className="prose max-w-3xl whitespace-pre-line p-8 text-gray-800">
<h1 className="mb-4 text-3xl font-serif text-text">Security</h1>
<div className="space-y-2 text-sm leading-relaxed" dangerouslySetInnerHTML={{ __html: content.replace(/\n/g, "<br />") }} />
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The legal page components use dangerouslySetInnerHTML which creates a potential XSS (Cross-Site Scripting) vulnerability. While the content is read from local markdown files at build time, this pattern is risky and unnecessary.

Instead of using dangerouslySetInnerHTML with simple replace(/\n/g, "<br />"), you should either:

  1. Use a proper markdown parser like react-markdown or marked
  2. Simply preserve whitespace with CSS: className="whitespace-pre-wrap" (which is already applied)

Since whitespace-pre-wrap is already in the className, the dangerouslySetInnerHTML and .replace(/\n/g, "<br />") are redundant. You can directly render the content as text.

Suggested change
<div className="space-y-2 text-sm leading-relaxed" dangerouslySetInnerHTML={{ __html: content.replace(/\n/g, "<br />") }} />
<div className="space-y-2 text-sm leading-relaxed">{content}</div>

Copilot uses AI. Check for mistakes.
{moduleCards.map((module) => (
<Card key={module.title} className="p-6">
<p className="text-xs uppercase tracking-[0.2em] text-gray-500">{module.title}</p>
<h3 className="mt-3 text-xl font-semibold text-text">{module.title}</h3>
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate module title is displayed twice - once in the uppercase label and once in the h3. Line 73 shows {module.title} in the h3, but the title is already displayed on line 72. This appears to be a copy-paste error.

The h3 should likely display a more descriptive heading or be removed, since the module title is already shown as the label. Looking at the moduleCards data structure (lines 4-25), each module only has title and description fields, so displaying title twice is redundant.

Suggested change
<h3 className="mt-3 text-xl font-semibold text-text">{module.title}</h3>

Copilot uses AI. Check for mistakes.
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{modules.map((module) => (
<Card key={module.title} className="p-6">
<p className="text-xs uppercase tracking-[0.2em] text-gray-500">{module.title}</p>
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar issue as on the landing page - the module title is displayed twice: once as the uppercase label (line 27) and again as the h3 heading (line 28). This is redundant.

Consider either:

  1. Removing the duplicate on line 28 if the label is sufficient
  2. Using a different field for the h3 (like a longer descriptive name)
  3. Removing line 27 if the h3 provides enough context
Suggested change
<p className="text-xs uppercase tracking-[0.2em] text-gray-500">{module.title}</p>

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +19
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?checkout=success`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/?checkout=cancel`,
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkout API doesn't validate that NEXT_PUBLIC_APP_URL is set before using it in the success/cancel URLs. If this environment variable is missing, the URLs will be malformed (e.g., undefined/dashboard?checkout=success), causing the Stripe session creation to fail.

Add validation:

if (!process.env.NEXT_PUBLIC_APP_URL) {
  return NextResponse.json({ error: "App URL not configured" }, { status: 500 });
}

Copilot uses AI. Check for mistakes.

PI² / pihoch2
Inhaberin: Julia Rappl
[Anschrift einfügen]
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The impressum file contains a placeholder [Anschrift einfügen] (Insert address) on line 6. This is sensitive information that should be filled in before deployment to production, as an impressum is legally required for German websites and must contain a complete address.

Consider adding a build-time check or validation to ensure this placeholder is replaced before production deployment.

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

@pappensex pappensex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
"name": "yoni-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"scan:secrets": "node scripts/scan-secrets.js",
"check:env": "node scripts/check-env.js",
"ci:security": "npm run scan:secrets && npm run check:env && npm run lint",
"ci:full": "npm run scan:secrets && npm run check:env && npm run lint && npm run build"
},
"dependencies": {
"next": "14.2.4",
"react": "18.3.1",
"react-dom": "18.3.1",
"@auth/prisma-adapter": "^1.5.0",
"@prisma/client": "^5.16.2",
"bcrypt": "^5.1.1",
"stripe": "^16.0.0",
"@stripe/stripe-js": "^2.5.0",
"zod": "^3.23.8",
"clsx": "^2.0.0"
},
"devDependencies": {
"eslint": "^8.57.0",
"eslint-config-next": "14.2.4",
"prettier": "^3.2.5"
}
}

Copy link
Owner Author

@pappensex pappensex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
"name": "yoni-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"scan:secrets": "node scripts/scan-secrets.js",
"check:env": "node scripts/check-env.js",
"ci:security": "npm run scan:secrets && npm run check:env && npm run lint",
"ci:full": "npm run scan:secrets && npm run check:env && npm run lint && npm run build"
},
"dependencies": {
"next": "14.2.4",
"react": "18.3.1",
"react-dom": "18.3.1",
"@auth/prisma-adapter": "^1.5.0",
"@prisma/client": "^5.16.2",
"bcrypt": "^5.1.1",
"stripe": "^16.0.0",
"@stripe/stripe-js": "^2.5.0",
"zod": "^3.23.8",
"clsx": "^2.0.0"
},
"devDependencies": {
"eslint": "^8.57.0",
"eslint-config-next": "14.2.4",
"prettier": "^3.2.5"
}
}
name: Security & Quality CI

on:
push:
branches:
- main
- develop
- feature/**
pull_request:
branches:
- main
- develop

jobs:
security-ci:
name: Security & Quality Checks
runs-on: ubuntu-latest

permissions:
  contents: read

steps:
  - name: Checkout repository
    uses: actions/checkout@v4

  - name: Setup Node.js
    uses:actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'npm'

  - name: Install dependencies
    run: |
      npm ci || npm install

  - name: Run secret leak scan
    run: node scripts/scan-secrets.js

  - name: Check env schema
    run: node scripts/check-env.js

  - name: Run lint
    run: |
      if npm run | grep -q "lint"; then
        npm run lint
      else
        echo "No lint script defined, skipping."
      fi

  - name: Run tests
    run: |
      if npm run | grep -q "test"; then
        npm test
      else
        echo "No test script defined, skipping."
      fi

// scripts/scan-secrets.js
// Fails CI if likely secrets appear in tracked files.

const { execSync } = require("node:child_process");
const fs = require("node:fs");

const SUSPICIOUS_PATTERNS = [
/otpauth://totp/gi,
/\b[A-Z2-7]{24,}\b/g,
/\bsk_(live|test)[0-9a-zA-Z]{16,}\b/g,
/\bpk
(live|test)[0-9a-zA-Z]{16,}\b/g,
/\bghp
[0-9a-zA-Z]{24,}\b/g,
/\bvercel_[0-9a-zA-Z]{24,}\b/g,
/\beyJ[A-Za-z0-9-]+.[A-Za-z0-9-]+.[A-Za-z0-9-]+\b/g,
/\bAIza[0-9A-Za-z-
]{20,}\b/g,
/\bAKIA[0-9A-Z]{16}\b/g
];

const IGNORED_PATHS = ["node_modules/", ".next/", "out/", "build/"];

function getTrackedFiles() {
const out = execSync("git ls-files", { encoding: "utf-8" });
return out
.split("\n")
.filter(
(f) => f.trim() !== "" && !IGNORED_PATHS.some((p) => f.startsWith(p))
);
}

function scanFile(path) {
const content = fs.readFileSync(path, "utf-8");
const matches = [];

for (const pattern of SUSPICIOUS_PATTERNS) {
let match;
const regex = new RegExp(pattern);
while ((match = regex.exec(content)) !== null) {
matches.push({
pattern: pattern.toString(),
snippet: match[0],
index: match.index
});
}
}
return matches;
}

function main() {
const files = getTrackedFiles();
const problems = [];

for (const file of files) {
const matches = scanFile(file);
if (matches.length > 0) problems.push({ file, matches });
}

if (problems.length > 0) {
console.error("❌ SECRET LEAK DETECTED\n");
for (const p of problems) {
console.error(File: ${p.file});
p.matches.forEach((m) =>
console.error(
Pattern ${m.pattern} | Snippet: "${m.snippet.slice( 0, 40 )}" at index ${m.index}
)
);
console.error("");
}
process.exit(1);
}

console.log("✅ No suspicious secrets detected.");
}

main();
// scripts/check-env.js
// Validates .env.example against required keys and basic sanity.

const fs = require("node:fs");
const path = require("node:path");

const REQUIRED_ENV_VARS = [
"NEXT_PUBLIC_APP_URL",
"REVALIDATE_SECRET",
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"OPENAI_API_KEY",
"DATABASE_URL"
];

function parseEnv(file) {
if (!fs.existsSync(file)) return {};

const lines = fs.readFileSync(file, "utf8").split("\n");
const env = {};

for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const i = trimmed.indexOf("=");
if (i === -1) continue;

env[trimmed.slice(0, i).trim()] = trimmed.slice(i + 1).trim();

}

return env;
}

function main() {
const file = path.join(process.cwd(), ".env.example");

if (!fs.existsSync(file)) {
console.warn("⚠️ .env.example not found, skipping env schema check.");
process.exit(0);
}

const env = parseEnv(file);
const missing = REQUIRED_ENV_VARS.filter((k) => !(k in env));

if (missing.length > 0) {
console.error("❌ Missing required keys in .env.example:\n");
missing.forEach((m) => console.error(" - " + m));
process.exit(1);
}

const suspicious = Object.entries(env).filter(([, v]) =>
/sk
(live|test)|ghp_|vercel_|eyJ/.test(v)
);

if (suspicious.length > 0) {
console.error("❌ .env.example contains real-looking secrets:\n");
suspicious.forEach(([k, v]) => console.error( - ${k} = ${v}));
process.exit(1);
}

console.log("✅ .env.example is valid.");
}

main();
git add .
git commit -m "Full overwrite to new Security & CI standard"
git push

@pappensex pappensex changed the base branch from main to codex/launch-live-love-feature November 22, 2025 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants