Введение
Мы собираемся разработать приложение генератора счетов для нашего бизнеса с помощью Refine и Strapi. Давайте вместе посмотрим, насколько простым, но функциональным оно может быть!
Эта статья будет состоять из двух частей, и мы постараемся подробно объяснить каждый шаг. В этой части мы создадим основные части нашего приложения.
В этой части мы создадим панель, где будет размещена информация о нашей собственной компании, где мы сможем создавать клиентов и создавать контакты с компаниями-клиентами.
Создание проекта Refine
Давайте начнем с создания нашего проекта Refine. Вы можете использовать супершаблон для создания проекта refine.
npx superplate-cli -p refine-react refine-invoice-genarator
✔ What will be the name of your app ·refine-invoice-genarator
✔ Package manager: · npm
✔ Do you want to using UI Framework? > Yes, I want Ant Design
✔ Do you want to customize theme?: … no
✔ Data Provider: Strapi
✔ Do you want to customize layout? … no
✔ i18n - Internationalization: · no
superplate быстро создаст наш проект refine в соответствии с выбранными нами функциями. Давайте продолжим, установив refine Strapi-v4 Data Provider, который мы будем использовать позже.
npm i @pankod/refine-strapi-v4
Наш проект refine и установки теперь готовы! Давайте начнем его использовать.
Использование
src/authProvider.ts:
import { AuthProvider } from "@pankod/refine-core";
import { AuthHelper } from "@pankod/refine-strapi-v4";
import { TOKEN_KEY, API_URL } from "./constants";
import axios from "axios";
export const axiosInstance = axios.create();
const strapiAuthHelper = AuthHelper(API_URL + "/api");
export const authProvider: AuthProvider = {
login: async ({ username, password }) => {
const { data, status } = await strapiAuthHelper.login(
username,
password,
);
if (status === 200) {
localStorage.setItem(TOKEN_KEY, data.jwt);
// set header axios instance
axiosInstance.defaults.headers = {
Authorization: `Bearer ${data.jwt}`,
};
return Promise.resolve();
}
return Promise.reject();
},
logout: () => {
localStorage.removeItem(TOKEN_KEY);
return Promise.resolve();
},
checkError: () => Promise.resolve(),
checkAuth: () => {
const token = localStorage.getItem(TOKEN_KEY);
if (token) {
axiosInstance.defaults.headers = {
Authorization: `Bearer ${token}`,
};
return Promise.resolve();
}
return Promise.reject();
},
getPermissions: () => Promise.resolve(),
getUserIdentity: async () => {
const token = localStorage.getItem(TOKEN_KEY);
if (!token) {
return Promise.reject();
}
const { data, status } = await strapiAuthHelper.me(token);
if (status === 200) {
const { id, username, email } = data;
return Promise.resolve({
id,
username,
email,
});
}
return Promise.reject();
},
};
Настройте Refine для Strapi-v4
src/App.tsx:
import { Refine } from "@pankod/refine-core";
import { notificationProvider, Layout, LoginPage } from "@pankod/refine-antd";
import routerProvider from "@pankod/refine-react-router";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { authProvider, axiosInstance } from "./authProvider";
import "@pankod/refine-antd/dist/styles.min.css";
function App() {
const API_URL = "Your_Strapi_Url";
const dataProvider = DataProvider(API_URL + "/api", axiosInstance);
return (
<Refine
routerProvider={routerProvider}
notificationProvider={notificationProvider}
Layout={Layout}
dataProvider={dataProvider}
authProvider={authProvider}
LoginPage={LoginPage}
/>
);
}
Создание коллекций на Strapi
Мы создали три коллекции на Strapi: company
, client
и contact
и добавили связь между ними. Подробную информацию о том, как создать коллекцию, вы можете найти здесь.
Company:
- Логотип: Media
- Имя: Текст
- Адрес: Текст
- Страна: Текст
- Город: Текст
- электронная почта: Email
- Веб-сайт: Текст
Client:
- Имя: Текст
- Контакты: Отношения с контактом
Contact:
- Имя_имя: Текст
- Фамилия: Текст
- Номер_телефона: Текст
- Электронная почта: электронная почта
- Работа: Текст
- Клиент: Отношения с клиентом
Мы создали наши коллекции с помощью Strapi, теперь мы можем создать клиентов и их контакты с помощью refine.
Страница подробной информации о вашей компании
В качестве первого шага, давайте начнем создавать часть, где будет располагаться наша собственная Компания
. Если есть другие компании, которыми вам нужно управлять, вы можете создать их на странице «Ваша компания» и просмотреть их здесь.
Компонент карточки компании
Давайте разработаем компонент, который будет содержать подробную информацию о нашей компании. Затем отобразим его с помощью refine-antd
List
. Мы поместим в компонент Card такую информацию, как название, логотип и адрес из коллекции Company, которую мы создали на Strapi.
src/components/company/CompanyItem.tsx:
import {
Card,
DeleteButton,
UrlField,
EmailField,
EditButton,
Typography,
} from "@pankod/refine-antd";
import { ICompany } from "interfaces";
import { API_URL } from "../../constants";
const { Title, Text } = Typography;
type CompanyItemProps = {
item: ICompany;
};
export const CompanyItem: React.FC<CompanyItemProps> = ({ item }) => {
const image = item.logo ? API_URL + item.logo.url : "./error.png";
return (
<Card
style={{ width: "300px" }}
cover={
<div style={{ display: "flex", justifyContent: "center" }}>
<img
style={{
width: 220,
height: 100,
padding: 24,
}}
src={image}
alt="logo"
/>
</div>
}
actions={[
<EditButton key="edit" size="small" hideText />,
<DeleteButton
key="delete"
size="small"
hideText
recordItemId={item.id}
/>,
]}
>
<Title level={5}>Company Name:</Title>
<Text>{item.name}</Text>
<Title level={5}>Company Address:</Title>
<Text>{item.address}</Text>
<Title level={5}>County:</Title>
<Text>{item.country}</Text>
<Title level={5}>City:</Title>
<Text>{item.city}</Text>
<Title level={5}>Email:</Title>
<EmailField value={item.email} />
<Title level={5}>Website:</Title>
<UrlField value={item.website} />
</Card>
);
};
Страница списка компаний
Поместим компонент CompanyItem
, который мы создали выше, в уточненный список (refine-antd List) и отобразим информацию о компании.
src/pages/company/CompanyList.tsx:
import { IResourceComponentsProps } from "@pankod/refine-core";
import { useSimpleList, AntdList, List } from "@pankod/refine-antd";
import { CompanyItem } from "components/company";
export const CompanyList: React.FC<IResourceComponentsProps> = () => {
const { listProps } = useSimpleList<ICompany>({
metaData: { populate: ["logo"] },
});
return (
<List title={"Your Companies"}>
<AntdList
grid={{ gutter: 16 }}
{...listProps}
renderItem={(item) => (
<AntdList.Item>
<CompanyItem item={item} />
</AntdList.Item>
)}
/>
</List>
);
};
src/App.tsx:
...
import { CompanyList } from "pages/company";
function App() {
const API_URL = "Your_Strapi_Url";
const dataProvider = DataProvider(API_URL + "/api", axiosInstance);
return (
<Refine
routerProvider={routerProvider}
notificationProvider={notificationProvider}
Layout={Layout}
dataProvider={dataProvider}
authProvider={authProvider}
LoginPage={LoginPage}
resources={[
{
name: "companies",
options: { label: "Your Company" },
list: CompanyList,
},
]}
/>
);
}
Мы получаем данные из коллекции Company
, которую мы создали в Strapi, благодаря уточнению dataProvider
, и помещаем их в созданный нами компонент карточки.
Контактная страница
Наша Contact Page
— это страница, связанная с Clients
. Связь с компаниями-клиентами будет осуществляться через контакты, которые мы создадим здесь. Страница контактов будет содержать информацию о людях, с которыми мы будем связываться. Давайте создадим наш список с помощью хука refine useTable.
src/pages/contact/ContactList.tsx:
import {
List,
Table,
TagField,
useTable,
Space,
EditButton,
DeleteButton,
useModalForm,
} from "@pankod/refine-antd";
import { IContact } from "interfaces";
import { CreateContact } from "components/contacts";
export const ContactsList: React.FC = () => {
const { tableProps } = useTable<IContact>({
metaData: { populate: ["client"] },
});
const {
formProps: createContactFormProps,
modalProps,
show,
} = useModalForm({
resource: "contacts",
action: "create",
redirect: false,
});
return (
<>
<List
createButtonProps={{
onClick: () => {
show();
},
}}
>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="ID" />
<Table.Column dataIndex="first_name" title="First Name" />
<Table.Column dataIndex="last_name" title="Last Name" />
<Table.Column
dataIndex="phone_number"
title="Phone Number"
/>
<Table.Column dataIndex="email" title="Email" />
<Table.Column
dataIndex="job"
title="Job"
render={(value: string) => (
<TagField color={"blue"} value={value} />
)}
/>
<Table.Column<{ id: string }>
title="Actions"
dataIndex="actions"
render={(_, record) => (
<Space>
<EditButton
hideText
size="small"
recordItemId={record.id}
/>
<DeleteButton
hideText
size="small"
recordItemId={record.id}
/>
</Space>
)}
/>
</Table>
</List>
<CreateContact
modalProps={modalProps}
formProps={createContactFormProps}
/>
</>
);
};
Страница списка клиентов
Выше мы создали пример компании и контактов. Теперь давайте создадим Client List
, где мы сможем просматривать наших клиентов.
Компонент карточки клиента
Давайте разработаем карточки, которые будут отображаться в списке клиентов.
src/components/client/ClientItem.tsx:
import { useDelete } from "@pankod/refine-core";
import {
Card,
TagField,
Typography,
Dropdown,
Menu,
Icons,
} from "@pankod/refine-antd";
import { IClient } from "interfaces";
const { FormOutlined, DeleteOutlined } = Icons;
const { Title, Text } = Typography;
type ClientItemProps = {
item: IClient;
editShow: (id?: string | undefined) => void;
};
export const ClientItem: React.FC<ClientItemProps> = ({ item, editShow }) => {
const { mutate } = useDelete();
return (
<Card style={{ width: 300, height: 300, borderColor: "black" }}>
<div style={{ position: "absolute", top: "10px", right: "5px" }}>
<Dropdown
overlay={
<Menu mode="vertical">
<Menu.Item
key="1"
style={{
fontWeight: 500,
}}
icon={
<FormOutlined
style={{
color: "green",
}}
/>
}
onClick={() => editShow(item.id)}
>
Edit Client
</Menu.Item>
<Menu.Item
key="2"
style={{
fontWeight: 500,
}}
icon={
<DeleteOutlined
style={{
color: "red",
}}
/>
}
onClick={() =>
mutate({
resource: "clients",
id: item.id,
mutationMode: "undoable",
undoableTimeout: 5000,
})
}
>
Delete Client
</Menu.Item>
</Menu>
}
trigger={["click"]}
>
<Icons.MoreOutlined
style={{
fontSize: 24,
}}
/>
</Dropdown>
</div>
<Title level={4}>{item.name}</Title>
<Title level={5}>Client Id:</Title>
<Text>{item.id}</Text>
<Title level={5}>Contacts:</Title>
{item.contacts.map((item) => {
return (
<TagField
color={"#d1c4e9"}
value={`${item.first_name} ${item.last_name}`}
/>
);
})}
</Card>
);
};
Страница создания и редактирования клиента
Страница клиента — это место, где вы можете обновлять информацию о своих клиентах и добавлять новых клиентов. Давайте создадим страницы «Создать» и «Редактировать» для создания новых и обновления существующих клиентов.
- Создать клиента
src/components/client/CreateClient.tsx
import {
Create,
Drawer,
DrawerProps,
Form,
FormProps,
Input,
ButtonProps,
Grid,
Select,
useSelect,
useModalForm,
Button,
} from "@pankod/refine-antd";
import { IContact } from "interfaces";
import { CreateContact } from "components/contacts";
type CreateClientProps = {
drawerProps: DrawerProps;
formProps: FormProps;
saveButtonProps: ButtonProps;
};
export const CreateClient: React.FC<CreateClientProps> = ({
drawerProps,
formProps,
saveButtonProps,
}) => {
const breakpoint = Grid.useBreakpoint();
const { selectProps } = useSelect<IContact>({
resource: "contacts",
optionLabel: "first_name",
});
const {
formProps: createContactFormProps,
modalProps,
show,
} = useModalForm({
resource: "contacts",
action: "create",
redirect: false,
});
return (
<>
<Drawer
{...drawerProps}
width={breakpoint.sm ? "500px" : "100%"}
bodyStyle={{ padding: 0 }}
>
<Create saveButtonProps={saveButtonProps}>
<Form
{...formProps}
layout="vertical"
initialValues={{
isActive: true,
}}
>
<Form.Item
label="Client Company Name"
name="name"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Select Contact">
<div style={{ display: "flex" }}>
<Form.Item name={"contacts"} noStyle>
<Select {...selectProps} mode="multiple" />
</Form.Item>
<Button type="link" onClick={() => show()}>
Create Contact
</Button>
</div>
</Form.Item>
</Form>
</Create>
</Drawer>
<CreateContact
modalProps={modalProps}
formProps={createContactFormProps}
/>
</>
);
};
- Редактировать клиента
src/components/client/EditClient.tsx:
import {
Edit,
Drawer,
DrawerProps,
Form,
FormProps,
Input,
ButtonProps,
Grid,
Select,
useSelect,
} from "@pankod/refine-antd";
type EditClientProps = {
drawerProps: DrawerProps;
formProps: FormProps;
saveButtonProps: ButtonProps;
};
export const EditClient: React.FC<EditClientProps> = ({
drawerProps,
formProps,
saveButtonProps,
}) => {
const breakpoint = Grid.useBreakpoint();
const { selectProps } = useSelect({
resource: "contacts",
optionLabel: "first_name",
});
return (
<Drawer
{...drawerProps}
width={breakpoint.sm ? "500px" : "100%"}
bodyStyle={{ padding: 0 }}
>
<Edit saveButtonProps={saveButtonProps}>
<Form
{...formProps}
layout="vertical"
initialValues={{
isActive: true,
}}
>
<Form.Item
label="Client Company Name"
name="name"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Select Contact" name="contacts">
<Select {...selectProps} mode="multiple" />
</Form.Item>
</Form>
</Edit>
</Drawer>
);
};
Страница списка клиентов
Выше мы создали компоненты Card, Create и Edit. Давайте определим и используем эти компоненты, созданные нами, в нашем ClientList
.
src/pages/client/ClientList.tsx:
import { IResourceComponentsProps, HttpError } from "@pankod/refine-core";
import {
useSimpleList,
AntdList,
List,
useDrawerForm,
CreateButton,
} from "@pankod/refine-antd";
import { IClient } from "interfaces";
import { ClientItem, CreateClient, EditClient } from "components/client";
export const ClientList: React.FC<IResourceComponentsProps> = () => {
const { listProps } = useSimpleList<IClient>({
metaData: { populate: ["contacts"] },
});
const {
drawerProps: createDrawerProps,
formProps: createFormProps,
saveButtonProps: createSaveButtonProps,
show: createShow,
} = useDrawerForm<IClient, HttpError, IClient>({
action: "create",
resource: "clients",
redirect: false,
});
const {
drawerProps: editDrawerProps,
formProps: editFormProps,
saveButtonProps: editSaveButtonProps,
show: editShow,
} = useDrawerForm<IClient, HttpError, IClient>({
action: "edit",
resource: "clients",
redirect: false,
});
return (
<>
<List
pageHeaderProps={{
extra: <CreateButton onClick={() => createShow()} />,
}}
>
<AntdList
grid={{ gutter: 24, xs: 1 }}
{...listProps}
renderItem={(item) => (
<AntdList.Item>
<ClientItem item={item} editShow={editShow} />
</AntdList.Item>
)}
/>
</List>
<CreateClient
drawerProps={createDrawerProps}
formProps={createFormProps}
saveButtonProps={createSaveButtonProps}
/>
<EditClient
drawerProps={editDrawerProps}
formProps={editFormProps}
saveButtonProps={editSaveButtonProps}
/>
</>
);
};
Мы создали наши страницы Client
и Contact
. Теперь давайте создадим клиента с уточнением и определим контакты для наших клиентов.
Заключение
Мы завершили первый этап нашего проекта, создав базовую платформу для пользователей для создания своей компании и клиентов. В следующем разделе мы добавим больше функциональности в эту программу, позволив пользователям генерировать счета и отслеживать платежи. Оставайтесь с нами, поскольку мы продолжаем работу над Refine Invoice Generator
!
Для получения дополнительной информации о программе refine ->
Живой пример CodeSandbox
Вы можете найти статью Refine Invoice Generator Part II здесь →