### **PR Type** Enhancement ___ ### **Description** - Add cross-framework Todos CRUD examples - Introduce file upload/download tutorials - Provide unified AuthProvider context implementations - Include email templates and backend actions ___ <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>19 files</summary><table> <tr> <td><strong>Todos.tsx</strong><dd><code>Add web demo Todos CRUD page</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-53f3b3d582fef21d5ec90cb590b73afcf09407071dba60883ed1ed7360955fc5">+648/-0</a> </td> </tr> <tr> <td><strong>todos.tsx</strong><dd><code>Add React Native tutorial Todos</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3f6be5ef4f443091687addd404fe71f219498b9db7dea992d18d78b4f1b6ffa3">+561/-0</a> </td> </tr> <tr> <td><strong>commonStyles.ts</strong><dd><code>Add common React Native tutorial styles</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-bf0de16179ecc80a8e88e223c890dc2c73c30b4a9b7cadd62e910ca015ce342b">+667/-0</a> </td> </tr> <tr> <td><strong>Todos.tsx</strong><dd><code>Add React tutorial Todos page</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-be700e4847b0a745821f156c381e583097f2083123065a45a20611c2ba1876a7">+504/-0</a> </td> </tr> <tr> <td><strong>files.tsx</strong><dd><code>Add React Native tutorial file upload</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-d116805053a943f271a3297d06d14dba39c0f5775080e67e1e9e2778c176e9da">+454/-0</a> </td> </tr> <tr> <td><strong>Files.tsx</strong><dd><code>Add React tutorial Files page</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-cdbca4ed68690df84463df7765dff52c85a60502f175c19519c8b42474e9282c">+404/-0</a> </td> </tr> <tr> <td><strong>signup.tsx</strong><dd><code>Enhance signup flow with email verification</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-262b09b9dd7234cad96fe092d7131a23451c9e50b98c126c9e36599b3a127ac6">+185/-141</a></td> </tr> <tr> <td><strong>FilesClient.tsx</strong><dd><code>Add Next.js Files client component</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ea74386f232b9ae7e7957ab4eb1f0d1d6076b338173e8b1e917369fb7f1b39bb">+359/-0</a> </td> </tr> <tr> <td><strong>TodosClient.tsx</strong><dd><code>Add Next.js Todos client component</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-96efb4db6bd61a7faee3c383e77b092eccee1a1876770c36691e7356268cfad4">+368/-0</a> </td> </tr> <tr> <td><strong>AuthProvider.tsx</strong><dd><code>Implement React AuthProvider context</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-4cc7e41420d71d448eeec4f77043e0c5bff2c606986439454dade5ffcd433e33">+174/-0</a> </td> </tr> <tr> <td><strong>AuthProvider.tsx</strong><dd><code>Implement RN AuthProvider context</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-93f9f27a35d0039a64cd6889a296d04d37542aa5a777925e61e8e60ee5a6d744">+148/-0</a> </td> </tr> <tr> <td><strong>actions.ts</strong><dd><code>Add Next.js server actions for Todos</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-76d57097940d3043c8e0ab29761767861b78fe86ab8a90a2d8209f1818131d31">+223/-0</a> </td> </tr> <tr> <td><strong>signup.tsx</strong><dd><code>Add RN signup tutorial screen</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-153c2f0bf6b3744bb84b95e356dd78c8771206a1b22218bc4c6f90641e4143ad">+183/-0</a> </td> </tr> <tr> <td><strong>explore.tsx</strong><dd><code>Add RN tutorial Explore screen</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-980313b6c7f75f2ecd45fc476895bea122364d000f5988e21564bf5db73d7f57">+125/-0</a> </td> </tr> <tr> <td><strong>route.ts</strong><dd><code>Add Next.js file download route</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-5c39cd02d478ad625a7cdb6df3f7b6d20a76f40488636fdd87282c66174b2bd8">+57/-0</a> </td> </tr> <tr> <td><strong>SignUp.tsx</strong><dd><code>Enhance demo signup with verification state</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-75657efa0e1c29f59692ced3cd90e9c734836977900dc64015dd5d217bb263da">+38/-3</a> </td> </tr> <tr> <td><strong>SignUpForm.tsx</strong><dd><code>Add Next.js signup client form</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-9034cd412c47033a01e604a8250984aa1d1ecefc9884b79ebd7f7f3af17e3167">+89/-0</a> </td> </tr> <tr> <td><strong>SignInForm.tsx</strong><dd><code>Add Next.js signin client form</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-c276aadee4507d515832909164d447c3c1a4870277d0adbd1f7e836f7c66259e">+75/-0</a> </td> </tr> <tr> <td><strong>server.tsx</strong><dd><code>Add server Nhost client for Next.js</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-9d650defc223584e4aa06ddbf1d9a97c47a5a7ec4c9589a72ac7ea5369853400">+89/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>_layout.tsx</strong><dd><code>Configure RN tutorial tab navigation</code> </dd></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-c3b0bac088aef9f1a2d5cd2f4b51dd19fb301034668a062860a1e6a3512c15c9">+39/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>101 files</summary><table> <tr> <td><strong>examples_tutorials_checks.yaml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-745712dc409289d30fe4caa74de567df9cc9fac97750ae5566f93a51e48bf539">+94/-0</a> </td> </tr> <tr> <td><strong>docs.json</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-873ce17c654718debe2fe308a2f2279bde8663686423c51f97fab2dd0722b8d9">+52/-7</a> </td> </tr> <tr> <td><strong>overview.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-8c9b35da559a5de5fe14ee078573e8d487453e26ed760c03ffd7f0ad476ca24d">+5/-5</a> </td> </tr> <tr> <td><strong>nextjs.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a5210d45e7d33a57d43078dbe2a2ccbf0667b157291fd92c3986092d7d33ab9c">+0/-508</a> </td> </tr> <tr> <td><strong>1-introduction.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-35413705a524a3eb2c7d096daef02d660b479fbc288674aa260e3f159988652b">+116/-0</a> </td> </tr> <tr> <td><strong>2-protected-routes.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-d901b3a6d8a96e3070f27afb934c34365aa79aeee1505238de2cb77a9ffd8546">+1364/-0</a></td> </tr> <tr> <td><strong>3-user-authentication.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-7b96576ad1b1b6b8e50dd3889e24df391662df1b7b51a111f0239f655135939f">+804/-0</a> </td> </tr> <tr> <td><strong>4-graphql-operations.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-16906637a292d22c018167d45f2da67a17fe73155ca161de91c3b329fee9d399">+958/-0</a> </td> </tr> <tr> <td><strong>5-file-uploads.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-d6153781dbf251499f2ffeeb707102349776d788859c2da0f63c8b0a8e35c821">+822/-0</a> </td> </tr> <tr> <td><strong>react.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-6f5adda9f7b29d98c68cab6ec754c0bac501666a49dd635ee830789e2c812b68">+0/-497</a> </td> </tr> <tr> <td><strong>1-introduction.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-4dac916b2b27af668674965d9680f4ba8d2a417f7b155d4b052b1043fc71beb6">+116/-0</a> </td> </tr> <tr> <td><strong>2-protected-routes.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-8ab0206ae4a4c73e540cf549fdbdd9de4ed7c8d59cc88ea26a4a7ddc0a9a4460">+1435/-0</a></td> </tr> <tr> <td><strong>3-user-authentication.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-e02466ba1abd5d457724c7508da083a94b16d700cab36384567592c7d772cbf6">+647/-0</a> </td> </tr> <tr> <td><strong>4-graphql-operations.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ae759670f7bf513c66bc08952556514e7193f23eb7cc536aebf38ad79e12c02f">+856/-0</a> </td> </tr> <tr> <td><strong>5-file-uploads.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-02dc5fa1da67b0478bb797ed99c89685be3ec4509023441fb29b730ba74fff4e">+715/-0</a> </td> </tr> <tr> <td><strong>reactnative.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a85b3aa1caaeaa0a2cb6219ca531f89aaf2b23b41cd424c189b3cb948af1fd57">+0/-443</a> </td> </tr> <tr> <td><strong>1-introduction.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-573f696fb008f0e047e97209a1595b1f5c69dd5a1fe4907b65d6883273c53096">+121/-0</a> </td> </tr> <tr> <td><strong>2-protected-routes.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-b4ab0ba037b62e9eb5ee10cf5f56fd1c148f1e053f95291e42113d9f924ae711">+1499/-0</a></td> </tr> <tr> <td><strong>3-user-authentication.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-dda89bc27be58e7fa58aef9aeb937dce455f69d9566e734a10ef4e0df993ab42">+805/-0</a> </td> </tr> <tr> <td><strong>4-graphql-operations.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-59a46fa74310dc797cdfd6e1a34d09fd8002e9accdade9d865b898419addc2bc">+881/-0</a> </td> </tr> <tr> <td><strong>5-file-uploads.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-cb48bca7fdd422250b2482167b9a136d76ab60d2d5efccb94fb1a022b814d449">+776/-0</a> </td> </tr> <tr> <td><strong>6-sign-in-with-apple.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a4da2a574e88122505df17fcf1f9705841a8240b33f60b6fcde86e068b8b6cfe">+684/-0</a> </td> </tr> <tr> <td><strong>1-introduction.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-1eb8289a9ffc0484d2f8f21ede882c9e892d169f36b69f2ed9af940e2d1b8faa">+116/-0</a> </td> </tr> <tr> <td><strong>2-protected-routes.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-bff6efc64eeda0dae0d2a133ab6c9be9dfe725ee24b364b89060ec93344ad436">+1308/-0</a></td> </tr> <tr> <td><strong>3-user-authentication.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-f6bac1bb9476f921272f61911c703529663fb6ff37217c38d1702a1c8b70171a">+578/-0</a> </td> </tr> <tr> <td><strong>4-graphql-operations.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3a43d1ebc0b4fd7f74699c4f33ea110bba5ffa2695e6228dccafbfa38798c633">+763/-0</a> </td> </tr> <tr> <td><strong>5-file-uploads.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-2244e838acdbe909772f82f66cc63800aa9dbf5fae292a40d2d4f663d6618dd4">+642/-0</a> </td> </tr> <tr> <td><strong>sveltekit.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-9ed4ad735d1142a65a2da2dbbd8d46c508b7aef3d032cdb102d0f329602c4ce1">+0/-497</a> </td> </tr> <tr> <td><strong>vue.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-f6c4215fa6909fd3accebe0691a7364d17befb8ef90da5a4aeaee83d598c0540">+0/-504</a> </td> </tr> <tr> <td><strong>1-introduction.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-9485f8a6ad6398b19aa7a3739af3eef5158a7d285dc5a55e8bc6c1d58970fb84">+116/-0</a> </td> </tr> <tr> <td><strong>2-protected-routes.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-5ba36ef4388ad18b7efc6d405e2b66a69b063d483d4521d6acb5b5da65aee97a">+1368/-0</a></td> </tr> <tr> <td><strong>3-user-authentication.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-63f56a07c48f78462b47f74420e58cb8411d46aef880cfdf24b11ddea4bb382e">+637/-0</a> </td> </tr> <tr> <td><strong>4-graphql-operations.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ad7a84d0c924b946d992f5277cde596fb9d42eff021b888b5a8046822afabca8">+828/-0</a> </td> </tr> <tr> <td><strong>5-file-uploads.mdx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-00c26651dea4650fd77222a24b31387569067783a87d9c6715935e2bf133428e">+720/-0</a> </td> </tr> <tr> <td><strong>auth_user_security_keys.yaml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-385036f3c18b6efd97138842e95f992a0569f612f627e71091c10952d8d31609">+2/-11</a> </td> </tr> <tr> <td><strong>auth_users.yaml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-aba47a0c058f0a0a8122dee4d7b4394c4a8997bac9d4ccea04c41f0d00819050">+0/-24</a> </td> </tr> <tr> <td><strong>storage_files.yaml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ea0619f75a1f7dd13ca80d81cc2e28529594a73fff0356dc0b1d49ee0d1cc9ad">+17/-36</a> </td> </tr> <tr> <td><strong>tables.yaml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a57c35142b6c4029fad7d5c8407305a61d18078cd0e264d41286625cf5cc4d30">+1/-5</a> </td> </tr> <tr> <td><strong>down.sql</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-d7be59389936d96f1c3b10f2147b177d488906d2155139536fae635201859c3e">+1/-0</a> </td> </tr> <tr> <td><strong>up.sql</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-69c6c175ba9819f7b41d6a27e78fd0390e222665c9cc95fbb7fde702f14a1f4c">+26/-0</a> </td> </tr> <tr> <td><strong>nhost.toml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-45587ca86b6441ceacd1318e72a60ee0c30c70c00edb0a06cf061e404998f3bc">+4/-4</a> </td> </tr> <tr> <td><strong>actions.ts</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a0279ec5849eb0c93ec7c2f444fbecd2b681f47d954a84da5221f72d9805c2ca">+12/-0</a> </td> </tr> <tr> <td><strong>page.tsx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-8fffbc65277c93c49eea289ae1174cdba0632e5a295b74668437e9b1b0669097">+61/-30</a> </td> </tr> <tr> <td><strong>middleware.ts</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-817666e0f29fa78d2d7f77ed93cff4efbc8d85a996ab58d0dd13ad61e47c9125">+5/-3</a> </td> </tr> <tr> <td><strong>App.tsx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-849f3aa52970f348de49a27094aac4e4b8cb8cf29580cada70d37f1a04249725">+2/-0</a> </td> </tr> <tr> <td><strong>Navigation.tsx</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-9845c755953914963f404a14104d93cd326f78e48614ece8c4d9df3ab1600ffd">+3/-0</a> </td> </tr> <tr> <td><strong>index.css</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-8e415aa656412ff29b45f56035e84691f3abcc924bfb5b4e3cd0c9ba5237543b">+200/-0</a> </td> </tr> <tr> <td><strong>+page.svelte</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-00da2a0f738ffa5c66a07437c7e85cacb7780551bad7e81899d905f065bf0892">+29/-8</a> </td> </tr> <tr> <td><strong>main.css</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-1d2ba653024753e96f2db949f818dccba45498456146171c4a2f883b58177d2f">+373/-0</a> </td> </tr> <tr> <td><strong>Navigation.vue</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-27e2f662a54c47023a1342005bcf92602a846ad7a59e9d7d528aa8fbf40a0250">+7/-0</a> </td> </tr> <tr> <td><strong>index.ts</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3bf427bc13a501168d321cf4bd428d09d8c3a042ef3be955c7a2e99bbf5f39dd">+7/-0</a> </td> </tr> <tr> <td><strong>SignUp.vue</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-1838012876d16508d53d0345615b91c7c6fbccca3c9333d2311ac5fcd7b4c210">+28/-3</a> </td> </tr> <tr> <td><strong>Todos.vue</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3334950776983589e7061d69be930b71663329a6ba56726bcbd3f1c2352dab47">+466/-0</a> </td> </tr> <tr> <td><strong>Makefile</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-85a3083c78e211e9eb36d741342bcbc85a1a0c375060f45c5426b560196de27f">+2/-2</a> </td> </tr> <tr> <td><strong>.secrets.example</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-be7a988f18be877344f8584befc094ec2e66fe7784a2007300053707ad8ec7f1">+16/-0</a> </td> </tr> <tr> <td><strong>Makefile</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-36623bab6fe16382fd3e61b06b9586f2b14bea7c1b492e50db14ea98935016a4">+7/-0</a> </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ed02e93d01774c3796335c83badc90cbc8a2035040965031946b69c8d91db3dd">+29/-0</a> </td> </tr> <tr> <td><strong>env-up.sh</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-b6846a8b7ff5eea35ba5ff2aac8773f526c118ef59712a750f69578f25afda54">+8/-0</a> </td> </tr> <tr> <td><strong>package.json</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-f0d63d57cf77fdb7f2526d10c2d32d51ed2b6d43e3495d3f8879499e0c04f04b">+13/-0</a> </td> </tr> <tr> <td><strong>tsconfig.json</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-cad0e83f030efa9fcad494c7797838c5099909f2c2ae0de0210812b966ffa6db">+11/-0</a> </td> </tr> <tr> <td><strong>config.yaml</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-997042f08b088725275a28a7e2d275b86e2e74f3c972800e8116785d5d0fea59">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3c921023f82fc74f5b657cb92bc8906ea19d7eb8f746713e4fae2a5a894a8a59">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-5f67fb54ebc23bfccd1e23fbb7dc955bad8ad9011fae19369c87bf3f4d3e58fa">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3c21997bc2db49d5714afc007f214316d17eb69d8ea70dd1cfe558bdb47df4bf">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-e3ab8737d4a9ea68a115f28ce578b7502107f68212d92f035bb86178ccdfb6fe">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ed34cbd1c3e612ac3124ccce382b84ea7cda7eb22051b07102b68f5c28c58e97">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-b8cead9967aaddc7c52be376fcd54b27c6ec223445e0063729c7255ab6c4f5fc">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-295cddc1774338f5b6ba396ef5ca9a0832b9b2346703ec22abf3b3874f903cc2">+43/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-de3e6c264684c0dddb2f6e70dcb236fe8cb1a27789c01a11b2a707c110ce7fc2">+1/-0</a> </td> </tr> <tr> <td><strong>body.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-ae93f5f97d52d1d42be91b50e2a44522aef884065559f993f781f1ebeaa89b66">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-13511e1d49a2796e2ee0bb2082f02ed59c49bef7f7533735e60ac62ad16cc2c6">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-293b460447946e2de5e4ba74cf64895a4f6fff7d879bb2e555c46ad7edd857a8">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-843300abe10319ac2b6ccffdd8daf3445b8eaa839486acb95566d398cad4e053">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-8ff22c43814b945c0a22ea9089755a144e54039bdd12a2f06a128b4b155b3e38">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-f9e07ca4b5b9ae54e1668c3dfb8ac32612df366eb7d44819163f17c792507295">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-4f829f73c5bce9bcbb67ed1357a111cdf9974c36d2db22493067d201b35f4cdf">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-64f6273ffcec137bbad9a104cd46e6a8776915a804bb3f89c24701071cbb2c50">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-e19bb81615465996e46da50eacac7fddf71dd8bb18200c410b1837575bd76f95">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a202edc97b202ec41b83decb7b358a6beb89f3edef46194b576ecaada51f073d">+43/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-c2338d16f093d53b8a86b9a859c7bef0d9ef2c83a762d46cd49ab9e2f1f2a773">+1/-0</a> </td> </tr> <tr> <td><strong>body.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-7fff5a89330dd91d3c9781e37d8f1e1f48bfc58c686b463b7fb1f43a2d01813d">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-c9031a1f7ac5ad3d0c3c94cff2f563560c1e3c05d1acc00353e603f3dcac439e">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-847d33f3faf4a7f928445eb389ba8aa24901b9025d4bae0c38becd5e9365a881">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-3f5c827ff0c5a3270dc4c7737b0afd09327800104cb379ed2a556029eb58aef3">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-e499fcf53b46507a64be23ae309eba07e2c3ee289be4de316bd8ee83c16ea42e">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-5a6337adab234155f13e6b7d63eac5c654d5c75f4ea207c9db16b67685ce3e43">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-b8f9c01ea13429ca9990a95e81ac6bedb273ec41198f34903584e9a68c22c6ef">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-5bfe4c29aee11546871f9127b5c5a954f313c64d2344cd4e6cd402fe22cf0ded">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-262bf98a3f4801aff364e19a4c00bf37197764454b274faf1dbd947506b843a7">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-8a4ad52e5c3cd679ede51f7d411ca5aaf89f2f519f49c540e09408bf13823323">+43/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-46f8a60e052ff7b9b3d8583f83f36b826aea9c7a24af9e9998cc0b699900b3b1">+1/-0</a> </td> </tr> <tr> <td><strong>body.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-03da104febe035cf096fccbce0dbbf32197d289de814810792a319154241200c">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-d7fff652e5e21d234b1ab733de23d179d5901ff82165ffbd9495b3a1ab1c7611">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-a84317c8ffe3b56140c923228666555d0e9e93c34eb5a8376b08ab97e6e4ae96">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-5025ef2a43d19c4f668177eea4e8002664a22522e11fef04aabbcdb2fbff3178">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-e941c399a4d0b15de5bf2b20e722c2e6ffc990ecdef15efc2367ae302a52ff63">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-126da45f26e2d9256a28fadd38f363277926de563e6b303027cbea24779c729c">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-4686c2e7f183e071c8ccd456189d9bd056ead4874078dba16b1a2b1e53c7e9e4">+1/-0</a> </td> </tr> <tr> <td><strong>body.html</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-331385785301bc1e55cfb6a3c707e3704b32d82a38f7ccd9aa4aa834a8569df4">+52/-0</a> </td> </tr> <tr> <td><strong>subject.txt</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-57d1b1020e0d2e327acca4154b0e4776f3a7a5db1d6c99edae3ee45561780c6a">+1/-0</a> </td> </tr> <tr> <td><strong>Additional files not shown</strong></td> <td><a href="https://github.com/nhost/nhost/pull/3469/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
432 lines
13 KiB
TypeScript
432 lines
13 KiB
TypeScript
import * as Linking from "expo-linking";
|
|
import { Link, router, useLocalSearchParams } from "expo-router";
|
|
import { useEffect, useState } from "react";
|
|
import {
|
|
ActivityIndicator,
|
|
KeyboardAvoidingView,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View,
|
|
} from "react-native";
|
|
import MagicLinkForm from "./components/MagicLinkForm";
|
|
import NativeLoginForm from "./components/NativeLoginForm";
|
|
import SocialLoginForm from "./components/SocialLoginForm";
|
|
import { useAuth } from "./lib/nhost/AuthProvider";
|
|
|
|
export default function SignUp() {
|
|
const { nhost, isAuthenticated } = useAuth();
|
|
const params = useLocalSearchParams();
|
|
|
|
const [email, setEmail] = useState<string>("");
|
|
const [password, setPassword] = useState<string>("");
|
|
const [displayName, setDisplayName] = useState<string>("");
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
const [appleAuthInProgress, setAppleAuthInProgress] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [success, setSuccess] = useState<boolean>(false);
|
|
const [activeTab, setActiveTab] = useState<
|
|
"password" | "magic" | "social" | "native"
|
|
>("password");
|
|
|
|
const magicLinkSent = params["magic"] === "success";
|
|
|
|
// If already authenticated, redirect to profile
|
|
useEffect(() => {
|
|
if (isAuthenticated) {
|
|
router.replace("/profile");
|
|
}
|
|
}, [isAuthenticated]);
|
|
|
|
const handleSubmit = async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
setSuccess(false);
|
|
|
|
try {
|
|
const response = await nhost.auth.signUpEmailPassword({
|
|
email,
|
|
password,
|
|
options: {
|
|
displayName,
|
|
redirectTo: Linking.createURL("verify"),
|
|
},
|
|
});
|
|
|
|
if (response.body?.session) {
|
|
// Successfully signed up and automatically signed in
|
|
router.replace("/profile");
|
|
} else {
|
|
// Verification email sent
|
|
setSuccess(true);
|
|
}
|
|
} catch (err) {
|
|
const errMessage =
|
|
err instanceof Error ? err.message : "An unexpected error occurred";
|
|
setError(`An error occurred during sign up: ${errMessage}`);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Social login is now handled by the SocialLoginForm component
|
|
|
|
return (
|
|
<KeyboardAvoidingView behavior="padding" style={styles.container}>
|
|
<ScrollView
|
|
contentContainerStyle={styles.scrollContainer}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
<Text style={styles.title}>Nhost SDK Demo</Text>
|
|
|
|
<View style={styles.card}>
|
|
{success ? (
|
|
<>
|
|
<Text style={styles.cardTitle}>Check Your Email</Text>
|
|
<View style={styles.messageContainer}>
|
|
<View style={styles.successMessageBox}>
|
|
<Text style={styles.successText}>
|
|
We've sent a verification link to{" "}
|
|
<Text style={styles.emailText}>{email}</Text>
|
|
</Text>
|
|
<Text style={styles.successText}>
|
|
Please check your email and click the verification link to
|
|
activate your account.
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity
|
|
style={styles.button}
|
|
onPress={() => router.replace("/signin")}
|
|
>
|
|
<Text style={styles.buttonText}>Back to Sign In</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Text style={styles.cardTitle}>Sign Up</Text>
|
|
|
|
{magicLinkSent ? (
|
|
<View style={styles.messageContainer}>
|
|
<Text style={styles.successText}>
|
|
Magic link sent! Check your email to sign in.
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={styles.secondaryButton}
|
|
onPress={() => router.setParams({ magic: "" })}
|
|
>
|
|
<Text style={styles.secondaryButtonText}>
|
|
Back to sign up
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<>
|
|
<View style={styles.tabContainer}>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.tabButton,
|
|
activeTab === "password" && styles.activeTab,
|
|
]}
|
|
onPress={() => setActiveTab("password")}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.tabText,
|
|
activeTab === "password" && styles.activeTabText,
|
|
]}
|
|
>
|
|
Password
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.tabButton,
|
|
activeTab === "magic" && styles.activeTab,
|
|
]}
|
|
onPress={() => setActiveTab("magic")}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.tabText,
|
|
activeTab === "magic" && styles.activeTabText,
|
|
]}
|
|
>
|
|
Magic Link
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.tabButton,
|
|
activeTab === "social" && styles.activeTab,
|
|
]}
|
|
onPress={() => setActiveTab("social")}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.tabText,
|
|
activeTab === "social" && styles.activeTabText,
|
|
]}
|
|
>
|
|
Social
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.tabButton,
|
|
activeTab === "native" && styles.activeTab,
|
|
]}
|
|
onPress={() => setActiveTab("native")}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.tabText,
|
|
activeTab === "native" && styles.activeTabText,
|
|
]}
|
|
>
|
|
Native
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={styles.form}>
|
|
{activeTab === "password" ? (
|
|
<>
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>Display Name</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={displayName}
|
|
onChangeText={setDisplayName}
|
|
placeholder="Enter your name"
|
|
autoCapitalize="words"
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>Email</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
placeholder="Enter your email"
|
|
keyboardType="email-address"
|
|
autoCapitalize="none"
|
|
autoComplete="email"
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>Password</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={password}
|
|
onChangeText={setPassword}
|
|
placeholder="Enter your password"
|
|
secureTextEntry
|
|
autoCapitalize="none"
|
|
/>
|
|
<Text style={styles.helperText}>
|
|
Password must be at least 8 characters long
|
|
</Text>
|
|
</View>
|
|
|
|
{error && <Text style={styles.errorText}>{error}</Text>}
|
|
|
|
<TouchableOpacity
|
|
style={styles.button}
|
|
onPress={handleSubmit}
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? (
|
|
<ActivityIndicator size="small" color="#fff" />
|
|
) : (
|
|
<Text style={styles.buttonText}>Sign Up</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</>
|
|
) : activeTab === "magic" ? (
|
|
<MagicLinkForm buttonLabel="Sign Up with Magic Link" />
|
|
) : activeTab === "social" ? (
|
|
<SocialLoginForm action="Sign Up" isLoading={isLoading} />
|
|
) : (
|
|
<NativeLoginForm
|
|
action="Sign Up"
|
|
isLoading={isLoading || appleAuthInProgress}
|
|
setAppleAuthInProgress={setAppleAuthInProgress}
|
|
/>
|
|
)}
|
|
</View>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</View>
|
|
|
|
<View style={styles.footer}>
|
|
<Text style={styles.footerText}>
|
|
Already have an account?{" "}
|
|
<Link href="/signin" style={styles.link}>
|
|
Sign In
|
|
</Link>
|
|
</Text>
|
|
</View>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: "#f5f5f5",
|
|
},
|
|
scrollContainer: {
|
|
flexGrow: 1,
|
|
justifyContent: "center",
|
|
padding: 20,
|
|
},
|
|
title: {
|
|
fontSize: 24,
|
|
fontWeight: "bold",
|
|
textAlign: "center",
|
|
marginBottom: 20,
|
|
color: "#333",
|
|
},
|
|
card: {
|
|
width: "100%",
|
|
maxWidth: 400,
|
|
backgroundColor: "#fff",
|
|
borderRadius: 10,
|
|
padding: 20,
|
|
alignSelf: "center",
|
|
shadowColor: "#000",
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 2,
|
|
},
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 3.84,
|
|
elevation: 5,
|
|
},
|
|
cardTitle: {
|
|
fontSize: 20,
|
|
fontWeight: "bold",
|
|
marginBottom: 20,
|
|
textAlign: "center",
|
|
},
|
|
tabContainer: {
|
|
flexDirection: "row",
|
|
marginBottom: 20,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: "#e2e8f0",
|
|
},
|
|
tabButton: {
|
|
flex: 1,
|
|
paddingVertical: 10,
|
|
alignItems: "center",
|
|
},
|
|
tabText: {
|
|
fontSize: 16,
|
|
color: "#718096",
|
|
},
|
|
activeTab: {
|
|
borderBottomWidth: 2,
|
|
borderBottomColor: "#6366f1",
|
|
},
|
|
activeTabText: {
|
|
color: "#6366f1",
|
|
fontWeight: "600",
|
|
},
|
|
form: {
|
|
width: "100%",
|
|
},
|
|
inputGroup: {
|
|
marginBottom: 15,
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
marginBottom: 5,
|
|
color: "#333",
|
|
},
|
|
input: {
|
|
height: 45,
|
|
borderWidth: 1,
|
|
borderColor: "#ddd",
|
|
borderRadius: 5,
|
|
paddingHorizontal: 10,
|
|
fontSize: 16,
|
|
backgroundColor: "#fafafa",
|
|
},
|
|
helperText: {
|
|
fontSize: 12,
|
|
color: "#666",
|
|
marginTop: 3,
|
|
},
|
|
errorText: {
|
|
color: "#e53e3e",
|
|
marginBottom: 10,
|
|
},
|
|
successText: {
|
|
color: "#38a169",
|
|
fontSize: 16,
|
|
textAlign: "center",
|
|
marginBottom: 15,
|
|
},
|
|
successMessageBox: {
|
|
backgroundColor: "#f0fff4",
|
|
borderColor: "#38a169",
|
|
borderWidth: 1,
|
|
borderRadius: 8,
|
|
padding: 16,
|
|
marginBottom: 20,
|
|
},
|
|
emailText: {
|
|
fontWeight: "bold",
|
|
color: "#2d3748",
|
|
},
|
|
messageContainer: {
|
|
alignItems: "center",
|
|
paddingVertical: 10,
|
|
},
|
|
button: {
|
|
backgroundColor: "#6366f1",
|
|
paddingVertical: 12,
|
|
borderRadius: 5,
|
|
alignItems: "center",
|
|
marginTop: 10,
|
|
},
|
|
buttonText: {
|
|
color: "#fff",
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
},
|
|
secondaryButton: {
|
|
backgroundColor: "#e2e8f0",
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 20,
|
|
borderRadius: 5,
|
|
alignItems: "center",
|
|
marginTop: 10,
|
|
},
|
|
secondaryButtonText: {
|
|
color: "#4a5568",
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
},
|
|
footer: {
|
|
marginTop: 20,
|
|
alignItems: "center",
|
|
},
|
|
footerText: {
|
|
color: "#666",
|
|
fontSize: 14,
|
|
},
|
|
link: {
|
|
color: "#6366f1",
|
|
fontWeight: "bold",
|
|
},
|
|
});
|