How to Handle User Data with FckSignups: A Privacy-First, Stateless Architecture
FckSignups (now branded NoSignups) processes user submissions—new tool suggestions and problem reports—through a three-layer pipeline that never stores personal data, requires no accounts, and persists only as GitHub issues for maintainer review.
This React + TypeScript project demonstrates how to build accountless contribution flows that respect user privacy while maintaining data integrity. The entire system is stateless: no databases, no cookies, no tracking—just validated payloads flowing from browser to Cloudflare Worker to GitHub API.
The Three-Layer Data Flow Overview
User data handling in FckSignups follows a strict path:
- Client-side modal – Dynamic forms in
src/hooks/useModal/useModal.tsxcollect and POST JSON payloads - Cloudflare Worker validation – Serverless handlers sanitize input and enforce schema rules
- GitHub Issue creation – Validated data becomes trackable issues via REST API
Each layer serves a distinct purpose: capture, validate, persist. Nothing is retained on intermediate servers.
Layer 1: Client-Side Form Capture
Modal System Architecture
The useModal.tsx hook manages all user interactions. When visitors click "Submit a Tool" or "Report a Tool", the <ModalRenderer> component:
- Reads configuration from
src/constants/ModalConfigs.tsx - Renders form fields based on
ModalConfigJSON schema - Posts to Cloudflare Worker endpoints via
fetch()
// src/hooks/useModal/useModal.tsx – handleSubmit implementation
async function handleSubmit(e: FormEvent) {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const payload = Object.fromEntries(formData);
const response = await fetch(submitURL, {
method: 'POST',
body: JSON.stringify(payload)
});
// Response handling triggers success/error UI states
}
The submitURL values are configured in ModalConfigs.tsx, pointing to:
https://fcksignups-submit.abdullahalkafajy.workers.dev/submit-toolhttps://fcksignups-submit.abdullahalkafajy.workers.dev/report-tool
Key privacy feature: The client never sends cookies, headers, or fingerprinting data beyond the form contents.
Layer 2: Server-Side Validation
The Cloudflare Worker acts as a stateless gatekeeper. All incoming payloads pass through a shared validate() function that:
- Checks required field presence
- Validates URL formats
- Enforces length limits
- Trims and sanitizes strings
Validation failures return HTTP 400 with descriptive JSON errors. Valid payloads proceed to GitHub integration.
Tool Submission Handler
handleSubmitTool.ts processes new tool suggestions:
// cloudflare-worker/urlHandlers/handleSubmitTool.ts
export async function handleSubmitTool(request: Request, env: Env, origin: string) {
const sub = validate(await request.json()); // Throws on malformed data
const issueBody = `
## Tool Submission
| Field | Value |
|-------|-------|
| **Name** | ${sub.name} |
| **Description** | ${sub.description} |
| **URL** | ${sub.url} |
| **Tags** | ${sub.tags.join(", ")} |
| **GitHub** | ${sub.github || "—"} |
| **Category** | ${sub.category} |
`;
const res = await fetch(
`https://api.github.com/repos/${env.GITHUB_REPO_OWNER}/${env.GITHUB_REPO_NAME}/issues`,
{
method: "POST",
headers: {
Authorization: `Bearer ${env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
Accept: "application/vnd.github+json",
"User-Agent": "fcksignups-worker/1.0",
},
body: JSON.stringify({
title: `[Tool Submission Request] ${sub.name}`,
body: issueBody,
labels: ["tool-submission", ...(sub.github ? [] : ["no-repo-link"])],
}),
}
);
if (!res.ok) throw new Error("GitHub issue creation failed");
return jsonResponse({ok: true}, 200, origin);
}
Tool Report Handler
handleReportTool.ts handles problem reports for existing tools:
// cloudflare-worker/urlHandlers/handleReportTool.ts
export async function handleReportTool(request: Request, env: Env, origin: string) {
const report = validate(await request.json());
const issueBody = `
## Tool Suggestion
| Field | Value |
|-------|-------|
| **ID** | ${report.toolId} |
| **Issue** | ${report.report} |
`;
const res = await fetch(
`https://api.github.com/repos/${env.GITHUB_REPO_OWNER}/${env.GITHUB_REPO_NAME}/issues`,
{
method: "POST",
headers: {
Authorization: `Bearer ${env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
Accept: "application/vnd.github+json",
},
body: JSON.stringify({
title: `[Tool Report] ${report.toolId}`,
body: issueBody,
labels: ["tool-report"],
}),
}
);
if (!res.ok) throw new Error("GitHub issue creation failed");
return jsonResponse({ok: true}, 200, origin);
}
Layer 3: GitHub as Persistent Store
Rather than operating a database, FckSignups externalizes persistence to GitHub Issues. This design choice provides:
- Auditability – All submissions are publicly reviewable
- Workflow integration – Maintainers use familiar GitHub tooling
- Zero data retention – The worker holds no state between requests
- Spam resistance – GitHub's abuse systems provide secondary filtering
The GITHUB_TOKEN environment variable authenticates API requests. Labels like tool-submission and tool-report enable automatic triage.
Data Loading: The Read Path
For displaying tools, src/hooks/useTools.ts fetches a public JSON catalogue:
// src/hooks/useTools.ts
async function loadTools(JSON_URL: string): Promise<ToolsData | null> {
const res = await fetch(JSON_URL);
if (!res.ok) return null;
return (await res.json()) as ToolsData;
}
When the network fails, src/constants/fallbackData.ts supplies static tool definitions—ensuring the site remains functional offline.
Key Architectural Decisions
| Decision | Implementation | Privacy Impact |
|---|---|---|
| No account system | Email/identity never collected | Eliminates PII storage |
| Stateless workers | No sessions, cookies, or KV storage | Request isolation |
| Validation at edge | validate() runs in Cloudflare Worker |
Rejects malformed input before persistence |
| GitHub Issues as backend | REST API for issue creation | Public audit trail, no proprietary database |
| Client-side form generation | ModalConfig JSON drives UI |
Flexible without server templating |
Summary
- FckSignups handles user data through a three-stage pipeline: client capture → server validation → GitHub persistence
useModal.tsxrenders dynamic forms and POSTs JSON to Cloudflare WorkershandleSubmitTool.tsandhandleReportTool.tsvalidate payloads and create issues via GitHub REST API- No accounts, cookies, or databases are required—the architecture is fully stateless
- Validation occurs at the edge, rejecting bad data before any persistence attempt
- GitHub Issues serve as the sole data store, providing transparency and maintainer workflow integration
Frequently Asked Questions
What happens to user data after form submission?
Submitted data is validated by the Cloudflare Worker, then immediately posted as a GitHub Issue. No intermediate storage occurs—the worker is stateless and discards the request context after responding. The only persistent copy exists as a publicly visible issue in the repository.
Can users edit or delete their submissions after sending?
No. Because FckSignups requires no authentication, there is no mechanism to link subsequent requests to original submissions. Users receive immediate confirmation of successful delivery, but modifications require maintainer intervention via GitHub issue comments.
How does FckSignups prevent spam or abuse?
Three mechanisms operate in sequence: client-side form validation, server-side schema enforcement in validate(), and GitHub's native abuse detection on the final API call. The stateless design also prevents automated session exploitation, as no user state exists to hijack.
Is any personal information collected automatically?
No. The fetch() calls in useModal.tsx deliberately exclude credentials and custom headers. Only form field values are transmitted. IP addresses and user agents are processed by Cloudflare's infrastructure but not logged or retained by the application code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →