We are going back to 1995! The perfect harmony of Modern stack and Win95
Tags: react, javascript, refine, showdev
Published: 2022-03-25 11:17:38

---
canonical_url: https://refine.dev/blog/awesome-react-windows95-ui-with-refine/
---

![refine Project Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ji8yqqzw3v68dp6bzou.gif)

With **refine**'s **headless** feature, you can include any UI in your project and take full advantage of all its features without worrying about compatibility. To build a project with a vintage `Windows95` style using [React95](https://react95.io/) UI components, we'll use the **refine** headless feature.

## Introduction

In this tutorial, we will use [Supabase](https://supabase.com/) Database in the backend of our project. Our goal with this is to create a `Windows95`-style admin panel using **refine** **headless** and [**refine** Supabase Data Provider](/docs/examples/data-provider/supabase/) features.

## Project Setup

Let's start by creating our **refine** project. You can use the [superplate](https://github.com/pankod/superplate) to create a refine project. superplate will quickly create our **refine** project according to the features we choose.

```bash
npx superplate-cli -p refine-react refine-react95-example

✔ What will be the name of your app › refine-react95-example

✔ Package manager: · npm

✔ Do you want to using UI Framework?: · no(headless)

✔ Data Provider: · supabase-data-provider

✔ i18n - Internationalization: · no

```

That's it! After the installation process is finished, our **refine** project is ready. In addition, Supabase Data Provider features will also come ready. As we mentioned above, since we are using the headless feature of **refine**, we will manage the UI processes ourselves. In this project, we will use `React95` for the UI. Let's continue by installing the necessary packages in our **refine** Project directory.

```bash
npm i react95 styled-components
```

### Manually Project Setup

```bash
npm install @pankod/refine-core @pankod/refine-supabase

npm install react95 styled-components
```

Let's begin editing our project now that it's ready to use.

## Usage

**refine**, automatically creates `supabaseClient` and `AuthProvider` for you. All you have to do is define your Database URL and Secret_Key. You can see how to use it in detail below.

### Supabase Client

`src/utility/supabaseClient.ts:`

```tsx
import { createClient } from "@pankod/refine-supabase";

const SUPABASE_URL = "YOUR_DATABASE_URL";
const SUPABASE_KEY = "YOUR_SUPABASE_KEY";

export const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY);
```

### AuthProvider

`src/authProvider.ts:`

```tsx
import { AuthProvider } from "@pankod/refine-core";

import { supabaseClient } from "utility";

const authProvider: AuthProvider = {
    login: async ({ username, password }) => {
        const { user, error } = await supabaseClient.auth.signIn({
            email: username,
            password,
        });

        if (error) {
            return Promise.reject(error);
        }

        if (user) {
            return Promise.resolve();
        }
    },
    logout: async () => {
        const { error } = await supabaseClient.auth.signOut();

        if (error) {
            return Promise.reject(error);
        }

        return Promise.resolve("/");
    },
    checkError: () => Promise.resolve(),
    checkAuth: () => {
        const session = supabaseClient.auth.session();

        if (session) {
            return Promise.resolve();
        }

        return Promise.reject();
    },
    getPermissions: async () => {
        const user = supabaseClient.auth.user();

        if (user) {
            return Promise.resolve(user.role);
        }
    },
    getUserIdentity: async () => {
        const user = supabaseClient.auth.user();

        if (user) {
            return Promise.resolve({
                ...user,
                name: user.email,
            });
        }
    },
};

export default authProvider;
```

### Configure Refine for Supabase

`src/App.tsx`

```tsx
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";

import { dataProvider } from "@pankod/refine-supabase";
import authProvider from "./authProvider";
import { supabaseClient } from "utility";

function App() {
    return (
        <Refine
            routerProvider={routerProvider}
            dataProvider={dataProvider(supabaseClient)}
            authProvider={authProvider}
        />
    );
}

export default App;
```

We've completed our project structure. Now we can easily access our Supabase Database and utilize our data in our user interface. To begin, let's define the React95 library and create a Login page to access our Supabase data.


### React95 Setup

`src/App.tsx:`

```tsx
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import { dataProvider } from "@pankod/refine-supabase";
import authProvider from "./authProvider";
import { supabaseClient } from "utility";

import original from "react95/dist/themes/original";
import { ThemeProvider } from "styled-components";

function App() {
    return (
        <ThemeProvider theme={original}>
            <Refine
                routerProvider={routerProvider}
                dataProvider={dataProvider(supabaseClient)}
                authProvider={authProvider}
            />
        </ThemeProvider>
    );
}

export default App;
```

In this step, we imported and defined the React95 library in our Refine project. We can now use React95 components and Refine features together in harmony. Let's design a Windows95-style Login page!

## Refine Login Page

`src/pages/login/LoginPage.tsx:`

```tsx
import { useState } from "react";
import { useLogin } from "@pankod/refine-core";

import {
    Window,
    WindowHeader,
    WindowContent,
    TextField,
    Button,
} from "react95";

interface ILoginForm {
    username: string;
    password: string;
}

export const LoginPage = () => {
    const [username, setUsername] = useState("info@refine.dev");
    const [password, setPassword] = useState("refine-supabase");

    const { mutate: login } = useLogin<ILoginForm>();

    return (
        <div
            style={{
                display: "flex",
                flexDirection: "column",
                justifyContent: "center",
                alignItems: "center",
                textAlign: "center",
                minHeight: "100vh",
                backgroundColor: "rgb(0, 128, 128)",
            }}
        >
            <Window>
                <WindowHeader active={true} className="window-header">
                    <span> Refine Login</span>
                </WindowHeader>
                <div style={{ marginTop: 8 }}>
                    <img src="./refine.png" alt="refine-logo" width={100} />
                </div>
                <WindowContent>
                    <form
                        onSubmit={(e) => {
                            e.preventDefault();
                            login({ username, password });
                        }}
                    >
                        <div style={{ width: 500 }}>
                            <div style={{ display: "flex" }}>
                                <TextField
                                    placeholder="User Name"
                                    fullWidth
                                    value={username}
                                    onChange={(e) => {
                                        setUsername(e.target.value);
                                    }}
                                />
                            </div>
                            <br />
                            <TextField
                                placeholder="Password"
                                fullWidth
                                type="password"
                                value={password}
                                onChange={(e) => {
                                    setPassword(e.target.value);
                                }}
                            />
                            <br />
                            <Button type="submit" value="login">
                                Sign in
                            </Button>
                        </div>
                    </form>
                </WindowContent>
            </Window>
        </div>
    );
};
```


![Refine Login Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f4gs1m4ckszijgmwea7.png)

We used React95 components to construct our Login page design. Then, using the **refine** [`<AuthProvider>`](/docs/core/providers/auth-provider/) [`<useLogin>`](/docs/core/hooks/auth/useLogin/) hook, we carried out the database sign-in operation. We can now access our database and fetch our Posts and Categories, as well as create our pages.

## Refine Post Page

After our login process, we'll get the posts from our Supabase Database and display them in the table. We will use React95 components for the UI portion of our table, as well as refine-react-table package to handle pagination, sorting, and filtering. You can use all the features of [React Table](https://react-table.tanstack.com/) with the `refine-react-table` adapter. On this page, we will use this adapter of **refine** to manage the table.

In this step, we'll show how to use the refine-react-table package to create a data table. We will begin by examining this page in two parts. In the first step, we'll utilize our refine-react-table package and React95 UI components to only use our data. Then, in the following stage, we'll arrange the sorting, pagination processes and our UI part. Let's start!

[Refer to the **refine** React Table packages documentation for detailed information. →](/docs/packages/react-table/)

`PostList Part I:`

```tsx
import { useMemo } from "react";
import { useOne } from "@pankod/refine-core";
import { useTable, Column } from "@pankod/refine-react-table";

import { IPost, ICategory, ICsvPost } from "interfaces";
import {
    Table,
    TableBody,
    TableHead,
    TableRow,
    TableHeadCell,
    TableDataCell,
    Window,
    WindowHeader,
    WindowContent,
} from "react95";

export const PostList = () => {
    const columns: Array<Column> = useMemo(
        () => [
            {
                id: "id",
                Header: "ID",
                accessor: "id",
            },
            {
                id: "title",
                Header: "Title",
                accessor: "title",
            },
            {
                id: "category.id",
                Header: "Category",
                accessor: "category.id",
                Cell: ({ cell }) => {
                    const { data, isLoading } = useOne<ICategory>({
                        resource: "categories",
                        id: cell.row.original.categoryId,
                    });

                    if (isLoading) {
                        return <p>loading..</p>;
                    }

                    return data?.data.title ?? "Not Found";
                },
            },
        ],
        [],
    );

    const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
        useTable<IPost>({ columns });

    return (
        <>
            <Window style={{ width: "100%" }}>
                <WindowHeader>Posts</WindowHeader>
                <WindowContent>
                    <Table {...getTableProps()}>
                        <TableHead>
                            {headerGroups.map((headerGroup) => (
                                <TableRow
                                    {...headerGroup.getHeaderGroupProps()}
                                >
                                    {headerGroup.headers.map((column) => (
                                        <TableHeadCell
                                            {...column.getHeaderProps()}
                                        >
                                            {column.render("Header")}
                                        </TableHeadCell>
                                    ))}
                                </TableRow>
                            ))}
                        </TableHead>
                        <TableBody {...getTableBodyProps()}>
                            {rows.map((row, i) => {
                                prepareRow(row);
                                return (
                                    <TableRow {...row.getRowProps()}>
                                        {row.cells.map((cell) => {
                                            return (
                                                <TableDataCell
                                                    {...cell.getCellProps()}
                                                >
                                                    {cell.render("Cell")}
                                                </TableDataCell>
                                            );
                                        })}
                                    </TableRow>
                                );
                            })}
                        </TableBody>
                    </Table>
                </WindowContent>
            </Window>
        </>
    );
};
```


![Refine Post Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4dyu4v90dejhaaxihew.png)

As you can see, our first step is complete. Thanks to the refine-react-table adapter, we fetch our Supabase data and process as table data. Then we placed this data in React95 components. Now let's move on to the second step.


`PostList Part II:`

```tsx
import { useMemo, useRef, useState } from "react";
import { useOne, useNavigation, useDelete } from "@pankod/refine-core";
import {
    useTable,
    Column,
    useSortBy,
    usePagination,
    useFilters,
} from "@pankod/refine-react-table";

import { IPost, ICategory } from "interfaces";
import {
    Table,
    TableBody,
    TableHead,
    TableRow,
    TableHeadCell,
    TableDataCell,
    Window,
    WindowHeader,
    WindowContent,
    Button,
    Select,
    NumberField,
    Progress,
} from "react95";

export const PostList = () => {
    const { edit, create } = useNavigation();
    const { mutate } = useDelete();

    const columns: Array<Column> = useMemo(
        () => [
            {
                id: "id",
                Header: "ID",
                accessor: "id",
            },
            {
                id: "title",
                Header: "Title",
                accessor: "title",
            },
            {
                id: "category.id",
                Header: "Category",
                accessor: "category.id",
                Cell: ({ cell }) => {
                    const { data, isLoading } = useOne<ICategory>({
                        resource: "categories",
                        id: cell.row.original.categoryId,
                    });

                    if (isLoading) {
                        return <p>loading..</p>;
                    }

                    return data?.data.title ?? "Not Found";
                },
            },
            {
                id: "action",
                Header: "Action",
                accessor: "id",
                Cell: ({ value }) => (
                    <div>
                        <Button onClick={() => edit("posts", value)}>
                            Edit
                        </Button>

                        <Button
                            style={{ marginLeft: 4, marginTop: 4 }}
                            onClick={() =>
                                mutate({ id: value, resource: "posts" })
                            }
                        >
                            Delete
                        </Button>
                    </div>
                ),
            },
        ],
        [],
    );

    const {
        getTableProps,
        getTableBodyProps,
        headerGroups,
        rows,
        prepareRow,
        pageOptions,
        setPageSize,
        gotoPage,
        state: { pageIndex, pageSize },
    } = useTable<IPost>({ columns }, useFilters, useSortBy, usePagination);

    return (
        <>
            <Window style={{ width: "100%" }}>
                <WindowHeader>Posts</WindowHeader>
                <WindowContent>
                    <Table {...getTableProps()}>
                        <TableHead>
                            {headerGroups.map((headerGroup) => (
                                <TableRow
                                    {...headerGroup.getHeaderGroupProps()}
                                >
                                    {headerGroup.headers.map((column) => (
                                        <TableHeadCell
                                            {...column.getHeaderProps(
                                                column.getSortByToggleProps(),
                                            )}
                                        >
                                            {column.render("Header")}
                                        </TableHeadCell>
                                    ))}
                                </TableRow>
                            ))}
                        </TableHead>
                        <TableBody {...getTableBodyProps()}>
                            {rows.map((row, i) => {
                                prepareRow(row);
                                return (
                                    <TableRow {...row.getRowProps()}>
                                        {row.cells.map((cell) => {
                                            return (
                                                <TableDataCell
                                                    {...cell.getCellProps()}
                                                >
                                                    {cell.render("Cell")}
                                                </TableDataCell>
                                            );
                                        })}
                                    </TableRow>
                                );
                            })}
                        </TableBody>
                    </Table>
                </WindowContent>
                <div
                    style={{
                        display: "flex",
                        justifyContent: "flex-end",
                        marginBottom: 8,
                        marginTop: 8,
                        alignItems: "flex-end",
                    }}
                >
                    <Select
                        style={{ marginLeft: 8 }}
                        value={pageSize}
                        onChange={(_, selection) => {
                            setPageSize(selection.value);
                        }}
                        options={opt}
                        defaultValue={"10"}
                    ></Select>
                    <span style={{ marginLeft: 8 }}>
                        Page{" "}
                        <strong>
                            {pageIndex + 1} of {pageOptions.length}
                        </strong>
                        <span style={{ marginLeft: 8 }}>
                            Go to page:
                            <NumberField
                                style={{ marginLeft: 8 }}
                                min={1}
                                defaultValue={pageIndex + 1}
                                width={130}
                                onChange={(value) => {
                                    const page = value ? Number(value) - 1 : 0;
                                    gotoPage(page);
                                }}
                            />
                        </span>
                    </span>
                </div>
            </Window>
        </>
    );
};

export const opt = [
    { value: 10, label: "10" },
    { value: 20, label: "20" },
    { value: 30, label: "30" },
    { value: 40, label: "40" },
];
```

You may quickly handle sorting and paging operations by simply adding a few lines thanks to **refine**'s out-of-the-box features. We have completed our Post page by adding the pagination and sorting features provided by the Refine `useTable` hook to our table.

## Refine Create and Edit Page

We have created our post page. Now we will create pages where we can create and edit posts. **refine** provides a [`refine-react-hook-form`](/docs/packages/react-hook-form/) adapter that you can use with the headless feature. All the features of [React Hook Form](https://react-hook-form.com/) work in harmony with **refine** and the form you will create.

-   `Create Page`

```tsx
import { Controller, useForm } from "@pankod/refine-react-hook-form";
import { useSelect, useNavigation } from "@pankod/refine-core";
import {
    Select,
    Fieldset,
    Button,
    TextField,
    Window,
    WindowHeader,
    WindowContent,
    ListItem,
} from "react95";

export const PostCreate: React.FC = () => {
    const {
        refineCore: { onFinish, formLoading },
        register,
        handleSubmit,
        control,
        formState: { errors },
    } = useForm();

    const { goBack } = useNavigation();

    const { options } = useSelect({
        resource: "categories",
    });

    return (
        <>
            <Window style={{ width: "100%", height: "100%" }}>
                <WindowHeader active={true} className="window-header">
                    <span>Create Post</span>
                </WindowHeader>
                <form onSubmit={handleSubmit(onFinish)}>
                    <WindowContent>
                        <label>Title: </label>
                        <br />
                        <br />
                        <TextField
                            {...register("title", { required: true })}
                            placeholder="Type here..."
                        />
                        {errors.title && <span>This field is required</span>}
                        <br />
                        <br />

                        <Controller
                            {...register("categoryId", { required: true })}
                            control={control}
                            render={({ field: { onChange, value } }) => (
                                <Fieldset label={"Category"}>
                                    <Select
                                        options={options}
                                        menuMaxHeight={160}
                                        width={160}
                                        variant="flat"
                                        onChange={onChange}
                                        value={value}
                                    />
                                </Fieldset>
                            )}
                        />
                        {errors.category && <span>This field is required</span>}
                        <br />
                        <label>Content: </label>
                        <br />
                        <TextField
                            {...register("content", { required: true })}
                            multiline
                            rows={10}
                            cols={50}
                        />

                        {errors.content && <span>This field is required</span>}
                        <br />
                        <Button type="submit" value="Submit">
                            Submit
                        </Button>
                        {formLoading && <p>Loading</p>}
                    </WindowContent>
                </form>
            </Window>
        </>
    );
};
```

-   `Edit Page`

```tsx
import { useEffect } from "react";
import { Controller, useForm } from "@pankod/refine-react-hook-form";
import { useSelect, useNavigation } from "@pankod/refine-core";
import {
    Select,
    Fieldset,
    Button,
    TextField,
    WindowContent,
    Window,
    WindowHeader,
    ListItem,
} from "react95";

export const PostEdit: React.FC = () => {
    const {
        refineCore: { onFinish, formLoading, queryResult },
        register,
        handleSubmit,
        resetField,
        control,
        formState: { errors },
    } = useForm();

    const { goBack } = useNavigation();

    const { options } = useSelect({
        resource: "categories",
        defaultValue: queryResult?.data?.data.categoryId,
    });

    useEffect(() => {
        resetField("categoryId");
    }, [options]);

    return (
        <>
            <Window style={{ width: "100%", height: "100%" }}>
                <form onSubmit={handleSubmit(onFinish)}>
                    <WindowHeader active={true} className="window-header">
                        <span>Edit Post</span>
                    </WindowHeader>
                    <WindowContent>
                        <label>Title: </label>
                        <br />
                        <TextField
                            {...register("title", { required: true })}
                            placeholder="Type here..."
                        />
                        {errors.title && <span>This field is required</span>}
                        <br />
                        <br />

                        <Controller
                            {...register("categoryId", { required: true })}
                            control={control}
                            render={({ field: { onChange, value } }) => (
                                <Fieldset label={"Category"}>
                                    <Select
                                        options={options}
                                        menuMaxHeight={160}
                                        width={160}
                                        variant="flat"
                                        onChange={onChange}
                                        value={value}
                                    />
                                </Fieldset>
                            )}
                        />
                        {errors.category && <span>This field is required</span>}
                        <br />
                        <label>Content: </label>
                        <br />
                        <TextField
                            {...register("content", { required: true })}
                            multiline
                            rows={10}
                            cols={50}
                        />

                        {errors.content && <span>This field is required</span>}
                        <br />
                        <Button type="submit" value="Submit">
                            Submit
                        </Button>
                        {formLoading && <p>Loading</p>}
                    </WindowContent>
                </form>
            </Window>
        </>
    );
};
```

We can manage our forms and generate Posts thanks to the `refine-react-hook-form` adapter, and we may save the Post that we created with the **refine** `onFinish` method directly to Supabase.


## Customize Refine Layout

Our app is almost ready. As a final step, let's edit our Layout to make our application more like Window95. Let's create a footer component first and then define it as a **refine** Layout.

[Refer to the refine Custom Layout docs for detailed usage. →](/docs/guides-and-concepts/custom-layout/)

-   `Footer`

`components/Footer.tsx`

```tsx
import React, { useState } from "react";
import { useLogout, useNavigation } from "@pankod/refine-core";
import { AppBar, Toolbar, Button, List, ListItem } from "react95";

export const Footer: React.FC = () => {
    const [open, setOpen] = useState(false);

    const { mutate: logout } = useLogout();
    const { push } = useNavigation();

    return (
        <AppBar style={{ top: "unset", bottom: 0 }}>
            <Toolbar style={{ justifyContent: "space-between" }}>
                <div style={{ position: "relative", display: "inline-block" }}>
                    <Button
                        onClick={() => setOpen(!open)}
                        active={open}
                        style={{ fontWeight: "bold" }}
                    >
                        <img
                            src={"./refine.png"}
                            alt="refine logo"
                            style={{ height: "20px", marginRight: 4 }}
                        />
                    </Button>
                    {open && (
                        <List
                            style={{
                                position: "absolute",
                                left: "0",
                                bottom: "100%",
                            }}
                            onClick={() => setOpen(false)}
                        >
                            <ListItem
                                onClick={() => {
                                    push("posts");
                                }}
                            >
                                Posts
                            </ListItem>
                            <ListItem
                                onClick={() => {
                                    push("categories");
                                }}
                            >
                                Categories
                            </ListItem>
                            <ListItem
                                onClick={() => {
                                    logout();
                                }}
                            >
                                <span role="img" aria-label="🔙">
                                    🔙
                                </span>
                                Logout
                            </ListItem>
                        </List>
                    )}
                </div>
            </Toolbar>
        </AppBar>
    );
};
```

```tsx
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import { dataProvider } from "@pankod/refine-supabase";
import authProvider from "./authProvider";
import { supabaseClient } from "utility";

import original from "react95/dist/themes/original";
import { ThemeProvider } from "styled-components";

import { PostList, PostEdit, PostCreate } from "pages/posts";
import { CategoryList, CategoryCreate, CategoryEdit } from "pages/category";
import { LoginPage } from "pages/login";
import { Footer } from "./components/footer";

import "./app.css";

function App() {
    return (
        <ThemeProvider theme={original}>
            <Refine
                routerProvider={routerProvider}
                dataProvider={dataProvider(supabaseClient)}
                authProvider={authProvider}
                LoginPage={LoginPage}
                Layout={({ children }) => {
                    return (
                        <div className="main">
                            <div className="layout">{children}</div>
                            <div>
                                <Footer />
                            </div>
                        </div>
                    );
                }}
                resources={[
                    {
                        name: "posts",
                        list: PostList,
                        create: PostCreate,
                        edit: PostEdit,
                    },
                ]}
            />
        </ThemeProvider>
    );
}

export default App;
```


![Refine Footer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8s30sy9pi3oyvueu70u.gif)


Now we'll make a top menu component that's specific to the Windows 95 design.

- `Top Menu`

`components/bar/TopMenu:`

```tsx
import React, { useState } from "react";
import { AppBar, Toolbar, Button, List } from "react95";

type TopMenuProps = {
    children: React.ReactNode[] | React.ReactNode;
};

export const TopMenu: React.FC<TopMenuProps> = ({ children }) => {
    const [open, setOpen] = useState(false);

    return (
        <AppBar style={{ zIndex: 1 }}>
            <Toolbar>
                <Button
                    variant="menu"
                    onClick={() => setOpen(!open)}
                    active={open}
                >
                    File
                </Button>
                <Button variant="menu" disabled>
                    Edit
                </Button>
                <Button variant="menu" disabled>
                    View
                </Button>
                <Button variant="menu" disabled>
                    Format
                </Button>
                <Button variant="menu" disabled>
                    Tools
                </Button>
                <Button variant="menu" disabled>
                    Table
                </Button>
                <Button variant="menu" disabled>
                    Window
                </Button>
                <Button variant="menu" disabled>
                    Help
                </Button>
                {open && (
                    <List
                        style={{
                            position: "absolute",
                            left: "0",
                            top: "100%",
                        }}
                        onClick={() => setOpen(false)}
                    >
                        {children}
                    </List>
                )}
            </Toolbar>
        </AppBar>
    );
};
```


## Project Overview

![Refine Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68weodkd7enbbyeqvm97.gif)


## Conclusion

**refine** is a very powerful and flexible internal tool development framework. The features it provides will greatly reduce your development time. In this example, we have shown step-by-step how a development can be quick and easy using a custom UI and refine-core features. **refine** does not restrict you, and it delivers almost all of your project's requirements via the hooks it provides, regardless of the UI.

[Live CodeSandbox Example](https://codesandbox.io/s/refine-react95-example-beie0q)

[Check out for detailed information about refine. →](https://refine.dev/docs/getting-started/overview/)