PageSourceSearch

https://campus-connect-surveys-psi.vercel.app/assets/about-cOzI8n6j.js

js campus-connect-surveys-psi.vercel.app collected 2026-10-03 06:20:52 UTC 11,806 bytes, 1 lines download raw bytes

1import{c,j as e,L as r}from"./index-DJNqRXLm.js";import{B as o}from"./button-CNtRYr0V.js";import{A as d}from"./arrow-left-CYwGv3mS.js";import{G as u}from"./graduation-cap-b5FOS2-l.js";import{S as h}from"./shield-check-BoBmE-QT.js";import{U as m}from"./users-CZfRto_c.js";import{C as x}from"./coins-7utAK022.js";import{L as p}from"./lock-djF0w6M1.js";import{F as f}from"./file-chart-column-increasing-D60HAAmg.js";import{S as g}from"./sparkles-Bnzr5Cve.js";import"./clsx-B-dksMZM.js";const y=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]],j=c("wifi",y);function s({id:a,eyebrow:i,title:n,children:l}){return e.jsxs("section",{id:a,className:"border-t border-foreground/10 py-16",children:[e.jsx("span",{className:"text-[11px] font-bold uppercase tracking-[0.2em] text-primary",children:i}),e.jsx("h2",{className:"mt-3 font-serif text-3xl tracking-tight sm:text-4xl",children:n}),e.jsx("div",{className:"mt-6 max-w-3xl space-y-4 text-base leading-relaxed text-muted-foreground",children:l})]})}function t({icon:a,title:i,children:n}){return e.jsxs("div",{className:"rounded-2xl border border-foreground
1/15 bg-card p-6",children:[e.jsx("div",{className:"flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary",children:e.jsx(a,{className:"h-5 w-5"})}),e.jsx("h3",{className:"mt-4 font-serif text-xl text-foreground",children:i}),e.jsx("p",{className:"mt-2 text-sm leading-relaxed text-muted-foreground",children:n})]})}function F(){return e.jsxs("div",{className:"min-h-screen bg-background",children:[e.jsxs("header",{className:"mx-auto flex max-w-5xl items-center justify-between px-6 py-6",children:[e.jsx(r,{to:"/",className:"font-serif text-2xl text-primary",children:"CampusVerify"}),e.jsx(r,{to:"/",children:e.jsxs(o,{variant:"ghost",className:"rounded-full",children:[e.jsx(d,{className:"mr-1 h-4 w-4"})," Back home"]})})]}),e.jsxs("main",{className:"mx-auto max-w-5xl px-6 pb-24",children:[e.jsxs("section",{className:"py-12 sm:py-16",children:[e.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-full bg-highlight px-3 py-1 text-[11px] font-bold uppercase tracking-wider text-highlight-foreground",children:[e.jsx(u,{className:"h-3 w-3"})," Project overview"]}),e.jsxs("h1",{className:"mt-5 font-serif text-5xl leading-[0.95] tracking-tight sm:text-6xl",children:["A campus-scoped survey platform built for"," ",e.jsx("em",{className:"text-primary",children:"trustworthy research."})]}),e.jsx("p",{className:"mt-6 max-w-2xl text-lg text-muted-foreground",children:"CampusVerify is a final-year project that helps university students run honest, verified surveys without paying for panels. This page documents the problem we tackled, the system we built, and the design decisions we made — at the level of detail an academic evaluator needs."})]}),e.jsxs(s,{id:"problem",eyebrow:"The problem",title:"Student research is broken in two ways.",children:[e.jsxs("p",{children:[e.jsx("strong",{className:"text-foreground",children:"Data quality."})," Free survey tools allow anyone to respond. A dissertation about engineering students at a Ghanaian university can end up answered by bots, click-farms, or strangers from another continent — invalidating the dataset."]}),e.jsxs("p",{children:[e.jsx("strong",{className:"text-foreground",children:"Access & cost."})," Paid panels are unaffordable for undergraduates, and social-media recruiting depends on the researcher's personal network. Students with smaller networks systematically get less data."]})]}),e.jsxs(s,{id:"solution",eyebrow:"The solution",title:"A credit-powered feed of verified classmates.",children:[e.jsxs("p",{children:["CampusVerify gates participation on a ",e.jsx("strong",{className:"text-foreground",children:"verified academic email"})," (",e.jsx("code",{children:".edu"}),", ",e.jsx("code",{children:".edu.gh"}),", ",e.jsx("code",{children:".ac.uk"}),","," ","etc.). The email's domain defines a student's campus, and surveys are scoped to that campus by default. Researchers can also opt-in to a wider public audie
1nce for general-interest studies."]}),e.jsxs("p",{children:["Instead of money, the platform runs on a"," ",e.jsx("strong",{className:"text-foreground",children:"credit economy"}),": you earn one credit for each quality response you give, and spend credits to publish your own surveys. This aligns incentives — every researcher is also a respondent."]})]}),e.jsx(s,{id:"pillars",eyebrow:"Core pillars",title:"Five concerns drove every design decision.",children:e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2 lg:grid-cols-3",children:[e.jsx(t,{icon:h,title:"Identity verification",children:"Sign-up requires an academic email and confirmation link. We never auto-sign-in; the email must be verified before the account is usable."}),e.jsx(t,{icon:m,title:"Campus scoping",children:'Email domain maps to a university. Default audience for any survey is "only your campus", protecting both relevance and respondent privacy.'}),e.jsx(t,{icon:x,title:"Credit economy",children:"Publishing costs credits; answering earns them. This funds the platform without real money and discourages low-effort surveys."}),e.jsx(t,{icon:p,title:"Defense-in-depth security",children:"Row-level security on every table, role checks via a security-definer function, and database triggers that protect sensitive columns even from the owner."}),e.jsx(t,{icon:j,title:"Offline-first",children:"Campus Wi-Fi and mobile data are unreliable. Surveys cache locally so respondents can answer without a connection and sync automatically when back online."}),e.jsx(t,{icon:f,title:"Report builder",children:"Survey owners get a configurable report — cover page, executive summary, per-question analysis, owner commentary — exported as a clean PDF document."})]})}),e.jsx(s,{id:"architecture",eyebrow:"System architecture",title:"A modern serverless stack chosen for cost and reliability.",children:e.jsxs("ul",{className:"list-disc space-y-2 pl-6",children:[e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Frontend:"})," React 19 + TanStack Start (file-based routing, server functions, SSR) on Vite 7, styled with Tailwind CSS v4 and shadcn/ui primitives."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Backend:"})," Postgres + Supabase Auth for identity. Business logic runs as TanStack server functions secured by an authentication middleware; webhooks live as server routes."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Data layer:"})," Row-Level Security on every table. A ",e.jsx("code",{children:"has_role()"})," security-definer function avoids RLS recursion. Triggers protect sensitive columns (credits, user types, survey rules) from direct modification."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Offline:"})," A service worker caches survey payloads; responses queue in IndexedDB and replay on reconnect."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Hosting:"})," Edge runtime (Cloudflare Workers) — cold-start free, geographically close to Ghanaian users."]})]})}),e.jsxs(s,{id:"security",eyebrow:"Security model",title:"Trust is enforced in the database, not just the UI.",children:[e.jsxs("p",{children:["Every table in the ",e.jsx("code",{children:"public"})," schema has RLS enabled. Roles are stored in a separate ",e.jsx("code",{children:"user_roles"})," table — never on the profile — and checked via",e.jsx("code",{children:" has_role(user_id, role)"}),", a ",e.jsx("code",{children:"SECURITY DEFINER"})," function that prevents privilege-escalation attacks common in naive Supabase apps."]}),e.jsx("p",{children:`Sensitive columns (a user's credit balance, a survey's published flag, a profile's verification status) cannot be edited via the API even by the row's owner. Database triggers reject any attempt; only internal server logic can update them. A "speed-trap" trigger flags suspiciously fast responses as fraud, while still honouring genuine offline-sync timing.`}),e.jsx("p",{children:"Realtime broadcasts are restricted to a safe column allow-list so sensitive fields (email hash, flagging metadata, raw identifiers) are never wired to the websocket."})]}),e.jsx(s,{id:"engineering",eyebrow:"Engineering practices",title:"Built to be evaluated, audited, and extended.",children:e.jsxs("ul",{className:"list-disc space-y-2 pl-6",children:[e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Migrations as source of truth."})," Every schema change ships as a numbered SQL migration in ",e.jsx("code",{children:"supabase/migrations/"}),"."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Type-safe routing."})," TanStack Router generates route types at build time; broken links fail the build, not production."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Semantic design tokens."})," No raw colours in components — every shade is a token defined in"," ",e.jsx("code",{children:"src/styles.css"}),", giving consistent light/dark behaviour."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"PWA installable."})," The app ships a manifest and icons; students can install it to their home screen."]})]})}),e.jsx(s,{id:"evaluators",eyebrow:"For evaluators",title:"How to assess CampusVerify in 10 minutes.",children:e.jsxs("ol",{className:"list-decimal space-y-2 pl-6",children:[e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Create an account"})," from the home page using any academic email; confirm the verification link."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Open the feed"})," — it shows surveys scoped to your campus, with credit rewards and remaining slots."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Answer one survey"})," to earn credits and observe the offline indicator (try toggling airplane mode mid-answer)."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Publish your own survey"}),"; spend credits, then open the Analytics view to see live results."]}),e.jsxs("li",{children:[e.jsx("strong",{className:"text-foreground",children:"Build a report"})," from the analytics page and export the PDF — this is the artefact a researcher would submit."]})]})}),e.jsxs("section",{className:"mt-16 rounded-3xl border border-foreground
1/15 bg-card p-8 text-center sm:p-12",children:[e.jsx(g,{className:"mx-auto h-6 w-6 text-primary"}),e.jsx("h2",{className:"mt-3 font-serif text-3xl tracking-tight",children:"Try it the way a student would."}),e.jsx("p",{className:"mx-auto mt-3 max-w-xl text-muted-foreground",children:"The best evaluation is hands-on. Create a free account, run a survey, and read the generated report — the full loop takes about ten minutes."}),e.jsxs("div",{className:"mt-6 flex flex-wrap justify-center gap-3",children:[e.jsx(r,{to:"/signup",children:e.jsx(o,{size:"lg",className:"h-12 rounded-full bg-primary px-7",children:"Create an account"})}),e.jsx(r,{to:"/",children:e.jsx(o,{size:"lg",variant:"outline",className:"h-12 rounded-full border-foreground/30 px-7",children:"Back to home"})})]})]})]}),e.jsxs("footer",{className:"border-t border-foreground/10 py-8 text-center text-xs text-muted-foreground",children:["CampusVerify · An academic project · ",e.jsx(r,{to:"/privacy",className:"underline",children:"Privacy"})," ","· ",e.jsx(r,{to:"/terms",className:"underline",children:"Terms"})]})]})}export{F as component};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.