docs(tutorials): add refine tutorial

This commit is contained in:
necatiozmen
2023-11-15 11:10:10 +03:00
parent 29680d1577
commit dc376b42da
20 changed files with 841 additions and 0 deletions

View File

@@ -73,6 +73,17 @@
</p> </p>
</a> </a>
</li> </li>
<li class="is-mobile-col-span-2">
<a href="/docs/tutorials/refine" class="aw-card is-normal">
<header class="u-flex u-cross-baseline u-gap-4">
<span class="icon-svelte aw-u-font-size-24" aria-hidden="true" />
<h4 class="aw-sub-body-500 aw-u-color-text-primary">Refine</h4>
</header>
<p class="aw-sub-body-400 u-margin-block-start-4">
Learn Appwrite Auth, Databases, and more with Refine.
</p>
</a>
</li>
<li class="is-mobile-col-span-2"> <li class="is-mobile-col-span-2">
<article class="aw-card is-full-color"> <article class="aw-card is-full-color">
<header class="u-flex u-cross-baseline u-gap-4"> <header class="u-flex u-cross-baseline u-gap-4">

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte';
export let data;
const tutorials = globToTutorial(data);
setContext('tutorials', tutorials);
</script>
<slot />

View File

@@ -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
};
};

View File

@@ -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');
};

View File

@@ -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

View File

@@ -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)

View File

@@ -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 = '<YOUR_API_ENDPOINT>'; // Replace with your Appwrite API Endpoint
const APPWRITE_PROJECT = "<YOUR_PROJECT_ID>"; // 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 };
```

View File

@@ -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';
...
<Refine
...
authProvider={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';
...
<Refine
...
authProvider={authProvider}
routerProvider={routerProvider}
>
```
# 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 (
<BrowserRouter>
<ConfigProvider theme={RefineThemes.Blue}>
<AntdApp>
<Refine
dataProvider={dataProvider(appwriteClient, {
databaseId: '<APPWRITE_DATABASE_ID>',
})}
liveProvider={liveProvider(appwriteClient, {
databaseId: '<APPWRITE_DATABASE_ID>',
})}
authProvider={authProvider}
routerProvider={routerProvider}
notificationProvider={useNotificationProvider}
>
<Routes>
<Route
element={
<Authenticated
fallback={
<CatchAllNavigate to="/login" />
}
>
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
></Route>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="<APPWRITE_COLLECTION_ID>" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage />} />
<Route
path="/register"
element={<AuthPage type="register" />}
/>
</Route>
<Route
element={
<Authenticated>
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</AntdApp>
</ConfigProvider>
</BrowserRouter>
);
};
export default App;
```
Key concepts to handle authentication and routing:
- The [`<AuthPage>`](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 [`<Authenticated>`](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 `<Authenticated>` component, we use the `<Outlet>` 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.

View File

@@ -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 `<Refine />` 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';
...
<Refine
dataProvider={dataProvider(appwriteClient, {
databaseId: "<APPWRITE_DATABASE_ID>",
})}
liveProvider={liveProvider(appwriteClient, {
databaseId: "<APPWRITE_DATABASE_ID>",
})}
authProvider={authProvider}
routerProvider={routerProvider}
resources={[
{
name: "<APPWRITE_COLLECTION_ID>", // 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.

View File

@@ -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<IResourceComponentsProps> = () => {
const { tableProps, sorters } = useTable<IPost>({
initialSorter: [
{
field: '$id',
order: 'asc',
},
],
});
return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column
dataIndex="id"
title="ID"
sorter
width={100}
defaultSortOrder={getDefaultSortOrder('id', sorters)}
/>
<Table.Column dataIndex="title" title="Title" sorter />
<Table.Column<IPost>
title="Actions"
dataIndex="actions"
fixed="right"
render={(_, record) => (
<Space>
<EditButton
hideText
size="small"
recordItemId={record.id}
/>
<ShowButton
hideText
size="small"
recordItemId={record.id}
/>
<DeleteButton
hideText
size="small"
recordItemId={record.id}
/>
</Space>
)}
/>
</Table>
</List>
);
};
```
# 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<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps } = useForm<
IPost,
HttpError,
IPostVariables
>();
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Content"
name="content"
rules={[
{
required: true,
},
]}
>
<Input.TextArea rows={5} />
</Form.Item>
</Form>
</Create>
);
};
```
# 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<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps } = useForm<
IPost,
HttpError,
IPostVariables
>();
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Content"
name="content"
rules={[
{
required: true,
},
]}
>
<Input.TextArea rows={5} />
</Form.Item>
</Form>
</Edit>
);
};
```
# 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<IResourceComponentsProps> = () => {
const { queryResult } = useShow<IPost>();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title level={5}>Id</Title>
<Text>{record?.id}</Text>
<Title level={5}>Title</Title>
<Text>{record?.title}</Text>
<Title level={5}>Content</Title>
<MarkdownField value={record?.content} />
</Show>
);
};
```
# 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 `<Route>` 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 (
<BrowserRouter>
<ConfigProvider theme={RefineThemes.Blue}>
<AntdApp>
<Refine
dataProvider={dataProvider(appwriteClient, {
databaseId: '<APPWRITE_DATABASE_ID>',
})}
liveProvider={liveProvider(appwriteClient, {
databaseId: '<APPWRITE_DATABASE_ID>',
})}
authProvider={authProvider}
routerProvider={routerProvider}
resources={[
{
name: '<APPWRITE_COLLECTION_ID>',
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,
}}
>
<Routes>
<Route
element={
<Authenticated
fallback={
<CatchAllNavigate to="/login" />
}
>
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route
index
element={
<NavigateToResource resource="<APPWRITE_COLLECTION_ID>" />
}
/>
<Route path="/posts">
<Route index element={<PostList />} />
<Route
path="create"
element={<PostCreate />}
/>
<Route
path="edit/:id"
element={<PostEdit />}
/>
<Route
path="show/:id"
element={<PostShow />}
/>
</Route>
</Route>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="<APPWRITE_COLLECTION_ID>" />
</Authenticated>
}
>
<Route
path="/login"
element={
<AuthPage
forgotPasswordLink={false}
/>
}
/>
<Route
path="/register"
element={<AuthPage type="register" />}
/>
</Route>
<Route
element={
<Authenticated>
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
<UnsavedChangesNotifier />
<DocumentTitleHandler />
</Refine>
</AntdApp>
</ConfigProvider>
</BrowserRouter>
);
};
export default App;
```

View File

@@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB