diff --git a/src/routes/docs/tutorials/+page.svelte b/src/routes/docs/tutorials/+page.svelte index 40429352e..1b36ac78d 100644 --- a/src/routes/docs/tutorials/+page.svelte +++ b/src/routes/docs/tutorials/+page.svelte @@ -73,6 +73,17 @@

+
  • + +
    +
    +

    + Learn Appwrite Auth, Databases, and more with Refine. +

    +
    +
  • diff --git a/src/routes/docs/tutorials/refine/+layout.svelte b/src/routes/docs/tutorials/refine/+layout.svelte new file mode 100644 index 000000000..fb9fb3980 --- /dev/null +++ b/src/routes/docs/tutorials/refine/+layout.svelte @@ -0,0 +1,10 @@ + + + diff --git a/src/routes/docs/tutorials/refine/+layout.ts b/src/routes/docs/tutorials/refine/+layout.ts new file mode 100644 index 000000000..562b11506 --- /dev/null +++ b/src/routes/docs/tutorials/refine/+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/refine/+page.ts b/src/routes/docs/tutorials/refine/+page.ts new file mode 100644 index 000000000..0dc5d1d58 --- /dev/null +++ b/src/routes/docs/tutorials/refine/+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/refine/step-1'); +}; diff --git a/src/routes/docs/tutorials/refine/step-1/+page.markdoc b/src/routes/docs/tutorials/refine/step-1/+page.markdoc new file mode 100644 index 000000000..51a2f9df5 --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-1/+page.markdoc @@ -0,0 +1,31 @@ +--- +layout: tutorial +title: Build an Admin Panel with Refine +description: Learn to build a Refine app with no backend code using an Appwrite backend. +step: 1 +difficulty: beginner +readtime: 10 +--- + +**Blog admin panel**: a CRUD app to manage Blog content. +In this tutorial, you will build admin panel app with Appwrite and [Refine](https://github.com/refinedev/refine). + + +![Create project screen](/images/docs/tutorials/refine/blog-admin-panel.png) + + +# 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/refine/step-2/+page.markdoc b/src/routes/docs/tutorials/refine/step-2/+page.markdoc new file mode 100644 index 000000000..8f073c589 --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-2/+page.markdoc @@ -0,0 +1,41 @@ +--- +layout: tutorial +title: Create app +description: Create a Refine app project and integrate with Appwrite. +step: 2 +--- + +# Create Refine project {% #create-react-project %} + +Create a Refine app with the `npm create` command. + +```sh +npm create refine-app@latest -- --preset refine-appwrite +``` + +We're using the `refine-appwrite` preset that installs the [`@refinedev/appwrite`](https://github.com/refinedev/refine/tree/master/packages/appwrite) which already has the Appwrite dependency pre-configured. + +To make this example more visual, we'll use the Ant Desing UI package which natively supported by Refine. + +{% info title="Note" %} + +No extra dependencies required for this tutorial. If you want to integrate Appwrite into an existing Refine app, use this command: +```sh +npm install @refinedev/appwrite +``` +Then follow [this](https://refine.dev/docs/packages/documentation/data-providers/appwrite) guide. +{% /info %} + + + +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 +``` + +Once the app is running, you should be greeted with the welcome screen. + + +![App welcome screen](/images/docs/tutorials/refine/refine-welcome-page.png) + diff --git a/src/routes/docs/tutorials/refine/step-3/+page.markdoc b/src/routes/docs/tutorials/refine/step-3/+page.markdoc new file mode 100644 index 000000000..b6d46f3ba --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-3/+page.markdoc @@ -0,0 +1,60 @@ +--- +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 Refine 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 %} + +Navigate to `src/utility/appwriteClient.ts` and add your API credentials. + + +```ts +import { Account, Appwrite, Storage } from "@refinedev/appwrite"; + +const APPWRITE_URL = ''; // Replace with your Appwrite API Endpoint +const APPWRITE_PROJECT = ""; // Replace with your project ID + +const appwriteClient = new Appwrite(); + +appwriteClient.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT); +const account = new Account(appwriteClient); +const storage = new Storage(appwriteClient); + +export { account, appwriteClient, storage }; + +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/refine/step-4/+page.markdoc b/src/routes/docs/tutorials/refine/step-4/+page.markdoc new file mode 100644 index 000000000..ba07eba4c --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-4/+page.markdoc @@ -0,0 +1,162 @@ +--- +layout: tutorial +title: Add authentication +description: Add authentication to your Refine application. +step: 4 +--- + +# Authentication Provider {% #auth-provider %} + + +Upon [creating a new project](https://refine.dev/docs/getting-started/quickstart/) with Appwrite preset, the CLI automatically creates [Auth Provider](https://refine.dev/docs/tutorial/understanding-authprovider/index/#what-is-auth-provider) file. + + +You'll see a file named [`src/authProvider.ts`](https://github.com/refinedev/refine/blob/master/examples/data-provider-appwrite-tutorial-docs/src/utility/authProvider.ts) created by CLI. This auto-generated file contains pre-defined functions using Appwrite Auth methods internally to perform authentication and authorization operations. + + +The auth provider registered to the Refine app by default in the `src/App.tsx`. +```ts + +import { authProvider } from './authProvider'; +... + + +``` + +Now, we can configure the routing and auth components to manage logins and sign ups. + + +# Routing {% #routing %} + +Refine offers router bindings and utilities for [React Router v6](https://reactrouter.com/en/main) and improves the user interface with the [routerProvider](https://refine.dev/docs/api-reference/core/components/refine-config/#routerprovider) prop. +This allows the framework to identify resources from routes and efficiently handle query parameters and navigation tasks. + + +```ts +import { authProvider } from './authProvider'; +import routerProvider from '@refinedev/react-router-v6'; +... + + +``` + +# Login page {% #login-page %} + + + + +We'll use [Routes](https://reactrouter.com/en/main/components/routes) component to connect routing mechanisim along with [AuthPage](https://refine.dev/docs/api-reference/antd/components/antd-auth-page/#usage) component which returns ready-to-use authentication pages for login, register, update, and forgot password actions. + +Update `src/App.tsx` to the following code. + +```ts +import { Authenticated, Refine } from '@refinedev/core'; +import { dataProvider, liveProvider } from '@refinedev/appwrite'; +import { + AuthPage, + ErrorComponent, + RefineThemes, + ThemedLayoutV2, + useNotificationProvider, +} from '@refinedev/antd'; +import routerProvider, { + CatchAllNavigate, + NavigateToResource, +} from '@refinedev/react-router-v6'; +import '@refinedev/antd/dist/reset.css'; + +import { App as AntdApp, ConfigProvider } from 'antd'; +import { BrowserRouter, Outlet, Route, Routes } from 'react-router-dom'; + +import { appwriteClient } from './utility'; +import { authProvider } from './authProvider'; + +const App: React.FC = () => { + return ( + + + + ', + })} + liveProvider={liveProvider(appwriteClient, { + databaseId: '', + })} + authProvider={authProvider} + routerProvider={routerProvider} + notificationProvider={useNotificationProvider} + > + + + } + > + + + + + } + > + + }> + + + } + > + } /> + } + /> + + + + + + + + } + > + } /> + + + + + + + ); +}; + +export default App; +``` +Key concepts to handle authentication and routing: + +- The [``](https://refine.dev/docs/api-reference/antd/components/antd-auth-page) component in Refine includes pages for login, registration, password reset, and password update functionalities. +- To manage authenticated routes effectively, the [``](https://refine.dev/docs/api-reference/core/components/auth/authenticated/) component using to determine the user's authentication status and accordingly directs them or displays relevant elements. +- Within the `` component, we use the `` component from `react-router-dom` to render secure routes that are accessible only to authenticated users. +- We set up a `/login` route for redirecting unauthenticated users, using Refine's AuthPage components with a `type="login"` prop to create the login page efficiently. + +When you refresh the page, the login screen appears. + +![Refine login screen](/images/docs/tutorials/refine/refine-login-page.png) + + +We'll activate the authentication mechanisim with Appwrite in the next section. + + diff --git a/src/routes/docs/tutorials/refine/step-5/+page.markdoc b/src/routes/docs/tutorials/refine/step-5/+page.markdoc new file mode 100644 index 000000000..b66475504 --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-5/+page.markdoc @@ -0,0 +1,83 @@ +--- +layout: tutorial +title: Add database +description: Add a database to your React application using Appwrite Web SDK. +step: 5 +--- +# Create collection {% #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/refine/posts-collection-dark.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/tutorials/refine/posts-collection-light.png) +{% /only_light %} + +Create a new collection with the following attributes: +| Field | Type | Required | +|-------------|--------|----------| +| title | String | Yes | +| content | String | Yes | + +# Connect database to the Refine app {% #blog-context %} + +Now that you have a collection to hold blog post contents, we can read and write to it from our app. + + + +To integrate Appwrite API with the Refine App, +- Add a resources array to the `` component in `App.tsx` +- Include your `Appwrite Database ID` in both `dataProvider` and `liveProvider`, and specify the `Appwrite Collection ID` in the resource name property. + + +```ts +import { dataProvider, liveProvider } from '@refinedev/appwrite'; +... + +", + })} + liveProvider={liveProvider(appwriteClient, { + databaseId: "", + })} + authProvider={authProvider} + routerProvider={routerProvider} + resources={[ + { + name: "", // resource name must be the same as APPWRITE_COLLECTION_ID. + list: "/posts", // Means that the list action of this resource will be available at /posts in your app + create: "/posts/create", // create action of this resource will be available at /posts/create + edit: "/posts/edit/:id", // edit action of this resource will be available at /posts/edit/:id + show: "/posts/show/:id", // show action of this resource will be available at /posts/show/:id + }, + ]} +... +/>; +``` + +Key concepts to performing CRUD operations: + +- A [resource](https://refine.dev/docs/api-reference/core/components/refine-config/#name) connects an API endpoint's entity with the app's pages, enabling them to interact with the API data. +In the resource configuration, path definitions allow Refine to automatically recognize the resource's available actions and identify it based on the current path, eliminating the need for manual specification in hooks and components. + +- The [data provider](https://refine.dev/docs/tutorial/understanding-dataprovider/index/#using-data-providers-in-refine) serves as your app's data layer, handling HTTP requests and managing data retrieval. Refine uses this through data hooks. +With built-in support for Appwrite in Refine, you only need to pass your Appwrite database ID to the `dataProvider` property. + + +{% info title="Note" %} +The [@refinedev/appwrite](https://www.npmjs.com/package/@refinedev/appwrite) package supports [Live/Realtime Provider](https://refine.dev/docs/api-reference/core/providers/live-provider/) natively. +{% /info %} + + + + +At this point, we created our Appwrite database and connected to the Refine App. + +You can now register and login to the app. + + + + + diff --git a/src/routes/docs/tutorials/refine/step-6/+page.markdoc b/src/routes/docs/tutorials/refine/step-6/+page.markdoc new file mode 100644 index 000000000..c0c2589cb --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-6/+page.markdoc @@ -0,0 +1,396 @@ +--- +layout: tutorial +title: Create CRUD pages +description: Add database queries and CRUD pages using Appwrite in your Refine application. +step: 6 +--- + +We're going to add CRUD pages to our admin panel so you can list, create, and view blog posts records. + + +# List page {% #list-page %} + + + First, create a listing page to show Appwrite API data in a table by copying the code below into `src/pages/posts` and saving it as `list.tsx`. + +```ts +import { IResourceComponentsProps } from '@refinedev/core'; +import { + List, + useTable, + EditButton, + ShowButton, + getDefaultSortOrder, + DeleteButton, +} from '@refinedev/antd'; +import { Table, Space } from 'antd'; + +import { IPost } from '../../interfaces'; + +export const PostList: React.FC = () => { + const { tableProps, sorters } = useTable({ + initialSorter: [ + { + field: '$id', + order: 'asc', + }, + ], + }); + + return ( + + + + + + + title="Actions" + dataIndex="actions" + fixed="right" + render={(_, record) => ( + + + + + + )} + /> +
    +
    + ); +}; +``` + +# Create page {% #create-page %} + + Create a new record page for the Appwrite API by copying the following code and saving it as `create.tsx`. + + +```ts +import { HttpError, IResourceComponentsProps } from '@refinedev/core'; +import { Create, useForm } from '@refinedev/antd'; +import { Form, Input } from 'antd'; + +import { IPost, IPostVariables } from '../../interfaces'; + +export const PostCreate: React.FC = () => { + const { formProps, saveButtonProps } = useForm< + IPost, + HttpError, + IPostVariables + >(); + + return ( + +
    + + + + + + + +
    +
    + ); +}; +``` + +# Edit page {% #edit-page %} + +Create a page for editing a records with the following code and saving it as `edit.tsx`. + + +```ts +import React from 'react'; +import { HttpError, IResourceComponentsProps } from '@refinedev/core'; +import { Edit, useForm } from '@refinedev/antd'; +import { Form, Input } from 'antd'; +import { IPost, IPostVariables } from '../../interfaces'; + +export const PostEdit: React.FC = () => { + const { formProps, saveButtonProps } = useForm< + IPost, + HttpError, + IPostVariables + >(); + + return ( + +
    + + + + + + +
    +
    + ); +}; +``` + +# Show page {% #show-page %} + + Create a page for showig the records with the following code and saving it as `edit.tsx`. + + +```ts +import { useShow, IResourceComponentsProps } from '@refinedev/core'; + +import { Show, MarkdownField } from '@refinedev/antd'; +import { Typography } from 'antd'; +import { IPost } from '../../interfaces'; + +const { Title, Text } = Typography; + +export const PostShow: React.FC = () => { + const { queryResult } = useShow(); + const { data, isLoading } = queryResult; + const record = data?.data; + + return ( + + Id + {record?.id} + + Title + {record?.title} + + Content + + + ); +}; +``` + +# Interfaces {% #interfaces %} + + We need to add interfaces to `src/interfaces/index.d.ts` file. + + +```ts +export interface IFile { + name: string; + percent: number; + size: number; + status: 'error' | 'success' | 'done' | 'uploading' | 'removed'; + type: string; + uid: string; + url: string; +} + +export interface IPost { + id: string; + title: string; + content: string; +} + +export interface IPostVariables { + id: string; + title: string; + content: string; +} +``` + +# Connect pages to the App + +Finally, import the pages into `App.tsx` and define them in the `` components. + +Simply, paste the following into `App.tsx`. + + +```ts +import { Authenticated, Refine } from '@refinedev/core'; +import { dataProvider, liveProvider } from '@refinedev/appwrite'; +import { + AuthPage, + ErrorComponent, + RefineThemes, + ThemedLayoutV2, + useNotificationProvider, +} from '@refinedev/antd'; +import routerProvider, { + CatchAllNavigate, + DocumentTitleHandler, + NavigateToResource, + UnsavedChangesNotifier, +} from '@refinedev/react-router-v6'; +import '@refinedev/antd/dist/reset.css'; + +import { App as AntdApp, ConfigProvider } from 'antd'; +import { BrowserRouter, Outlet, Route, Routes } from 'react-router-dom'; + +import { appwriteClient } from './utility'; +import { authProvider } from './authProvider'; +import { PostCreate, PostEdit, PostList, PostShow } from './pages/posts'; + +const App: React.FC = () => { + return ( + + + + ', + })} + liveProvider={liveProvider(appwriteClient, { + databaseId: '', + })} + authProvider={authProvider} + routerProvider={routerProvider} + resources={[ + { + name: '', + list: '/posts', + create: '/posts/create', + edit: '/posts/edit/:id', + show: '/posts/show/:id', + meta: { + label: 'Posts', + }, + }, + ]} + notificationProvider={useNotificationProvider} + options={{ + liveMode: 'auto', + syncWithLocation: true, + warnWhenUnsavedChanges: true, + }} + > + + + } + > + + + +
    + } + > + + } + /> + + + } /> + } + /> + } + /> + } + /> + + + + }> + +
    + } + > + + } + /> + } + /> + + + + + + + + } + > + } /> + + + + +
    + + + + ); +}; + +export default App; + + + +``` + diff --git a/src/routes/docs/tutorials/refine/step-7/+page.markdoc b/src/routes/docs/tutorials/refine/step-7/+page.markdoc new file mode 100644 index 000000000..14e92155c --- /dev/null +++ b/src/routes/docs/tutorials/refine/step-7/+page.markdoc @@ -0,0 +1,30 @@ +--- +layout: tutorial +title: Next steps +description: Run your Refine project built with Appwrite +step: 7 +--- + + + +# Test your project {% #test-project %} +Run your project with `npm run dev -- --open --port 3000` and open [http://localhost:3000](http://localhost:3000) in your browser. + +Now, we are able to listing the records retrieved from Appwrite backend on table, show the each record, edit the existing records, and delete functionality on records. + + +- List Page + +![Project list page](/images/docs/tutorials/refine/refine-list-page.png) + +- Create Page + +![Project create page](/images/docs/tutorials/refine/refine-create-page.png) + +- Edit Page + +![Project edit screen](/images/docs/tutorials/refine/refine-edit-page.png) + +- Show Page + +![Project show screen](/images/docs/tutorials/refine/refine-show-page.png) diff --git a/static/images/docs/tutorials/refine/blog-admin-panel.png b/static/images/docs/tutorials/refine/blog-admin-panel.png new file mode 100644 index 000000000..e5d5b814b Binary files /dev/null and b/static/images/docs/tutorials/refine/blog-admin-panel.png differ diff --git a/static/images/docs/tutorials/refine/posts-collection-dark.png b/static/images/docs/tutorials/refine/posts-collection-dark.png new file mode 100644 index 000000000..0c39148d1 Binary files /dev/null and b/static/images/docs/tutorials/refine/posts-collection-dark.png differ diff --git a/static/images/docs/tutorials/refine/posts-collection-light.png b/static/images/docs/tutorials/refine/posts-collection-light.png new file mode 100644 index 000000000..dacf4d86f Binary files /dev/null and b/static/images/docs/tutorials/refine/posts-collection-light.png differ diff --git a/static/images/docs/tutorials/refine/refine-create-page.png b/static/images/docs/tutorials/refine/refine-create-page.png new file mode 100644 index 000000000..21752a9c0 Binary files /dev/null and b/static/images/docs/tutorials/refine/refine-create-page.png differ diff --git a/static/images/docs/tutorials/refine/refine-edit-page.png b/static/images/docs/tutorials/refine/refine-edit-page.png new file mode 100644 index 000000000..c64764c00 Binary files /dev/null and b/static/images/docs/tutorials/refine/refine-edit-page.png differ diff --git a/static/images/docs/tutorials/refine/refine-list-page.png b/static/images/docs/tutorials/refine/refine-list-page.png new file mode 100644 index 000000000..085d4fa99 Binary files /dev/null and b/static/images/docs/tutorials/refine/refine-list-page.png differ diff --git a/static/images/docs/tutorials/refine/refine-login-page.png b/static/images/docs/tutorials/refine/refine-login-page.png new file mode 100644 index 000000000..e5b2998b3 Binary files /dev/null and b/static/images/docs/tutorials/refine/refine-login-page.png differ diff --git a/static/images/docs/tutorials/refine/refine-show-page.png b/static/images/docs/tutorials/refine/refine-show-page.png new file mode 100644 index 000000000..0e0c0a0ff Binary files /dev/null and b/static/images/docs/tutorials/refine/refine-show-page.png differ diff --git a/static/images/docs/tutorials/refine/refine-welcome-page.png b/static/images/docs/tutorials/refine/refine-welcome-page.png new file mode 100644 index 000000000..e79652c50 Binary files /dev/null and b/static/images/docs/tutorials/refine/refine-welcome-page.png differ