├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── bin └── goose ├── migrations └── 20211213225443_create_waitlist_table.sql ├── next-env.d.ts ├── next.config.js ├── package.json ├── public ├── favicon.ico ├── fonts │ ├── roboto-v30-latin-300.eot │ ├── roboto-v30-latin-300.svg │ ├── roboto-v30-latin-300.ttf │ ├── roboto-v30-latin-300.woff │ ├── roboto-v30-latin-300.woff2 │ ├── roboto-v30-latin-500.eot │ ├── roboto-v30-latin-500.svg │ ├── roboto-v30-latin-500.ttf │ ├── roboto-v30-latin-500.woff │ ├── roboto-v30-latin-500.woff2 │ ├── roboto-v30-latin-700.eot │ ├── roboto-v30-latin-700.svg │ ├── roboto-v30-latin-700.ttf │ ├── roboto-v30-latin-700.woff │ ├── roboto-v30-latin-700.woff2 │ ├── roboto-v30-latin-regular.eot │ ├── roboto-v30-latin-regular.svg │ ├── roboto-v30-latin-regular.ttf │ ├── roboto-v30-latin-regular.woff │ ├── roboto-v30-latin-regular.woff2 │ ├── styles.css │ ├── syne-v14-latin-500.eot │ ├── syne-v14-latin-500.svg │ ├── syne-v14-latin-500.ttf │ ├── syne-v14-latin-500.woff │ ├── syne-v14-latin-500.woff2 │ ├── syne-v14-latin-700.eot │ ├── syne-v14-latin-700.svg │ ├── syne-v14-latin-700.ttf │ ├── syne-v14-latin-700.woff │ ├── syne-v14-latin-700.woff2 │ ├── syne-v14-latin-800.eot │ ├── syne-v14-latin-800.svg │ ├── syne-v14-latin-800.ttf │ ├── syne-v14-latin-800.woff │ ├── syne-v14-latin-800.woff2 │ ├── syne-v14-latin-regular.eot │ ├── syne-v14-latin-regular.svg │ ├── syne-v14-latin-regular.ttf │ ├── syne-v14-latin-regular.woff │ └── syne-v14-latin-regular.woff2 └── images │ └── background.jpg ├── src ├── misc │ └── pat.ts ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ └── join.ts │ └── index.tsx ├── services │ ├── _courier.ts │ └── _postgres.ts ├── stores │ ├── useCountStore.ts │ └── useJoinedStore.ts ├── styles │ └── overrides.css └── ui │ ├── components │ ├── JoinWaitlistForm.tsx │ └── WaitlistCounter.tsx │ └── mui-theme.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = crlf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node-terminal", 9 | "request": "launch", 10 | "name": "Debug Next.js Application", 11 | "skipFiles": [ 12 | "/**", 13 | "build/**", 14 | ".next" 15 | ], 16 | "command": "yarn dev" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Polygon Standard License 2 | 3 | Copyright (c) 2021, Michael Grigoryan 4 | All rights reserved. 5 | 6 | Use in source or binary forms, with or without modification, and 7 | patenting the software is prohibited. 8 | 9 | Neither the name of the copyright holder nor the names of 10 | contributors may be used to endorse or promote products derived from 11 | this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polygon 2 | 3 | Polygon is an upcoming open-source & privacy-oriented social network that is not hungry for your data. This repository contains the source code for the [waitlist website](https://polygon.am/) of Polygon 4 | 5 | ## Contributor guide 6 | 7 | To contribute to the waitlist of polygon you will need to have the following: 8 | 9 | - [Supabase](https://supabase.com/) account. **(Free)** 10 | - Go to `Database` > `Connection Pooling` > scroll down to `Connection string` at the bottom and copy the PostgreSQL connection string 11 | - [Courier](https://courier.com/) account. **(Free)** 12 | - Make sure to [create a brand](https://help.courier.com/en/articles/4181342-customize-your-default-brand) at Courier. 13 | - Make sure to [create an event](https://help.courier.com/en/articles/4202416-how-to-create-and-map-event-triggers-for-your-notifications) at Courier. 14 | - [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) configuration from your email provider. **(Free / Paid)** 15 | 16 | After completing the steps above, make sure to create a `.env.local` file in project root. 17 | 18 | Environment configuration for Supabase PostgreSQL: 19 | 20 | ```txt 21 | POSTGRES_URL="postgresql connection string" 22 | # When deploying to services such as Vercel, make sure to specify a `GOOSE_DBSTRING` environment variable with the same value 23 | ``` 24 | 25 | for Courier: 26 | 27 | ``` 28 | COURIER_TOKEN="courier api token" 29 | COURIER_BRAND_ID="courier brand id" 30 | COURIER_EVENT_ID="courier event id" 31 | ``` 32 | 33 | for SMTP: 34 | 35 | ``` 36 | SMTP_PORT=123 37 | SMTP_PASS="somepassword" 38 | SMTP_USER="someone@polygon.am" 39 | SMTP_HOST="smtp.yourdomain.com" 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /bin/goose: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/bin/goose -------------------------------------------------------------------------------- /migrations/20211213225443_create_waitlist_table.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | -- +goose StatementBegin 3 | CREATE TABLE waitlist ( 4 | id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 5 | email VARCHAR NOT NULL, 6 | created_at TIMESTAMPTZ DEFAULT NOW() 7 | ); 8 | -- +goose StatementEnd 9 | 10 | -- +goose Down 11 | -- +goose StatementBegin 12 | DROP TABLE waitlist; 13 | -- +goose StatementEnd 14 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | compress: true, 4 | webpack5: true, 5 | reactStrictMode: true, 6 | poweredByHeader: false, 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polygon-teaser", 3 | "private": true, 4 | "scripts": { 5 | "lint": "next lint", 6 | "build": "next build", 7 | "prestart": "yarn migrate up", 8 | "dev": "cross-env NODE_ENV=development next dev", 9 | "migrate": "./bin/goose -dir=migrations postgres", 10 | "start": "cross-env NODE_ENV=production next start" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.9.0", 14 | "@emotion/styled": "^11.8.1", 15 | "@mui/icons-material": "^5.8.2", 16 | "@mui/lab": "^5.0.0-alpha.84", 17 | "@mui/material": "^5.8.2", 18 | "@splitbee/web": "^0.3.0", 19 | "@trycourier/courier": "^3.2.0", 20 | "cookie": "^0.5.0", 21 | "cross-env": "^7.0.3", 22 | "date-fns": "^2.27.0", 23 | "next": "12.1.6", 24 | "next-cookies": "^2.0.3", 25 | "next-seo": "^5.4.0", 26 | "pg": "^8.7.1", 27 | "react": "18.1.0", 28 | "react-dom": "^18.1.0", 29 | "react-hook-form": "^7.31.3", 30 | "zustand": "^4.0.0-rc.1" 31 | }, 32 | "devDependencies": { 33 | "@types/cookie": "^0.5.1", 34 | "@types/pg": "^8.6.1", 35 | "@types/react": "18.0.10", 36 | "eslint": "8.16.0", 37 | "eslint-config-next": "12.1.6", 38 | "typescript": "4.7.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-300.eot -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-300.ttf -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-300.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-300.woff2 -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-500.eot -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-500.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 38 | 39 | 41 | 43 | 44 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 58 | 59 | 61 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 77 | 78 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 98 | 100 | 102 | 103 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 118 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 139 | 141 | 143 | 144 | 145 | 148 | 149 | 152 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | 163 | 164 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 178 | 181 | 183 | 184 | 185 | 186 | 188 | 190 | 192 | 193 | 195 | 196 | 197 | 198 | 200 | 201 | 202 | 203 | 204 | 205 | 207 | 209 | 211 | 213 | 216 | 219 | 220 | 222 | 223 | 224 | 225 | 227 | 228 | 229 | 231 | 233 | 235 | 237 | 240 | 243 | 246 | 249 | 251 | 253 | 255 | 257 | 259 | 260 | 261 | 262 | 263 | 265 | 267 | 269 | 271 | 273 | 276 | 278 | 280 | 282 | 283 | 284 | 285 | 287 | 288 | 290 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-500.ttf -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-500.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-500.woff2 -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-700.eot -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-700.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 38 | 39 | 41 | 43 | 44 | 46 | 48 | 50 | 51 | 52 | 53 | 54 | 56 | 59 | 60 | 62 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 101 | 103 | 104 | 106 | 107 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 139 | 141 | 143 | 144 | 145 | 148 | 150 | 153 | 155 | 156 | 157 | 158 | 161 | 162 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 176 | 177 | 178 | 180 | 183 | 185 | 186 | 187 | 188 | 190 | 192 | 194 | 195 | 197 | 198 | 199 | 200 | 202 | 203 | 204 | 205 | 207 | 208 | 210 | 212 | 214 | 216 | 219 | 222 | 223 | 225 | 226 | 227 | 228 | 230 | 231 | 232 | 234 | 236 | 238 | 240 | 243 | 246 | 249 | 252 | 254 | 256 | 258 | 260 | 263 | 264 | 265 | 266 | 268 | 270 | 272 | 274 | 276 | 278 | 281 | 283 | 285 | 287 | 288 | 289 | 290 | 292 | 293 | 294 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-700.ttf -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-700.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-700.woff2 -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-regular.eot -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 40 | 42 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 59 | 60 | 62 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 102 | 103 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 118 | 120 | 122 | 123 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 140 | 142 | 144 | 145 | 146 | 149 | 150 | 153 | 155 | 156 | 157 | 158 | 161 | 162 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 176 | 177 | 178 | 180 | 183 | 185 | 186 | 187 | 188 | 190 | 192 | 194 | 195 | 197 | 198 | 199 | 200 | 202 | 203 | 204 | 205 | 206 | 207 | 209 | 211 | 213 | 215 | 218 | 221 | 222 | 224 | 225 | 226 | 228 | 230 | 231 | 232 | 234 | 236 | 238 | 240 | 243 | 246 | 249 | 252 | 254 | 256 | 258 | 260 | 262 | 263 | 264 | 265 | 266 | 268 | 270 | 272 | 274 | 276 | 279 | 281 | 283 | 285 | 286 | 287 | 288 | 290 | 291 | 293 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-regular.ttf -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-regular.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v30-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/roboto-v30-latin-regular.woff2 -------------------------------------------------------------------------------- /public/fonts/styles.css: -------------------------------------------------------------------------------- 1 | /* syne-regular - latin */ 2 | @font-face { 3 | font-family: "Syne"; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url("/fonts/syne-v14-latin-regular.eot"); /* IE9 Compat Modes */ 7 | src: local(""), 8 | url("/fonts/syne-v14-latin-regular.eot?#iefix") format("embedded-opentype"), 9 | /* IE6-IE8 */ url("/fonts/syne-v14-latin-regular.woff2") format("woff2"), 10 | /* Super Modern Browsers */ url("/fonts/syne-v14-latin-regular.woff") 11 | format("woff"), 12 | /* Modern Browsers */ url("/fonts/syne-v14-latin-regular.ttf") 13 | format("truetype"), 14 | /* Safari, Android, iOS */ url("/fonts/syne-v14-latin-regular.svg#Syne") 15 | format("svg"); /* Legacy iOS */ 16 | } 17 | /* syne-500 - latin */ 18 | @font-face { 19 | font-family: "Syne"; 20 | font-style: normal; 21 | font-weight: 500; 22 | src: url("/fonts/syne-v14-latin-500.eot"); /* IE9 Compat Modes */ 23 | src: local(""), 24 | url("/fonts/syne-v14-latin-500.eot?#iefix") format("embedded-opentype"), 25 | /* IE6-IE8 */ url("/fonts/syne-v14-latin-500.woff2") format("woff2"), 26 | /* Super Modern Browsers */ url("/fonts/syne-v14-latin-500.woff") 27 | format("woff"), 28 | /* Modern Browsers */ url("/fonts/syne-v14-latin-500.ttf") 29 | format("truetype"), 30 | /* Safari, Android, iOS */ url("/fonts/syne-v14-latin-500.svg#Syne") 31 | format("svg"); /* Legacy iOS */ 32 | } 33 | /* syne-800 - latin */ 34 | @font-face { 35 | font-family: "Syne"; 36 | font-style: normal; 37 | font-weight: 800; 38 | src: url("/fonts/syne-v14-latin-800.eot"); /* IE9 Compat Modes */ 39 | src: local(""), 40 | url("/fonts/syne-v14-latin-800.eot?#iefix") format("embedded-opentype"), 41 | /* IE6-IE8 */ url("/fonts/syne-v14-latin-800.woff2") format("woff2"), 42 | /* Super Modern Browsers */ url("/fonts/syne-v14-latin-800.woff") 43 | format("woff"), 44 | /* Modern Browsers */ url("/fonts/syne-v14-latin-800.ttf") 45 | format("truetype"), 46 | /* Safari, Android, iOS */ url("/fonts/syne-v14-latin-800.svg#Syne") 47 | format("svg"); /* Legacy iOS */ 48 | } 49 | 50 | /* Apache License 51 | Version 2.0, January 2004 52 | http://www.apache.org/licenses/ 53 | 54 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 55 | 56 | 1. Definitions. 57 | 58 | "License" shall mean the terms and conditions for use, reproduction, 59 | and distribution as defined by Sections 1 through 9 of this document. 60 | 61 | "Licensor" shall mean the copyright owner or entity authorized by 62 | the copyright owner that is granting the License. 63 | 64 | "Legal Entity" shall mean the union of the acting entity and all 65 | other entities that control, are controlled by, or are under common 66 | control with that entity. For the purposes of this definition, 67 | "control" means (i) the power, direct or indirect, to cause the 68 | direction or management of such entity, whether by contract or 69 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 70 | outstanding shares, or (iii) beneficial ownership of such entity. 71 | 72 | "You" (or "Your") shall mean an individual or Legal Entity 73 | exercising permissions granted by this License. 74 | 75 | "Source" form shall mean the preferred form for making modifications, 76 | including but not limited to software source code, documentation 77 | source, and configuration files. 78 | 79 | "Object" form shall mean any form resulting from mechanical 80 | transformation or translation of a Source form, including but 81 | not limited to compiled object code, generated documentation, 82 | and conversions to other media types. 83 | 84 | "Work" shall mean the work of authorship, whether in Source or 85 | Object form, made available under the License, as indicated by a 86 | copyright notice that is included in or attached to the work 87 | (an example is provided in the Appendix below). 88 | 89 | "Derivative Works" shall mean any work, whether in Source or Object 90 | form, that is based on (or derived from) the Work and for which the 91 | editorial revisions, annotations, elaborations, or other modifications 92 | represent, as a whole, an original work of authorship. For the purposes 93 | of this License, Derivative Works shall not include works that remain 94 | separable from, or merely link (or bind by name) to the interfaces of, 95 | the Work and Derivative Works thereof. 96 | 97 | "Contribution" shall mean any work of authorship, including 98 | the original version of the Work and any modifications or additions 99 | to that Work or Derivative Works thereof, that is intentionally 100 | submitted to Licensor for inclusion in the Work by the copyright owner 101 | or by an individual or Legal Entity authorized to submit on behalf of 102 | the copyright owner. For the purposes of this definition, "submitted" 103 | means any form of electronic, verbal, or written communication sent 104 | to the Licensor or its representatives, including but not limited to 105 | communication on electronic mailing lists, source code control systems, 106 | and issue tracking systems that are managed by, or on behalf of, the 107 | Licensor for the purpose of discussing and improving the Work, but 108 | excluding communication that is conspicuously marked or otherwise 109 | designated in writing by the copyright owner as "Not a Contribution." 110 | 111 | "Contributor" shall mean Licensor and any individual or Legal Entity 112 | on behalf of whom a Contribution has been received by Licensor and 113 | subsequently incorporated within the Work. 114 | 115 | 2. Grant of Copyright License. Subject to the terms and conditions of 116 | this License, each Contributor hereby grants to You a perpetual, 117 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 118 | copyright license to reproduce, prepare Derivative Works of, 119 | ly display, ly perform, sublicense, and distribute the 120 | Work and such Derivative Works in Source or Object form. 121 | 122 | 3. Grant of Patent License. Subject to the terms and conditions of 123 | this License, each Contributor hereby grants to You a perpetual, 124 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 125 | (except as stated in this section) patent license to make, have made, 126 | use, offer to sell, sell, import, and otherwise transfer the Work, 127 | where such license applies only to those patent claims licensable 128 | by such Contributor that are necessarily infringed by their 129 | Contribution(s) alone or by combination of their Contribution(s) 130 | with the Work to which such Contribution(s) was submitted. If You 131 | institute patent litigation against any entity (including a 132 | cross-claim or counterclaim in a lawsuit) alleging that the Work 133 | or a Contribution incorporated within the Work constitutes direct 134 | or contributory patent infringement, then any patent licenses 135 | granted to You under this License for that Work shall terminate 136 | as of the date such litigation is filed. 137 | 138 | 4. Redistribution. You may reproduce and distribute copies of the 139 | Work or Derivative Works thereof in any medium, with or without 140 | modifications, and in Source or Object form, provided that You 141 | meet the following conditions: 142 | 143 | (a) You must give any other recipients of the Work or 144 | Derivative Works a copy of this License; and 145 | 146 | (b) You must cause any modified files to carry prominent notices 147 | stating that You changed the files; and 148 | 149 | (c) You must retain, in the Source form of any Derivative Works 150 | that You distribute, all copyright, patent, trademark, and 151 | attribution notices from the Source form of the Work, 152 | excluding those notices that do not pertain to any part of 153 | the Derivative Works; and 154 | 155 | (d) If the Work includes a "NOTICE" text file as part of its 156 | distribution, then any Derivative Works that You distribute must 157 | include a readable copy of the attribution notices contained 158 | within such NOTICE file, excluding those notices that do not 159 | pertain to any part of the Derivative Works, in at least one 160 | of the following places: within a NOTICE text file distributed 161 | as part of the Derivative Works; within the Source form or 162 | documentation, if provided along with the Derivative Works; or, 163 | within a display generated by the Derivative Works, if and 164 | wherever such third-party notices normally appear. The contents 165 | of the NOTICE file are for informational purposes only and 166 | do not modify the License. You may add Your own attribution 167 | notices within Derivative Works that You distribute, alongside 168 | or as an addendum to the NOTICE text from the Work, provided 169 | that such additional attribution notices cannot be construed 170 | as modifying the License. 171 | 172 | You may add Your own copyright statement to Your modifications and 173 | may provide additional or different license terms and conditions 174 | for use, reproduction, or distribution of Your modifications, or 175 | for any such Derivative Works as a whole, provided Your use, 176 | reproduction, and distribution of the Work otherwise complies with 177 | the conditions stated in this License. 178 | 179 | 5. Submission of Contributions. Unless You explicitly state otherwise, 180 | any Contribution intentionally submitted for inclusion in the Work 181 | by You to the Licensor shall be under the terms and conditions of 182 | this License, without any additional terms or conditions. 183 | Notwithstanding the above, nothing herein shall supersede or modify 184 | the terms of any separate license agreement you may have executed 185 | with Licensor regarding such Contributions. 186 | 187 | 6. Trademarks. This License does not grant permission to use the trade 188 | names, trademarks, service marks, or product names of the Licensor, 189 | except as required for reasonable and customary use in describing the 190 | origin of the Work and reproducing the content of the NOTICE file. 191 | 192 | 7. Disclaimer of Warranty. Unless required by applicable law or 193 | agreed to in writing, Licensor provides the Work (and each 194 | Contributor provides its Contributions) on an "AS IS" BASIS, 195 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 196 | implied, including, without limitation, any warranties or conditions 197 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 198 | PARTICULAR PURPOSE. You are solely responsible for determining the 199 | appropriateness of using or redistributing the Work and assume any 200 | risks associated with Your exercise of permissions under this License. 201 | 202 | 8. Limitation of Liability. In no event and under no legal theory, 203 | whether in tort (including negligence), contract, or otherwise, 204 | unless required by applicable law (such as deliberate and grossly 205 | negligent acts) or agreed to in writing, shall any Contributor be 206 | liable to You for damages, including any direct, indirect, special, 207 | incidental, or consequential damages of any character arising as a 208 | result of this License or out of the use or inability to use the 209 | Work (including but not limited to damages for loss of goodwill, 210 | work stoppage, computer failure or malfunction, or any and all 211 | other commercial damages or losses), even if such Contributor 212 | has been advised of the possibility of such damages. 213 | 214 | 9. Accepting Warranty or Additional Liability. While redistributing 215 | the Work or Derivative Works thereof, You may choose to offer, 216 | and charge a fee for, acceptance of support, warranty, indemnity, 217 | or other liability obligations and/or rights consistent with this 218 | License. However, in accepting such obligations, You may act only 219 | on Your own behalf and on Your sole responsibility, not on behalf 220 | of any other Contributor, and only if You agree to indemnify, 221 | defend, and hold each Contributor harmless for any liability 222 | incurred by, or claims asserted against, such Contributor by reason 223 | of your accepting any such warranty or additional liability. */ 224 | 225 | /* roboto-300 - latin */ 226 | @font-face { 227 | font-family: "Roboto"; 228 | font-style: normal; 229 | font-weight: 300; 230 | src: url("/fonts/roboto-v30-latin-300.eot"); /* IE9 Compat Modes */ 231 | src: local(""), 232 | url("/fonts/roboto-v30-latin-300.eot?#iefix") format("embedded-opentype"), 233 | /* IE6-IE8 */ url("/fonts/roboto-v30-latin-300.woff2") format("woff2"), 234 | /* Super Modern Browsers */ url("/fonts/roboto-v30-latin-300.woff") 235 | format("woff"), 236 | /* Modern Browsers */ url("/fonts/roboto-v30-latin-300.ttf") 237 | format("truetype"), 238 | /* Safari, Android, iOS */ url("/fonts/roboto-v30-latin-300.svg#Roboto") 239 | format("svg"); /* Legacy iOS */ 240 | } 241 | /* roboto-regular - latin */ 242 | @font-face { 243 | font-family: "Roboto"; 244 | font-style: normal; 245 | font-weight: 400; 246 | src: url("/fonts/roboto-v30-latin-regular.eot"); /* IE9 Compat Modes */ 247 | src: local(""), 248 | url("/fonts/roboto-v30-latin-regular.eot?#iefix") 249 | format("embedded-opentype"), 250 | /* IE6-IE8 */ url("/fonts/roboto-v30-latin-regular.woff2") format("woff2"), 251 | /* Super Modern Browsers */ url("/fonts/roboto-v30-latin-regular.woff") 252 | format("woff"), 253 | /* Modern Browsers */ url("/fonts/roboto-v30-latin-regular.ttf") 254 | format("truetype"), 255 | /* Safari, Android, iOS */ url("/fonts/roboto-v30-latin-regular.svg#Roboto") 256 | format("svg"); /* Legacy iOS */ 257 | } 258 | /* roboto-500 - latin */ 259 | @font-face { 260 | font-family: "Roboto"; 261 | font-style: normal; 262 | font-weight: 500; 263 | src: url("/fonts/roboto-v30-latin-500.eot"); /* IE9 Compat Modes */ 264 | src: local(""), 265 | url("/fonts/roboto-v30-latin-500.eot?#iefix") format("embedded-opentype"), 266 | /* IE6-IE8 */ url("/fonts/roboto-v30-latin-500.woff2") format("woff2"), 267 | /* Super Modern Browsers */ url("/fonts/roboto-v30-latin-500.woff") 268 | format("woff"), 269 | /* Modern Browsers */ url("/fonts/roboto-v30-latin-500.ttf") 270 | format("truetype"), 271 | /* Safari, Android, iOS */ url("/fonts/roboto-v30-latin-500.svg#Roboto") 272 | format("svg"); /* Legacy iOS */ 273 | } 274 | /* roboto-700 - latin */ 275 | @font-face { 276 | font-family: "Roboto"; 277 | font-style: normal; 278 | font-weight: 700; 279 | src: url("/fonts/roboto-v30-latin-700.eot"); /* IE9 Compat Modes */ 280 | src: local(""), 281 | url("/fonts/roboto-v30-latin-700.eot?#iefix") format("embedded-opentype"), 282 | /* IE6-IE8 */ url("/fonts/roboto-v30-latin-700.woff2") format("woff2"), 283 | /* Super Modern Browsers */ url("/fonts/roboto-v30-latin-700.woff") 284 | format("woff"), 285 | /* Modern Browsers */ url("/fonts/roboto-v30-latin-700.ttf") 286 | format("truetype"), 287 | /* Safari, Android, iOS */ url("/fonts/roboto-v30-latin-700.svg#Roboto") 288 | format("svg"); /* Legacy iOS */ 289 | } 290 | 291 | -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-500.eot -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-500.ttf -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-500.woff -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-500.woff2 -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-700.eot -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-700.ttf -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-700.woff -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-700.woff2 -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-800.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-800.eot -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-800.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-800.ttf -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-800.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-800.woff -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-800.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-800.woff2 -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-regular.eot -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-regular.ttf -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-regular.woff -------------------------------------------------------------------------------- /public/fonts/syne-v14-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/fonts/syne-v14-latin-regular.woff2 -------------------------------------------------------------------------------- /public/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getpolygon/waitlist/b49cfec7cbb160ee720243c3b87c53da9359d1c1/public/images/background.jpg -------------------------------------------------------------------------------- /src/misc/pat.ts: -------------------------------------------------------------------------------- 1 | export const emailPattern = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "~/styles/overrides.css"; 2 | import * as React from "react"; 3 | import { ThemeProvider } from "@mui/material"; 4 | import CssBaseline from "@mui/material/CssBaseline"; 5 | import Head from "next/head"; 6 | import { NextSeo } from "next-seo"; 7 | import type { AppProps } from "next/app"; 8 | import { newCustomTheme } from "~/ui/mui-theme"; 9 | 10 | const App = ({ Component, pageProps }: AppProps) => { 11 | const theme = React.useMemo(() => newCustomTheme("light"), []); 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default App; 43 | 44 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from "next/document"; 2 | 3 | export default class _Document extends Document { 4 | public render() { 5 | return ( 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | ); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/pages/api/join.ts: -------------------------------------------------------------------------------- 1 | import { serialize } from "cookie"; 2 | import { NextApiHandler } from "next"; 3 | import { emailPattern } from "~/misc/pat"; 4 | import { Courier } from "~/services/_courier"; 5 | import { Postgres } from "~/services/_postgres"; 6 | 7 | const smtpHost = process.env.SMTP_HOST; 8 | const smtpPort = process.env.SMTP_PORT; 9 | const smtpUser = process.env.SMTP_USER; 10 | const smtpPass = process.env.SMTP_PASS; 11 | const courierBrand = process.env.COURIER_BRAND_ID!; 12 | const courierEvent = process.env.COURIER_EVENT_ID!; 13 | const courierOverrides = { 14 | smtp: { 15 | config: { 16 | auth: { 17 | user: smtpUser!, 18 | pass: smtpPass!, 19 | }, 20 | secure: true, 21 | host: smtpHost!, 22 | port: Number(smtpPort!), 23 | }, 24 | }, 25 | }; 26 | 27 | const join: NextApiHandler = async (req, res) => { 28 | if (req.method?.toLowerCase() === "post") { 29 | const { email } = JSON.parse(req.body) as { email?: string; }; 30 | if (!email) { 31 | return res.status(400).json("no email provided"); 32 | } 33 | 34 | if (emailPattern.test(email)) { 35 | try { 36 | const pg = await Postgres.createOrGet(); 37 | const { rowCount } = await pg?.query("SELECT * FROM waitlist WHERE email = $1", [req.body.email])!; 38 | if (rowCount === 1) { 39 | return res.status(403).json("email is already registered"); 40 | } 41 | 42 | await pg?.query("INSERT INTO waitlist (email) VALUES ($1)", [email]); 43 | // Only sending an actual message in production environment. 44 | if (process.env.NODE_ENV === "production") { 45 | try { 46 | const courier = Courier.createOrGet(); 47 | await courier.send({ profile: { email }, recipientId: email, brand: courierBrand, eventId: courierEvent, override: courierOverrides }); 48 | } catch (error) { 49 | console.error(error); 50 | } 51 | } 52 | 53 | return res.status(200).setHeader("Set-Cookie", serialize("joined", String(1), { path: "/" })).json(null); 54 | } catch (error) { 55 | console.error(error); 56 | return res.status(500).json("internal error"); 57 | } 58 | } 59 | 60 | return res.status(400).json("invalid email address"); 61 | }; 62 | 63 | return res.status(405).json("method not allowed"); 64 | }; 65 | 66 | export default join; 67 | 68 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic"; 2 | import cookies from "next-cookies"; 3 | import { Typography, Box } from "@mui/material"; 4 | import { Postgres } from "~/services/_postgres"; 5 | import type { GetServerSideProps, NextPage } from "next"; 6 | import { useJoinedStore } from "~/stores/useJoinedStore"; 7 | import { useEffect } from "react"; 8 | import { useCountStore } from "~/stores/useCountStore"; 9 | 10 | const Image = dynamic(() => import("next/image")); 11 | const Stack = dynamic(() => import("@mui/material/Stack")); 12 | const WaitlistCounter = dynamic(() => import("~/ui/components/WaitlistCounter")); 13 | const JoinWaitlistForm = dynamic(() => import("~/ui/components/JoinWaitlistForm")); 14 | 15 | interface Props { 16 | count: number; 17 | joined: number; 18 | } 19 | 20 | export const getServerSideProps: GetServerSideProps = async (ctx) => { 21 | const pg = await Postgres.createOrGet(); 22 | const { rows: data } = await pg?.query("SELECT COUNT(id) FROM waitlist;")!; 23 | const joined = Number(cookies(ctx).joined); 24 | return { 25 | props: { 26 | joined, 27 | count: Number(data[0]?.count), 28 | } 29 | }; 30 | }; 31 | 32 | const Home: NextPage = ({ count: __count, joined: __joined }) => { 33 | const setCount = useCountStore((state) => state.setCount); 34 | const setJoined = useJoinedStore((state) => state.setJoined); 35 | const joined = useJoinedStore((state) => Boolean(state.joined) || Boolean(__joined)); 36 | const count = useCountStore((state) => __count >= state.count ? __count : state.count); 37 | 38 | useEffect(() => { 39 | setJoined(Number(joined)); setCount(count); 40 | }, [joined, count]); 41 | 42 | return ( 43 | 50 | Mesh Gradient Background 58 | 59 | 60 | 61 | 62 | 63 | 64 | Polygon 65 | 66 | 67 | 68 | Polygon Project is an effort to create a fast, scalable, privacy-focused and open social networking protocol. 69 | 70 | 71 | 72 | 73 | 74 | {!joined && } 75 | 76 | 77 | 78 | 79 | 80 | © {new Date().getFullYear()} Polygon Project 81 | 82 | 83 | 84 | 85 | 86 | ); 87 | }; 88 | 89 | export default Home; 90 | 91 | -------------------------------------------------------------------------------- /src/services/_courier.ts: -------------------------------------------------------------------------------- 1 | import { CourierClient, ICourierClient } from "@trycourier/courier"; 2 | 3 | const authorizationToken = process.env.COURIER_TOKEN; 4 | 5 | export class Courier { 6 | public static __client: ICourierClient; 7 | 8 | public static createOrGet(): ICourierClient { 9 | if (typeof window !== "undefined") { 10 | throw new Error("usage of courier on client-side is forbidden"); 11 | } 12 | 13 | // Checking if Courier client is undefined or null. 14 | if (!Courier.__client) { 15 | const courier = CourierClient({ authorizationToken }); 16 | Courier.__client = courier; 17 | } 18 | 19 | return Courier.__client; 20 | } 21 | } -------------------------------------------------------------------------------- /src/services/_postgres.ts: -------------------------------------------------------------------------------- 1 | import pg from "pg"; 2 | 3 | type __PgPoolClient = pg.PoolClient | null | undefined; 4 | 5 | const connectionString = process.env.POSTGRES_URL; 6 | 7 | export class Postgres { 8 | public static __client: __PgPoolClient 9 | 10 | public static async createOrGet(): Promise<__PgPoolClient> { 11 | if (typeof window !== "undefined") { 12 | throw new Error("usage of pg on client-side is forbidden") 13 | } 14 | 15 | // Checking if the postgres pool is undefined or null. 16 | if (!Postgres.__client) { 17 | const pool = new pg.Pool({ connectionString }); 18 | Postgres.__client = await pool.connect(); 19 | } 20 | 21 | return Postgres.__client; 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/stores/useCountStore.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | interface Store { 4 | count: number; 5 | setCount(count: number): void; 6 | } 7 | 8 | export const useCountStore = create((set) => ({ 9 | count: 0, 10 | setCount: (count) => set({ count }), 11 | })); 12 | -------------------------------------------------------------------------------- /src/stores/useJoinedStore.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | interface Store { 4 | joined: number; 5 | setJoined(joined: number): void; 6 | } 7 | 8 | export const useJoinedStore = create((set) => ({ 9 | joined: 0, 10 | setJoined: (joined) => set({ joined }), 11 | })); 12 | -------------------------------------------------------------------------------- /src/styles/overrides.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/ui/components/JoinWaitlistForm.tsx: -------------------------------------------------------------------------------- 1 | import { useForm } from "react-hook-form"; 2 | import { useJoinedStore } from "~/stores/useJoinedStore"; 3 | import { Box, Button, FilledInput, FormControl, InputLabel, Stack } from "@mui/material"; 4 | import { useCountStore } from "~/stores/useCountStore"; 5 | 6 | interface Fields { 7 | email: string; 8 | }; 9 | 10 | const JoinWaitlistForm = () => { 11 | const { register, setError, handleSubmit, formState } = useForm({ mode: "onChange", criteriaMode: "firstError" }); 12 | const { errors, dirtyFields: changedFields, isDirty: isChanged, isSubmitting, isValid } = formState; 13 | const [count, setCount] = useCountStore((state) => [state.count, state.setCount]); 14 | const setJoined = useJoinedStore((state) => state.setJoined); 15 | 16 | const join = async (payload: Fields) => { 17 | if (isValid && !!changedFields && isChanged && !isSubmitting && !!errors) { 18 | const options = { method: "POST", body: JSON.stringify(payload) }; 19 | const request = await fetch("/api/join", options); 20 | const response = await request.json(); 21 | if (request.ok) { 22 | const { status } = request; 23 | switch (status) { 24 | case 500: 25 | return setError("email", { message: "Unknown error" }); 26 | case 403: 27 | return setError("email", { message: "Address already registered" }); 28 | case 400: 29 | return setError("email", { message: "Provide a correct email address" }); 30 | case 200: 31 | setJoined(1); setCount(count + 1); 32 | return; 33 | default: 34 | console.warn("unknown response case:", status); 35 | return; 36 | } 37 | } else { 38 | console.warn("unhandled error occurred:"); 39 | console.log({ request, response }); 40 | } 41 | } 42 | }; 43 | 44 | return ( 45 | 46 |
51 | 52 | 53 | Email address 54 | 58 | 59 | 60 | 63 | 64 |
65 |
66 | ); 67 | }; 68 | 69 | export default JoinWaitlistForm; 70 | 71 | -------------------------------------------------------------------------------- /src/ui/components/WaitlistCounter.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from "@mui/material"; 2 | import { useCountStore } from "~/stores/useCountStore"; 3 | import { useJoinedStore } from "~/stores/useJoinedStore"; 4 | 5 | const WaitlistCounter = () => { 6 | const count = useCountStore((state) => state.count); 7 | const joined = useJoinedStore((state) => Boolean(state.joined)); 8 | 9 | return ( 10 | 11 | 12 | {count > 0 ? ( 13 | <> 14 | {count} {count === 1 ? "person" : "people"}{joined && ", including yourself,"} {count > 1 ? "have" : "has"} already joined 15 | the waitlist! 16 | 17 | ) : ( 18 | "Nobody has joined the waitlist yet. You can be the first person!" 19 | )} 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default WaitlistCounter; 26 | 27 | -------------------------------------------------------------------------------- /src/ui/mui-theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from "@mui/material"; 2 | 3 | export const newCustomTheme = (mode: "light" | "dark") => createTheme({ 4 | typography: { 5 | fontFamily: "Syne", 6 | allVariants: { 7 | color: "whitesmoke" 8 | } 9 | }, 10 | 11 | components: { 12 | MuiButton: { 13 | defaultProps: { 14 | style: { 15 | fontWeight: "500", 16 | fontFamily: "Syne" 17 | } 18 | }, 19 | }, 20 | 21 | }, 22 | 23 | palette: { 24 | mode, 25 | primary: { 26 | main: "#A379C9", 27 | }, 28 | }, 29 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "noEmit": true, 5 | "allowJs": true, 6 | "target": "es5", 7 | "baseUrl": "src", 8 | "jsx": "preserve", 9 | "module": "esnext", 10 | "alwaysStrict": true, 11 | "skipLibCheck": true, 12 | "isolatedModules": true, 13 | "esModuleInterop": true, 14 | "inlineSourceMap": true, 15 | "resolveJsonModule": true, 16 | "moduleResolution": "node", 17 | "lib": [ 18 | "dom", 19 | "dom.iterable", 20 | "esnext" 21 | ], 22 | "forceConsistentCasingInFileNames": true, 23 | "incremental": true, 24 | "paths": { 25 | "~/*": [ 26 | "./*" 27 | ] 28 | } 29 | }, 30 | "include": [ 31 | "**/*.ts", 32 | "**/*.tsx", 33 | "next-env.d.ts", 34 | "tools/migrator/index.js" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } --------------------------------------------------------------------------------