Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin dashboard #116

Open
wants to merge 8 commits into
base: feat/admin-panel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
571 changes: 397 additions & 174 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"prettier:fix": "prettier --write \"resources/js/**/*.{js,jsx,ts,tsx,css,md,json}\""
},
"devDependencies": {
"@ant-design/pro-layout": "^7.8.3",
"@loadable/component": "^5.15.3",
"@reduxjs/toolkit": "^1.9.3",
"@types/loadable__component": "^5.13.4",
"@types/nprogress": "^0.2.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
Expand All @@ -25,13 +29,15 @@
"eslint-plugin-react": "^7.32.1",
"laravel-vite-plugin": "^0.7.2",
"lodash": "^4.17.19",
"nprogress": "^0.2.0",
"postcss": "^8.4.21",
"prettier": "^2.8.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.8.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.0",
"redux-persist": "^6.0.0",
"sonner": "^0.1.6",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
Expand Down
25 changes: 6 additions & 19 deletions resources/js/admin/App.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
import { ConfigProvider } from 'antd';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { antdConfig } from '../shared/constants';
import ErrorPage from './components/ErrorPage';
import { RouterProvider } from 'react-router-dom';
import { Toaster } from 'sonner';
import { browserRouter } from './routes/browserRouter';
import 'antd/dist/reset.css';
import '../shared/assets/css/index.css';
import { webRoutes } from './routes/web';
import { Toaster } from 'sonner';

const router = createBrowserRouter([
{
path: webRoutes.login.url,
element: webRoutes.login.component,
errorElement: <ErrorPage />,
},
]);

const App = () => {
return (
<ConfigProvider {...antdConfig}>
<div className="fade-in">
<Toaster />
<div className="fade-in">
<RouterProvider router={router} />
</div>
</ConfigProvider>
<RouterProvider router={browserRouter} />
</div>
);
};

Expand Down
12 changes: 4 additions & 8 deletions resources/js/admin/components/auth/AuthLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ReactNode } from 'react';
import { Outlet } from 'react-router';
import loginBg from '../../../shared/assets/img/login-bg.jpg';

export type AuthLayoutProps = {
children: ReactNode;
};

const AuthLayout = ({ children }: AuthLayoutProps) => {
const AuthLayout = () => {
return (
<div className="relative">
<div className="absolute inset-x-0 -top-48 -bottom-14 overflow-hidden bg-indigo-50">
Expand All @@ -26,9 +22,9 @@ const AuthLayout = ({ children }: AuthLayoutProps) => {
<div className="mx-auto max-w-2xl lg:max-w-4xl lg:px-12">
<section>
<div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<div className="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0">
<div className="w-full bg-gradient-to-b from-white rounded-lg md:mt-0 sm:max-w-md xl:p-0 opacity-90">
<div className="p-8 space-y-4 md:space-y-6 md:p-10">
{children}
<Outlet />
</div>
</div>
</div>
Expand Down
23 changes: 15 additions & 8 deletions resources/js/admin/components/auth/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import AuthLayout from './AuthLayout';
import { Form, Input } from 'antd';
import { useEffect, useState } from 'react';
import { Fragment, useEffect, useState } from 'react';
import { handleErrorResponse, setPageTitle } from '../../../shared/utils';
import axios from 'axios';
import { apiRoutes } from '../../routes/api';
import { useDispatch, useSelector } from 'react-redux';
import { login } from '../../store/slices/adminSlice';
import { Admin } from '../../inrterfaces/admin';
import Button from '../../../shared/components/atoms/button';
import { RootState } from '../../store';
import { useLocation, useNavigate } from 'react-router-dom';
import { webRoutes } from '../../routes/web';
import { defaultHttp } from '../../../shared/utils/http';

type FormValues = {
email: string;
password: string;
};

const Login = () => {
const admin = useSelector((state: RootState) => state.admin);
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || webRoutes.dashboard;
const admin = useSelector((state: RootState) => state.admin);
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();

Expand All @@ -26,13 +30,15 @@ const Login = () => {
}, []);

useEffect(() => {
// redirect to dashboard
if (admin) {
navigate(from, { replace: true });
}
}, [admin]);

const onSubmit = (values: FormValues) => {
setLoading(true);

axios
defaultHttp
.post(apiRoutes.login, {
email: values.email,
password: values.password,
Expand All @@ -55,7 +61,7 @@ const Login = () => {
};

return (
<AuthLayout>
<Fragment>
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl text-left">
Admin Login
</h1>
Expand Down Expand Up @@ -123,13 +129,14 @@ const Login = () => {
className="mt-4 bg-neutral text-primary-content"
block
loading={loading}
size="large"
htmlType={'submit'}
>
Login
</Button>
</div>
</Form>
</AuthLayout>
</Fragment>
);
};

Expand Down
2 changes: 1 addition & 1 deletion resources/js/admin/components/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const Dashboard = () => {
return 'Dashboard';
return <div>DASHBOARD</div>;
};

export default Dashboard;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Result } from 'antd';

const ErrorPage = () => {
return (
<div className="mt-8">
<div className="flex items-center justify-center h-screen">
<Result
status="500"
title="500"
Expand Down
5 changes: 0 additions & 5 deletions resources/js/admin/components/layout/PrivateRoute.tsx

This file was deleted.

14 changes: 14 additions & 0 deletions resources/js/admin/components/layout/adminRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { webRoutes } from '../../routes/web';
import { RootState } from '../../store';

const AdminRedirect = () => {
const admin = useSelector((state: RootState) => state.admin);

return (
<Navigate to={admin ? webRoutes.dashboard : webRoutes.login} replace />
);
};

export default AdminRedirect;
117 changes: 117 additions & 0 deletions resources/js/admin/components/layout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { webRoutes } from '../../routes/web';
import ProLayout, { ProLayoutProps } from '@ant-design/pro-layout';
import logo from '../../../shared/assets/img/logo.svg';
import { Dropdown } from 'antd';
import {
UserOutlined,
LogoutOutlined,
QuestionCircleFilled,
} from '@ant-design/icons';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../../store';
import { config } from '../../../shared/constants';
import { logout } from '../../store/slices/adminSlice';
import { memo, useEffect } from 'react';
import { sidebar } from './sidebar';
import http from '../../../shared/utils/http';
import { apiRoutes } from '../../routes/api';
import { handleErrorResponse } from '../../../shared/utils';

const Layout = () => {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const admin = useSelector((state: RootState) => state.admin);

useEffect(() => {
if (!admin) {
navigate(webRoutes.login);
}
}, [admin]);

const defaultProps: ProLayoutProps = {
title: config.appName,
logo: logo,
fixedHeader: true,
fixSiderbar: true,
layout: 'mix',
route: {
routes: sidebar,
},
};

const logoutAdmin = () => {
http
.post(apiRoutes.logout)
.then(() => {
dispatch(logout());
})
.catch((error) => {
handleErrorResponse(error);
});
};

return (
<div
style={{
height: '100vh',
}}
>
<ProLayout
{...defaultProps}
location={location}
onMenuHeaderClick={() => navigate(webRoutes.dashboard)}
menuItemRender={(item, dom) => (
<a
onClick={(e) => {
e.preventDefault();
item.path && navigate(item.path);
}}
href={item.path}
>
{dom}
</a>
)}
avatarProps={{
src: admin?.avatarUrl,
icon: <UserOutlined />,
size: 'small',
title: admin?.name.split(' ')[0],
render: (_, dom) => {
return (
<Dropdown
menu={{
items: [
{
key: 'logout',
icon: <LogoutOutlined />,
label: 'Logout',
onClick: () => {
logoutAdmin();
},
},
],
}}
>
{dom}
</Dropdown>
);
},
}}
actionsRender={() => {
return [
<QuestionCircleFilled
key="QuestionCircleFilled"
onClick={() => window.open(config.helpLink, '_blank')}
/>,
];
}}
>
<Outlet />
</ProLayout>
</div>
);
};

export default memo(Layout);
17 changes: 17 additions & 0 deletions resources/js/admin/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { webRoutes } from '../../routes/web';
import { UserOutlined, HomeOutlined } from '@ant-design/icons';

export const sidebar = [
{
path: webRoutes.dashboard,
key: webRoutes.dashboard,
name: 'Dashboard',
icon: <HomeOutlined />,
},
{
path: webRoutes.users,
key: webRoutes.users,
name: 'Users',
icon: <UserOutlined />,
},
];
12 changes: 12 additions & 0 deletions resources/js/admin/components/loading/pageLoading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Spin } from 'antd';
import { TbGridDots } from 'react-icons/tb';

const PageLoading = () => {
return (
<div className="flex items-center justify-center h-screen">
<Spin size="large" indicator={<TbGridDots className="icon-spin" />} />
</div>
);
};

export default PageLoading;
Loading