Crowdify

Developer setup: integrate without iframes

Implementation brief for the Crowdify developers. Target: mount both tools directly in the existing Crowdify pages, keep the current calculations and output, and connect email, storage and analytics. This page specifies work to implement; the embeddable widget described below has not been built yet.

1. Current state and ownership

  • Repository: qrisr/ronorp-html-page. Stack: TanStack Start, React, Vite, server routes, HTTP database access.
  • Current standalone tools: /crowdifypotential and /crowdfunding-kampage-erstellen. Keep the existing spelling of the generator path for compatibility.
  • Their UI lives in public/potential/index.html and public/generator/index.html. The React wrappers currently render iframes. Do not use these wrappers as the new integration.
  • Crowdify developers own host templates, CMS script support, reverse proxy and CSP. Tool developers own widget extraction, API configuration and regression checks. Agree one release owner and one lead destination before implementation.

The earlier discussion considered both responsive iframes and Web Components. The current requirement is explicit: no iframe. Confirm that the Crowdify CMS permits a module script and custom elements. If it cannot, add the mount point in a controlled site template; do not silently fall back to an iframe.

2. Target integration: custom elements with Shadow DOM

Build one versioned JavaScript module which registers <crowdify-potential> and <crowdify-campaign>. Mount each tool in its own Shadow Root so the host CSS and form IDs do not collide. Both tools remain in the host document’s normal flow: natural height, one page scrollbar, no fixed viewport wrapper.

Proposed embedding contract — illustrative, not a working snippet yet:

<!-- Proposed published assets on crowdify.net -->
<script type="module" src="/assets/crowdify-tools/v1/widgets.js"></script>

<!-- Calculator page -->
<crowdify-potential
  lang="de"
  api-base="/crowdify-tools-api">
</crowdify-potential>

<!-- Generator page -->
<crowdify-campaign
  lang="de"
  api-base="/crowdify-tools-api">
</crowdify-campaign>
  • Use a dedicated Vite library build for the widgets; keep the existing TanStack server build. Agree the asset publication process and test the emitted files before using this snippet.
  • Serve immutable, versioned assets. Keep the previous release available for rollback. The example paths above must be created by the developers.
  • If assets are served from another origin instead, configure module-script CORS and the host CSP explicitly. The same-origin asset path above avoids that dependency.

3. Refactor the existing UI; preserve its behaviour

  1. Extract the two existing form views, rendering functions and calculation logic into mountable modules in this repository. Do not create a separate implementation of the formulas.
  2. Replace global document.getElementById and document.querySelector calls with queries scoped to the component’s root. Replace global body/:root styles with :host or local selectors.
  3. Replace all hard-coded API URLs with a single configured API client per widget. Scope language, run ID, visitor ID, inputs and event handlers to each instance.
  4. Clean up listeners and abort requests when the component disconnects. Register custom elements only once. Test both tools on one page to catch shared-state problems.
  5. Keep all six calculator fields visible in a single column. Preserve DE/FR/IT, the email gate, retry behaviour and all eight report sections. Do not silently change consent or verification rules.
  6. Keep the generator’s SSE protocol and its incomplete-section recovery. Preserve content, not merely the success message.
  7. Use the original Crowdify logo. Do not re-create the wordmark using styled text.

Shadow DOM does not isolate JavaScript from the host and is not a security boundary. Render user/AI text with the existing escaping rules; do not execute returned HTML or scripts.

4. Route API calls through Crowdify

Preferred setup: add a same-origin reverse proxy from https://www.crowdify.net/crowdify-tools-api/* to the deployed tool backend’s /api/public/*. The external backend origin must be agreed with the deployment owner; do not guess it or change DNS to fix layout.

POST /crowdify-tools-api/request-code
POST /crowdify-tools-api/verify-code
POST /crowdify-tools-api/lead
POST /crowdify-tools-api/track
POST /crowdify-tools-api/generate-konzept
POST /crowdify-tools-api/potential-empfehlungen
  • Map the prefix once; preserve JSON bodies, status codes and response headers. Do not put any API secret into the widget or CMS.
  • For generation, preserve text/event-stream; disable proxy buffering/caching and choose a timeout that allows the complete response. The client uses streaming fetch with POST, not an EventSource GET request.
  • Parse chunks across network boundaries. Abort when the user cancels; treat an incomplete section as incomplete even if the HTTP response ended normally.
  • If a cross-origin API is unavoidable, implement OPTIONS and consistent origin-specific CORS on every public endpoint. Current endpoints do not all implement the same CORS policy. Do not expose admin endpoints through the public proxy.

5. Configure the backend in Lovable

Set secrets through the deployment platform. Values must never appear in this page, client code or build output.

  • LOVABLE_API_KEY: server-side AI generation. Confirm available quota and test a complete real concept.
  • RESEND_API_KEY, MAIL_FROM: sender verified with the mail provider; CODE_SECRET: code-signing secret. Test delivery, invalid/expired codes and resend.
  • SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY: persistent storage through HTTP. Review sql/schema.sql against the existing database before applying anything. Preserve existing records.
  • LEAD_WEBHOOK_URL and, when required, LEAD_WEBHOOK_SECRET: agreed Crowdify receiving system. Verify an acknowledged receipt, not just a request from the browser.
  • ADMIN_CODE: explicitly set the agreed access code. Missing configuration denies online access. The local demo code does not configure the deployed server.

Do not copy keys from another project. Publish through the existing GitHub → Lovable workflow, then verify the real public URL and deployed asset version.

6. Persistence, consent and usage analytics

  • Keep personen, laeufe and events as the existing server-side record set. Preserve the source values potential-kalkulator and kampagnen-generator.
  • Keep separate states for address entered, code verified, marketing consent, stored and forwarded. Consent is not preselected; an unverified address must not become a verified one.
  • Internal/master-code runs must remain excluded from external lead forwarding.
  • Check actual lead-response flags (gespeichert, weitergeleitet). Agree retry/deduplication behaviour before calling delivery complete.
  • The existing admin view is /admin. Verify access, people, run counts and source filters against the database. Code rejection is not evidence of missing data.
  • Moving from an iframe origin to crowdify.net changes the browser storage origin. Existing localStorage is not automatically migrated or a reliable historical source. Keep server records; agree any browser-only recovery separately.

7. Mobile behaviour and PDF export

  • Test at 375 CSS pixels inside the actual Crowdify template, plus iPhone Safari and Android Chrome. Test with the keyboard open and long results.
  • No horizontal page or inner-container scrolling. Show scenario tables as labelled cards on small screens. Keep touch targets at least 44 px and inputs at least 16 px.
  • Keep the host page’s navigation and scroll position predictable. Move focus deliberately on step changes and show validation beside the relevant input.
  • Do not assume the existing window.print() behaviour survives Shadow DOM. Render a dedicated printable document from the same report data, including component styles, then test all sections and page breaks. A printable new window is acceptable; an iframe is not required.
  • Keep DE/FR/IT, CHF formatting and the original generator PDF structure.

8. Acceptance test: exact calculator reference

Social project; family 10; personal network 20; newsletter 20; social media 20; communities 20; influencer/media contacts 20.

Reach = 90. Project factor = 1.5. Influencer factor = 1.6.
Average contribution = CHF 120.

Low:  90 × 0.04 × 1.5 × 1.6 =  8.64 →  9 boosters; CHF 1'037
Mid:  90 × 0.06 × 1.5 × 1.6 = 12.96 → 13 boosters; CHF 1'555
Best: 90 × 0.09 × 1.5 × 1.6 = 19.44 → 19 boosters; CHF 2'333

Displayed scenario sum: 41 boosters; CHF 4'925.

Calculate money from unrounded boosters, then round each displayed amount. Sum the displayed scenario values. Keep the explanation that the scenarios are alternatives, not cumulative funding. Verify all eight analysis sections, PDF output, empty optional fields, zero inputs and all influencer thresholds.

9. Complete one real end-to-end test before release

  1. Open the real Crowdify page and identify the deployed widget version.
  2. Submit each tool using explicitly approved test data. Record actual responses and output.
  3. Receive a real email, enter its code and verify rejection of an invalid/expired code.
  4. Generate the full six-section campaign, confirm no truncation, export and inspect the PDF.
  5. Find the correct person and both source-tagged runs in storage and in the admin view.
  6. Verify receipt at the agreed lead destination. Also test denied consent and internal runs.
  7. Test API failure, mail failure, storage failure, interrupted generation and retry without duplicate leads or lost inputs.
  8. Repeat the critical path on actual mobile browsers in the host template, then obtain release acceptance.

Release is blocked if any required service is missing or if a UI success message is the only evidence. Keep the last working standalone pages and previous widget asset version for rollback; retain existing database records.

10. Implementation references

Project references: src/components/Vollbild.tsx, src/routes/api/public/, src/lib/mailcode.ts, src/lib/speicher.ts and sql/schema.sql.