diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ed785b5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotenv.enableAutocloaking": false +} \ No newline at end of file diff --git a/example-auth/README.md b/example-auth/README.md index 708c31e..5b4fd9f 100644 --- a/example-auth/README.md +++ b/example-auth/README.md @@ -1,4 +1,5 @@ # Appwrite + ReactJS =❤️ + This example is to showcase [Appwrite's JS API](https://github.com/appwrite/sdk-for-js) with [React](https://reactjs.org/) by creating a simple login/register page. ## Prerequisites @@ -8,29 +9,36 @@ This example is to showcase [Appwrite's JS API](https://github.com/appwrite/sdk- - [A locally running appwrite instance](https://appwrite.io/docs/installation). ## Getting Started + To get started quickly we will use [Vite](https://vitejs.dev/) to create the boilerplate that our code will be built on. ```shell yarn create vite cd appwrite-react ``` + And then follow the creation process, in our case we're going to use the `react-typescript` template. While we are in the CLI we will also install the Appwrite JS API by running: + ```shell yarn add appwrite ``` + and finally we will launch the React development server with: + ```shell yarn dev ``` + This should launch a server on `localhost:5173` with Live Reload. ## Introducing the Appwrite SDK + With the boilerplate now complete we can now initialise the Appwrite SDK in the project before working on the login page. To keep things clean we will initialise this in it's own file, we will create this file in `src/` and call it `appwrite.ts`. Within this file go ahead and paste the following code: ```ts -import { Client } from "appwrite" +import { Client } from "appwrite"; const client = new Client() .setEndpoint(import.meta.env.VITE_APPWRITE_URL) @@ -49,6 +57,7 @@ VITE_APPWRITE_PROJECT= Now, you can export the initialized client or keep the Appwrite functions in this file. ## Creating the App.tsx + We are now going to replace the `src/App.tsx` with our own. For this example, we're going to use [react-router](https://reactrouter.com/en/main) to develop our SPA. First of all, install `react-router` @@ -60,19 +69,19 @@ yarn add react-router-dom@latest Then, in our `main.tsx` file: ```tsx -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import "./index.css" -import { BrowserRouter } from "react-router-dom" +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; +import { BrowserRouter } from "react-router-dom"; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( -) +); ``` Great! Now we have a functional router for our SPA. @@ -80,27 +89,27 @@ Great! Now we have a functional router for our SPA. Back to our `App.tsx` file... ```tsx -import { Routes, Route } from "react-router-dom" -import Layout from './components/layout' -import SignUp from './components/signup' -import LogIn from './components/login' -import Home from "./components/home" +import { Routes, Route } from "react-router-dom"; +import Layout from "./components/layout"; +import SignUp from "./components/signup"; +import LogIn from "./components/login"; +import Home from "./components/home"; function App() { return ( <> - }> + }> } /> - } /> - } /> + } /> + } /> - ) + ); } -export default App +export default App; ``` In this file we're going to just define the routes we want to use. We'll explain them later. But before that, let's write the functions that connect our site with Appwrite. @@ -116,13 +125,13 @@ So, let's create our functions. ```ts export const getUserData = async () => { try { - const account = new Account(client) - return account.get() + const account = new Account(client); + return account.get(); } catch (error) { const appwriteError = error as AppwriteException; - throw new Error(appwriteError.message) + throw new Error(appwriteError.message); } -} +}; ``` This function will get the current user's preferences, but, if there's an error, will throw it as an `AppwriteException`. @@ -132,13 +141,13 @@ This function will get the current user's preferences, but, if there's an error, ```ts export const login = async (email: string, password: string) => { try { - const account = new Account(client) - return account.createEmailSession(email, password) + const account = new Account(client); + return account.createEmailSession(email, password); } catch (error) { const appwriteError = error as AppwriteException; - throw new Error(appwriteError.message) + throw new Error(appwriteError.message); } -} +}; ``` This function will create a session from the email and password we pass to it, if they get a match in the database. If not, throws an `AppwriteException`. @@ -148,13 +157,13 @@ This function will create a session from the email and password we pass to it, i ```ts export const logout = async () => { try { - const account = new Account(client) - return account.deleteSession('current') + const account = new Account(client); + return account.deleteSession("current"); } catch (error: unknown) { const appwriteError = error as AppwriteException; - throw new Error(appwriteError.message) + throw new Error(appwriteError.message); } -} +}; ``` This function will logout the user deleting the current session, throwing an `AppwriteException` if there's an error. @@ -164,21 +173,21 @@ This function will logout the user deleting the current session, throwing an `Ap ```ts export const register = async (email: string, password: string) => { try { - const account = new Account(client) - return account.create('unique()', email, password) + const account = new Account(client); + return account.create("unique()", email, password); } catch (error) { const appwriteError = error as AppwriteException; - throw new Error(appwriteError.message) + throw new Error(appwriteError.message); } -} +}; ``` This function will create a new user from their email and password. Note the `'unique()'` parameter, as it's explained in Appwrite docs, if we aren't using a custom ID generating solution, using this key phrase will tell Appwrite to generate a random one. And that's all!, you can tell that all of our functions are asynchronous, and that's because we don't want to block the main execution thread while asking the server something and freeze all the site. - ## Creating the pages + Now we're ready to do some React stuff. Let's get there. ### Log in page @@ -187,45 +196,43 @@ In our `src/components` folder, we create a new `login.tsx` file and write the f ```tsx import { FormEvent, useState } from "react"; -import { useNavigate } from "react-router-dom" +import { useNavigate } from "react-router-dom"; import { login } from "../appwrite"; export default function LogIn() { const [email, setEmail] = useState(); const [password, setPassword] = useState(); - const navigate = useNavigate() + const navigate = useNavigate(); const handleSubmit = (event: FormEvent) => { event.preventDefault(); if (!email) { - alert('Email is required.') + alert("Email is required."); return; } if (!password) { - alert('Password is required.') + alert("Password is required."); return; } login(email, password) - .then((account) => alert(`Successfully logged in from: ${account.osName}`)) - .finally(() => navigate('/')) - } + .then((account) => + alert(`Successfully logged in from: ${account.osName}`) + ) + .finally(() => navigate("/")); + }; return (
- + setEmail(e.target.value)} /> - + Log In
- ) + ); } ``` @@ -255,37 +262,35 @@ export default function SignUp() { const handleSubmit = (event: FormEvent) => { event.preventDefault(); if (!email) { - alert('Email is required.') + alert("Email is required."); return; } if (!password) { - alert('Password is required.') + alert("Password is required."); return; } if (password.length < 8) { - alert('Password must be at least 8 characters long.') + alert("Password must be at least 8 characters long."); return; } - register(email, password).then((account) => alert(`Successfully created account with ID: ${account.$id}`)) - } + register(email, password).then((account) => + alert(`Successfully created account with ID: ${account.$id}`) + ); + }; return (
- + setEmail(e.target.value)} /> - + Sign up
- ) + ); } ``` @@ -306,30 +311,30 @@ If you want, you can create a custom form component to avoid repeating code. ```tsx import { Models } from "appwrite"; -import { useNavigate } from "react-router-dom" +import { useNavigate } from "react-router-dom"; import { useEffect, useState } from "react"; import { getUserData, logout } from "../appwrite"; export default function Home() { - const navigate = useNavigate() - const [user, setUser] = useState>() + const navigate = useNavigate(); + const [user, setUser] = useState>(); useEffect(() => { getUserData() .then((account) => setUser(account)) - .catch((error) => navigate('/login')) - }, []) + .catch((error) => navigate("/login")); + }, []); - const handleLogOut = () => logout().then(() => navigate('/login')) + const handleLogOut = () => logout().then(() => navigate("/login")); - if (!user) return

You aren't logged in.

+ if (!user) return

You aren't logged in.

; return (

Logged in as {user.email}

- ) + ); } ``` @@ -338,9 +343,9 @@ Our home page is only accessible by authenticated users, as you can see in our ` ### Layout ```tsx -import { Outlet, Link } from "react-router-dom" -import appwriteLogo from "../../public/appwrite.svg" -import Footer from "./footer" +import { Outlet, Link } from "react-router-dom"; +import appwriteLogo from "../../public/appwrite.svg"; +import Footer from "./footer"; export default function Layout() { return ( @@ -368,8 +373,8 @@ export default function Layout() {