75 lines
3 KiB
Markdown
75 lines
3 KiB
Markdown
# 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.
|
||
|
||
```bash
|
||
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
|
||
|
||
1. **On page load**, [`js/script.js`](js/script.js) requests an OAuth2
|
||
access token from Gooyal's `client_credentials` token endpoint, using
|
||
Basic auth (base64 of `client_id:client_secret`) and scope
|
||
`accounts.invite:submit`. The token is cached in memory for reuse.
|
||
2. **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.
|
||
3. 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.
|
||
4. 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 own `status_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.
|