3 KiB
Winsoo Invite Page
A single-page invite/referral landing page for Winsoo. A visitor lands on the page via an invite link, enters their mobile number, and the page submits that number against the invite code embedded in the URL. Built with plain HTML, CSS, and JavaScript — no build step, no framework, no dependencies.
Project structure
InvitePage/
├── index.html Markup for the page
├── favicon.svg Browser tab icon
├── css/
│ └── style.css Styles + @font-face declarations
├── js/
│ └── script.js OAuth token fetch + invite submission logic
└── assets/
├── images/
│ ├── logo.png Winsoo wordmark
│ └── illustration.png Hero illustration
└── fonts/
└── iransans/ IRANSans font family (all weights/variants)
Running locally
The page must be served over http:// or https://, not opened directly
as a file:// URL — the invite API's CORS policy is keyed to real origins,
and some browsers handle a file:// "null" origin inconsistently.
python3 -m http.server 8765
Then open:
http://localhost:8765/index.html?inviteCode=<uuid>
The inviteCode query parameter is required — it's the invite code that
gets sent to the API along with the submitted phone number.
How it works
- On page load,
js/script.jsrequests an OAuth2 access token from Gooyal'sclient_credentialstoken endpoint, using Basic auth (base64 ofclient_id:client_secret) and scopeaccounts.invite:submit. The token is cached in memory for reuse. - On submit, the phone number is normalized to E.164 format:
- Persian (
۰-۹) and Arabic-Indic (٠-٩) digits are converted to Latin digits. - Common prefixes (
0,98,+98,0098) are stripped and replaced with+98. - The result must match a 10-digit Iranian mobile number (
9XXXXXXXXX). - Invalid input highlights the field and shows an inline error instead of calling the API.
- Persian (
- The invite code is read from the page's own URL (
?inviteCode=...) and POSTed together with the mobile number to the invite request endpoint, authenticated with the bearer token from step 1. - If the invite request fails with
401(expired/invalid token), the token is refetched once and the request is retried automatically. Any other failure surfaces the API's ownstatus_message(falling back to a generic Persian error) in the inline message area below the input. A successful submission shows a success message.
Known limitation
js/script.js contains the OAuth client ID and client secret in plain
text, which means anyone viewing the page source can read them. This is
acceptable for the current staging environment, but before shipping to
production, the client_credentials token exchange should move to a
small backend/proxy that holds the secret server-side; the browser should
never see it directly.