diff --git a/components/core/Shell/Footer/Footer.jsx b/components/core/Shell/Footer/Footer.jsx
new file mode 100644
index 0000000..d938c49
--- /dev/null
+++ b/components/core/Shell/Footer/Footer.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import styled from '@emotion/styled';
+
+const FooterContainer = styled('div')`
+ padding-left: 10px;
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ flex-shrink: 0;
+ height: 30px;
+ padding-top: 6px;
+ background: #585858;
+ color: white;
+ font-family: "WDXL Lubrifont TC";
+`;
+
+export const Footer = () => {
+ return (
+
+ {`© ${new Date().getFullYear()} ShiftSync`}
+
+ )
+}
\ No newline at end of file
diff --git a/components/core/Shell/Footer/index.js b/components/core/Shell/Footer/index.js
new file mode 100644
index 0000000..f6e9523
--- /dev/null
+++ b/components/core/Shell/Footer/index.js
@@ -0,0 +1 @@
+export { Footer } from './Footer';
\ No newline at end of file
diff --git a/components/core/Shell/NavBar/NavBar.jsx b/components/core/Shell/NavBar/NavBar.jsx
new file mode 100644
index 0000000..c18cc1e
--- /dev/null
+++ b/components/core/Shell/NavBar/NavBar.jsx
@@ -0,0 +1,181 @@
+import React, { useEffect, useRef, useState } from 'react';
+import styled from '@emotion/styled';
+import {
+ useLocation,
+ useNavigate,
+ useParams
+} from 'react-router';
+import { Stack } from '@mui/material';
+
+const NavBox = styled(Stack)`
+ width: 100%;
+`;
+
+const LeftContainer = styled(Stack)`
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ flex-flow: row wrap;
+`;
+
+const RightContainer = styled(Stack)`
+ display: flex;
+ flex-direction: row;
+ flex-flow: row wrap;
+`;
+
+const NavGroup = styled(Stack)`
+ display: flex;
+ background: #ffffff
+ width: 100%;
+ height: 60px;
+ padding: 4px;
+ justify-content: space-between !important
+`;
+
+const Title = styled('p')`
+ &:hover {
+ cursor: pointer;
+ }
+`;
+
+const Tab = styled('div')`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-end;
+ height: 100%;
+ padding: 6px 16px;
+ font-size: 20px;
+ font-weight: 800;
+ color: grey;
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-family: "WDXL Lubrifont TC";
+ letter-spacing: .05em;
+ word-spacing: .2em;
+`;
+
+const SlidingIndicator = styled('div')`
+ position: absolute;
+ bottom: -6px;
+ height: 2px;
+ background-color: red;
+ transition: left 0.3s ease, width 0.3s ease;
+`;
+
+const UserButton = styled(Stack)`
+ width: 100%;
+`;
+
+const Border = styled('div')`
+ height: 2px;
+ width: 100%;
+ background-color: #A8A8A8;
+`;
+
+export const NavBar = ({ notifications, disableNav, settings }) => {
+ const { pathname } = useLocation();
+ const navigate = useNavigate();
+ const routeParams = useParams();
+
+ const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
+ const tabRefs = useRef({});
+
+ const isAdmin = true; // to be changed to check
+
+ const navItems = [
+ {
+ name: 'Home',
+ id: 'home',
+ href: '/',
+ matches: ['', 'home']
+ },
+ {
+ name: 'About Us',
+ id: 'about',
+ href: '/about',
+ matches: ['about', 'aboutus']
+ },
+ {
+ name: 'Administrator Panel',
+ id: 'admin',
+ href: '/admin',
+ matches: ['admin'],
+ hidden: !isAdmin
+ }
+ ];
+
+ let activeTab = navItems.find((tab) => {
+ return tab.matches.some((item) => {
+ return item === pathname?.split('/')[1];
+ });
+ });
+
+ const onShiftSyncClick = () => {
+ navigate('/');
+ }
+
+ const onNavButtonClick = (tab) => {
+ activeTab = tab;
+ navigate(tab.href);
+ };
+
+ const onUserIconClick = () => {
+ navigate('/settings');
+ };
+
+ useEffect(() => {
+ if (activeTab?.id && tabRefs.current[activeTab.id]) {
+ const el = tabRefs.current[activeTab.id];
+ const rect = el.getBoundingClientRect();
+ const containerRect = el.parentNode.getBoundingClientRect();
+ setIndicatorStyle({
+ left: rect.left - containerRect.left,
+ width: rect.width
+ });
+ }
+}, [pathname, activeTab?.id]);
+
+ return (
+
+
+
+ {/* */}
+
+ ShiftSync
+
+
+
+ {navItems?.map((item) => {
+ if (!item?.hidden) {
+ return (
+ { tabRefs.current[item.id] = el; }}
+ onClick={() => onNavButtonClick(item)}
+ >
+ {item?.name}
+
+ );
+ }
+ return null;
+ }).filter((item) => item !== null)}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/components/core/Shell/NavBar/index.js b/components/core/Shell/NavBar/index.js
new file mode 100644
index 0000000..46c69f1
--- /dev/null
+++ b/components/core/Shell/NavBar/index.js
@@ -0,0 +1 @@
+export { NavBar } from './NavBar';
\ No newline at end of file
diff --git a/components/core/Shell/Shell.jsx b/components/core/Shell/Shell.jsx
new file mode 100644
index 0000000..494505a
--- /dev/null
+++ b/components/core/Shell/Shell.jsx
@@ -0,0 +1,70 @@
+import React, { useEffect, useState, useRef } from 'react';
+import styled from '@emotion/styled';
+import { NavBar } from './NavBar';
+import { Footer } from './Footer';
+
+const minHeight = 'calc(100vh - 30px)';
+
+const Body = styled.div({
+ display: 'flex',
+ flexDirection: 'column',
+ position: 'relative',
+ overflow: 'hidden',
+ height: '100vh',
+ minHeight
+});
+
+const MainContainer = styled.div({
+ display: 'flex',
+ textAlign: 'center',
+ width: '100%',
+ flexDirection: 'row',
+ height: minHeight
+});
+
+const Main = styled.div({
+ background: 'linear-gradient(100deg, #f3f9ff 80%, rgba(217, 217, 217) 100%);',
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+ height: minHeight
+});
+
+const Content = styled.div(({ cssProps }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+ alignContent: 'center',
+ overflowY: 'auto',
+ height: cssProps?.minHeightMain || '100%'
+}));
+
+export const Shell = ({ children }) => {
+ const bannerRef = useRef();
+ const nonContentHeight = 92 + (bannerRef?.current?.offsetHeight || 0);
+ const minHeightMain = `calc(100vh - ${nonContentHeight}px)`;
+
+ useEffect(() => {
+ window.scrollTo(0,0);
+ }, []);
+
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/components/core/Shell/index.js b/components/core/Shell/index.js
new file mode 100644
index 0000000..651c518
--- /dev/null
+++ b/components/core/Shell/index.js
@@ -0,0 +1 @@
+export { Shell } from './Shell';
\ No newline at end of file
diff --git a/components/core/index.js b/components/core/index.js
new file mode 100644
index 0000000..651c518
--- /dev/null
+++ b/components/core/index.js
@@ -0,0 +1 @@
+export { Shell } from './Shell';
\ No newline at end of file
diff --git a/components/index.js b/components/index.js
new file mode 100644
index 0000000..02bb11c
--- /dev/null
+++ b/components/index.js
@@ -0,0 +1 @@
+export * from './core';
\ No newline at end of file
diff --git a/index.html b/index.html
index fc663ce..05efba8 100644
--- a/index.html
+++ b/index.html
@@ -3,6 +3,9 @@
+
+
+
ShiftSync
diff --git a/package-lock.json b/package-lock.json
index 0ed139b..2b73535 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"react-router-dom": "^7.6.0"
},
"devDependencies": {
+ "@emotion/babel-plugin": "^11.13.5",
"@eslint/js": "^9.25.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
diff --git a/package.json b/package.json
index 4be8921..0eaf438 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"react-router-dom": "^7.6.0"
},
"devDependencies": {
+ "@emotion/babel-plugin": "^11.13.5",
"@eslint/js": "^9.25.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..4aba840
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1 @@
+export * from '../components';
\ No newline at end of file
diff --git a/src/router/AppRouter.jsx b/src/router/AppRouter.jsx
index ce7f379..0b6d2d4 100644
--- a/src/router/AppRouter.jsx
+++ b/src/router/AppRouter.jsx
@@ -1,13 +1,16 @@
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { Home, About } from '@src/pages';
+import { Shell } from '../../components';
const AppRouter = () => {
return (
-
- } />
- } />
-
+
+
+ } />
+ } />
+
+
);
};