diff --git a/app.json b/app.json
index d8e36a6e..6a1cdecc 100644
--- a/app.json
+++ b/app.json
@@ -17,10 +17,12 @@
"supportsTablet": true
},
"android": {
+ "softwareKeyboardLayoutMode": "pan",
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
- }
+ },
+ "softwareKeyboardLayoutMode": "pan"
},
"web": {
"favicon": "./assets/favicon.png"
diff --git a/assets/Frame 88.svg b/assets/Frame 88.svg
new file mode 100644
index 00000000..3b243e9f
--- /dev/null
+++ b/assets/Frame 88.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/bx_x.svg b/assets/bx_x.svg
new file mode 100644
index 00000000..2f3f79e8
--- /dev/null
+++ b/assets/bx_x.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/formkit_hidden.svg b/assets/formkit_hidden.svg
new file mode 100644
index 00000000..2df2a82c
--- /dev/null
+++ b/assets/formkit_hidden.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons.tsx b/assets/icons.tsx
index 03a92b5f..05654a56 100644
--- a/assets/icons.tsx
+++ b/assets/icons.tsx
@@ -6,12 +6,18 @@ export type IconType =
| 'home_outline'
| 'document_outline'
| 'search_outline'
- | 'close_modal_button';
+ | 'close_modal_button'
+ | 'red_x'
+ | 'green_check'
+ | 'hide_password'
+ | 'grey_dot'
+ | 'settings_gear';
const IconSvgs: Record = {
home_outline: ,
search_outline: ,
document_outline: ,
+ settings_gear: ,
close_modal_button: (
= {
`}
/>
),
+ red_x: (
+
+
+
+
+ `}
+ />
+ ),
+ green_check: (
+
+
+
+
+ `}
+ />
+ ),
+ hide_password: (
+
+
+
+
+
+
+ `}
+ />
+ ),
+ grey_dot: (
+
+
+
+
+ `}
+ />
+ ),
};
type Props = {
className?: string;
diff --git a/assets/material-symbols_check.svg b/assets/material-symbols_check.svg
new file mode 100644
index 00000000..ac8060ab
--- /dev/null
+++ b/assets/material-symbols_check.svg
@@ -0,0 +1,5 @@
+
diff --git a/package-lock.json b/package-lock.json
index d85cab47..c3b4e9ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,7 @@
"@mui/material": "^5.14.13",
"@mui/styled-engine-sc": "^6.0.0-alpha.1",
"@mui/system": "^5.14.13",
- "@react-native-async-storage/async-storage": "^1.18.2",
+ "@react-native-async-storage/async-storage": "^1.19.5",
"@react-native-community/datetimepicker": "7.2.0",
"@react-navigation/bottom-tabs": "^6.5.9",
"@react-navigation/material-bottom-tabs": "^6.2.17",
@@ -24,6 +24,7 @@
"@react-navigation/stack": "^6.3.18",
"@rneui/themed": "^4.0.0-rc.8",
"@supabase/supabase-js": "^2.36.0",
+ "@types/validator": "^13.11.5",
"axios": "^1.5.0",
"deprecated-react-native-prop-types": "^4.2.1",
"dom-parser": "^0.1.6",
@@ -40,6 +41,7 @@
"react-native-gesture-handler": "~2.12.0",
"react-native-htmlview": "^0.16.0",
"react-native-ionicons": "^4.6.5",
+ "react-native-otp-textinput": "^1.1.3",
"react-native-paper": "^5.10.6",
"react-native-render-html": "^6.3.4",
"react-native-root-siblings": "^4.1.1",
@@ -48,7 +50,8 @@
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-url-polyfill": "^2.0.0",
- "react-native-vector-icons": "^10.0.0",
+ "validator": "^13.11.0",
+ "react-native-vector-icons": "^10.0.2",
"react-scroll-to-top": "^3.0.0"
},
"devDependencies": {
@@ -61,6 +64,7 @@
"eslint-config-universe": "^12.0.0",
"husky": "^8.0.3",
"prettier": "^3.0.3",
+ "supabase": "^1.110.1",
"typescript": "^5.1.3"
}
},
@@ -4767,9 +4771,9 @@
}
},
"node_modules/@react-native-async-storage/async-storage": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.18.2.tgz",
- "integrity": "sha512-dM8AfdoeIxlh+zqgr0o5+vCTPQ0Ru1mrPzONZMsr7ufp5h+6WgNxQNza7t0r5qQ6b04AJqTlBNixTWZxqP649Q==",
+ "version": "1.19.5",
+ "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.5.tgz",
+ "integrity": "sha512-zLT7oNPXpW8BxJyHyq8AJbXtlHE/eonFWuJt44y0WeCGnp4KOJ8mfqD8mtAIKLyrYHHE1uadFe/s4C+diYAi8g==",
"dependencies": {
"merge-options": "^3.0.4"
},
@@ -7326,6 +7330,11 @@
"resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.20.tgz",
"integrity": "sha512-77Mq/2BeHU894J364dUv9tSwxxyCLtcX228Pc8TwZpP5bvOoMns+gZoftp3LYl3FBH8vChpWbuagKGiMki2c1A=="
},
+ "node_modules/@types/validator": {
+ "version": "13.11.5",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz",
+ "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q=="
+ },
"node_modules/@types/websocket": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.7.tgz",
@@ -8379,6 +8388,46 @@
"node": ">=0.6"
}
},
+ "node_modules/bin-links": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.3.tgz",
+ "integrity": "sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==",
+ "dev": true,
+ "dependencies": {
+ "cmd-shim": "^6.0.0",
+ "npm-normalize-package-bin": "^3.0.0",
+ "read-cmd-shim": "^4.0.0",
+ "write-file-atomic": "^5.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/bin-links/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/bin-links/node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@@ -8906,6 +8955,15 @@
"node": ">=6"
}
},
+ "node_modules/cmd-shim": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.2.tgz",
+ "integrity": "sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@@ -9298,6 +9356,15 @@
"resolved": "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz",
"integrity": "sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw=="
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.10",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
@@ -11209,6 +11276,29 @@
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/fetch-retry": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz",
@@ -11433,6 +11523,18 @@
"node": ">= 6"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "dev": true,
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/freeport-async": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz",
@@ -14996,6 +15098,25 @@
"node": ">= 0.10.5"
}
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -15063,6 +15184,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npm-normalize-package-bin": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",
+ "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/npm-package-arg": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz",
@@ -16289,6 +16419,17 @@
"react-native": "*"
}
},
+ "node_modules/react-native-otp-textinput": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/react-native-otp-textinput/-/react-native-otp-textinput-1.1.3.tgz",
+ "integrity": "sha512-C32Tz9spngydOpiyjI20SCDDJ5vGGkR0EBYM0ynVhyPZ8gUHZEfz7zxdtvGhAp+PKiU7d6rvI3i5ZpTQJpPg+g==",
+ "dependencies": {
+ "react": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react-native": "^0.72.4"
+ }
+ },
"node_modules/react-native-paper": {
"version": "5.10.6",
"resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.10.6.tgz",
@@ -16420,9 +16561,9 @@
}
},
"node_modules/react-native-vector-icons": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.0.tgz",
- "integrity": "sha512-efMOVbZIebY8RszZPzPBoXi9pvD/NFYmjIDYxRoc9LYSzV8rMJtT8FfcO2hPu85Rn2x9xktha0+qn0B7EqMAcQ==",
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.2.tgz",
+ "integrity": "sha512-ZwhUkJhIMkGL3cW7IT4sEEHu2AOzerqsRQ73UzXsB+ecBpVK5bRmp0XswiQleZKZalZfs/WIfWLXLfTQHcQo6A==",
"dependencies": {
"prop-types": "^15.7.2",
"yargs": "^16.1.1"
@@ -16554,6 +16695,15 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/read-cmd-shim": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz",
+ "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -17770,6 +17920,68 @@
"resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-8.2.5.tgz",
"integrity": "sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw=="
},
+ "node_modules/supabase": {
+ "version": "1.110.1",
+ "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.110.1.tgz",
+ "integrity": "sha512-pQVfbs/n8ZBDuSDv6YJKIH1Uh/QBRxjp6pLW52YkKgjgfwndlKwKuJoPiuWDxBkRG1QXxmCHi3Hk+JeNx9/FRg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "bin-links": "^4.0.1",
+ "https-proxy-agent": "^7.0.2",
+ "node-fetch": "^3.2.10",
+ "tar": "6.2.0"
+ },
+ "bin": {
+ "supabase": "bin/supabase"
+ },
+ "engines": {
+ "npm": ">=8"
+ }
+ },
+ "node_modules/supabase/node_modules/agent-base": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
+ "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/supabase/node_modules/https-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/supabase/node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "dev": true,
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -18583,6 +18795,14 @@
"builtins": "^1.0.3"
}
},
+ "node_modules/validator": {
+ "version": "13.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+ "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -18617,6 +18837,15 @@
"defaults": "^1.0.3"
}
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+ "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/package.json b/package.json
index d978c864..4f7a1498 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"@react-navigation/stack": "^6.3.18",
"@rneui/themed": "^4.0.0-rc.8",
"@supabase/supabase-js": "^2.36.0",
+ "@types/validator": "^13.11.5",
"axios": "^1.5.0",
"deprecated-react-native-prop-types": "^4.2.1",
"dom-parser": "^0.1.6",
@@ -44,6 +45,7 @@
"react-native-gesture-handler": "~2.12.0",
"react-native-htmlview": "^0.16.0",
"react-native-ionicons": "^4.6.5",
+ "react-native-otp-textinput": "^1.1.3",
"react-native-paper": "^5.10.6",
"react-native-render-html": "^6.3.4",
"react-native-root-siblings": "^4.1.1",
@@ -52,7 +54,8 @@
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-url-polyfill": "^2.0.0",
- "react-native-vector-icons": "^10.0.0",
+ "validator": "^13.11.0",
+ "react-native-vector-icons": "^10.0.2",
"react-scroll-to-top": "^3.0.0"
},
"devDependencies": {
@@ -65,6 +68,7 @@
"eslint-config-universe": "^12.0.0",
"husky": "^8.0.3",
"prettier": "^3.0.3",
+ "supabase": "^1.110.1",
"typescript": "^5.1.3"
},
"private": true
diff --git a/src/app/(tabs)/home/index.tsx b/src/app/(tabs)/home/index.tsx
index 63953427..a8a37052 100644
--- a/src/app/(tabs)/home/index.tsx
+++ b/src/app/(tabs)/home/index.tsx
@@ -1,102 +1,166 @@
-import { Link } from 'expo-router';
-import { Button, ScrollView, Text, View } from 'react-native';
+import { router } from 'expo-router';
+import { useEffect, useState } from 'react';
+import { Pressable, ScrollView, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import styles from './styles';
+import Icon from '../../../../assets/icons';
import ContentCard from '../../../components/ContentCard/ContentCard';
-import SearchCard from '../../../components/SearchCard/SearchCard';
+import PreviewCard from '../../../components/PreviewCard/PreviewCard';
+import RecentSearchCard from '../../../components/RecentSearchCard/RecentSearchCard';
+import { fetchUsername } from '../../../queries/profiles';
+import {
+ fetchFeaturedStoriesDescription,
+ fetchFeaturedStoryPreviews,
+ fetchNewStories,
+ fetchRecommendedStories,
+} from '../../../queries/stories';
+import { StoryCard, StoryPreview } from '../../../queries/types';
import globalStyles from '../../../styles/globalStyles';
-
-const dummyStories = [
- {
- title: 'The Witch and Her Wings',
- author: 'Victoria V.',
- image: '',
- author_image: '',
- tags: ['Non-fiction', 'Mysterious', 'Goofy', 'Silly', 'Adventurous'],
- },
- {
- title: "It's Carnival",
- author: 'Victoria V.',
- image: '',
- author_image: '',
- tags: ['Non-fiction', 'Mysterious'],
- },
- {
- title: "Akshay's Adventures",
- author: 'Victoria V.',
- image: '',
- author_image: '',
- tags: ['Non-fiction', 'Mysterious'],
- },
-];
+import { useSession } from '../../../utils/AuthContext';
function HomeScreen() {
+ const { user } = useSession();
+ const [username, setUsername] = useState('');
+ const [loading, setLoading] = useState(true);
+ const [featuredStories, setFeaturedStories] = useState([]);
+ const [featuredStoriesDescription, setFeaturedStoriesDescription] =
+ useState('');
+ const [recommendedStories, setRecommendedStories] = useState([]);
+ const [newStories, setNewStories] = useState([]);
+
+ useEffect(() => {
+ (async () => {
+ const [
+ usernameResponse,
+ featuredStoryResponse,
+ featuredStoryDescriptionResponse,
+ recommendedStoriesResponse,
+ newStoriesResponse,
+ ] = await Promise.all([
+ fetchUsername(user?.id).catch(() => ''),
+ fetchFeaturedStoryPreviews().catch(() => []),
+ fetchFeaturedStoriesDescription().catch(() => ''),
+ fetchRecommendedStories().catch(() => []),
+ fetchNewStories().catch(() => []),
+ ]);
+ setUsername(usernameResponse);
+ setFeaturedStories(featuredStoryResponse);
+ setFeaturedStoriesDescription(featuredStoryDescriptionResponse);
+ setRecommendedStories(recommendedStoriesResponse);
+ setNewStories(newStoriesResponse);
+ })().finally(() => {
+ setLoading(false);
+ });
+ }, [user]);
return (
-
+
+ {loading && (
+
+ Loading
+
+ )}
- Welcome, Brenda
-
-
-
-
- Featured Stories
-
- Celebrate lorem ipsum by diving into related stories from our talented
- authors.
-
-
- {dummyStories.map(story => (
- null}
- />
- ))}
+
+ {username ? `Welcome, ${username}` : 'Welcome!'}
+
+ router.push('/settings')}>
+
+
+
+
- Recommended For You
-
- {dummyStories.map(story => (
- null}
- image={story.image}
- />
- ))}
-
- New Stories
-
- {dummyStories.map(story => (
- null}
- image={story.image}
- />
- ))}
-
+ {featuredStories.length > 0 && (
+
+ Featured Stories
+
+ {featuredStoriesDescription}
+
+
+ {featuredStories.map(story => (
+
+ router.push({
+ pathname: '/story',
+ params: { storyId: story.id.toString() },
+ })
+ }
+ />
+ ))}
+
+
+ )}
+ {recommendedStories.length > 0 && (
+
+
+ Recommended For You
+
+
+ {recommendedStories.map(story => (
+
+ router.push({
+ pathname: '/story',
+ params: { storyId: story.id.toString() },
+ })
+ }
+ image={story.featured_media}
+ />
+ ))}
+
+
+ )}
+ {newStories.length > 0 && (
+
+
+ New Stories
+
+
+ {newStories.map(story => (
+
+ router.push({
+ pathname: '/story',
+ params: { storyId: story.id.toString() },
+ })
+ }
+ image={story.featured_media}
+ />
+ ))}
+
+
+ )}
);
diff --git a/src/app/(tabs)/home/styles.ts b/src/app/(tabs)/home/styles.ts
index 7372b237..14345b9a 100644
--- a/src/app/(tabs)/home/styles.ts
+++ b/src/app/(tabs)/home/styles.ts
@@ -1,21 +1,23 @@
-import { StyleSheet } from 'react-native';
+import { Dimensions, StyleSheet } from 'react-native';
+
+import colors from '../../../styles/colors';
+const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
- subheading: {
- fontSize: 16,
- fontWeight: '800',
- textAlign: 'left',
- lineHeight: 24,
- color: 'black',
- marginBottom: 12,
+ loading: {
+ backgroundColor: colors.white,
+ flex: 1,
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width,
+ height,
+ zIndex: 1,
+ padding: 50,
},
- featuredSubheading: {
- fontSize: 16,
- fontWeight: '800',
- textAlign: 'left',
- lineHeight: 24,
- color: 'black',
- marginBottom: 8,
+ subheading: {
+ marginTop: 12,
+ marginBottom: 16,
},
scrollView: {
marginBottom: 20,
@@ -30,8 +32,8 @@ const styles = StyleSheet.create({
},
featuredDescription: {
marginBottom: 16,
+ marginTop: 8,
},
- featuredContainer: {},
});
export default styles;
diff --git a/src/app/(tabs)/search/index.tsx b/src/app/(tabs)/search/index.tsx
index a18ca242..c8cc1952 100644
--- a/src/app/(tabs)/search/index.tsx
+++ b/src/app/(tabs)/search/index.tsx
@@ -1,26 +1,56 @@
+import AsyncStorage from '@react-native-async-storage/async-storage';
import { SearchBar } from '@rneui/themed';
-import { Link, router } from 'expo-router';
+import { router } from 'expo-router';
import React, { useEffect, useState } from 'react';
-import { Button, FlatList, View } from 'react-native';
+import { Button, FlatList, View, Text, Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import styles from './styles';
import FilterModal from '../../../components/FilterModal/FilterModal';
-import SearchCard from '../../../components/SearchCard/SearchCard';
+import SearchCard from '../../../components/PreviewCard/PreviewCard';
+import RecentSearchCard from '../../../components/RecentSearchCard/RecentSearchCard';
import { fetchAllStoryPreviews } from '../../../queries/stories';
-import { StoryPreview } from '../../../queries/types';
+import { StoryPreview, RecentSearch } from '../../../queries/types';
import globalStyles from '../../../styles/globalStyles';
+const getRecentSearch = async () => {
+ try {
+ const jsonValue = await AsyncStorage.getItem('GWN_RECENT_SEARCHES_ARRAY');
+ return jsonValue != null ? JSON.parse(jsonValue) : null;
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+const setRecentSearch = async (searchResult: RecentSearch[]) => {
+ try {
+ const jsonValue = JSON.stringify(searchResult);
+ await AsyncStorage.setItem('GWN_RECENT_SEARCHES_ARRAY', jsonValue);
+ } catch (error) {
+ console.log(error);
+ }
+};
+
function SearchScreen() {
const [allStories, setAllStories] = useState([]);
const [searchResults, setSearchResults] = useState([]);
const [search, setSearch] = useState('');
const [filterVisible, setFilterVisible] = useState(false);
+ const [recentSearches, setRecentSearches] = useState([]);
+
+ useEffect(() => {
+ (async () => {
+ const data: StoryPreview[] = await fetchAllStoryPreviews();
+ setAllStories(data);
+
+ setRecentSearches(await getRecentSearch());
+ })();
+ }, []);
const searchFunction = (text: string) => {
if (text === '') {
setSearch(text);
- setSearchResults(allStories);
+ setSearchResults([]);
return;
}
const updatedData = allStories.filter((item: StoryPreview) => {
@@ -33,61 +63,130 @@ function SearchScreen() {
setSearchResults(updatedData);
};
- useEffect(() => {
- (async () => {
- const data: StoryPreview[] = await fetchAllStoryPreviews();
- setAllStories(data);
- })();
- }, []);
+ const clearRecentSearches = () => {
+ setRecentSearches([]);
+ setRecentSearch([]);
+ };
+
+ const searchResultStacking = (searchString: string) => {
+ if (searchString !== '') {
+ const maxArrayLength = 5;
+
+ const newRecentSearches = recentSearches;
+
+ for (let i = 0; i < recentSearches.length; i++) {
+ if (searchString === recentSearches[i].value) {
+ newRecentSearches.splice(i, 1);
+ break;
+ }
+ }
+
+ if (newRecentSearches.length >= maxArrayLength) {
+ newRecentSearches.splice(-1, 1);
+ }
+
+ const result: RecentSearch = {
+ value: searchString,
+ numResults: searchResults.length,
+ };
+
+ newRecentSearches.splice(0, 0, result);
+
+ setRecentSearch(newRecentSearches);
+ setRecentSearches(newRecentSearches);
+ }
+ };
return (
-
+
- searchFunction(text)}
- value={search}
- />
-
);
}
diff --git a/src/app/(tabs)/search/styles.ts b/src/app/(tabs)/search/styles.ts
index f069ff55..f5818a65 100644
--- a/src/app/(tabs)/search/styles.ts
+++ b/src/app/(tabs)/search/styles.ts
@@ -1,16 +1,17 @@
import { Dimensions, StyleSheet } from 'react-native';
+import colors from '../../../styles/colors';
+
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
+ width: '100%',
+ marginTop: 24,
flex: 1,
- backgroundColor: 'white',
- justifyContent: 'flex-start',
- paddingLeft: 24,
- paddingRight: 24,
- paddingTop: 20,
- gap: 14,
+ },
+ default: {
+ marginHorizontal: 8,
},
searchContainer: {
backgroundColor: 'transparent',
@@ -43,6 +44,34 @@ const styles = StyleSheet.create({
width,
height,
},
+ recentSpacing: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginTop: 0,
+ marginBottom: 8,
+ marginHorizontal: 8,
+ },
+ searchText: {
+ fontWeight: '500',
+ fontSize: 14,
+ },
+ numDisplay: {
+ marginTop: 24,
+ marginBottom: 8,
+ },
+ clearAll: {
+ color: colors.gwnOrange,
+ fontSize: 12,
+ fontWeight: '400',
+ },
+ contentContainerRecents: {
+ paddingHorizontal: 8,
+ },
+ contentCotainerStories: {
+ paddingHorizontal: 8,
+ paddingBottom: 24,
+ },
});
export default styles;
diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index 3cefea46..92819933 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -1,17 +1,20 @@
import { Stack } from 'expo-router';
+import { SafeAreaProvider } from 'react-native-safe-area-context';
import { AuthContextProvider } from '../utils/AuthContext';
function StackLayout() {
return (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/app/auth/_layout.tsx b/src/app/auth/_layout.tsx
index ff3803ea..9d255f97 100644
--- a/src/app/auth/_layout.tsx
+++ b/src/app/auth/_layout.tsx
@@ -3,7 +3,6 @@ import { Stack } from 'expo-router';
function StackLayout() {
return (
-
diff --git a/src/app/auth/forgotPassword/_layout.tsx b/src/app/auth/forgotPassword/_layout.tsx
new file mode 100644
index 00000000..d021a461
--- /dev/null
+++ b/src/app/auth/forgotPassword/_layout.tsx
@@ -0,0 +1,11 @@
+import { Stack } from 'expo-router';
+
+function StackLayout() {
+ return (
+
+
+
+ );
+}
+
+export default StackLayout;
diff --git a/src/app/auth/forgotPassword.tsx b/src/app/auth/forgotPassword/index.tsx
similarity index 79%
rename from src/app/auth/forgotPassword.tsx
rename to src/app/auth/forgotPassword/index.tsx
index 18c9bc8c..ce73303d 100644
--- a/src/app/auth/forgotPassword.tsx
+++ b/src/app/auth/forgotPassword/index.tsx
@@ -1,10 +1,11 @@
import { router } from 'expo-router';
import React, { useState } from 'react';
-import { Alert, TextInput, View, StyleSheet } from 'react-native';
+import { Alert, TextInput, View } from 'react-native';
import { Button, Input } from 'react-native-elements';
-import globalStyles from '../../styles/globalStyles';
-import { useSession } from '../../utils/AuthContext';
+import styles from './styles';
+import globalStyles from '../../../styles/globalStyles';
+import { useSession } from '../../../utils/AuthContext';
function ForgotPasswordScreen() {
const { updateUser, signOut, resetPassword, verifyOtp } = useSession();
@@ -49,7 +50,6 @@ function ForgotPasswordScreen() {
const { error } = await updateUser({ password });
if (error) {
- console.error(error);
Alert.alert('Updating password failed');
} else {
await signOut();
@@ -60,8 +60,8 @@ function ForgotPasswordScreen() {
};
return (
-
-
+
+
-
+
@@ -84,13 +84,13 @@ function ForgotPasswordScreen() {
maxLength={6}
/>
-
+
{changingPassword && (
<>
-
+
-
+
- Auth
- router.replace('/auth/onboarding')}
- />
- router.replace('/auth/signup')} />
- router.push('/home')} />
-
- );
-}
-
-export default LoginScreen;
diff --git a/src/app/auth/login.tsx b/src/app/auth/login.tsx
deleted file mode 100644
index 572553fa..00000000
--- a/src/app/auth/login.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Link, router } from 'expo-router';
-import React, { useState } from 'react';
-import { Alert, View } from 'react-native';
-import { Button, Input } from 'react-native-elements';
-
-import globalStyles from '../../styles/globalStyles';
-import { useSession } from '../../utils/AuthContext';
-
-function LoginScreen() {
- const sessionHandler = useSession();
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
- const [loading, setLoading] = useState(false);
-
- const resetAndPushToRouter = (path: string) => {
- while (router.canGoBack()) {
- router.back();
- }
- router.replace(path);
- };
-
- const signInWithEmail = async () => {
- setLoading(true);
- const { error } = await sessionHandler.signInWithEmail(email, password);
-
- if (error) Alert.alert(error.message);
- else resetAndPushToRouter('/home');
- setLoading(false);
- };
-
- return (
-
-
- setEmail(text)}
- value={email}
- placeholder="email@address.com"
- autoCapitalize="none"
- />
-
-
- setPassword(text)}
- value={password}
- secureTextEntry
- placeholder="Password"
- autoCapitalize="none"
- />
-
- Forgot password?
-
-
-
- Don't have an account? Sign Up
-
- );
-}
-
-export default LoginScreen;
diff --git a/src/app/auth/login/_layout.tsx b/src/app/auth/login/_layout.tsx
new file mode 100644
index 00000000..d021a461
--- /dev/null
+++ b/src/app/auth/login/_layout.tsx
@@ -0,0 +1,11 @@
+import { Stack } from 'expo-router';
+
+function StackLayout() {
+ return (
+
+
+
+ );
+}
+
+export default StackLayout;
diff --git a/src/app/auth/login/index.tsx b/src/app/auth/login/index.tsx
new file mode 100644
index 00000000..d57f289d
--- /dev/null
+++ b/src/app/auth/login/index.tsx
@@ -0,0 +1,126 @@
+import { Link, router } from 'expo-router';
+import React, { useState } from 'react';
+import { Text, View } from 'react-native';
+import { Icon } from 'react-native-elements';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import validator from 'validator';
+
+import styles from './styles';
+import { isEmailUsed, queryEmailByUsername } from '../../../queries/auth';
+import globalStyles from '../../../styles/globalStyles';
+import { useSession } from '../../../utils/AuthContext';
+import StyledButton from '../../../components/StyledButton/StyledButton';
+import UserStringInput from '../../../components/UserStringInput/UserStringInput';
+
+function LoginScreen() {
+ const sessionHandler = useSession();
+ const [emailOrUsername, setEmailOrUsername] = useState('');
+ const [error, setError] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordTextHidden, setPasswordTextHidden] = useState(true);
+ const [loading, setLoading] = useState(false);
+
+ const resetAndPushToRouter = (path: string) => {
+ while (router.canGoBack()) {
+ router.back();
+ }
+ router.replace(path);
+ };
+
+ const signIn = async () => {
+ setLoading(true);
+
+ let email;
+ if (validator.isEmail(emailOrUsername)) {
+ email = emailOrUsername;
+
+ const used = await isEmailUsed(email);
+
+ if (!used) {
+ setError(
+ 'An account with that email does not exist. Please try again.',
+ );
+ setLoading(false);
+ return;
+ }
+ } else {
+ const { data, error } = await queryEmailByUsername(emailOrUsername);
+
+ if (data && data?.length > 0 && !error) {
+ email = data[0].email;
+ } else {
+ setError(
+ 'An account with that username does not exist. Please try again.',
+ );
+ setLoading(false);
+ return;
+ }
+ }
+ const { error } = await sessionHandler.signInWithEmail(email, password);
+
+ if (error)
+ setError('The username and/or password is incorrect. Please try again.');
+ else resetAndPushToRouter('/home');
+
+ setLoading(false);
+ };
+
+ return (
+
+
+ {'Read stories from \nyoung creators'}
+
+
+
+ setPasswordTextHidden(!passwordTextHidden)}
+ />
+
+
+
+ Forgot password?
+
+ {error && {error}}
+
+
+
+
+
+
+
+
+ Don't have an account?{' '}
+
+ Sign Up
+
+
+
+
+ );
+}
+
+export default LoginScreen;
diff --git a/src/app/auth/login/styles.tsx b/src/app/auth/login/styles.tsx
new file mode 100644
index 00000000..644dc93d
--- /dev/null
+++ b/src/app/auth/login/styles.tsx
@@ -0,0 +1,36 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ flex: {
+ justifyContent: 'space-between',
+ },
+ forgotPassword: {
+ fontSize: 12,
+ textDecorationLine: 'underline',
+ paddingTop: 18,
+ paddingBottom: 16,
+ fontWeight: '400',
+ },
+ link: {
+ fontWeight: '700',
+ textDecorationLine: 'underline',
+ },
+ redirectText: {
+ textAlign: 'center',
+ marginTop: 16,
+ marginBottom: 64,
+ },
+ title: {
+ lineHeight: 33,
+ paddingTop: 64,
+ marginBottom: 28,
+ fontSize: 24,
+ fontWeight: '700',
+ },
+ error: {
+ color: 'red',
+ },
+ icon: {
+ paddingRight: 10,
+ },
+});
diff --git a/src/app/auth/onboarding/_layout.tsx b/src/app/auth/onboarding/_layout.tsx
new file mode 100644
index 00000000..d021a461
--- /dev/null
+++ b/src/app/auth/onboarding/_layout.tsx
@@ -0,0 +1,11 @@
+import { Stack } from 'expo-router';
+
+function StackLayout() {
+ return (
+
+
+
+ );
+}
+
+export default StackLayout;
diff --git a/src/app/auth/onboarding.tsx b/src/app/auth/onboarding/index.tsx
similarity index 78%
rename from src/app/auth/onboarding.tsx
rename to src/app/auth/onboarding/index.tsx
index 7f3e914e..06ac3761 100644
--- a/src/app/auth/onboarding.tsx
+++ b/src/app/auth/onboarding/index.tsx
@@ -1,13 +1,14 @@
import DateTimePicker from '@react-native-community/datetimepicker';
import { Redirect, router } from 'expo-router';
import { useState, useEffect } from 'react';
-import { View, Alert, ScrollView, Platform } from 'react-native';
-import { Button, Input } from 'react-native-elements';
+import { Alert, ScrollView, Platform } from 'react-native';
+import { Button } from 'react-native-elements';
-import UserStringInput from '../../components/UserStringInput';
-import globalStyles from '../../styles/globalStyles';
-import { useSession } from '../../utils/AuthContext';
-import supabase from '../../utils/supabase';
+import styles from './styles';
+import UserStringInput from '../../../components/UserStringInput/UserStringInput';
+import { useSession } from '../../../utils/AuthContext';
+import supabase from '../../../utils/supabase';
+import StyledButton from '../../../components/StyledButton/StyledButton';
function OnboardingScreen() {
const { session } = useSession();
@@ -106,23 +107,31 @@ function OnboardingScreen() {
}
return (
-
-
-
-
+
+
-
+
@@ -146,16 +155,16 @@ function OnboardingScreen() {
}}
/>
)}
-
-
-
-
- router.replace('/home')} />
-
+
+ router.replace('/home')}
+ disabled={false}
+ />
);
}
diff --git a/src/app/auth/onboarding/styles.tsx b/src/app/auth/onboarding/styles.tsx
new file mode 100644
index 00000000..daea4952
--- /dev/null
+++ b/src/app/auth/onboarding/styles.tsx
@@ -0,0 +1,21 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ input: {
+ height: 40,
+ borderColor: 'gray',
+ borderWidth: 1,
+ marginTop: 10,
+ padding: 5,
+ },
+ verticallySpaced: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ alignSelf: 'stretch',
+ },
+ container: {
+ paddingVertical: 63,
+ paddingLeft: 43,
+ paddingRight: 44,
+ },
+});
diff --git a/src/app/auth/signup.tsx b/src/app/auth/signup.tsx
deleted file mode 100644
index 134d6c3e..00000000
--- a/src/app/auth/signup.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Redirect, Link, router } from 'expo-router';
-import React, { useState } from 'react';
-import { Alert, View } from 'react-native';
-import { Button, Input } from 'react-native-elements';
-
-import globalStyles from '../../styles/globalStyles';
-import { useSession } from '../../utils/AuthContext';
-
-function SignUpScreen() {
- const { session, signUp } = useSession();
-
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
- const [loading, setLoading] = useState(false);
-
- if (session) {
- return ;
- }
-
- const signUpWithEmail = async () => {
- setLoading(true);
- const { error } = await signUp(email, password);
-
- if (error) Alert.alert(error.message);
- else router.replace('/auth/verify');
-
- setLoading(false);
- };
-
- return (
-
-
- setEmail(text)}
- value={email}
- placeholder="email@address.com"
- autoCapitalize="none"
- />
-
-
-
- setPassword(text)}
- value={password}
- secureTextEntry
- placeholder="Password"
- autoCapitalize="none"
- />
-
- Already have an account? Log In
-
-
-
-
-
- );
-}
-
-export default SignUpScreen;
diff --git a/src/app/auth/signup/_layout.tsx b/src/app/auth/signup/_layout.tsx
new file mode 100644
index 00000000..d021a461
--- /dev/null
+++ b/src/app/auth/signup/_layout.tsx
@@ -0,0 +1,11 @@
+import { Stack } from 'expo-router';
+
+function StackLayout() {
+ return (
+
+
+
+ );
+}
+
+export default StackLayout;
diff --git a/src/app/auth/signup/index.tsx b/src/app/auth/signup/index.tsx
new file mode 100644
index 00000000..6812b857
--- /dev/null
+++ b/src/app/auth/signup/index.tsx
@@ -0,0 +1,318 @@
+import { Link, router } from 'expo-router';
+import React, { useEffect, useRef, useState } from 'react';
+import { Alert, Text, View } from 'react-native';
+import { Icon as RNEIcon } from 'react-native-elements';
+import { ScrollView } from 'react-native-gesture-handler';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import validator from 'validator';
+
+import styles from './styles';
+import Icon from '../../../../assets/icons';
+import StyledButton from '../../../components/StyledButton/StyledButton';
+import UserStringInput from '../../../components/UserStringInput/UserStringInput';
+import colors from '../../../styles/colors';
+import globalStyles from '../../../styles/globalStyles';
+import { useSession } from '../../../utils/AuthContext';
+import supabase from '../../../utils/supabase';
+
+function SignUpScreen() {
+ const { signUp } = useSession();
+
+ const [usernameError, setUsernameError] = useState('');
+ const [emailError, setEmailError] = useState('');
+ const [username, setUsername] = useState('');
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordTextHidden, setPasswordTextHidden] = useState(true);
+ const [loading, setLoading] = useState(false);
+ let lastUsernameCheck = useRef(Date.now());
+ const validUsernameCharacters = /^\w+$/g;
+
+ const [passwordComplexity, setPasswordComplexity] = useState(false);
+ const [hasUppercase, setHasUppercase] = useState(false);
+ const [hasLowercase, setHasLowercase] = useState(false);
+ const [hasNumber, setHasNumber] = useState(false);
+ const [hasLength, setHasLength] = useState(false);
+
+ useEffect(() => {
+ if (hasUppercase && hasLowercase && hasNumber && hasLength) {
+ setPasswordComplexity(true);
+ } else {
+ setPasswordComplexity(false);
+ }
+ }, [hasUppercase, hasLowercase, hasNumber, hasLength]);
+
+ const setAndCheckUsername = async (newUsername: string) => {
+ const start = Date.now();
+ lastUsernameCheck.current = start;
+ setUsername(newUsername);
+
+ if (newUsername.length === 0) {
+ setUsernameError('');
+ return;
+ }
+
+ const usernameCharactersValid =
+ newUsername.length <= 12 &&
+ newUsername.match(validUsernameCharacters) !== null;
+
+ if (!usernameCharactersValid) {
+ setUsernameError(
+ 'Usernames must be 12 characters or less and may contain letters, numbers, or underscores.',
+ );
+ return;
+ }
+
+ const { count } = await supabase
+ .from('profiles')
+ .select(`*`, { count: 'exact' })
+ .limit(1)
+ .eq('username', newUsername);
+ const usernameIsTaken = (count ?? 0) >= 1;
+
+ if (lastUsernameCheck.current !== start) {
+ return;
+ }
+
+ if (usernameIsTaken) {
+ setUsernameError('That username is not available. Please try again.');
+ return;
+ }
+
+ setUsernameError('');
+ };
+
+ const setAndCheckEmail = async (newEmail: string) => {
+ setEmail(newEmail);
+ if (newEmail.length === 0) {
+ setEmailError('');
+ return;
+ }
+
+ if (!validator.isEmail(newEmail)) {
+ setEmailError('This email is not a valid email. Please try again.');
+ return;
+ }
+
+ const { count } = await supabase
+ .from('profiles')
+ .select(`*`, { count: 'exact' })
+ .limit(1)
+ .eq('email', newEmail);
+ const emailIsTaken = (count ?? 0) >= 1;
+
+ if (emailIsTaken) {
+ setEmailError('That email is not available. Please try again.');
+ return;
+ }
+
+ setEmailError('');
+ };
+
+ const checkPassword = (text: string) => {
+ setPassword(text);
+ if (text !== '') {
+ if (text !== text.toLowerCase()) {
+ setHasUppercase(true);
+ } else {
+ setHasUppercase(false);
+ }
+ if (text !== text.toUpperCase()) {
+ setHasLowercase(true);
+ } else {
+ setHasLowercase(false);
+ }
+ if (/[0-9]/.test(text)) {
+ setHasNumber(true);
+ } else {
+ setHasNumber(false);
+ }
+ if (text.length >= 8) {
+ setHasLength(true);
+ } else {
+ setHasLength(false);
+ }
+ }
+ };
+
+ const signUpWithEmail = async () => {
+ setLoading(true);
+ if (usernameError) {
+ Alert.alert('Invalid username');
+ return;
+ }
+
+ const { error } = await signUp(email, password, {
+ username,
+ first_name: firstName,
+ last_name: lastName,
+ });
+
+ if (error) Alert.alert(error.message);
+ else router.replace('/auth/verify');
+
+ setLoading(false);
+ };
+
+ return (
+
+
+
+
+ {'Read stories from \nyoung creators'}
+
+
+
+ {usernameError && (
+ {usernameError}
+ )}
+
+
+
+
+ {emailError && {emailError}}
+
+ {
+ setPassword(text);
+ checkPassword(text);
+ }}
+ value={password}
+ attributes={{
+ textContentType: 'password',
+ secureTextEntry: passwordTextHidden,
+ }}
+ >
+ setPasswordTextHidden(!passwordTextHidden)}
+ />
+
+
+
+ {password !== '' && (
+
+
+
+ At least 1 uppercase letter
+
+
+ )}
+ {password !== '' && (
+
+
+
+ At least 1 lowercase letter
+
+
+ )}
+ {password !== '' && (
+
+
+
+ At least 1 number
+
+
+ )}
+ {password !== '' && (
+
+
+
+ At least 8 characters
+
+
+ )}
+
+
+
+
+
+
+ Already have an account?{' '}
+
+ Log In
+
+
+
+
+
+ );
+}
+
+export default SignUpScreen;
diff --git a/src/app/auth/signup/styles.tsx b/src/app/auth/signup/styles.tsx
new file mode 100644
index 00000000..63d3b557
--- /dev/null
+++ b/src/app/auth/signup/styles.tsx
@@ -0,0 +1,51 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ flex: {
+ flexGrow: 1,
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ },
+ inputError: {
+ color: 'red',
+ marginTop: 8,
+ },
+ link: {
+ fontWeight: '700',
+ textDecorationLine: 'underline',
+ },
+ redirectText: {
+ textAlign: 'center',
+ marginBottom: 64,
+ marginTop: 16,
+ },
+ title: {
+ fontSize: 24,
+ paddingTop: 64,
+ marginBottom: 23,
+ fontWeight: '700',
+ },
+ icon: {
+ marginRight: 10,
+ },
+ passwordComplexity: {
+ display: 'flex',
+ flexDirection: 'row',
+ paddingBottom: 8,
+ },
+ passwordErrorText: {
+ fontSize: 12,
+ marginLeft: 8,
+ },
+ verticallySpaced: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ alignSelf: 'stretch',
+ },
+ inputs: {
+ paddingBottom: 8,
+ },
+ navigation: {
+ // marginTop: 'auto',
+ },
+});
diff --git a/src/app/auth/verify/_layout.tsx b/src/app/auth/verify/_layout.tsx
new file mode 100644
index 00000000..d021a461
--- /dev/null
+++ b/src/app/auth/verify/_layout.tsx
@@ -0,0 +1,11 @@
+import { Stack } from 'expo-router';
+
+function StackLayout() {
+ return (
+
+
+
+ );
+}
+
+export default StackLayout;
diff --git a/src/app/auth/verify.tsx b/src/app/auth/verify/index.tsx
similarity index 66%
rename from src/app/auth/verify.tsx
rename to src/app/auth/verify/index.tsx
index 89cb3181..e2d9679a 100644
--- a/src/app/auth/verify.tsx
+++ b/src/app/auth/verify/index.tsx
@@ -1,16 +1,28 @@
import { router } from 'expo-router';
import React, { useState } from 'react';
-import { Alert, TextInput, View, StyleSheet } from 'react-native';
+import { Alert, TextInput, View } from 'react-native';
import { Button } from 'react-native-elements';
+import { SafeAreaView } from 'react-native-safe-area-context';
-import globalStyles from '../../styles/globalStyles';
-import { useSession } from '../../utils/AuthContext';
+import styles from './styles';
+import globalStyles from '../../../styles/globalStyles';
+import { useSession } from '../../../utils/AuthContext';
function VerificationScreen() {
const { user, verifyOtp, resendVerification } = useSession();
const [loading, setLoading] = useState(false);
const [verificationCode, setCode] = useState('');
+ // let otpInput = useRef(null);
+
+ // const clearText = () => {
+ // otpInput.current.clear();
+ // }
+
+ // const setText = () => {
+ // otpInput.current.setValue("1234");
+ // }
+
const verifyAccount = async () => {
setLoading(true);
@@ -32,9 +44,8 @@ function VerificationScreen() {
setLoading(true);
if (user?.email) {
- const { error, data } = await resendVerification(user.email);
+ const { error } = await resendVerification(user.email);
- console.log(data);
if (error) Alert.alert(error.message);
else Alert.alert(`Verification email sent to ${user.email}.`);
} else {
@@ -45,7 +56,7 @@ function VerificationScreen() {
};
return (
-
+
-
-
+
+
-
+
-
+
);
}
export default VerificationScreen;
-
-const styles = StyleSheet.create({
- input: {
- height: 40,
- borderColor: 'gray',
- borderWidth: 1,
- marginTop: 10,
- padding: 5,
- },
-});
diff --git a/src/app/auth/verify/styles.tsx b/src/app/auth/verify/styles.tsx
new file mode 100644
index 00000000..119cf0bb
--- /dev/null
+++ b/src/app/auth/verify/styles.tsx
@@ -0,0 +1,19 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ input: {
+ height: 40,
+ borderColor: 'gray',
+ borderWidth: 1,
+ marginTop: 10,
+ padding: 5,
+ },
+ verticallySpaced: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ alignSelf: 'stretch',
+ },
+ container: {
+ justifyContent: 'flex-start',
+ },
+});
diff --git a/src/app/settings.tsx b/src/app/settings.tsx
index 5330ed38..133c866c 100644
--- a/src/app/settings.tsx
+++ b/src/app/settings.tsx
@@ -1,11 +1,12 @@
import DateTimePicker from '@react-native-community/datetimepicker';
import { Redirect, router } from 'expo-router';
import { useEffect, useState } from 'react';
-import { Text, View, Alert, Platform } from 'react-native';
+import { Text, StyleSheet, View, Alert, Platform } from 'react-native';
import { Button, Input } from 'react-native-elements';
import { SafeAreaView } from 'react-native-safe-area-context';
-import UserStringInput from '../components/UserStringInput';
+import StyledButton from '../components/StyledButton/StyledButton';
+import UserStringInput from '../components/UserStringInput/UserStringInput';
import globalStyles from '../styles/globalStyles';
import { useSession } from '../utils/AuthContext';
import supabase from '../utils/supabase';
@@ -119,22 +120,30 @@ function SettingsScreen() {
return (
Settings
-
-
-
+
-
+
@@ -158,16 +167,22 @@ function SettingsScreen() {
}}
/>
)}
-
-
-
-
+
+
);
}
+const styles = StyleSheet.create({
+ verticallySpaced: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ alignSelf: 'stretch',
+ },
+});
+
export default SettingsScreen;
diff --git a/src/components/ContentCard/ContentCard.tsx b/src/components/ContentCard/ContentCard.tsx
index 54aa5dbd..99e99f13 100644
--- a/src/components/ContentCard/ContentCard.tsx
+++ b/src/components/ContentCard/ContentCard.tsx
@@ -27,7 +27,7 @@ function ContentCard({
-
+
{title}
diff --git a/src/components/ContentCard/styles.ts b/src/components/ContentCard/styles.ts
index 877e0416..1ea6f118 100644
--- a/src/components/ContentCard/styles.ts
+++ b/src/components/ContentCard/styles.ts
@@ -1,5 +1,7 @@
import { StyleSheet } from 'react-native';
+import colors from '../../styles/colors';
+
const styles = StyleSheet.create({
contentCard: {
marginRight: 20,
@@ -9,7 +11,7 @@ const styles = StyleSheet.create({
image: {
height: 140,
width: 148,
- backgroundColor: '#D9D9D9',
+ backgroundColor: colors.lime,
borderRadius: 4,
marginBottom: 8,
},
diff --git a/src/components/PreviewCard/PreviewCard.tsx b/src/components/PreviewCard/PreviewCard.tsx
new file mode 100644
index 00000000..5de2d2ce
--- /dev/null
+++ b/src/components/PreviewCard/PreviewCard.tsx
@@ -0,0 +1,73 @@
+import {
+ GestureResponderEvent,
+ Image,
+ Pressable,
+ Text,
+ View,
+} from 'react-native';
+
+import styles from './styles';
+import globalStyles from '../../styles/globalStyles';
+
+type PreviewCardProps = {
+ title: string;
+ image: string;
+ author: string;
+ authorImage: string;
+ excerpt: { html: string };
+ tags: string[];
+ pressFunction: (event: GestureResponderEvent) => void;
+};
+
+function PreviewCard({
+ title,
+ image,
+ author,
+ authorImage,
+ excerpt,
+ tags,
+ pressFunction,
+}: PreviewCardProps) {
+ return (
+
+
+
+
+
+
+ {title}
+
+
+
+
+ {author}
+
+
+
+ {excerpt.html.slice(3, -3)}
+
+
+
+ {/*
+
+ {tags.map(tag => (
+
+ {tag}
+
+ ))}
+
+
+
+ more tags
+
+
+ */}
+
+
+ );
+}
+
+export default PreviewCard;
diff --git a/src/components/SearchCard/styles.ts b/src/components/PreviewCard/styles.ts
similarity index 55%
rename from src/components/SearchCard/styles.ts
rename to src/components/PreviewCard/styles.ts
index 8c72ed95..6b2454fc 100644
--- a/src/components/SearchCard/styles.ts
+++ b/src/components/PreviewCard/styles.ts
@@ -4,20 +4,42 @@ import colors from '../../styles/colors';
const styles = StyleSheet.create({
card: {
+ flexDirection: 'column',
+ justifyContent: 'flex-end',
+ backgroundColor: colors.white,
+ borderRadius: 6,
+ marginTop: 8,
+ marginBottom: 8,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.5,
+ elevation: 4,
+ paddingRight: 8,
+ },
+ top: {
+ flex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
- alignItems: 'center',
- backgroundColor: colors.lightGrey,
- borderRadius: 4,
- marginBottom: 16,
- maxHeight: 100,
+ alignItems: 'flex-start',
paddingLeft: 12,
paddingRight: 12,
},
+ bottom: {
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'flex-start',
+ alignItems: 'flex-start',
+ height: 48,
+ backgroundColor: colors.lightGrey,
+ overflow: 'hidden',
+ borderRadius: 6,
+ paddingHorizontal: 12,
+ paddingTop: 8,
+ },
image: {
- height: 76,
- width: 84,
- backgroundColor: colors.darkGrey,
+ height: 96,
+ width: 96,
+ backgroundColor: colors.lilac,
borderRadius: 4,
marginBottom: 12,
marginTop: 12,
@@ -26,27 +48,25 @@ const styles = StyleSheet.create({
marginLeft: 8,
},
authorImage: {
- height: 21,
- width: 21,
- backgroundColor: colors.darkGrey,
- borderRadius: 21 / 2,
+ height: 22,
+ width: 22,
+ backgroundColor: colors.gwnOrange,
+ borderRadius: 22 / 2,
},
cardTextContainer: {
flex: 1,
- overflow: 'hidden',
marginLeft: 16,
marginTop: 12,
- marginBottom: 12,
- maxHeight: 80,
+ marginBottom: 8,
},
authorContainer: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
- marginBottom: 4,
+ marginBottom: 12,
},
title: {
- marginBottom: 4,
+ marginBottom: 8,
},
tag: {
paddingHorizontal: 8,
@@ -55,14 +75,14 @@ const styles = StyleSheet.create({
borderRadius: 10,
width: 'auto',
marginRight: 8,
- marginBottom: 8,
+ marginBottom: 10,
},
tagsContainer: {
+ // backgroundColor: colors.darkGrey,
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'wrap',
- overflow: 'hidden',
},
});
diff --git a/src/components/RecentSearchCard/RecentSearchCard.tsx b/src/components/RecentSearchCard/RecentSearchCard.tsx
new file mode 100644
index 00000000..eb77fcc6
--- /dev/null
+++ b/src/components/RecentSearchCard/RecentSearchCard.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { GestureResponderEvent, Pressable, View, Text } from 'react-native';
+import Icon from 'react-native-vector-icons/AntDesign';
+
+import styles from './styles';
+import globalStyles from '../../styles/globalStyles';
+
+type RecentSearchCardProps = {
+ value: string;
+ numResults: number;
+ pressFunction: (event: GestureResponderEvent) => void;
+};
+
+function RecentSearchCard({
+ value,
+ numResults,
+ pressFunction,
+}: RecentSearchCardProps) {
+ return (
+
+
+
+
+ {value}
+
+
+ {numResults} Results
+
+
+
+
+ );
+}
+
+export default RecentSearchCard;
diff --git a/src/components/RecentSearchCard/styles.ts b/src/components/RecentSearchCard/styles.ts
new file mode 100644
index 00000000..676937de
--- /dev/null
+++ b/src/components/RecentSearchCard/styles.ts
@@ -0,0 +1,45 @@
+import { StyleSheet } from 'react-native';
+
+const styles = StyleSheet.create({
+ card: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ backgroundColor: 'white',
+ borderRadius: 4,
+ marginTop: 8,
+ marginBottom: 8,
+ paddingLeft: 12,
+ paddingRight: 12,
+ paddingBottom: 10,
+ paddingTop: 10,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.5,
+ elevation: 4,
+ },
+ leftItems: {
+ gap: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ rightItems: {
+ gap: 10,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ searchValueText: {
+ color: 'black',
+ fontWeight: '400',
+ fontSize: 14,
+ justifyContent: 'center',
+ },
+ numResultsText: {
+ color: '#A7A5A5',
+ fontWeight: '400',
+ fontSize: 10,
+ justifyContent: 'center',
+ },
+});
+
+export default styles;
diff --git a/src/components/SearchCard/SearchCard.tsx b/src/components/SearchCard/SearchCard.tsx
deleted file mode 100644
index da160c1d..00000000
--- a/src/components/SearchCard/SearchCard.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import {
- GestureResponderEvent,
- Image,
- Pressable,
- Text,
- View,
-} from 'react-native';
-
-import styles from './styles';
-
-type SearchCardProps = {
- title: string;
- author: string;
- image: string;
- authorImage: string;
- tags: string[];
- pressFunction: (event: GestureResponderEvent) => void;
-};
-
-function SearchCard({
- title,
- author,
- image,
- authorImage,
- tags,
- pressFunction,
-}: SearchCardProps) {
- return (
-
-
-
-
- {title}
-
-
- {author}
-
-
- {tags.map(tag => (
-
- {tag}
-
- ))}
-
-
-
-
- );
-}
-
-export default SearchCard;
diff --git a/src/components/StyledButton/StyledButton.tsx b/src/components/StyledButton/StyledButton.tsx
new file mode 100644
index 00000000..1c6f2118
--- /dev/null
+++ b/src/components/StyledButton/StyledButton.tsx
@@ -0,0 +1,25 @@
+import { Button } from 'react-native-elements';
+
+import styles from './styles';
+
+type StyledButtonProps = {
+ disabled: boolean;
+ text: string;
+ onPress: () => void;
+};
+
+function StyledButton({ disabled, onPress, text }: StyledButtonProps) {
+ return (
+
+ );
+}
+
+export default StyledButton;
diff --git a/src/components/StyledButton/styles.tsx b/src/components/StyledButton/styles.tsx
new file mode 100644
index 00000000..49ad97db
--- /dev/null
+++ b/src/components/StyledButton/styles.tsx
@@ -0,0 +1,28 @@
+import { StyleSheet } from 'react-native';
+
+import colors from '../../styles/colors';
+
+export default StyleSheet.create({
+ disabledStyle: {
+ borderRadius: 5,
+ backgroundColor: colors.gwnOrangeDisabled,
+ },
+ buttonStyle: {
+ borderRadius: 5,
+ backgroundColor: colors.gwnOrange,
+ },
+ disabledTitleStyle: {
+ fontSize: 21,
+ fontWeight: '400',
+ paddingHorizontal: 24,
+ paddingVertical: 10,
+ color: 'white',
+ },
+ titleStyle: {
+ fontSize: 21,
+ fontWeight: '400',
+ paddingHorizontal: 24,
+ paddingVertical: 10,
+ color: 'white',
+ },
+});
diff --git a/src/components/UserStringInput.tsx b/src/components/UserStringInput.tsx
deleted file mode 100644
index d85ab21e..00000000
--- a/src/components/UserStringInput.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { View, StyleSheet } from 'react-native';
-import { Input } from 'react-native-elements';
-
-export const styles = StyleSheet.create({
- verticallySpaced: {
- paddingTop: 4,
- paddingBottom: 4,
- alignSelf: 'stretch',
- },
-});
-
-type UserStringInputProps = {
- label: string;
- value: string | undefined;
- onChange: React.Dispatch>;
-};
-
-export default function UserStringInput({
- label,
- value,
- onChange,
-}: UserStringInputProps) {
- return (
-
- onChange(text)}
- />
-
- );
-}
diff --git a/src/components/UserStringInput/UserStringInput.tsx b/src/components/UserStringInput/UserStringInput.tsx
new file mode 100644
index 00000000..f345729e
--- /dev/null
+++ b/src/components/UserStringInput/UserStringInput.tsx
@@ -0,0 +1,39 @@
+import { ReactNode } from 'react';
+import { View, Text, TextInput } from 'react-native';
+
+import styles from './styles';
+
+type UserStringInputProps = {
+ placeholder: string;
+ value: string | null;
+ attributes?: TextInput['props'] | null;
+ children?: ReactNode;
+ label?: string;
+ onChange?: (text: string) => any;
+};
+
+export default function UserStringInput({
+ placeholder,
+ value,
+ attributes = {},
+ label,
+ children,
+ onChange = _ => {},
+}: UserStringInputProps) {
+ return (
+
+ {label && {label}}
+
+ onChange(e.nativeEvent.text)}
+ value={value ?? ''}
+ style={styles.inputField}
+ placeholder={placeholder}
+ placeholderTextColor="#000"
+ {...attributes}
+ />
+ {children}
+
+
+ );
+}
diff --git a/src/components/UserStringInput/styles.tsx b/src/components/UserStringInput/styles.tsx
new file mode 100644
index 00000000..e5c28efc
--- /dev/null
+++ b/src/components/UserStringInput/styles.tsx
@@ -0,0 +1,35 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ mt16: {
+ marginTop: 16,
+ },
+ label: {
+ fontSize: 12,
+ fontStyle: 'normal',
+ fontWeight: '400',
+ },
+ container: {
+ paddingRight: 10,
+ marginTop: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderWidth: 1,
+ borderRadius: 5,
+ borderColor: 'black',
+ },
+ inputField: {
+ flex: 1,
+ fontSize: 14,
+ fontFamily: 'Manrope',
+ padding: 10,
+ color: '#000000',
+ },
+ button: {
+ backgroundColor: 'gray',
+ },
+ verticallySpaced: {
+ alignSelf: 'stretch',
+ },
+});
diff --git a/src/queries/auth.tsx b/src/queries/auth.tsx
new file mode 100644
index 00000000..9ef740f4
--- /dev/null
+++ b/src/queries/auth.tsx
@@ -0,0 +1,21 @@
+import supabase from '../utils/supabase';
+
+export const isEmailUsed = async (email: string) => {
+ const { count } = await supabase
+ .from('profiles')
+ .select(`*`, { count: 'exact' })
+ .eq('email', email);
+
+ console.log(count);
+ console.log(email);
+
+ return count !== 0;
+};
+
+export const queryEmailByUsername = async (username: string) => {
+ return await supabase
+ .from('profiles')
+ .select(`email`)
+ .limit(1)
+ .eq('username', username);
+};
diff --git a/src/queries/profiles.tsx b/src/queries/profiles.tsx
new file mode 100644
index 00000000..df1dcdca
--- /dev/null
+++ b/src/queries/profiles.tsx
@@ -0,0 +1,17 @@
+import supabase from '../utils/supabase';
+
+export async function fetchUsername(user_id: string | undefined) {
+ const { data, error } = await supabase
+ .from('profiles')
+ .select('username')
+ .eq('user_id', user_id);
+
+ if (error) {
+ console.log(error);
+ throw new Error(
+ `An error occured when trying to fetch username: ${error.details}`,
+ );
+ } else {
+ return data[0].username as string;
+ }
+}
diff --git a/src/queries/stories.tsx b/src/queries/stories.tsx
index f596fa15..68bfafc4 100644
--- a/src/queries/stories.tsx
+++ b/src/queries/stories.tsx
@@ -1,4 +1,4 @@
-import { Story, StoryPreview } from './types';
+import { Story, StoryPreview, StoryCard } from './types';
import supabase from '../utils/supabase';
export async function fetchAllStoryPreviews(): Promise {
@@ -29,3 +29,57 @@ export async function fetchStory(storyId: number): Promise {
return data;
}
}
+
+export async function fetchFeaturedStoryPreviews(): Promise {
+ const { data, error } = await supabase.rpc('fetch_featured_story_previews');
+
+ if (error) {
+ console.log(error);
+ throw new Error(
+ `An error occured when trying to fetch featured story previews: ${error}`,
+ );
+ } else {
+ return data;
+ }
+}
+
+export async function fetchFeaturedStoriesDescription(): Promise {
+ const { data, error } = await supabase
+ .from('featured_stories')
+ .select('description');
+
+ if (error) {
+ console.log(error);
+ throw new Error(
+ `An error occured when trying to fetch featured story description: ${error}`,
+ );
+ } else {
+ return data[0].description;
+ }
+}
+
+export async function fetchRecommendedStories(): Promise {
+ const { data, error } = await supabase.rpc('fetch_recommended_stories');
+
+ if (error) {
+ console.log(error);
+ throw new Error(
+ `An error occured when trying to fetch recommended stories: ${error}`,
+ );
+ } else {
+ return data;
+ }
+}
+
+export async function fetchNewStories(): Promise {
+ const { data, error } = await supabase.rpc('fetch_new_stories');
+
+ if (error) {
+ console.log(error);
+ throw new Error(
+ `An error occured when trying to fetch new stories: ${error}`,
+ );
+ } else {
+ return data;
+ }
+}
diff --git a/src/queries/types.tsx b/src/queries/types.tsx
index a0079025..cecfdf42 100644
--- a/src/queries/types.tsx
+++ b/src/queries/types.tsx
@@ -27,3 +27,14 @@ export interface Story {
process: { html: string };
link: string;
}
+
+export interface StoryCard {
+ id: number;
+ title: string;
+ author_name: string;
+ featured_media: string;
+}
+export interface RecentSearch {
+ value: string;
+ numResults: number;
+}
diff --git a/src/styles/colors.ts b/src/styles/colors.ts
index dff608c5..32463846 100644
--- a/src/styles/colors.ts
+++ b/src/styles/colors.ts
@@ -1,16 +1,23 @@
const colors = {
// Primary colors
- redPrimary: '#CC502F',
- bluePrimary: '#356095',
+ gwnOrange: '#EB563B',
+ gwnOrangeDisabled: 'rgba(235, 86, 59, 0.4)',
+ lilac: '#B49BC6',
+ lime: '#ACC073',
+ melanin: '#703929',
+ citrus: '#E66E3F',
// Text colors
textPrimary: '#000000', // black
textSecondary: '#484848', // gray for subtitles
textWhite: '#FFFFFF', // white
+ textGreen: '#5A7906', //for sign in
+ textGrey: '#797979', // for sign in
// Surface colors -- used for backgrounds
darkGrey: '#D9D9D9',
lightGrey: '#F9F9F9',
+ white: '#FEFEFA',
// Shadow colors
shadowDark: '#000000', // black
diff --git a/src/styles/globalStyles.ts b/src/styles/globalStyles.ts
index cbe46541..57d49c3f 100644
--- a/src/styles/globalStyles.ts
+++ b/src/styles/globalStyles.ts
@@ -9,65 +9,63 @@ export default StyleSheet.create({
paddingLeft: 24,
paddingRight: 24,
},
- auth_container: {
- marginTop: 40,
- padding: 12,
+ authContainer: {
+ marginHorizontal: 38,
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'space-between',
},
h1: {
// fontFamily: 'DMSans-Bold',
- fontSize: 50,
- fontWeight: '700',
+ fontSize: 40,
+ fontWeight: '400',
textAlign: 'left',
color: 'black',
},
h2: {
// fontFamily: 'DMSans-Bold',
- fontSize: 40,
- fontWeight: '400',
+ fontSize: 32,
+ fontWeight: '600',
textAlign: 'left',
color: 'black',
},
h3: {
// fontFamily: 'DMSans-Regular',
- fontSize: 32,
- fontWeight: '400',
+ fontSize: 20,
+ fontWeight: '600',
textAlign: 'left',
color: 'black',
},
h4: {
// fontFamily: 'DMSans-Regular',
- fontSize: 24,
- fontWeight: '800',
+ fontSize: 16,
+ fontWeight: '500',
textAlign: 'left',
color: 'black',
+ lineHeight: 22,
},
body1: {
// fontFamily: 'DMSans-Regular',
- fontSize: 12,
+ fontSize: 16,
fontWeight: '400',
textAlign: 'left',
color: 'black',
- lineHeight: 18,
+ lineHeight: 22,
},
body2: {
// fontFamily: 'DMSans-Regular',
- fontSize: 10,
+ fontSize: 14,
fontWeight: '400',
textAlign: 'left',
color: 'black',
},
body3: {
fontFamily: 'Avenir',
- fontSize: 8,
+ fontSize: 12,
fontWeight: '400',
textAlign: 'left',
color: 'black',
},
- verticallySpaced: {
- paddingTop: 4,
- paddingBottom: 4,
- alignSelf: 'stretch',
- },
mt20: {
marginTop: 20,
},
diff --git a/src/utils/AuthContext.tsx b/src/utils/AuthContext.tsx
index 88ec9dd1..4273a1cb 100644
--- a/src/utils/AuthContext.tsx
+++ b/src/utils/AuthContext.tsx
@@ -21,7 +21,11 @@ export interface AuthState {
user: User | null;
isLoading: boolean;
signIn: (newSession: Session | null) => void;
- signUp: (email: string, password: string) => Promise;
+ signUp: (
+ email: string,
+ password: string,
+ options: object,
+ ) => Promise;
signInWithEmail: (email: string, password: string) => Promise;
verifyOtp: (email: string, token: string) => Promise;
resendVerification: (email: string) => Promise;
@@ -68,6 +72,7 @@ export function AuthContextProvider({
.getSession()
.then(({ data: { session: newSession } }) => {
setSession(newSession);
+ setUser(newSession ? newSession.user : null);
})
.finally(() => {
setIsLoading(false);
@@ -92,13 +97,17 @@ export function AuthContextProvider({
return value;
};
- const signUp = async (email: string, password: string) => {
+ const signUp = async (email: string, password: string, metaData: object) => {
const value = await supabase.auth.signUp({
email,
password,
- }); // will trigger the use effect to update the session
+ options: {
+ data: {
+ ...metaData,
+ },
+ },
+ });
- console.log(value);
setUser(value.data.user);
return value;
};