diff --git a/src/routes/docs/tutorials/+page.svelte b/src/routes/docs/tutorials/+page.svelte index 40429352e5..a57ffdab17 100644 --- a/src/routes/docs/tutorials/+page.svelte +++ b/src/routes/docs/tutorials/+page.svelte @@ -1,109 +1,125 @@ - - {title} - - - - - - - - - - - - + + {title} + + + + + + + + + + + +
-
-
-
-
-

Platforms

-
-
-
-
-
-
-

Client

- -
-
-
+
+
+
+
+

Platforms

+
+
+
+
+
+
+

Client

+ +
+
+
- -
+ + \ No newline at end of file diff --git a/src/routes/docs/tutorials/typescript/+layout.svelte b/src/routes/docs/tutorials/typescript/+layout.svelte new file mode 100644 index 0000000000..fb9fb3980f --- /dev/null +++ b/src/routes/docs/tutorials/typescript/+layout.svelte @@ -0,0 +1,10 @@ + + + diff --git a/src/routes/docs/tutorials/typescript/+layout.ts b/src/routes/docs/tutorials/typescript/+layout.ts new file mode 100644 index 0000000000..562b11506f --- /dev/null +++ b/src/routes/docs/tutorials/typescript/+layout.ts @@ -0,0 +1,11 @@ +import type { LayoutLoad } from './$types'; + +export const load: LayoutLoad = ({ url }) => { + const tutorials = import.meta.glob('./**/*.markdoc', { + eager: true + }); + return { + tutorials, + pathname: url.pathname + }; +}; diff --git a/src/routes/docs/tutorials/typescript/+page.ts b/src/routes/docs/tutorials/typescript/+page.ts new file mode 100644 index 0000000000..0402216193 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/+page.ts @@ -0,0 +1,6 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async () => { + throw redirect(303, '/docs/tutorials/typescript/step-1'); +}; diff --git a/src/routes/docs/tutorials/typescript/step-1/+page.markdoc b/src/routes/docs/tutorials/typescript/step-1/+page.markdoc new file mode 100644 index 0000000000..707c02a664 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-1/+page.markdoc @@ -0,0 +1,34 @@ +--- +layout: tutorial +title: Build an ideas tracker with TypeScript +description: Learn to build a TypeScript app with no backend code using an Appwrite backend. +step: 1 +difficulty: beginner +readtime: 10 +--- + +**Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish. +In this tutorial, you will build Idea tracker with Appwrite and TypeScript with React. + +{% only_dark %} +![Create project screen](/images/docs/tutorials/dark/idea-tracker.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/tutorials/idea-tracker.png) +{% /only_light %} + +# Concepts {% #concepts %} + +This tutorial will introduce the following concepts: + +1. Setting up your first project +2. Authentication +3. Databases and collections +4. Queries and pagination +5. Storage + + +# Prerequisites {% #prerequisites %} + +1. Basic knowledge of TypeScript and React. +2. Have [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer \ No newline at end of file diff --git a/src/routes/docs/tutorials/typescript/step-2/+page.markdoc b/src/routes/docs/tutorials/typescript/step-2/+page.markdoc new file mode 100644 index 0000000000..3ed4f8ac98 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-2/+page.markdoc @@ -0,0 +1,32 @@ +--- +layout: tutorial +title: Create app +description: Create a React app project and integrate with Appwrite. +step: 2 +--- + +# Create React project {% #create-react-project %} + +Create a React app with the `npm create` command. + +```sh +npm create vite@latest ideas-tracker && cd ideas-tracker +``` + +Select React and then TypeScript from CLI. + +![Create project screen](/images/docs/tutorials/react-typescript.png) + +# Add dependencies {% #add-dependencies %} + +Install the TypeScript Appwrite SDK. + +```sh +npm install appwrite +``` + +You can start the development server to watch your app update in the browser as you make changes. + +```sh +npm run dev -- --open --port 3000 +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/typescript/step-3/+page.markdoc b/src/routes/docs/tutorials/typescript/step-3/+page.markdoc new file mode 100644 index 0000000000..7b1393eada --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-3/+page.markdoc @@ -0,0 +1,58 @@ +--- +layout: tutorial +title: Set up Appwrite +description: Import and initialize Appwrite for your react application. +step: 3 +--- + +# Create project {% #create-project %} + +Head to the [Appwrite Console](https://cloud.appwrite.io/console). + +{% only_dark %} +![Create project screen](/images/docs/quick-starts/dark/create-project.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/quick-starts/create-project.png) +{% /only_light %} + +If this is your first time using Appwrite, create an account and create your first project. + +Then, under **Add a platform**, add a **Web app**. The **Hostname** should be localhost. + +{% only_dark %} +![Add a platform](/images/docs/quick-starts/dark/add-platform.png) +{% /only_dark %} +{% only_light %} +![Add a platform](/images/docs/quick-starts/add-platform.png) +{% /only_light %} + +You can skip optional steps. + +# Initialize Appwrite SDK {% #init-sdk %} + +To use Appwrite in our Svelte app, we'll need to find our project ID. Find your project's ID in the **Settings** page. + +{% only_dark %} +![Project settings screen](/images/docs/quick-starts/dark/project-id.png) +{% /only_dark %} +{% only_light %} +![Project settings screen](/images/docs/quick-starts/project-id.png) +{% /only_light %} + +Create a new file `src/lib/appwrite.ts` to hold our Appwrite related code. +Only one instance of the `Client()` class should be created per app. +Add the following code to it, replacing `` with your project ID. + +```ts +import { Client, Databases, Account } from "appwrite"; + +const client = new Client(); +client + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject(""); // Replace with your project ID + +export const account: Account = new Account(client); +export const databases: Databases = new Databases(client); + +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/typescript/step-4/+page.markdoc b/src/routes/docs/tutorials/typescript/step-4/+page.markdoc new file mode 100644 index 0000000000..47042fc113 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-4/+page.markdoc @@ -0,0 +1,208 @@ +--- +layout: tutorial +title: Add authentication +description: Add authentication to your react application. +step: 4 +--- + +# User context {% #user-context %} + +In React, you can use [context](https://reactjs.org/docs/context.html) to share data between components. +We'll use context and a custom hook to manage our user's data. + +Create a new file `src/lib/context/user.tsx` and add the following code to it. + +```ts +import { createContext, useContext, useEffect, useState, ReactNode } from "react"; +import { account } from "../appwrite"; + +interface UserContextType { + current: any; // Replace 'any' with the actual type of 'user' + login: (email: string, password: string) => Promise; + logout: () => Promise; + register: (email: string, password: string, userid: string) => Promise; +} + +const UserContext = createContext(undefined); + +export function useUser() { + const context = useContext(UserContext); + if (context === undefined) { + throw new Error("useUser must be used within a UserProvider"); + } + return context; +} + +interface UserProviderProps { + children: ReactNode; +} + +export function UserProvider(props: UserProviderProps) { + const [user, setUser] = useState(null); // Replace 'any' with the actual type of 'user' + + async function login(email: string, password: string) { + const loggedIn = await account.createEmailSession(email, password); + setUser(loggedIn); + } + + async function logout() { + await account.deleteSession("current"); + setUser(null); + } + + async function register(email: string, password: string, userid: string) { + await account.create(email, password, { userid }); + await login(email, password); + } + + async function init() { + try { + const loggedIn = await account.get(); + setUser(loggedIn); + } catch (err) { + setUser(null); + } + } + + useEffect(() => { + init(); + }, []); + + return ( + + {props.children} + + ); +} + +``` + +Now, we can use the `useUser` hook to access the user's data from any component. However, we first need to wrap our app with the `UserProvider`. + +# Basic routing {% #basic-routing %} + +First, wrap the `main` element with the `UserProvider` component. + +Update `src/App.tsx` to the following code. + +```ts +import { Home } from "./pages/Home"; +import { UserProvider } from "./lib/context/user"; + +function App() { + const isLoginPage: boolean = window.location.pathname === "/login"; + + return ( +
+ +
Home page
+
+
+ ); +} + +export default App; + +``` + +Then, optionally render the `Login` component if the path is `/login`, otherwise render the `Home` component. + +Update `src/App.tsx` to the following code. + +```ts +import { Login } from './pages/Login'; +import { Home } from './pages/Home'; +import { UserProvider } from './lib/context/user'; + +function App() { + const isLoginPage: boolean = window.location.pathname === '/login'; + + return ( +
+ +
{isLoginPage ? : }
+
+
+ ); +} + +export default App; + +``` + +# Home page {% #home-page %} + +Create a new file `src/pages/Home.tsx` and add the following stub code to it. + +```ts +// We'll complete this component later +export function Home(): React.ReactNode { + return ( +

I'm the home page

+ ); +} + +``` + +# Login page {% #login-page %} + +Finally, we are able to create the login page. Users will be able to login or register from this page, so we'll need to add two buttons. + +Create a new file `src/pages/Login.tsx` and add the following code to it. + +```ts +import React, { useState, ChangeEvent } from "react"; +import { useUser } from "../lib/context/user"; + +export function Login() { + const user = useUser(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleEmailChange = (event: ChangeEvent) => { + setEmail(event.target.value); + }; + + const handlePasswordChange = (event: ChangeEvent) => { + setPassword(event.target.value); + }; + + const handleLogin = () => { + user.login(email, password); + }; + + const handleRegister = () => { + user.register(email, password); + }; + + return ( +
+

Login or register

+
+ + +
+ + +
+
+
+ ); +} + +``` + diff --git a/src/routes/docs/tutorials/typescript/step-5/+page.markdoc b/src/routes/docs/tutorials/typescript/step-5/+page.markdoc new file mode 100644 index 0000000000..c914743261 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-5/+page.markdoc @@ -0,0 +1,61 @@ +--- +layout: tutorial +title: Add navigation +description: Add navigation to your React applicating using Appwrite. +step: 5 +--- + +In our app we want to have a navigation bar that is always visible. We will add it to the `App` component and use the `useUser` hook to show either: +- a logout button if the user is logged in. +- a login button if the user is not logged in. + +Update the App componenent in `src/App.tsx`: + +```ts +import { Login } from "./pages/Login"; +import { Home } from "./pages/Home"; +import { UserProvider, useUser } from "./lib/context/user"; + +interface User { + email: string; + // Add other user properties if necessary +} + +function App() { + const isLoginPage = window.location.pathname === "/login"; + + return ( +
+ + +
{isLoginPage ? : }
+
+
+ ); +} + +function Navbar() { + const user = useUser(); + + return ( + + ); +} + +export default App; + +``` diff --git a/src/routes/docs/tutorials/typescript/step-6/+page.markdoc b/src/routes/docs/tutorials/typescript/step-6/+page.markdoc new file mode 100644 index 0000000000..a936a25690 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-6/+page.markdoc @@ -0,0 +1,105 @@ +--- +layout: tutorial +title: Add database +description: Add a database to your React application using Appwrite Web SDK. +step: 6 +--- +# Create collection +In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. + +{% only_dark %} +![Create project screen](/images/docs/tutorials/dark/idea-tracker-collection.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/tutorials/idea-tracker-collection.png) +{% /only_light %} + +Create a new collection with the following attributes: +| Field | Type | Required | +|-------------|--------|----------| +| userId | String | Yes | +| title | String | Yes | +| description | String | No | + +# Ideas context + +Now that you have a collection to hold ideas, we can read and write to it from our app. Like we did with the user data, we will create a React context to hold our ideas. +Create a new file `src/lib/context/ideas.tsx` and add the following code to it. + +```ts +import { createContext, useContext, useEffect, useState, ReactNode } from "react"; +import { databases } from "../appwrite"; +import { ID, Query } from "appwrite"; + +export const IDEAS_DATABASE_ID = ""; // Replace with your database ID +export const IDEAS_COLLECTION_ID = ""; // Replace with your collection ID + +interface Idea { + $id?: string; + title: string; + description: string; + userId: string; + // Add more properties as needed +} + +interface IdeasContextType { + current: Idea[]; + add: (idea: Idea) => Promise; + remove: (id: string) => Promise; +} + +const IdeasContext = createContext(undefined); + +export function useIdeas() { + const context = useContext(IdeasContext); + if (context === undefined) { + throw new Error("useIdeas must be used within an IdeasProvider"); + } + return context; +} + +interface IdeasProviderProps { + children: ReactNode; +} + +export function IdeasProvider(props: IdeasProviderProps) { + const [ideas, setIdeas] = useState([]); + + async function add(idea: Idea) { + const response = await databases.createDocument( + IDEAS_DATABASE_ID, + IDEAS_COLLECTION_ID, + ID.unique(), + { ...idea } + ); + setIdeas((currentIdeas) => [response.$id, ...currentIdeas].slice(0, 10)); + } + + async function remove(id: string) { + await databases.deleteDocument(IDEAS_DATABASE_ID, IDEAS_COLLECTION_ID, id); + setIdeas((currentIdeas) => currentIdeas.filter((idea) => idea.$id !== id)); + await init(); // Refetch ideas to ensure we have 10 items + } + + async function init() { + const response = await databases.listDocuments( + IDEAS_DATABASE_ID, + IDEAS_COLLECTION_ID, + [Query.orderDesc("$createdAt"), Query.limit(10)] + ); + setIdeas(response.documents); + } + + useEffect(() => { + init(); + }, []); + + return ( + + {props.children} + + ); +} + +``` + diff --git a/src/routes/docs/tutorials/typescript/step-7/+page.markdoc b/src/routes/docs/tutorials/typescript/step-7/+page.markdoc new file mode 100644 index 0000000000..eaecb38853 --- /dev/null +++ b/src/routes/docs/tutorials/typescript/step-7/+page.markdoc @@ -0,0 +1,86 @@ +--- +layout: tutorial +title: Create ideas page +description: Add database queries and pagination using Appwrite in your React application. +step: 7 +--- + +Using the `useIdeas` hook we can now display the ideas on the page. We will also add a form to submit new ideas. + +Overwrite the contents of `src/pages/Home.tsx` with the following: + +```ts +import { useState, ChangeEvent } from "react"; +import { useUser } from "../lib/context/user"; +import { useIdeas } from "../lib/context/ideas"; + +export function Home() { + const user = useUser(); + const ideas = useIdeas(); + + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + + const handleTitleChange = (event: ChangeEvent) => { + setTitle(event.target.value); + }; + + const handleDescriptionChange = (event: ChangeEvent) => { + setDescription(event.target.value); + }; + + return ( + <> + {/* Show the submit form to logged in users. */} + {user.current ? ( +
+

Submit Idea

+
+ +