init refine
app
This commit is contained in:
16
.eslintrc.cjs
Normal file
16
.eslintrc.cjs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
||||||
|
plugins: ["react-refresh"],
|
||||||
|
rules: {
|
||||||
|
"react-refresh/only-export-components": "warn",
|
||||||
|
},
|
||||||
|
};
|
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
\*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
|
||||||
|
.vscode/_
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
_.suo
|
||||||
|
_.ntvs_
|
||||||
|
_.njsproj
|
||||||
|
_.sln
|
||||||
|
\*.sw?
|
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# This Dockerfile uses `serve` npm package to serve the static files with node process.
|
||||||
|
# You can find the Dockerfile for nginx in the following link:
|
||||||
|
# https://github.com/refinedev/dockerfiles/blob/main/vite/Dockerfile.nginx
|
||||||
|
FROM refinedev/node:18 AS base
|
||||||
|
|
||||||
|
FROM base as deps
|
||||||
|
|
||||||
|
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||||
|
elif [ -f package-lock.json ]; then npm ci; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||||
|
else echo "Lockfile not found." && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM base as builder
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
COPY --from=deps /app/refine/node_modules ./node_modules
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM base as runner
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
RUN npm install -g serve
|
||||||
|
|
||||||
|
COPY --from=builder /app/refine/dist ./
|
||||||
|
|
||||||
|
USER refine
|
||||||
|
|
||||||
|
CMD ["serve"]
|
49
README.MD
Normal file
49
README.MD
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# white-nights
|
||||||
|
|
||||||
|
<div align="center" style="margin: 30px;">
|
||||||
|
<a href="https://refine.dev">
|
||||||
|
<img alt="refine logo" src="https://refine.ams3.cdn.digitaloceanspaces.com/readme/refine-readme-banner.png">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
This [Refine](https://github.com/refinedev/refine) project was generated with [create refine-app](https://github.com/refinedev/refine/tree/master/packages/create-refine-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility ✨
|
||||||
|
|
||||||
|
Refine's hooks and components simplifies the development process and eliminates the repetitive tasks by providing industry-standard solutions for crucial aspects of a project, including authentication, access control, routing, networking, state management, and i18n.
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
### Running the development server.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building for production.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the production server.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about **Refine**, please check out the [Documentation](https://refine.dev/docs)
|
||||||
|
|
||||||
|
- **REST Data Provider** [Docs](https://refine.dev/docs/core/providers/data-provider/#overview)
|
||||||
|
- **Material UI** [Docs](https://refine.dev/docs/ui-frameworks/mui/tutorial/)
|
||||||
|
- **React Router** [Docs](https://refine.dev/docs/core/providers/router-provider/)
|
||||||
|
- **Custom Auth Provider** [Docs](https://refine.dev/docs/core/providers/auth-provider/)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
41
index.html
Normal file
41
index.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="refine | Build your React-based CRUD applications, without constraints."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
data-rh="true"
|
||||||
|
property="og:image"
|
||||||
|
content="https://refine.dev/img/refine_social.png"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
data-rh="true"
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://refine.dev/img/refine_social.png"
|
||||||
|
/>
|
||||||
|
<title>
|
||||||
|
Refine - Build your React-based CRUD applications, without constraints.
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm dev` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
60
package.json
Normal file
60
package.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "white-nights",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@refinedev/cli": "^2.16.21",
|
||||||
|
"@refinedev/core": "^4.47.1",
|
||||||
|
"@refinedev/devtools": "^1.1.32",
|
||||||
|
"@refinedev/kbar": "^1.3.6",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"react-router": "^7.0.2",
|
||||||
|
"@refinedev/simple-rest": "^5.0.1",
|
||||||
|
"@refinedev/mui": "^6.0.0",
|
||||||
|
"@refinedev/react-hook-form": "^4.8.14",
|
||||||
|
"@mui/icons-material": "^6.1.6",
|
||||||
|
"@emotion/react": "^11.8.2",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/lab": "^6.0.0-beta.14",
|
||||||
|
"@mui/material": "^6.1.7",
|
||||||
|
"@mui/x-data-grid": "^7.22.2",
|
||||||
|
"react-hook-form": "^7.30.0",
|
||||||
|
"@refinedev/react-router": "^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.16.2",
|
||||||
|
"@types/react": "^18.0.0",
|
||||||
|
"@types/react-dom": "^18.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||||
|
"@typescript-eslint/parser": "^5.57.1",
|
||||||
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
|
"eslint": "^8.38.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.3.4",
|
||||||
|
"typescript": "^5.4.2",
|
||||||
|
"vite": "^4.3.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "refine dev",
|
||||||
|
"build": "tsc && refine build",
|
||||||
|
"start": "refine start",
|
||||||
|
"refine": "refine"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"refine": {
|
||||||
|
"projectId": "Wv044J-t53S3s-PcbJGe"
|
||||||
|
}
|
||||||
|
}
|
6401
pnpm-lock.yaml
generated
Normal file
6401
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
154
src/App.tsx
Normal file
154
src/App.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import {
|
||||||
|
Refine,
|
||||||
|
GitHubBanner,
|
||||||
|
WelcomePage,
|
||||||
|
Authenticated,
|
||||||
|
} from "@refinedev/core";
|
||||||
|
import { DevtoolsPanel, DevtoolsProvider } from "@refinedev/devtools";
|
||||||
|
import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AuthPage,
|
||||||
|
ErrorComponent,
|
||||||
|
useNotificationProvider,
|
||||||
|
RefineSnackbarProvider,
|
||||||
|
ThemedLayoutV2,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
|
||||||
|
import dataProvider from "@refinedev/simple-rest";
|
||||||
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
|
import GlobalStyles from "@mui/material/GlobalStyles";
|
||||||
|
import { BrowserRouter, Route, Routes, Outlet } from "react-router";
|
||||||
|
import routerBindings, {
|
||||||
|
NavigateToResource,
|
||||||
|
CatchAllNavigate,
|
||||||
|
UnsavedChangesNotifier,
|
||||||
|
DocumentTitleHandler,
|
||||||
|
} from "@refinedev/react-router";
|
||||||
|
import {
|
||||||
|
BlogPostList,
|
||||||
|
BlogPostCreate,
|
||||||
|
BlogPostEdit,
|
||||||
|
BlogPostShow,
|
||||||
|
} from "./pages/blog-posts";
|
||||||
|
import {
|
||||||
|
CategoryList,
|
||||||
|
CategoryCreate,
|
||||||
|
CategoryEdit,
|
||||||
|
CategoryShow,
|
||||||
|
} from "./pages/categories";
|
||||||
|
import { ColorModeContextProvider } from "./contexts/color-mode";
|
||||||
|
import { Header } from "./components/header";
|
||||||
|
import { Login } from "./pages/login";
|
||||||
|
import { Register } from "./pages/register";
|
||||||
|
import { ForgotPassword } from "./pages/forgotPassword";
|
||||||
|
import { authProvider } from "./authProvider";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<GitHubBanner />
|
||||||
|
<RefineKbarProvider>
|
||||||
|
<ColorModeContextProvider>
|
||||||
|
<CssBaseline />
|
||||||
|
<GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} />
|
||||||
|
<RefineSnackbarProvider>
|
||||||
|
<DevtoolsProvider>
|
||||||
|
<Refine
|
||||||
|
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
|
||||||
|
notificationProvider={useNotificationProvider}
|
||||||
|
routerProvider={routerBindings}
|
||||||
|
authProvider={authProvider}
|
||||||
|
resources={[
|
||||||
|
{
|
||||||
|
name: "blog_posts",
|
||||||
|
list: "/blog-posts",
|
||||||
|
create: "/blog-posts/create",
|
||||||
|
edit: "/blog-posts/edit/:id",
|
||||||
|
show: "/blog-posts/show/:id",
|
||||||
|
meta: {
|
||||||
|
canDelete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "categories",
|
||||||
|
list: "/categories",
|
||||||
|
create: "/categories/create",
|
||||||
|
edit: "/categories/edit/:id",
|
||||||
|
show: "/categories/show/:id",
|
||||||
|
meta: {
|
||||||
|
canDelete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
options={{
|
||||||
|
syncWithLocation: true,
|
||||||
|
warnWhenUnsavedChanges: true,
|
||||||
|
useNewQueryKeys: true,
|
||||||
|
projectId: "Wv044J-t53S3s-PcbJGe",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<Authenticated
|
||||||
|
key="authenticated-inner"
|
||||||
|
fallback={<CatchAllNavigate to="/login" />}
|
||||||
|
>
|
||||||
|
<ThemedLayoutV2 Header={Header}>
|
||||||
|
<Outlet />
|
||||||
|
</ThemedLayoutV2>
|
||||||
|
</Authenticated>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={<NavigateToResource resource="blog_posts" />}
|
||||||
|
/>
|
||||||
|
<Route path="/blog-posts">
|
||||||
|
<Route index element={<BlogPostList />} />
|
||||||
|
<Route path="create" element={<BlogPostCreate />} />
|
||||||
|
<Route path="edit/:id" element={<BlogPostEdit />} />
|
||||||
|
<Route path="show/:id" element={<BlogPostShow />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="/categories">
|
||||||
|
<Route index element={<CategoryList />} />
|
||||||
|
<Route path="create" element={<CategoryCreate />} />
|
||||||
|
<Route path="edit/:id" element={<CategoryEdit />} />
|
||||||
|
<Route path="show/:id" element={<CategoryShow />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="*" element={<ErrorComponent />} />
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<Authenticated
|
||||||
|
key="authenticated-outer"
|
||||||
|
fallback={<Outlet />}
|
||||||
|
>
|
||||||
|
<NavigateToResource />
|
||||||
|
</Authenticated>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/register" element={<Register />} />
|
||||||
|
<Route
|
||||||
|
path="/forgot-password"
|
||||||
|
element={<ForgotPassword />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
|
||||||
|
<RefineKbar />
|
||||||
|
<UnsavedChangesNotifier />
|
||||||
|
<DocumentTitleHandler />
|
||||||
|
</Refine>
|
||||||
|
<DevtoolsPanel />
|
||||||
|
</DevtoolsProvider>
|
||||||
|
</RefineSnackbarProvider>
|
||||||
|
</ColorModeContextProvider>
|
||||||
|
</RefineKbarProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
59
src/authProvider.ts
Normal file
59
src/authProvider.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import type { AuthProvider } from "@refinedev/core";
|
||||||
|
|
||||||
|
export const TOKEN_KEY = "refine-auth";
|
||||||
|
|
||||||
|
export const authProvider: AuthProvider = {
|
||||||
|
login: async ({ username, email, password }) => {
|
||||||
|
if ((username || email) && password) {
|
||||||
|
localStorage.setItem(TOKEN_KEY, username);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
redirectTo: "/",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
name: "LoginError",
|
||||||
|
message: "Invalid username or password",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
logout: async () => {
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
redirectTo: "/login",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
check: async () => {
|
||||||
|
const token = localStorage.getItem(TOKEN_KEY);
|
||||||
|
if (token) {
|
||||||
|
return {
|
||||||
|
authenticated: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
authenticated: false,
|
||||||
|
redirectTo: "/login",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getPermissions: async () => null,
|
||||||
|
getIdentity: async () => {
|
||||||
|
const token = localStorage.getItem(TOKEN_KEY);
|
||||||
|
if (token) {
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
name: "John Doe",
|
||||||
|
avatar: "https://i.pravatar.cc/300",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onError: async (error) => {
|
||||||
|
console.error(error);
|
||||||
|
return { error };
|
||||||
|
},
|
||||||
|
};
|
80
src/components/header/index.tsx
Normal file
80
src/components/header/index.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import DarkModeOutlined from "@mui/icons-material/DarkModeOutlined";
|
||||||
|
import LightModeOutlined from "@mui/icons-material/LightModeOutlined";
|
||||||
|
import AppBar from "@mui/material/AppBar";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { useGetIdentity } from "@refinedev/core";
|
||||||
|
import { HamburgerMenu, RefineThemedLayoutV2HeaderProps } from "@refinedev/mui";
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
import { ColorModeContext } from "../../contexts/color-mode";
|
||||||
|
|
||||||
|
type IUser = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||||
|
sticky = true,
|
||||||
|
}) => {
|
||||||
|
const { mode, setMode } = useContext(ColorModeContext);
|
||||||
|
|
||||||
|
const { data: user } = useGetIdentity<IUser>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar position={sticky ? "sticky" : "relative"}>
|
||||||
|
<Toolbar>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
width="100%"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<HamburgerMenu />
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
width="100%"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => {
|
||||||
|
setMode();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{mode === "dark" ? <LightModeOutlined /> : <DarkModeOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
{(user?.avatar || user?.name) && (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
gap="16px"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
{user?.name && (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
display: {
|
||||||
|
xs: "none",
|
||||||
|
sm: "inline-block",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
{user?.name}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Avatar src={user?.avatar} alt={user?.name} />
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
1
src/components/index.ts
Normal file
1
src/components/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { Header } from "./header";
|
59
src/contexts/color-mode/index.tsx
Normal file
59
src/contexts/color-mode/index.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React, {
|
||||||
|
PropsWithChildren,
|
||||||
|
createContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
|
import { RefineThemes } from "@refinedev/mui";
|
||||||
|
|
||||||
|
type ColorModeContextType = {
|
||||||
|
mode: string;
|
||||||
|
setMode: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ColorModeContext = createContext<ColorModeContextType>(
|
||||||
|
{} as ColorModeContextType
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ColorModeContextProvider: React.FC<PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const colorModeFromLocalStorage = localStorage.getItem("colorMode");
|
||||||
|
const isSystemPreferenceDark = window?.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)"
|
||||||
|
).matches;
|
||||||
|
|
||||||
|
const systemPreference = isSystemPreferenceDark ? "dark" : "light";
|
||||||
|
const [mode, setMode] = useState(
|
||||||
|
colorModeFromLocalStorage || systemPreference
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.localStorage.setItem("colorMode", mode);
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
|
const setColorMode = () => {
|
||||||
|
if (mode === "light") {
|
||||||
|
setMode("dark");
|
||||||
|
} else {
|
||||||
|
setMode("light");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColorModeContext.Provider
|
||||||
|
value={{
|
||||||
|
setMode: setColorMode,
|
||||||
|
mode,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
// you can change the theme colors here. example: mode === "light" ? RefineThemes.Magenta : RefineThemes.MagentaDark
|
||||||
|
theme={mode === "light" ? RefineThemes.Blue : RefineThemes.BlueDark}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
</ColorModeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
13
src/index.tsx
Normal file
13
src/index.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
const container = document.getElementById("root") as HTMLElement;
|
||||||
|
const root = createRoot(container);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
120
src/pages/blog-posts/create.tsx
Normal file
120
src/pages/blog-posts/create.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { Autocomplete, Box, MenuItem, Select, TextField } from "@mui/material";
|
||||||
|
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||||
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
|
import React from "react";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
export const BlogPostCreate = () => {
|
||||||
|
const {
|
||||||
|
saveButtonProps,
|
||||||
|
refineCore: { formLoading },
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({});
|
||||||
|
|
||||||
|
const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||||
|
resource: "categories",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
{...register("title", {
|
||||||
|
required: "This field is required",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.title}
|
||||||
|
helperText={(errors as any)?.title?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type="text"
|
||||||
|
label={"Title"}
|
||||||
|
name="title"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
{...register("content", {
|
||||||
|
required: "This field is required",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.content}
|
||||||
|
helperText={(errors as any)?.content?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
multiline
|
||||||
|
label={"Content"}
|
||||||
|
name="content"
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={"category.id"}
|
||||||
|
rules={{ required: "This field is required" }}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
defaultValue={null as any}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Autocomplete
|
||||||
|
{...categoryAutocompleteProps}
|
||||||
|
{...field}
|
||||||
|
onChange={(_, value) => {
|
||||||
|
field.onChange(value.id);
|
||||||
|
}}
|
||||||
|
getOptionLabel={(item) => {
|
||||||
|
return (
|
||||||
|
categoryAutocompleteProps?.options?.find((p) => {
|
||||||
|
const itemId =
|
||||||
|
typeof item === "object"
|
||||||
|
? item?.id?.toString()
|
||||||
|
: item?.toString();
|
||||||
|
const pId = p?.id?.toString();
|
||||||
|
return itemId === pId;
|
||||||
|
})?.title ?? ""
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isOptionEqualToValue={(option, value) => {
|
||||||
|
const optionId = option?.id?.toString();
|
||||||
|
const valueId =
|
||||||
|
typeof value === "object"
|
||||||
|
? value?.id?.toString()
|
||||||
|
: value?.toString();
|
||||||
|
return value === undefined || optionId === valueId;
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label={"Category"}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
error={!!(errors as any)?.category?.id}
|
||||||
|
helperText={(errors as any)?.category?.id?.message}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="status"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
value={field?.value || "draft"}
|
||||||
|
label={"Status"}
|
||||||
|
>
|
||||||
|
<MenuItem value="draft">Draft</MenuItem>
|
||||||
|
<MenuItem value="published">Published</MenuItem>
|
||||||
|
<MenuItem value="rejected">Rejected</MenuItem>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Create>
|
||||||
|
);
|
||||||
|
};
|
125
src/pages/blog-posts/edit.tsx
Normal file
125
src/pages/blog-posts/edit.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { Autocomplete, Box, Select, TextField } from "@mui/material";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||||
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
|
import React from "react";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
export const BlogPostEdit = () => {
|
||||||
|
const {
|
||||||
|
saveButtonProps,
|
||||||
|
refineCore: { queryResult, formLoading },
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({});
|
||||||
|
|
||||||
|
const blogPostsData = queryResult?.data?.data;
|
||||||
|
|
||||||
|
const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||||
|
resource: "categories",
|
||||||
|
defaultValue: blogPostsData?.category?.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Edit isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
{...register("title", {
|
||||||
|
required: "This field is required",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.title}
|
||||||
|
helperText={(errors as any)?.title?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type="text"
|
||||||
|
label={"Title"}
|
||||||
|
name="title"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
{...register("content", {
|
||||||
|
required: "This field is required",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.content}
|
||||||
|
helperText={(errors as any)?.content?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
multiline
|
||||||
|
label={"Content"}
|
||||||
|
name="content"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={"category.id"}
|
||||||
|
rules={{ required: "This field is required" }}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
defaultValue={null as any}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Autocomplete
|
||||||
|
{...categoryAutocompleteProps}
|
||||||
|
{...field}
|
||||||
|
onChange={(_, value) => {
|
||||||
|
field.onChange(value.id);
|
||||||
|
}}
|
||||||
|
getOptionLabel={(item) => {
|
||||||
|
return (
|
||||||
|
categoryAutocompleteProps?.options?.find((p) => {
|
||||||
|
const itemId =
|
||||||
|
typeof item === "object"
|
||||||
|
? item?.id?.toString()
|
||||||
|
: item?.toString();
|
||||||
|
const pId = p?.id?.toString();
|
||||||
|
return itemId === pId;
|
||||||
|
})?.title ?? ""
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isOptionEqualToValue={(option, value) => {
|
||||||
|
const optionId = option?.id?.toString();
|
||||||
|
const valueId =
|
||||||
|
typeof value === "object"
|
||||||
|
? value?.id?.toString()
|
||||||
|
: value?.toString();
|
||||||
|
return value === undefined || optionId === valueId;
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label={"Category"}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
error={!!(errors as any)?.category?.id}
|
||||||
|
helperText={(errors as any)?.category?.id?.message}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="status"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
value={field?.value || "draft"}
|
||||||
|
label={"Status"}
|
||||||
|
>
|
||||||
|
<MenuItem value="draft">Draft</MenuItem>
|
||||||
|
<MenuItem value="published">Published</MenuItem>
|
||||||
|
<MenuItem value="rejected">Rejected</MenuItem>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
|
};
|
4
src/pages/blog-posts/index.ts
Normal file
4
src/pages/blog-posts/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./create";
|
||||||
|
export * from "./edit";
|
||||||
|
export * from "./list";
|
||||||
|
export * from "./show";
|
124
src/pages/blog-posts/list.tsx
Normal file
124
src/pages/blog-posts/list.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
||||||
|
import { useMany } from "@refinedev/core";
|
||||||
|
import {
|
||||||
|
DateField,
|
||||||
|
DeleteButton,
|
||||||
|
EditButton,
|
||||||
|
List,
|
||||||
|
ShowButton,
|
||||||
|
useDataGrid,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
import { Typography } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const BlogPostList = () => {
|
||||||
|
const { dataGridProps } = useDataGrid({});
|
||||||
|
|
||||||
|
const { data: categoryData, isLoading: categoryIsLoading } = useMany({
|
||||||
|
resource: "categories",
|
||||||
|
ids:
|
||||||
|
dataGridProps?.rows
|
||||||
|
?.map((item: any) => item?.category?.id)
|
||||||
|
.filter(Boolean) ?? [],
|
||||||
|
queryOptions: {
|
||||||
|
enabled: !!dataGridProps?.rows,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
field: "id",
|
||||||
|
headerName: "ID",
|
||||||
|
type: "number",
|
||||||
|
minWidth: 50,
|
||||||
|
display: "flex",
|
||||||
|
align: "left",
|
||||||
|
headerAlign: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "title",
|
||||||
|
headerName: "Title",
|
||||||
|
minWidth: 200,
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "content",
|
||||||
|
flex: 1,
|
||||||
|
headerName: "Content",
|
||||||
|
minWidth: 250,
|
||||||
|
display: "flex",
|
||||||
|
renderCell: function render({ value }) {
|
||||||
|
if (!value) return "-";
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
component="p"
|
||||||
|
whiteSpace="pre"
|
||||||
|
overflow="hidden"
|
||||||
|
textOverflow="ellipsis"
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "category",
|
||||||
|
headerName: "Category",
|
||||||
|
minWidth: 160,
|
||||||
|
display: "flex",
|
||||||
|
valueGetter: (_, row) => {
|
||||||
|
const value = row?.category;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
renderCell: function render({ value }) {
|
||||||
|
return categoryIsLoading ? (
|
||||||
|
<>Loading...</>
|
||||||
|
) : (
|
||||||
|
categoryData?.data?.find((item) => item.id === value?.id)?.title
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "status",
|
||||||
|
headerName: "Status",
|
||||||
|
minWidth: 80,
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "createdAt",
|
||||||
|
headerName: "Created at",
|
||||||
|
minWidth: 120,
|
||||||
|
display: "flex",
|
||||||
|
renderCell: function render({ value }) {
|
||||||
|
return <DateField value={value} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "actions",
|
||||||
|
headerName: "Actions",
|
||||||
|
align: "right",
|
||||||
|
headerAlign: "right",
|
||||||
|
minWidth: 120,
|
||||||
|
sortable: false,
|
||||||
|
display: "flex",
|
||||||
|
renderCell: function render({ row }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditButton hideText recordItemId={row.id} />
|
||||||
|
<ShowButton hideText recordItemId={row.id} />
|
||||||
|
<DeleteButton hideText recordItemId={row.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[categoryData, categoryIsLoading]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<DataGrid {...dataGridProps} columns={columns} />
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
59
src/pages/blog-posts/show.tsx
Normal file
59
src/pages/blog-posts/show.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Stack, Typography } from "@mui/material";
|
||||||
|
import { useOne, useShow } from "@refinedev/core";
|
||||||
|
import {
|
||||||
|
DateField,
|
||||||
|
MarkdownField,
|
||||||
|
NumberField,
|
||||||
|
Show,
|
||||||
|
TextFieldComponent as TextField,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
|
||||||
|
export const BlogPostShow = () => {
|
||||||
|
const { query } = useShow({});
|
||||||
|
|
||||||
|
const { data, isLoading } = query;
|
||||||
|
|
||||||
|
const record = data?.data;
|
||||||
|
|
||||||
|
const { data: categoryData, isLoading: categoryIsLoading } = useOne({
|
||||||
|
resource: "categories",
|
||||||
|
id: record?.category?.id || "",
|
||||||
|
queryOptions: {
|
||||||
|
enabled: !!record,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show isLoading={isLoading}>
|
||||||
|
<Stack gap={1}>
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"ID"}
|
||||||
|
</Typography>
|
||||||
|
<TextField value={record?.id} />
|
||||||
|
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"Title"}
|
||||||
|
</Typography>
|
||||||
|
<TextField value={record?.title} />
|
||||||
|
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"Content"}
|
||||||
|
</Typography>
|
||||||
|
<MarkdownField value={record?.content} />
|
||||||
|
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"Category"}
|
||||||
|
</Typography>
|
||||||
|
{categoryIsLoading ? <>Loading...</> : <>{categoryData?.data?.title}</>}
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"Status"}
|
||||||
|
</Typography>
|
||||||
|
<TextField value={record?.status} />
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"CreatedAt"}
|
||||||
|
</Typography>
|
||||||
|
<DateField value={record?.createdAt} />
|
||||||
|
</Stack>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
};
|
36
src/pages/categories/create.tsx
Normal file
36
src/pages/categories/create.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Box, TextField } from "@mui/material";
|
||||||
|
import { Create } from "@refinedev/mui";
|
||||||
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
|
|
||||||
|
export const CategoryCreate = () => {
|
||||||
|
const {
|
||||||
|
saveButtonProps,
|
||||||
|
refineCore: { formLoading },
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
{...register("title", {
|
||||||
|
required: "This field is required",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.title}
|
||||||
|
helperText={(errors as any)?.title?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type="text"
|
||||||
|
label={"Title"}
|
||||||
|
name="title"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Create>
|
||||||
|
);
|
||||||
|
};
|
35
src/pages/categories/edit.tsx
Normal file
35
src/pages/categories/edit.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Box, TextField } from "@mui/material";
|
||||||
|
import { Edit } from "@refinedev/mui";
|
||||||
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
|
|
||||||
|
export const CategoryEdit = () => {
|
||||||
|
const {
|
||||||
|
saveButtonProps,
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Edit saveButtonProps={saveButtonProps}>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
{...register("title", {
|
||||||
|
required: "This field is required",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.title}
|
||||||
|
helperText={(errors as any)?.title?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type="text"
|
||||||
|
label={"Title"}
|
||||||
|
name="title"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
|
};
|
4
src/pages/categories/index.ts
Normal file
4
src/pages/categories/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./create";
|
||||||
|
export * from "./edit";
|
||||||
|
export * from "./list";
|
||||||
|
export * from "./show";
|
59
src/pages/categories/list.tsx
Normal file
59
src/pages/categories/list.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
||||||
|
import {
|
||||||
|
DeleteButton,
|
||||||
|
EditButton,
|
||||||
|
List,
|
||||||
|
ShowButton,
|
||||||
|
useDataGrid,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const CategoryList = () => {
|
||||||
|
const { dataGridProps } = useDataGrid({});
|
||||||
|
|
||||||
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
field: "id",
|
||||||
|
headerName: "ID",
|
||||||
|
type: "number",
|
||||||
|
minWidth: 50,
|
||||||
|
display: "flex",
|
||||||
|
align: "left",
|
||||||
|
headerAlign: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "title",
|
||||||
|
flex: 1,
|
||||||
|
headerName: "Title",
|
||||||
|
minWidth: 200,
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "actions",
|
||||||
|
headerName: "Actions",
|
||||||
|
align: "right",
|
||||||
|
headerAlign: "right",
|
||||||
|
minWidth: 120,
|
||||||
|
sortable: false,
|
||||||
|
display: "flex",
|
||||||
|
renderCell: function render({ row }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditButton hideText recordItemId={row.id} />
|
||||||
|
<ShowButton hideText recordItemId={row.id} />
|
||||||
|
<DeleteButton hideText recordItemId={row.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<DataGrid {...dataGridProps} columns={columns} />
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
29
src/pages/categories/show.tsx
Normal file
29
src/pages/categories/show.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Stack, Typography } from "@mui/material";
|
||||||
|
import { useShow } from "@refinedev/core";
|
||||||
|
import {
|
||||||
|
NumberField,
|
||||||
|
Show,
|
||||||
|
TextFieldComponent as TextField,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
|
||||||
|
export const CategoryShow = () => {
|
||||||
|
const { query } = useShow({});
|
||||||
|
const { data, isLoading } = query;
|
||||||
|
|
||||||
|
const record = data?.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show isLoading={isLoading}>
|
||||||
|
<Stack gap={1}>
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"ID"}
|
||||||
|
</Typography>
|
||||||
|
<TextField value={record?.id} />
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
{"Title"}
|
||||||
|
</Typography>
|
||||||
|
<TextField value={record?.title} />
|
||||||
|
</Stack>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
};
|
5
src/pages/forgotPassword/index.tsx
Normal file
5
src/pages/forgotPassword/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { AuthPage } from "@refinedev/mui";
|
||||||
|
|
||||||
|
export const ForgotPassword = () => {
|
||||||
|
return <AuthPage type="forgotPassword" />;
|
||||||
|
};
|
12
src/pages/login/index.tsx
Normal file
12
src/pages/login/index.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { AuthPage } from "@refinedev/mui";
|
||||||
|
|
||||||
|
export const Login = () => {
|
||||||
|
return (
|
||||||
|
<AuthPage
|
||||||
|
type="login"
|
||||||
|
formProps={{
|
||||||
|
defaultValues: { email: "demo@refine.dev", password: "demodemo" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
5
src/pages/register/index.tsx
Normal file
5
src/pages/register/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { AuthPage } from "@refinedev/mui";
|
||||||
|
|
||||||
|
export const Register = () => {
|
||||||
|
return <AuthPage type="register" />;
|
||||||
|
};
|
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
tsconfig.node.json
Normal file
8
tsconfig.node.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
Reference in New Issue
Block a user