diff --git a/.env b/.env
new file mode 100644
index 0000000..fc17da7
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+VITE_API_BASE_URL=http://learn-hub-backend.test/api/v1
\ No newline at end of file
diff --git a/.env.sample b/.env.sample
new file mode 100644
index 0000000..2d1bf5f
--- /dev/null
+++ b/.env.sample
@@ -0,0 +1 @@
+VITE_API_BASE_URL=
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 60dd285..9eda637 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,8 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@tanstack/react-query": "^5.50.1",
+ "axios": "^1.7.2",
+ "formik": "^2.4.6",
"framer-motion": "^11.2.13",
"highlight.js": "^11.10.0",
"react": "^18.3.1",
@@ -19,7 +21,9 @@
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"react-quill": "^2.0.0",
- "react-router-dom": "^6.24.1"
+ "react-router-dom": "^6.24.1",
+ "react-toastify": "^10.0.5",
+ "yup": "^1.4.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
@@ -2610,6 +2614,15 @@
"@types/estree": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/lodash": {
"version": "4.17.6",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz",
@@ -2987,6 +3000,21 @@
"node": ">=8"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
+ "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -3154,6 +3182,14 @@
"node": ">=0.8"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3172,6 +3208,17 @@
"resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz",
"integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog=="
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/compute-scroll-into-view": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz",
@@ -3301,6 +3348,14 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -3333,6 +3388,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -3907,6 +3970,67 @@
"node": ">=10"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formik": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
+ "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://opencollective.com/formik"
+ }
+ ],
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/formik/node_modules/react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+ },
"node_modules/framer-motion": {
"version": "11.2.13",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.13.tgz",
@@ -4584,6 +4708,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5350,6 +5479,25 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -5624,6 +5772,16 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5906,6 +6064,18 @@
}
}
},
+ "node_modules/react-toastify": {
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
+ "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
+ "dependencies": {
+ "clsx": "^2.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@@ -6259,11 +6429,21 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -6289,6 +6469,11 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -6718,6 +6903,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/yup": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",
+ "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ },
+ "node_modules/yup/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/package.json b/package.json
index e37557d..301ead1 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,8 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@tanstack/react-query": "^5.50.1",
+ "axios": "^1.7.2",
+ "formik": "^2.4.6",
"framer-motion": "^11.2.13",
"highlight.js": "^11.10.0",
"react": "^18.3.1",
@@ -21,7 +23,9 @@
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"react-quill": "^2.0.0",
- "react-router-dom": "^6.24.1"
+ "react-router-dom": "^6.24.1",
+ "react-toastify": "^10.0.5",
+ "yup": "^1.4.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
diff --git a/src/App.tsx b/src/App.tsx
index 4af8306..a28fc26 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -21,6 +21,7 @@ import UserViews from '@pages/Users/UserViews'
import Login from '@pages/Auth/Login'
import Register from '@pages/Auth/Register'
+import PrivateRoutes from './Route/privateRoute'
const routes = createBrowserRouter(
createRoutesFromElements(
@@ -29,15 +30,17 @@ const routes = createBrowserRouter(
} />
} />
} />
- } />
} />
} />
- } />
- } />
- } />
- } />
+ {/* private route */}
+ } />} />
+ } />} />
+ } />} />
+ } />} />
+ } />} />
+ {/* end private route */}
} />
} />
@@ -50,7 +53,7 @@ const routes = createBrowserRouter(
const App = () => {
return (
-
+
)
}
diff --git a/src/Route/privateRoute.tsx b/src/Route/privateRoute.tsx
new file mode 100644
index 0000000..7edae13
--- /dev/null
+++ b/src/Route/privateRoute.tsx
@@ -0,0 +1,19 @@
+import { FunctionComponent, ReactElement } from "react";
+import { Navigate, useLocation } from "react-router-dom";
+
+interface PrivateRouteProps {
+ element: ReactElement;
+}
+
+const PrivateRoutes: FunctionComponent = ({ element }) => {
+ const location = useLocation();
+ const isAuthenticated = !!localStorage.getItem("ucType_");
+
+ return isAuthenticated ? (
+ element
+ ) : (
+
+ )
+}
+
+export default PrivateRoutes
\ No newline at end of file
diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts
new file mode 100644
index 0000000..0743a06
--- /dev/null
+++ b/src/api/axiosInstance.ts
@@ -0,0 +1,38 @@
+import axios from 'axios'
+
+import { API_BASE_URL } from '@api/constant'
+
+axios.defaults.baseURL = API_BASE_URL;
+
+export const axiosInstance = axios.create({
+ baseURL: API_BASE_URL,
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ 'X-Requested-With': 'XMLHttpRequest',
+ },
+ withCredentials: true,
+});
+
+axiosInstance.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('ucType_');
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+);
+
+axiosInstance.interceptors.response.use(
+ response => response,
+ error => {
+ if (axios.isAxiosError(error)) {
+ throw new Error(error.response?.data?.message || 'An unexpected error occurred');
+ }
+ return Promise.reject(new Error('An unexpected error occurred'));
+ }
+);
diff --git a/src/api/constant.ts b/src/api/constant.ts
new file mode 100644
index 0000000..acee7f3
--- /dev/null
+++ b/src/api/constant.ts
@@ -0,0 +1 @@
+export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL
\ No newline at end of file
diff --git a/src/api/endpoints/authEndpoints.ts b/src/api/endpoints/authEndpoints.ts
new file mode 100644
index 0000000..c70bf19
--- /dev/null
+++ b/src/api/endpoints/authEndpoints.ts
@@ -0,0 +1,8 @@
+import { API_BASE_URL } from '@api/constant'
+
+export const REGISTER_ENDPOINT = `${API_BASE_URL}/auth/register`;
+
+export const LOGIN_ENDPOINT = `${API_BASE_URL}/auth/login`;
+
+export const LOGOUT_ENDPOINT = `${API_BASE_URL}/auth/logout`;
+
diff --git a/src/api/endpoints/userEndpoints.ts b/src/api/endpoints/userEndpoints.ts
new file mode 100644
index 0000000..75bec35
--- /dev/null
+++ b/src/api/endpoints/userEndpoints.ts
@@ -0,0 +1,3 @@
+import { API_BASE_URL } from '@api/constant'
+
+export const USER_ENDPOINT = `${API_BASE_URL}/user`;
\ No newline at end of file
diff --git a/src/api/index.ts b/src/api/index.ts
new file mode 100644
index 0000000..2fdc0c5
--- /dev/null
+++ b/src/api/index.ts
@@ -0,0 +1,7 @@
+// Re-export endpoints
+export * from '@api/endpoints/authEndpoints'
+export * from '@api/endpoints/userEndpoints'
+
+// Re-export types
+export * from '@api/types/auth'
+export * from '@api/types/user'
diff --git a/src/api/types/auth/index.ts b/src/api/types/auth/index.ts
new file mode 100644
index 0000000..1cd5c76
--- /dev/null
+++ b/src/api/types/auth/index.ts
@@ -0,0 +1,19 @@
+export interface SignupRequest {
+ fullname: string;
+ email: string;
+ password: string;
+}
+
+export interface SignupResponse {
+ message: string;
+}
+
+export interface SigninRequest {
+ email: string;
+ password: string;
+}
+
+export interface SigninResponse {
+ message: string;
+ access_token: string;
+}
\ No newline at end of file
diff --git a/src/api/types/user/index.ts b/src/api/types/user/index.ts
new file mode 100644
index 0000000..47e5f5c
--- /dev/null
+++ b/src/api/types/user/index.ts
@@ -0,0 +1,5 @@
+export interface UserResponse {
+ id: string | number;
+ fullname: string;
+ email: string;
+}
diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx
index 5a6eae7..d35978e 100644
--- a/src/components/Input/index.tsx
+++ b/src/components/Input/index.tsx
@@ -1,4 +1,4 @@
-import { FunctionComponent } from 'react'
+import { ChangeEvent, FocusEvent, FunctionComponent } from 'react'
import { Input as ChakraInput } from '@chakra-ui/react'
import { colors } from '../../colors'
@@ -9,8 +9,8 @@ interface IProps {
name: string;
width?: string;
placeholder: string;
- onBlur?: () => void;
- onChange?: () => void;
+ onBlur?: (event: FocusEvent) => void;
+ onChange?: (event: ChangeEvent) => void;
}
const Input: FunctionComponent = ({
@@ -18,7 +18,9 @@ const Input: FunctionComponent = ({
value,
name,
width,
- placeholder
+ placeholder,
+ onBlur,
+ onChange
}) => {
return (
@@ -38,7 +40,8 @@ const Input: FunctionComponent = ({
fontSize: "14px",
fontWeight: 400,
}}
-
+ onBlur={onBlur}
+ onChange={onChange}
/>
)
}
diff --git a/src/components/NavBar/navBarLg.tsx b/src/components/NavBar/navBarLg.tsx
index 731fae5..acbac5d 100644
--- a/src/components/NavBar/navBarLg.tsx
+++ b/src/components/NavBar/navBarLg.tsx
@@ -19,9 +19,16 @@ import { IoIosArrowUp, IoIosArrowDown } from 'react-icons/io'
import Button from '@components/Button'
import { colors } from '../../colors'
import { Menu } from '@constant/Menu'
+import { useUser } from '@context/userContext'
+import { useSignOut } from '@hooks/auth/useSignOut'
-const NavBarLg: FunctionComponent = () => {
- const isLoggedIn = false;
+const NavBarLg: FunctionComponent = () => {
+ const { user } = useUser();
+ const { signOutMutation } = useSignOut()
+
+ const handleLoggedOut = () => {
+ signOutMutation.mutate();
+ };
return (
{
))}
-
- {isLoggedIn ? (
+ {user ? (
{({ isOpen }) => (
@@ -75,15 +81,15 @@ const NavBarLg: FunctionComponent = () => {
- Justice Chimobi
+ {user?.data?.fullname}
@@ -107,7 +113,7 @@ const NavBarLg: FunctionComponent = () => {
-
) : (
-
+
+
+
+ Don't have an account? {" "}
+
+
+ Sign Up
+
+
+
+
diff --git a/src/pages/Auth/Register/index.tsx b/src/pages/Auth/Register/index.tsx
index 23438ba..e016c78 100644
--- a/src/pages/Auth/Register/index.tsx
+++ b/src/pages/Auth/Register/index.tsx
@@ -9,12 +9,39 @@ import {
CardHeader,
Heading,
Text,
+ FormErrorMessage,
} from '@chakra-ui/react'
+import { useFormik } from 'formik'
import { Button, Input } from '@components/index'
import { colors } from '../../../colors'
+import { useSignup } from '@hooks/auth/useSignup'
+import { signUpvalidateSchema } from '@validations/signup'
+import { SignupRequest } from '@api/index'
const Register: FunctionComponent = () => {
+ const { signupMutation } = useSignup();
+
+ const _handleSignup = (values: SignupRequest) => {
+ signupMutation.mutate({
+ fullname: values.fullname,
+ email: values.email,
+ password: values.password,
+ })
+ }
+
+ const formilk = useFormik({
+ initialValues: {
+ fullname: '',
+ email: '',
+ password: '',
+ },
+ onSubmit: _handleSignup,
+ validationSchema: signUpvalidateSchema,
+ });
+
+ const { handleChange, handleBlur, handleSubmit, errors, values } = formilk;
+
return (
{
Sign Up
-
-
- Full Name
-
-
-
-
-
- Email address
-
-
-
+
+
+ Already have an account? {" "}
+
+
+ Sign In
+
+
+
+
diff --git a/src/pages/Forum/index.tsx b/src/pages/Forum/index.tsx
index 96b4527..9fcba15 100644
--- a/src/pages/Forum/index.tsx
+++ b/src/pages/Forum/index.tsx
@@ -21,8 +21,11 @@ import truncate from '@helpers/truncate'
import { Button, Search, FollowCard, RecommendTopicCard } from '@components/index'
import DiscussionCard from '@pages/Forum/components/discussionCard'
import AvatarPic from '@assets/images/avatar.jpg'
+import { useUser } from '@context/userContext'
const Forum: FunctionComponent = () => {
+ const { user } = useUser();
+
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return (
@@ -32,20 +35,22 @@ const Forum: FunctionComponent = () => {
>
Forum
-
-
-
- Create Forum
-
-
-
+ {user && (
+
+
+
+ Create Forum
+
+
+
+ )}
=> {
+ const response = await axiosInstance.post(REGISTER_ENDPOINT, data);
+ return response.data;
+}
+
+export const signinUser = async (data: SigninRequest): Promise => {
+ const response = await axiosInstance.post(LOGIN_ENDPOINT, data);
+ return response.data;
+}
+
+export const signoutUser = async () => {
+ const response = await axiosInstance.post(LOGOUT_ENDPOINT, null);
+ return response.data
+}
\ No newline at end of file
diff --git a/src/services/user/index.tsx b/src/services/user/index.tsx
new file mode 100644
index 0000000..b68f918
--- /dev/null
+++ b/src/services/user/index.tsx
@@ -0,0 +1,7 @@
+import { axiosInstance } from '@api/axiosInstance'
+import { USER_ENDPOINT, UserResponse } from '@api/index'
+
+export const getUser = async (): Promise => {
+ const response = await axiosInstance.get(USER_ENDPOINT);
+ return response.data;
+}
\ No newline at end of file
diff --git a/src/validations/signin.ts b/src/validations/signin.ts
new file mode 100644
index 0000000..f5c5742
--- /dev/null
+++ b/src/validations/signin.ts
@@ -0,0 +1,9 @@
+import * as Yup from 'yup'
+
+export const signInvalidateSchema = () => Yup.object({
+ email: Yup.string()
+ .email("Invalid email address")
+ .required("Required"),
+ password: Yup.string()
+ .required("Required")
+});
\ No newline at end of file
diff --git a/src/validations/signup.ts b/src/validations/signup.ts
new file mode 100644
index 0000000..e6e3a70
--- /dev/null
+++ b/src/validations/signup.ts
@@ -0,0 +1,12 @@
+import * as Yup from 'yup'
+
+export const signUpvalidateSchema = Yup.object({
+ fullname: Yup.string().required("Required"),
+ email: Yup.string()
+ .email("Invalid email address")
+ .required("Required"),
+ password: Yup.string()
+ .required("Required")
+ .min(8, "Password must be more than 8")
+ .max(15, "Password should be atleast 15 characters"),
+});
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
index ea323c0..f8679b4 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -10,7 +10,10 @@
"@helpers/*": ["src/helpers/*"],
"@context/*": ["src/context/*"],
"@constant/*": ["src/constant/*"],
- "@pages/*": ["src/pages/*"]
+ "@pages/*": ["src/pages/*"],
+ "@api/*": ["src/api/*"],
+ "@services/*": ["src/services/*"],
+ "@validations/*": ["src/validations/*"],
},
"composite": true,
diff --git a/vite.config.ts b/vite.config.ts
index 1753031..5b1b760 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -14,7 +14,10 @@ export default defineConfig({
'@errorBoundary': '/src/ErrorBoundary',
'@helpers': '/src/helpers',
'@context': '/src/context',
- '@constant': '/src/constant'
+ '@constant': '/src/constant',
+ '@api': '/src/api',
+ '@services': '/src/services',
+ '@validations': '/src/validations'
}
}
})