├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── assets │ ├── avatarresi.png │ ├── homepage.jpg │ ├── homepage0.png │ ├── homepage2.jpg │ ├── homepage3.jpg │ ├── homepage4.png │ ├── homepage5.png │ ├── stars.jpg │ └── starso.jpg └── vite.svg ├── src ├── App.css ├── App.tsx ├── Folder.tsx ├── Layout.tsx ├── Pages │ └── Home.tsx ├── Provider │ └── TabProvider.tsx ├── assets │ └── react.svg ├── components │ ├── Apps │ │ ├── Github │ │ │ ├── Content.tsx │ │ │ ├── RepoItem.tsx │ │ │ └── index.tsx │ │ ├── Notepad │ │ │ └── index.tsx │ │ ├── Photos │ │ │ ├── data.ts │ │ │ └── index.tsx │ │ ├── Todo │ │ │ ├── Content.tsx │ │ │ └── index.tsx │ │ ├── View │ │ │ ├── FolderView.tsx │ │ │ └── ViewNav.tsx │ │ └── Weather │ │ │ ├── Content.tsx │ │ │ ├── DayCard.tsx │ │ │ ├── Display.tsx │ │ │ ├── MiniDisplay.tsx │ │ │ └── index.tsx │ ├── ContextMenu │ │ ├── ContextItem.tsx │ │ └── index.tsx │ ├── Day.tsx │ ├── Explorer │ │ ├── About │ │ │ └── index.tsx │ │ ├── Breadcrumb.tsx │ │ ├── Documents │ │ │ └── index.tsx │ │ ├── ExplorerNav.tsx │ │ ├── ExplorerView.tsx │ │ ├── Links │ │ │ ├── index.tsx │ │ │ └── links.ts │ │ ├── NavBars.tsx │ │ ├── Pictures │ │ │ └── index.tsx │ │ └── Projects │ │ │ ├── Detail │ │ │ ├── Images.tsx │ │ │ └── index.tsx │ │ │ ├── Menu.tsx │ │ │ ├── ProjectItem.tsx │ │ │ ├── Row.tsx │ │ │ ├── data.ts │ │ │ ├── index.tsx │ │ │ └── types.ts │ ├── Loader.tsx │ ├── LockScreen.tsx │ ├── Modal │ │ ├── Button.tsx │ │ ├── Content.tsx │ │ ├── Footer.tsx │ │ ├── Overlay.tsx │ │ └── index.tsx │ ├── Start │ │ ├── StartItem.tsx │ │ ├── data.ts │ │ └── index.tsx │ └── Taskbar │ │ ├── Battery.tsx │ │ ├── ItemsList.tsx │ │ ├── Network.tsx │ │ ├── StatusBar.tsx │ │ ├── Taskbar.tsx │ │ ├── TaskbarItem.tsx │ │ └── Time.tsx ├── data │ └── notes.ts ├── hooks │ ├── useOutsideClick.ts │ └── useTabStorage.ts ├── index.css ├── main.tsx ├── react-query │ └── queries │ │ └── index.ts ├── services │ ├── github │ │ ├── github.ts │ │ └── types.ts │ └── weather │ │ ├── index.ts │ │ └── types.ts ├── utils │ └── date.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | "plugin:@tanstack/eslint-plugin-query/recommended", 9 | ], 10 | ignorePatterns: ["dist", ".eslintrc.cjs"], 11 | parser: "@typescript-eslint/parser", 12 | plugins: ["react-refresh", "@tanstack/query"], 13 | rules: { 14 | "react-refresh/only-export-components": [ 15 | "warn", 16 | { allowConstantExport: true }, 17 | ], 18 | "no-unused-vars": "warn", 19 | "@typescript-eslint/no-unused-vars": "warn", 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .note 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gadisa Teklu 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portfolio", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@tanstack/react-query": "^5.40.0", 14 | "@tanstack/react-query-devtools": "^5.40.0", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.23.1", 18 | "tailwind-scrollbar-hide": "^1.1.7" 19 | }, 20 | "devDependencies": { 21 | "@tanstack/eslint-plugin-query": "^5.35.6", 22 | "@types/node": "^20.12.13", 23 | "@types/react": "^18.2.66", 24 | "@types/react-anchor-link-smooth-scroll": "^1.0.5", 25 | "@types/react-dom": "^18.2.22", 26 | "@typescript-eslint/eslint-plugin": "^7.2.0", 27 | "@typescript-eslint/parser": "^7.2.0", 28 | "@vitejs/plugin-react": "^4.2.1", 29 | "autoprefixer": "^10.4.19", 30 | "eslint": "^8.57.0", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "eslint-plugin-react-refresh": "^0.4.6", 33 | "postcss": "^8.4.38", 34 | "tailwindcss": "^3.4.3", 35 | "typescript": "^5.2.2", 36 | "vite": "^5.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/avatarresi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/avatarresi.png -------------------------------------------------------------------------------- /public/assets/homepage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/homepage.jpg -------------------------------------------------------------------------------- /public/assets/homepage0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/homepage0.png -------------------------------------------------------------------------------- /public/assets/homepage2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/homepage2.jpg -------------------------------------------------------------------------------- /public/assets/homepage3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/homepage3.jpg -------------------------------------------------------------------------------- /public/assets/homepage4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/homepage4.png -------------------------------------------------------------------------------- /public/assets/homepage5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/homepage5.png -------------------------------------------------------------------------------- /public/assets/stars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/stars.jpg -------------------------------------------------------------------------------- /public/assets/starso.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sukkoth/windows-portfolio/da0e848d314a0012b3fa2f397974c0da8491dc18/public/assets/starso.jpg -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .modal-icons { 2 | @apply size-9 hover:bg-stone-600 p-3 object-contain; 3 | } 4 | 5 | .repo-item { 6 | width: 300px; 7 | height: 400px; 8 | border-radius: 20px; 9 | display: grid; 10 | position: relative; 11 | overflow: hidden; 12 | } 13 | 14 | .repo-item:hover::before { 15 | content: ""; 16 | position: absolute; 17 | width: 150px; 18 | height: 600px; 19 | left: 75px; 20 | top: -100px; 21 | background-image: linear-gradient(#ff0000, #0000ff); 22 | animation: spiral 10s linear infinite; 23 | } 24 | 25 | @keyframes spiral { 26 | 0% { 27 | transform: rotate(0deg); 28 | } 29 | 100% { 30 | transform: rotate(3600deg); 31 | } 32 | } 33 | 34 | .repo-item:hover::after { 35 | content: ""; 36 | position: absolute; 37 | inset: 2px; 38 | background-color: #04042c; 39 | border-radius: 10px; 40 | } 41 | 42 | .wifi-item { 43 | top: 100%; 44 | left: 100%; 45 | transform: translate(-50%, -50%); 46 | border-radius: 100%; 47 | } 48 | 49 | .custom-before::before { 50 | content: ""; 51 | position: absolute; 52 | top: -4px; 53 | right: -4px; 54 | bottom: -4px; 55 | left: -4px; 56 | z-index: -10; 57 | border-radius: 0.5rem; /* Adjust this value if you need a different rounding */ 58 | background-color: #303032; /* Assuming --hover-color is defined in your CSS */ 59 | } 60 | 61 | .task-items-list .task-item { 62 | @apply relative p-1 hover:before:hover-effect; 63 | } 64 | 65 | .task-items-list .active { 66 | @apply after:inset-x-1/4 after:h-[4px] after:rounded-lg after:bg-orange-500 after:absolute before:hover-effect; 67 | } 68 | 69 | .explorer-menu .active { 70 | @apply bg-stone-700/50; 71 | } 72 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | import Layout from "@/Layout"; 3 | import Home from "@/Pages/Home"; 4 | 5 | import "@/App.css"; 6 | import FolderView from "@/components/Apps/View/FolderView"; 7 | import Github from "@/components/Apps/Github"; 8 | import Weather from "@/components/Apps/Weather"; 9 | import Todo from "@/components/Apps/Todo"; 10 | import Projects from "@/components/Explorer/Projects"; 11 | import ExplorerView from "@/components/Explorer/ExplorerView"; 12 | import Documents from "@/components/Explorer/Documents"; 13 | import Pictures from "@/components/Explorer/Pictures"; 14 | import Links from "@/components/Explorer/Links"; 15 | import About from "@/components/Explorer/About"; 16 | import Detail from "@/components/Explorer/Projects/Detail"; 17 | import Images from "@/components/Explorer/Projects/Detail/Images"; 18 | import Notepad from "@/components/Apps/Notepad"; 19 | import Photos from "@/components/Apps/Photos"; 20 | 21 | function App() { 22 | return ( 23 | 24 | }> 25 | } /> 26 | }> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | } /> 32 | 33 | }> 34 | }> 35 | 36 | } /> 37 | } /> 38 | } /> 39 | 40 | } /> 41 | } /> 42 | } /> 43 | } /> 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | export default App; 52 | -------------------------------------------------------------------------------- /src/Folder.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | 3 | function Folder() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default Folder; 12 | -------------------------------------------------------------------------------- /src/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet, useLocation, useNavigate } from "react-router-dom"; 2 | import TaskBar from "./components/Taskbar/Taskbar"; 3 | import { useEffect, useMemo, useState } from "react"; 4 | 5 | function Layout() { 6 | const [showPrev, setShowPrev] = useState(false); 7 | const [history, setHistory] = useState([]); 8 | 9 | const keysPressed: { [key: string]: boolean } = useMemo(() => { 10 | return {}; 11 | }, []); 12 | 13 | const { pathname } = useLocation(); 14 | const navigate = useNavigate(); 15 | 16 | useEffect(() => { 17 | if (history.some((item) => item === pathname)) return; 18 | setHistory((prev) => [...prev, pathname]); 19 | }, [pathname, history]); 20 | 21 | useEffect(() => { 22 | function keyDownFunc(event: KeyboardEvent) { 23 | keysPressed[event.key] = true; 24 | 25 | if (event.key === "Tab") { 26 | event.preventDefault(); 27 | } 28 | 29 | if (keysPressed["Tab"] && event.key == "Alt") { 30 | setShowPrev(true); 31 | } 32 | } 33 | 34 | function keyUpFunc(event: KeyboardEvent) { 35 | delete keysPressed[event.key]; 36 | setShowPrev(false); 37 | } 38 | 39 | document.addEventListener("keydown", keyDownFunc); 40 | document.addEventListener("keyup", keyUpFunc); 41 | 42 | return () => { 43 | document.removeEventListener("keydown", keyDownFunc); 44 | document.removeEventListener("keyup", keyUpFunc); 45 | }; 46 | }, [keysPressed]); 47 | 48 | return ( 49 |
50 |
51 | 52 | {showPrev && ( 53 |
54 | {[...new Set(history)].map((item) => ( 55 |
{ 59 | setShowPrev(false); 60 | navigate(item); 61 | }} 62 | > 63 |

{item}

64 |
65 | ))} 66 |
67 | )} 68 |
69 | 70 |
71 | ); 72 | } 73 | 74 | export default Layout; 75 | -------------------------------------------------------------------------------- /src/Pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import ContextMenu from "@/components/ContextMenu"; 2 | import Day from "@/components/Day"; 3 | import { useState } from "react"; 4 | 5 | function Home() { 6 | const [showMenu, setShowMenu] = useState(false); 7 | const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); 8 | 9 | const handleRightClick = ( 10 | event: React.MouseEvent 11 | ) => { 12 | event.preventDefault(); 13 | setMenuPosition({ x: event.clientX, y: event.clientY }); 14 | setShowMenu(true); 15 | }; 16 | 17 | const handleClick = () => { 18 | setShowMenu(false); 19 | }; 20 | 21 | return ( 22 |
27 | {showMenu && } 28 | 29 |
30 | ); 31 | } 32 | 33 | export default Home; 34 | -------------------------------------------------------------------------------- /src/Provider/TabProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from "react"; 2 | 3 | //DEFINE CONTEXT 4 | export const TabContext = createContext<{ 5 | activeTab: string | null; 6 | toggleTab: (setOpen: string | null) => void; 7 | toggleAsleep: (state: boolean) => void; 8 | asleep: boolean; 9 | }>({ 10 | activeTab: null, 11 | toggleTab: () => {}, 12 | toggleAsleep: () => {}, 13 | asleep: false, 14 | }); 15 | 16 | type Props = { 17 | children: React.ReactNode; 18 | }; 19 | function TabProvider({ children }: Props) { 20 | const [activeTab, setActiveTab] = useState(null); 21 | const [asleep, setAsleep] = useState(false); 22 | 23 | function toggleTab(setOpen: string | null) { 24 | setActiveTab(setOpen); 25 | } 26 | function toggleAsleep(state: boolean) { 27 | localStorage.setItem("locked", JSON.stringify(state)); 28 | setAsleep(state); 29 | } 30 | 31 | useEffect(() => { 32 | setAsleep(JSON.parse(localStorage.getItem("locked") || "false")); 33 | }, []); 34 | 35 | return ( 36 | 44 | {children} 45 | 46 | ); 47 | } 48 | 49 | // eslint-disable-next-line react-refresh/only-export-components 50 | export function useTab() { 51 | return useContext(TabContext); 52 | } 53 | 54 | export default TabProvider; 55 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Apps/Github/Content.tsx: -------------------------------------------------------------------------------- 1 | import RepoItem from "./RepoItem"; 2 | import { useGetRepos } from "@/react-query/queries"; 3 | import Loader from "@/components/Loader"; 4 | 5 | function Content() { 6 | const getRepos = useGetRepos(); 7 | return ( 8 | <> 9 |

10 | Github Repositories 11 |

12 |
13 | {!getRepos.isLoading && getRepos.error ? ( 14 |

{getRepos.error.message || "Could not fetch repositories"}

15 | ) : !getRepos.isLoading ? ( 16 | getRepos.data?.length ? ( 17 | getRepos.data.map((repo) => ) 18 | ) : ( 19 |

No repo data found

20 | ) 21 | ) : ( 22 | 23 | )} 24 |
25 | 26 | ); 27 | } 28 | 29 | export default Content; 30 | -------------------------------------------------------------------------------- /src/components/Apps/Github/RepoItem.tsx: -------------------------------------------------------------------------------- 1 | import { Repository } from "@/services/github/types"; 2 | 3 | function RepoItem({ repo }: { repo: Repository }) { 4 | return ( 5 | 10 |
11 |
12 | 16 |
17 |
18 |

19 | {repo.name} 20 |

21 | 22 |

23 | Created at: 24 | {new Date(repo.updated_at).toLocaleDateString()} 25 |

26 |

27 | Last Updated: 28 | {new Date(repo.updated_at).toLocaleDateString()} 29 |

30 |
31 |
32 |
33 | ); 34 | } 35 | 36 | export default RepoItem; 37 | -------------------------------------------------------------------------------- /src/components/Apps/Github/index.tsx: -------------------------------------------------------------------------------- 1 | import Content from "./Content"; 2 | 3 | function Github() { 4 | return ; 5 | } 6 | 7 | export default Github; 8 | -------------------------------------------------------------------------------- /src/components/Apps/Notepad/index.tsx: -------------------------------------------------------------------------------- 1 | import { notes } from "@/data/notes"; 2 | import { useLocation } from "react-router-dom"; 3 | 4 | function Notepad() { 5 | const location = useLocation(); 6 | 7 | const noteId = location.state?.noteId; 8 | 9 | const content = notes.find((note) => noteId === note.id)?.content; 10 | 11 | return ( 12 |
13 |
18 |
19 | ); 20 | } 21 | 22 | export default Notepad; 23 | -------------------------------------------------------------------------------- /src/components/Apps/Photos/data.ts: -------------------------------------------------------------------------------- 1 | export const pictures = [ 2 | { 3 | id: 1, 4 | projectId: 2, 5 | path: "/public/assets/homepage.jpg", 6 | }, 7 | { 8 | id: 1, 9 | projectId: 2, 10 | path: "/public/assets/homepage0.png", 11 | }, 12 | { 13 | id: 1, 14 | projectId: 2, 15 | path: "/public/assets/homepage2.jpg", 16 | }, 17 | { 18 | id: 1, 19 | projectId: 2, 20 | path: "/public/assets/homepage3.jpg", 21 | }, 22 | { 23 | id: 1, 24 | projectId: 2, 25 | path: "/public/assets/homepage4.png", 26 | }, 27 | { 28 | id: 1, 29 | projectId: 2, 30 | path: "/public/assets/homepage5.png", 31 | }, 32 | ]; 33 | -------------------------------------------------------------------------------- /src/components/Apps/Photos/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | import { pictures } from "./data"; 4 | 5 | function Photos() { 6 | const location = useLocation(); 7 | const [showing, setShowing] = useState(0); 8 | 9 | // const imagePath = location.state?.imagePath; 10 | function handleNav(increment: boolean = false) { 11 | if (increment) { 12 | if (showing >= pictures.length - 1) { 13 | setShowing(0); 14 | } else { 15 | setShowing((prev) => prev + 1); 16 | } 17 | } else { 18 | if (showing === 0) { 19 | setShowing(pictures.length - 1); 20 | } else setShowing((prev) => prev - 1); 21 | } 22 | } 23 | 24 | return ( 25 |
26 |
27 |
28 | image 33 |
34 |
35 | handleNav()} 37 | imageUrl='https://img.icons8.com/?size=100&id=1806&format=png&color=ffffff' 38 | /> 39 |

40 | {showing + 1} | {pictures.length} 41 |

42 | handleNav(true)} 44 | imageUrl='https://img.icons8.com/?size=100&id=AgsBKswRnV1i&format=png&color=ffffff' 45 | /> 46 |
47 |
48 |
49 | ); 50 | } 51 | 52 | type MenuItemProps = { 53 | imageUrl: string; 54 | onClick?: () => void; 55 | }; 56 | function MenuItem({ imageUrl, onClick }: MenuItemProps) { 57 | return ( 58 | prev 64 | ); 65 | } 66 | 67 | export default Photos; 68 | -------------------------------------------------------------------------------- /src/components/Apps/Todo/Content.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | function Content() { 4 | const [isLoading, setIsLoading] = useState(true); 5 | return ( 6 | <> 7 | 12 | {isLoading && ( 13 |
14 |

15 | Loading . . . .{" "} 16 |

17 |
18 | )} 19 | 20 | ); 21 | } 22 | 23 | export default Content; 24 | -------------------------------------------------------------------------------- /src/components/Apps/Todo/index.tsx: -------------------------------------------------------------------------------- 1 | import Content from "./Content"; 2 | 3 | function Todo() { 4 | return ; 5 | } 6 | 7 | export default Todo; 8 | -------------------------------------------------------------------------------- /src/components/Apps/View/FolderView.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | import ViewNav from "./ViewNav"; 4 | 5 | function FolderView({ 6 | children, 7 | type = "app", 8 | onClose, 9 | withOutlet = true, 10 | }: { 11 | children?: React.ReactNode; 12 | type: "app" | "folder"; 13 | onClose?: () => void; 14 | withOutlet?: boolean; 15 | }) { 16 | const [maxView, setMaxView] = useState( 17 | JSON.parse(localStorage.getItem("maxView") || "true") 18 | ); 19 | 20 | function toggleMaxView() { 21 | setMaxView((prev) => !prev); 22 | localStorage.setItem("maxView", JSON.stringify(!maxView)); 23 | } 24 | 25 | const maxToggledClass = `${!maxView ? "scale-75" : ""}`; 26 | 27 | return ( 28 |
31 | 32 |
33 | {withOutlet ? : children} 34 |
35 |
36 | ); 37 | } 38 | 39 | export default FolderView; 40 | -------------------------------------------------------------------------------- /src/components/Apps/View/ViewNav.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation, useNavigate } from "react-router-dom"; 2 | 3 | type Props = { 4 | onSetView: () => void; 5 | onClose?: () => void; 6 | type: "folder" | "app"; 7 | }; 8 | 9 | function ViewNav({ onSetView, type = "app", onClose }: Props) { 10 | const navigate = useNavigate(); 11 | const { pathname, state } = useLocation(); 12 | 13 | const paths = pathname.split("/"); 14 | const pathToDisplay = paths[paths.length - 1]; 15 | 16 | function handleClose() { 17 | //*case 1: if you have onClose, handle that using the close button 18 | //*case 2: //if state has backTo, the apps and folders need to go back specific 19 | //*route rather than navigating back (-1) 20 | //*case 3: navigate back (-1) 21 | 22 | return onClose 23 | ? onClose() 24 | : state?.backTo 25 | ? navigate(state.backTo, { 26 | replace: state.replace, 27 | }) 28 | : navigate("/", { 29 | replace: true, 30 | }); 31 | } 32 | 33 | return ( 34 |
35 | {type === "folder" ? ( 36 |
37 |

{pathToDisplay}

38 |

42 | x 43 |

44 |
45 | ) : ( 46 |
47 | )} 48 |
49 | 55 | 61 |
62 |
63 | ); 64 | } 65 | 66 | export default ViewNav; 67 | -------------------------------------------------------------------------------- /src/components/Apps/Weather/Content.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Position } from "@/services/weather/types"; 3 | import MiniDisplay from "./MiniDisplay"; 4 | import Display from "./Display"; 5 | import { useGetWeatherData } from "@/react-query/queries"; 6 | 7 | function Content() { 8 | //default to addis ababa 9 | const [location, setLocation] = useState({ 10 | latitude: 9.0192, 11 | longitude: 38.7525, 12 | }); 13 | 14 | useEffect(() => { 15 | navigator.geolocation.getCurrentPosition((position) => { 16 | setLocation({ 17 | latitude: position.coords.latitude, 18 | longitude: position.coords.longitude, 19 | }); 20 | }); 21 | }, []); 22 | 23 | const { data, isLoading, error, isError } = useGetWeatherData(location); 24 | 25 | return ( 26 | <> 27 |
28 | {isLoading ? ( 29 |

Loading . . .

30 | ) : isError ? ( 31 |

{error.message}

32 | ) : data ? ( 33 | <> 34 | 35 | 36 | 37 | ) : ( 38 |

Could not get weather data for your location

39 | )} 40 |
41 | 42 | ); 43 | } 44 | 45 | export default Content; 46 | -------------------------------------------------------------------------------- /src/components/Apps/Weather/DayCard.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | top?: string; 3 | middle?: string; 4 | bottom?: string; 5 | icon?: string; 6 | }; 7 | 8 | function DayCard(props: Props) { 9 | return ( 10 |
11 | {props.icon && } 12 |

{props.top || "Today"}

13 |

14 | {props.middle || "20"} 15 |

16 | {!props.icon && ( 17 |

18 | {props.bottom || "Mist"} 19 |

20 | )} 21 |
22 | ); 23 | } 24 | 25 | export default DayCard; 26 | -------------------------------------------------------------------------------- /src/components/Apps/Weather/Display.tsx: -------------------------------------------------------------------------------- 1 | import { getDayName, getFormattedDate } from "@/utils/date"; 2 | import DayCard from "./DayCard"; 3 | import { Weather } from "@/services/weather/types"; 4 | 5 | type Props = { 6 | weather: Weather; 7 | }; 8 | 9 | function Display({ weather }: Props) { 10 | return ( 11 |
12 |
13 |

14 | {weather.location.name}, {weather.location.country} 15 |

16 |

{getFormattedDate()}

17 |
18 |
19 | icon 24 |
25 |
26 |

27 | 28 | {weather.current.temp_c} 29 | 30 |

31 |
32 |
33 | wind 38 |

{weather.current.wind_mph} mph

39 |
40 |
41 | humidity 46 |

{weather.current.humidity}%

47 |
48 |
49 |
50 |

51 | {weather.current.condition.text} 52 |

53 | 54 |
55 | {weather.forecast.forecastday.map((day, index) => ( 56 | 63 | ))} 64 |
65 |
66 | ); 67 | } 68 | 69 | export default Display; 70 | -------------------------------------------------------------------------------- /src/components/Apps/Weather/MiniDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { formatTime, getGreeting } from "@/utils/date"; 2 | import DayCard from "./DayCard"; 3 | import { Weather } from "@/services/weather/types"; 4 | 5 | type Props = { 6 | weather: Weather; 7 | }; 8 | 9 | function MiniDisplay({ weather }: Props) { 10 | return ( 11 |
12 |

{getGreeting()}

13 |

{formatTime()}

14 | {/* //! */} 15 | 16 |
17 | icon 25 |
26 |
27 |

28 | 29 | {weather.forecast.forecastday[0].hour[new Date().getHours()].temp_c} 30 | 31 |

32 |
33 |
34 | wind 39 |

40 | { 41 | weather.forecast.forecastday[0].hour[new Date().getHours()] 42 | .wind_mph 43 | }{" "} 44 | mph 45 |

46 |
47 |
48 | humidity 53 |

54 | { 55 | weather.forecast.forecastday[0].hour[new Date().getHours()] 56 | .humidity 57 | } 58 | % 59 |

60 |
61 |
62 |
63 |

64 | { 65 | weather.forecast.forecastday[0].hour[new Date().getHours()].condition 66 | .text 67 | } 68 |

69 | {/* //! */} 70 |

Hourly Forecast

71 |
72 | {weather.forecast.forecastday[0].hour 73 | .slice(new Date().getHours(), new Date().getHours() + 6) 74 | .map((hourData, index) => ( 75 | 82 | ))} 83 |
84 |
85 | ); 86 | } 87 | 88 | export default MiniDisplay; 89 | -------------------------------------------------------------------------------- /src/components/Apps/Weather/index.tsx: -------------------------------------------------------------------------------- 1 | import Content from "./Content"; 2 | 3 | function Weather() { 4 | return ; 5 | } 6 | 7 | export default Weather; 8 | -------------------------------------------------------------------------------- /src/components/ContextMenu/ContextItem.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | function ContextItem({ 4 | children, 5 | icon, 6 | onClick, 7 | }: { 8 | children: ReactNode; 9 | icon?: string; 10 | onClick?: () => void; 11 | }) { 12 | return ( 13 |
17 | {icon && } 18 |

{children}

19 |
20 | ); 21 | } 22 | 23 | export default ContextItem; 24 | -------------------------------------------------------------------------------- /src/components/ContextMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import ContextItem from "./ContextItem"; 2 | 3 | function ContextMenu({ position }: { position: { x: number; y: number } }) { 4 | return ( 5 |
12 |
13 | window.open("https://github.com/sukkoth", "_blank")} 15 | icon={ 16 | "https://img.icons8.com/?size=120&id=AZOZNnY73haj&format=png&color=000000" 17 | } 18 | > 19 | Visit Github Profile 20 | 21 | 23 | window.open( 24 | "https://www.linkedin.com/in/gadisa-teklu-8020a991", 25 | "_blank" 26 | ) 27 | } 28 | icon='https://img.icons8.com/?size=100&id=13930&format=png&color=000000' 29 | > 30 | View Linkedin 31 | 32 | window.open("https://t.me/sukkoth", "_blank")} 34 | icon='https://img.icons8.com/?size=100&id=oWiuH0jFiU0R&format=png&color=000000' 35 | > 36 | Contact on Telegram 37 | 38 |
39 |
40 | ); 41 | } 42 | 43 | export default ContextMenu; 44 | -------------------------------------------------------------------------------- /src/components/Day.tsx: -------------------------------------------------------------------------------- 1 | function Day() { 2 | const weekdays = [ 3 | "Sunday", 4 | "Monday", 5 | "Tuesday", 6 | "Wednesday", 7 | "Thursday", 8 | "Friday", 9 | "Saturday", 10 | ]; 11 | const today = new Date().getDay(); 12 | 13 | return ( 14 |

15 | {weekdays[today]} 16 |

17 | ); 18 | } 19 | 20 | export default Day; 21 | -------------------------------------------------------------------------------- /src/components/Explorer/About/index.tsx: -------------------------------------------------------------------------------- 1 | function About() { 2 | return ( 3 |
4 |

About

5 |
6 | ); 7 | } 8 | 9 | export default About; 10 | -------------------------------------------------------------------------------- /src/components/Explorer/Breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from "react-router-dom"; 2 | 3 | function Breadcrumb() { 4 | const { pathname } = useLocation(); 5 | const paths = pathname.split("/"); 6 | return ( 7 |
8 | {paths.map((path) => ( 9 |
10 |
{path}
11 | {">"} 12 |
13 | ))} 14 |
15 | ); 16 | } 17 | 18 | export default Breadcrumb; 19 | -------------------------------------------------------------------------------- /src/components/Explorer/Documents/index.tsx: -------------------------------------------------------------------------------- 1 | function Documents() { 2 | return ( 3 |
4 |

Docs

5 |
6 | ); 7 | } 8 | 9 | export default Documents; 10 | -------------------------------------------------------------------------------- /src/components/Explorer/ExplorerNav.tsx: -------------------------------------------------------------------------------- 1 | import Breadcrumb from "./Breadcrumb"; 2 | import NavBars from "./NavBars"; 3 | 4 | function ExplorerNav() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | 13 | export default ExplorerNav; 14 | -------------------------------------------------------------------------------- /src/components/Explorer/ExplorerView.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import Menu from "./Projects/Menu"; 3 | import ExplorerNav from "./ExplorerNav"; 4 | 5 | function ExplorerView() { 6 | return ( 7 |
8 | {/* //back forward up reload component */} 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 | ); 19 | } 20 | 21 | export default ExplorerView; 22 | -------------------------------------------------------------------------------- /src/components/Explorer/Links/index.tsx: -------------------------------------------------------------------------------- 1 | import { getFormattedDate } from "@/utils/date"; 2 | import Row from "../Projects/Row"; 3 | import { links } from "./links"; 4 | 5 | function Links() { 6 | return ( 7 |
8 |
9 | 10 | Name 11 | Status 12 | Type 13 | Created at 14 | Updated at 15 | 16 | {links.map((link) => ( 17 |
window.open(link.url, "_blank")} 19 | key={link.id} 20 | > 21 | 22 | {link.title} 23 | {"active"} 24 | Internet Shortcut 25 | {getFormattedDate(new Date(link.createdAt))} 26 | {getFormattedDate(new Date(link.updatedAt))} 27 | 28 |
29 | ))} 30 |
31 |
32 | ); 33 | } 34 | 35 | export default Links; 36 | -------------------------------------------------------------------------------- /src/components/Explorer/Links/links.ts: -------------------------------------------------------------------------------- 1 | export const links = [ 2 | { 3 | id: "1", 4 | title: "LinkedIn", 5 | url: "https://www.linkedin.com/in/gadisa-teklu-8020a991", 6 | createdAt: "2023-05-01T10:15:30Z", 7 | updatedAt: "2023-06-01T11:00:00Z", 8 | }, 9 | { 10 | id: "2", 11 | title: "Telegram", 12 | url: "https://t.me/sukkoth", 13 | createdAt: "2023-05-01T10:15:30Z", 14 | updatedAt: "2023-06-01T11:00:00Z", 15 | }, 16 | { 17 | id: "3", 18 | title: "Github", 19 | url: "https://github.com/sukkoth", 20 | createdAt: "2023-05-01T10:15:30Z", 21 | updatedAt: "2023-06-01T11:00:00Z", 22 | }, 23 | { 24 | id: "4", 25 | title: "Email", 26 | url: "mailto:suukootj@gmail.com", 27 | createdAt: "2023-05-01T10:15:30Z", 28 | updatedAt: "2023-06-01T11:00:00Z", 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /src/components/Explorer/NavBars.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | 3 | function NavBars() { 4 | const navigate = useNavigate(); 5 | 6 | return ( 7 |
8 | navigate(-1)} 12 | /> 13 | 14 | navigate(1)} 18 | /> 19 | 20 | 25 | 26 | window.location.reload()} 30 | /> 31 |
32 | ); 33 | } 34 | 35 | export default NavBars; 36 | -------------------------------------------------------------------------------- /src/components/Explorer/Pictures/index.tsx: -------------------------------------------------------------------------------- 1 | function Pictures() { 2 | return ( 3 |
4 |

Pictures

5 |
6 | ); 7 | } 8 | 9 | export default Pictures; 10 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/Detail/Images.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import Row from "../Row"; 3 | 4 | function Images() { 5 | const navigate = useNavigate(); 6 | return ( 7 |
8 |
9 | 10 | Name 11 | Type 12 | Created at 13 | Updated at 14 | 15 |
17 | navigate("/app/photos", { 18 | state: { 19 | noteId: 2, 20 | backTo: location.pathname, 21 | replace: true, 22 | }, 23 | replace: true, 24 | }) 25 | } 26 | > 27 | 28 | Images 1 29 | File Folder 30 | Created at 31 | Updated at 32 | 33 |
34 |
35 |
36 | ); 37 | } 38 | 39 | export default Images; 40 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/Detail/index.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import Row from "../Row"; 3 | 4 | function Detail() { 5 | const navigate = useNavigate(); 6 | return ( 7 |
8 |
9 | 10 | Name 11 | Type 12 | Created at 13 | Updated at 14 | 15 |
navigate(`images`)}> 16 | 17 | Images 18 | File Folder 19 | Created at 20 | Updated at 21 | 22 |
23 | 24 |
26 | navigate("/app/notepad", { 27 | state: { 28 | noteId: 2, 29 | backTo: location.pathname, 30 | replace: true, 31 | }, 32 | replace: true, 33 | }) 34 | } 35 | > 36 | 37 | ReadMe 38 | Text file 39 | Created at 40 | Updated at 41 | 42 |
43 |
alert("redirect to github")}> 44 | 45 | Github Repo 46 | Internet Shortcut 47 | Created at 48 | Updated at 49 | 50 |
51 |
52 | 53 | Show More Details . . . 54 | Program Shortcut 55 | Created at 56 | Updated at 57 | 58 |
59 |
60 |
61 | ); 62 | } 63 | 64 | export default Detail; 65 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/Menu.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink } from "react-router-dom"; 2 | 3 | function Menu() { 4 | return ( 5 |
6 | 11 | 16 | 17 | 22 | 27 | 28 | 33 |
34 | ); 35 | } 36 | 37 | type MenuItemProps = { 38 | imageUrl: string; 39 | title: string; 40 | to: string; 41 | }; 42 | function MenuItem({ imageUrl, title, to }: MenuItemProps) { 43 | return ( 44 | 49 | {" "} 50 |

{title}

51 |
52 | ); 53 | } 54 | 55 | export default Menu; 56 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/ProjectItem.tsx: -------------------------------------------------------------------------------- 1 | function ProjectItem() { 2 | return ( 3 |
4 | image 9 |
10 |
11 | Title: 12 | Taskade 13 |
14 |
15 | Description: 16 | 17 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum, 18 | expedita quod? Voluptates aperiam consequatur dolore labore! Tempora 19 | minus ullam vitae. 20 | 21 |
22 |
23 | Stack: 24 | {["JavaScript", "TailwindCSS"].map((stack) => ( 25 | #{stack} 26 | ))} 27 |
28 | 31 |
32 |
33 | ); 34 | } 35 | 36 | export default ProjectItem; 37 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/Row.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | children: React.ReactNode; 5 | enableHover?: boolean; 6 | gridClass?: string; 7 | }; 8 | 9 | function Row({ 10 | children, 11 | enableHover = true, 12 | gridClass = "grid-cols-folder", 13 | }: Props) { 14 | return ( 15 |
20 | {children} 21 |
22 | ); 23 | } 24 | 25 | type ItemProps = { 26 | children: React.ReactNode; 27 | iconType?: 28 | | "folder" 29 | | "imageFolder" 30 | | "shortcut" 31 | | "link" 32 | | "image" 33 | | "github" 34 | | "textFile"; 35 | }; 36 | 37 | function Item({ children, iconType }: ItemProps) { 38 | const matchIcons = { 39 | link: "https://img.icons8.com/?size=100&id=n9d0Hm43JCPK&format=png&color=000000", 40 | folder: 41 | "https://img.icons8.com/?size=100&id=dINnkNb1FBl4&format=png&color=000000", 42 | imageFolder: 43 | "https://img.icons8.com/?size=100&id=K5ZTlMA55wO5&format=png&color=000000", 44 | image: 45 | "https://img.icons8.com/?size=100&id=bjHuxcHTNosO&format=png&color=000000", 46 | github: "https://img.icons8.com/?size=100&id=16318&format=png&color=ffffff", 47 | program: 48 | "https://img.icons8.com/?size=100&id=l0UsZRTvcGel&format=png&color=000000", 49 | shortcut: 50 | "https://img.icons8.com/?size=100&id=i1z7pQ2orcJk&format=png&color=000000", 51 | textFile: 52 | "https://img.icons8.com/?size=100&id=Ygov9LJC2LzE&format=png&color=000000", 53 | }; 54 | 55 | if (iconType) { 56 | return ( 57 |
58 | ic 59 |

{children}

60 |
61 | ); 62 | } 63 | return

{children}

; 64 | } 65 | 66 | type TitleProps = { 67 | children: React.ReactNode; 68 | }; 69 | 70 | function Title({ children }: TitleProps) { 71 | return

{children}

; 72 | } 73 | 74 | Row.Item = Item; 75 | Row.Title = Title; 76 | 77 | export default Row; 78 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/data.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "./types"; 2 | 3 | export const projects: Project[] = [ 4 | { 5 | id: 1, 6 | projectName: "Task Mate", 7 | status: true, 8 | platform: "Web", 9 | createdAt: "2023-05-01T10:15:30Z", 10 | updatedAt: "2023-06-01T11:00:00Z", 11 | }, 12 | { 13 | id: 2, 14 | projectName: "Portfolio", 15 | status: true, 16 | platform: "Web", 17 | createdAt: "2023-04-15T08:20:45Z", 18 | updatedAt: "2023-05-20T09:30:25Z", 19 | }, 20 | { 21 | id: 3, 22 | projectName: "Movie Plus", 23 | status: true, 24 | platform: "Web", 25 | createdAt: "2023-03-10T14:25:10Z", 26 | updatedAt: "2023-04-15T16:45:50Z", 27 | }, 28 | { 29 | id: 4, 30 | projectName: "Kairos", 31 | status: true, 32 | platform: "Web", 33 | createdAt: "2023-02-20T09:10:55Z", 34 | updatedAt: "2023-03-22T10:50:30Z", 35 | }, 36 | { 37 | id: 5, 38 | projectName: "Typy", 39 | status: true, 40 | platform: "Web", 41 | createdAt: "2023-01-30T07:35:20Z", 42 | updatedAt: "2023-02-25T08:55:40Z", 43 | }, 44 | { 45 | id: 6, 46 | projectName: "TaskStar", 47 | status: true, 48 | platform: "Web", 49 | createdAt: "2023-01-30T07:35:20Z", 50 | updatedAt: "2023-02-25T08:55:40Z", 51 | }, 52 | { 53 | id: 7, 54 | projectName: "Menu Extraction", 55 | status: true, 56 | platform: "Web", 57 | createdAt: "2023-01-30T07:35:20Z", 58 | updatedAt: "2023-02-25T08:55:40Z", 59 | }, 60 | { 61 | id: 8, 62 | projectName: "Eshop", 63 | status: true, 64 | platform: "Web", 65 | createdAt: "2023-01-30T07:35:20Z", 66 | updatedAt: "2023-02-25T08:55:40Z", 67 | }, 68 | { 69 | id: 9, 70 | projectName: "Eshop Backend", 71 | status: true, 72 | platform: "Backend", 73 | createdAt: "2023-01-30T07:35:20Z", 74 | updatedAt: "2023-02-25T08:55:40Z", 75 | }, 76 | { 77 | id: 10, 78 | projectName: "Quote Generator", 79 | status: true, 80 | platform: "Web", 81 | createdAt: "2023-01-30T07:35:20Z", 82 | updatedAt: "2023-02-25T08:55:40Z", 83 | }, 84 | ]; 85 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/index.tsx: -------------------------------------------------------------------------------- 1 | import Row from "@/components/Explorer/Projects/Row"; 2 | import { projects } from "@/components/Explorer/Projects/data"; 3 | import { getFormattedDate } from "@/utils/date"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | function Projects() { 7 | const navigate = useNavigate(); 8 | function getStatusImg(status: boolean) { 9 | return status ? activeImg : inactiveImg; 10 | } 11 | 12 | return ( 13 |
14 |
15 | 16 | Name 17 | Status 18 | Type 19 | Platform 20 | Created at 21 | Updated at 22 | 23 | 24 | {projects.map((project) => ( 25 |
navigate(`2`)} key={project.id}> 26 | 30 | {project.projectName} 31 | 32 | {getStatusImg(project.status)} 33 | 34 | {"File Folder"} 35 | {project.platform} 36 | 37 | {getFormattedDate(new Date(project.createdAt))} 38 | 39 | 40 | {getFormattedDate(new Date(project.updatedAt))} 41 | 42 | 43 |
44 | ))} 45 |
46 |
47 | ); 48 | } 49 | 50 | const activeImg = ( 51 | 55 | ); 56 | const inactiveImg = ( 57 | 61 | ); 62 | 63 | export default Projects; 64 | -------------------------------------------------------------------------------- /src/components/Explorer/Projects/types.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | id: number; 3 | projectName: string; 4 | status: boolean; 5 | platform: "Web" | "App" | "Backend"; 6 | createdAt: string; 7 | updatedAt: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Loader.tsx: -------------------------------------------------------------------------------- 1 | function Loader() { 2 | return ( 3 |
4 |

LOADING . . .

5 |
6 | ); 7 | } 8 | 9 | export default Loader; 10 | -------------------------------------------------------------------------------- /src/components/LockScreen.tsx: -------------------------------------------------------------------------------- 1 | import { Position } from "@/services/weather/types"; 2 | import { formatTime, getDayName } from "@/utils/date"; 3 | import DayCard from "./Folder/Weather/DayCard"; 4 | import { useEffect, useState } from "react"; 5 | import { useGetWeatherData } from "@/react-query/queries"; 6 | 7 | function LockScreen() { 8 | const formattedDate = new Date().toLocaleDateString("en-US", { 9 | weekday: "long", 10 | month: "long", 11 | day: "numeric", 12 | }); 13 | 14 | const [location, setLocation] = useState(null); 15 | 16 | useEffect(() => { 17 | navigator.geolocation.getCurrentPosition((position) => { 18 | setLocation({ 19 | latitude: position.coords.latitude, 20 | longitude: position.coords.longitude, 21 | }); 22 | }); 23 | }, []); 24 | 25 | const { data: weather } = useGetWeatherData(location); 26 | 27 | return ( 28 |
29 |

30 | {formatTime()} 31 |

32 |

{formattedDate}

33 | 38 |

🤩 Double Click to unlock

39 | {weather && ( 40 |
41 |
42 | {weather.forecast.forecastday.map((day, index) => ( 43 | 50 | ))} 51 |
52 |
53 | )} 54 |
55 | ); 56 | } 57 | 58 | export default LockScreen; 59 | -------------------------------------------------------------------------------- /src/components/Modal/Button.tsx: -------------------------------------------------------------------------------- 1 | import { cloneElement, useContext } from "react"; 2 | import { ModalContext } from "."; 3 | import { useTab } from "@/Provider/TabProvider"; 4 | 5 | type TOGGLER_BUTTON = { 6 | type: "TOGGLER"; 7 | children: React.ReactElement; 8 | className?: string; 9 | id?: string; 10 | }; 11 | 12 | type CLOSE_BUTTON = { 13 | type: "CLOSE"; 14 | text?: string; 15 | children?: React.ReactElement; //if child, clone it else, just return with onclose 16 | }; 17 | 18 | type CONFIRM_BUTTON = { 19 | type: "CONFIRM"; 20 | text?: string; 21 | closeModalOnConfirm?: boolean; // 22 | onConfirm: () => void; 23 | children?: React.ReactElement; // 24 | }; 25 | 26 | type ButtonProps = TOGGLER_BUTTON | CLOSE_BUTTON | CONFIRM_BUTTON; 27 | 28 | export default function Button(props: ButtonProps) { 29 | const { toggleModal } = useContext(ModalContext); 30 | const { toggleTab, activeTab } = useTab(); 31 | 32 | //toggles the modal, the user has to pass a child 33 | if (props.type === "TOGGLER") { 34 | const cloned = cloneElement(props.children, { 35 | onClick: () => { 36 | toggleTab(props.id === undefined ? null : props.id); 37 | toggleModal(true); 38 | }, 39 | className: `relative after:w-[15px] after:h-[4px] after:rounded-lg after:bg-orange-500 after:left-[8.5px] after:absolute ${ 40 | activeTab === props.id ? "after:block" : "after:hidden" 41 | }`, 42 | }); 43 | 44 | return cloned; 45 | } 46 | 47 | // this are displayed on the footer mainly 48 | if (props.type === "CLOSE") { 49 | const { children, text = "Close" } = props; 50 | 51 | return children ? ( 52 | cloneElement(children, { 53 | onClick: () => { 54 | toggleTab(null); 55 | toggleModal(); 56 | }, 57 | }) 58 | ) : ( 59 | 65 | ); 66 | } 67 | 68 | if (props.type === "CONFIRM") { 69 | const { 70 | closeModalOnConfirm = true, 71 | onConfirm, 72 | children, 73 | text = "Confirm", 74 | } = props; 75 | 76 | return children ? ( 77 | children 78 | ) : ( 79 | 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/components/Modal/Content.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { ModalContext } from "."; 3 | import { createPortal } from "react-dom"; 4 | import Overlay from "./Overlay"; 5 | 6 | type ContentProps = { 7 | title?: string; 8 | children: React.ReactNode; 9 | }; 10 | 11 | export default function Content({ children, title }: ContentProps) { 12 | const { isOpen, toggleModal } = useContext(ModalContext); 13 | 14 | if (isOpen) 15 | return createPortal( 16 | 17 | {children} 18 | , 19 | document.body 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Modal/Footer.tsx: -------------------------------------------------------------------------------- 1 | type FooterProps = { 2 | children: React.ReactNode; 3 | }; 4 | 5 | export default function Footer({ children }: FooterProps) { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Modal/Overlay.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import useOutsideClick from "../../hooks/useOutsideClick"; 3 | 4 | function Overlay({ 5 | children, 6 | onClose, 7 | }: { 8 | children: React.ReactNode; 9 | onClose: () => void; 10 | title?: string; 11 | }) { 12 | const { handler } = useOutsideClick(onClose); 13 | 14 | return ( 15 |
16 |
20 | {children} 21 |
22 |
23 | ); 24 | } 25 | 26 | export default Overlay; 27 | -------------------------------------------------------------------------------- /src/components/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useState } from "react"; 2 | import Button from "./Button"; 3 | import Content from "./Content"; 4 | import Footer from "./Footer"; 5 | 6 | //DEFINE CONTEXT 7 | export const ModalContext = createContext<{ 8 | isOpen: boolean; 9 | toggleModal: (setOpen?: boolean) => void; 10 | }>({ 11 | isOpen: false, 12 | toggleModal: () => {}, 13 | }); 14 | 15 | type ModalProps = { 16 | children: React.ReactNode; 17 | }; 18 | 19 | //MODAL 20 | function Modal({ children }: ModalProps) { 21 | const [isOpen, setIsOpen] = useState(false); 22 | function toggleModal(setOpen?: boolean) { 23 | setIsOpen((prev) => (setOpen !== undefined ? setOpen : !prev)); 24 | } 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | } 32 | 33 | // eslint-disable-next-line react-refresh/only-export-components 34 | export function useModal() { 35 | return useContext(ModalContext); 36 | } 37 | 38 | Modal.Button = Button; 39 | Modal.Content = Content; 40 | Modal.Footer = Footer; 41 | 42 | export default Modal; 43 | -------------------------------------------------------------------------------- /src/components/Start/StartItem.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | imageUrl: string; 3 | title: string; 4 | }; 5 | 6 | function StartItem({ imageUrl, title }: Props) { 7 | return ( 8 |
9 | 10 |

{title}

11 |
12 | ); 13 | } 14 | 15 | export default StartItem; 16 | -------------------------------------------------------------------------------- /src/components/Start/data.ts: -------------------------------------------------------------------------------- 1 | type StartItemType = { 2 | title: string; 3 | imageUrl: string; 4 | }; 5 | 6 | export const startItems: StartItemType[] = [ 7 | { 8 | title: "VSCode", 9 | imageUrl: 10 | "https://img.icons8.com/?size=100&id=0OQR1FYCuA9f&format=png&color=000000", 11 | }, 12 | { 13 | title: "Edge", 14 | imageUrl: 15 | "https://img.icons8.com/?size=100&id=dGm9KIZPpukc&format=png&color=000000", 16 | }, 17 | { 18 | title: "Brave", 19 | imageUrl: 20 | "https://img.icons8.com/?size=100&id=cM42lftaD9Z3&format=png&color=000000", 21 | }, 22 | { 23 | title: "Github", 24 | imageUrl: 25 | "https://img.icons8.com/?size=120&id=AZOZNnY73haj&format=png&color=000000", 26 | }, 27 | 28 | { 29 | title: "Postman", 30 | imageUrl: 31 | "https://img.icons8.com/?size=100&id=IoYmHUxgvrFB&format=png&color=000000", 32 | }, 33 | { 34 | title: "NPM", 35 | imageUrl: 36 | "https://img.icons8.com/?size=100&id=b6eVatKY9ihI&format=png&color=000000", 37 | }, 38 | { 39 | title: "Terminal", 40 | imageUrl: 41 | "https://img.icons8.com/?size=100&id=WbRVMGxHh74X&format=png&color=000000", 42 | }, 43 | { 44 | title: "Supabase", 45 | imageUrl: "https://avatars.githubusercontent.com/u/54469796?s=200&v=4", 46 | }, 47 | { 48 | title: "ChatGPT", 49 | imageUrl: 50 | "https://img.icons8.com/?size=100&id=FBO05Dys9QCg&format=png&color=ffffff", 51 | }, 52 | { 53 | title: "Gemini", 54 | imageUrl: 55 | "https://img.icons8.com/?size=100&id=eoxMN35Z6JKg&format=png&color=000000", 56 | }, 57 | 58 | { 59 | title: "MongoDB Atlas", 60 | imageUrl: 61 | "https://img.icons8.com/?size=100&id=bosfpvRzNOG8&format=png&color=000000", 62 | }, 63 | { 64 | title: "ESlint", 65 | imageUrl: 66 | "https://img.icons8.com/?size=100&id=Mh3AeBFmcOPK&format=png&color=000000", 67 | }, 68 | ]; 69 | 70 | export const languages: StartItemType[] = [ 71 | { 72 | title: "JavaScript", 73 | imageUrl: 74 | "https://img.icons8.com/?size=100&id=PXTY4q2Sq2lG&format=png&color=000000", 75 | }, 76 | { 77 | title: "TypeScript", 78 | imageUrl: 79 | "https://img.icons8.com/?size=100&id=uJM6fQYqDaZK&format=png&color=000000", 80 | }, 81 | { 82 | title: "PHP", 83 | imageUrl: 84 | "https://img.icons8.com/?size=100&id=13460&format=png&color=000000", 85 | }, 86 | { 87 | title: "CSS", 88 | imageUrl: 89 | "https://img.icons8.com/?size=100&id=7gdY5qNXaKC0&format=png&color=000000", 90 | }, 91 | { 92 | title: "HTML", 93 | imageUrl: 94 | "https://img.icons8.com/?size=100&id=v8RpPQUwv0N8&format=png&color=000000", 95 | }, 96 | { 97 | title: "Tailwind CSS", 98 | imageUrl: 99 | "https://img.icons8.com/?size=100&id=4PiNHtUJVbLs&format=png&color=000000", 100 | }, 101 | 102 | { 103 | title: "ChakraUI", 104 | imageUrl: 105 | "https://img.icons8.com/?size=100&id=r9QJ0VFFrn7T&format=png&color=000000", 106 | }, 107 | { 108 | title: "Bootstrap", 109 | imageUrl: 110 | "https://img.icons8.com/?size=100&id=EzPCiQUqWWEa&format=png&color=000000", 111 | }, 112 | { 113 | title: "ReactJS", 114 | imageUrl: 115 | "https://img.icons8.com/?size=100&id=asWSSTBrDlTW&format=png&color=000000", 116 | }, 117 | { 118 | title: "NodeJS", 119 | imageUrl: 120 | "https://img.icons8.com/?size=100&id=54087&format=png&color=000000", 121 | }, 122 | { 123 | title: "ExpressJS", 124 | imageUrl: 125 | "https://img.icons8.com/?size=100&id=kg46nzoJrmTR&format=png&color=ffffff", 126 | }, 127 | { 128 | title: "NestJS", 129 | imageUrl: 130 | "https://img.icons8.com/?size=100&id=9ESZMOeUioJS&format=png&color=000000", 131 | }, 132 | { 133 | title: "Laravel", 134 | imageUrl: "https://laravel.com/img/logomark.min.svg", 135 | }, 136 | { 137 | title: "MySQL", 138 | imageUrl: 139 | "https://img.icons8.com/?size=100&id=UFXRpPFebwa2&format=png&color=000000", 140 | }, 141 | { 142 | title: "Mongo DB", 143 | imageUrl: 144 | "https://img.icons8.com/?size=100&id=bosfpvRzNOG8&format=png&color=000000", 145 | }, 146 | ]; 147 | -------------------------------------------------------------------------------- /src/components/Start/index.tsx: -------------------------------------------------------------------------------- 1 | import useOutsideClick from "@/hooks/useOutsideClick"; 2 | import StartItem from "./StartItem"; 3 | import { languages, startItems } from "./data"; 4 | import ContextItem from "../ContextMenu/ContextItem"; 5 | import { useState } from "react"; 6 | import { useTab } from "@/Provider/TabProvider"; 7 | 8 | function Start({ 9 | onClose, 10 | blackList, 11 | }: { 12 | onClose: () => void; 13 | blackList: HTMLDivElement | null; 14 | }) { 15 | const { handler } = useOutsideClick({ 16 | action: onClose, 17 | blackList: [blackList], 18 | }); 19 | const [showPowerOptions, setShowPowerOptions] = useState(false); 20 | const { toggleAsleep } = useTab(); 21 | 22 | function handleShutDown() { 23 | window.close(); 24 | } 25 | 26 | function handleRestart() { 27 | location.reload(); 28 | } 29 | 30 | return ( 31 |
35 |
36 |

Skills

37 |
38 | {languages.map((item) => ( 39 | 44 | ))} 45 |
46 |

Recently used tools

47 |
48 | {startItems.map((item) => ( 49 | 54 | ))} 55 |
56 |
57 |
58 |
{ 60 | window.open("https://t.me/sukkoth", "_blank"); 61 | }} 62 | className='relative flex items-center gap-2 text-sm cursor-pointer before:content-[""] before:-inset-2 before:absolute before:bg-stone-800/60 before:hidden before:hover:block before:-z-50 z-10 before:rounded-lg' 63 | > 64 |
65 | 66 |
67 |

Gadisa Teklu

68 |
69 |
showPowerOptions && setShowPowerOptions(false)} 72 | > 73 | setShowPowerOptions((prev) => !prev)} 75 | src='https://img.icons8.com/?size=100&id=85077&format=png&color=ffffff' 76 | className='size-5 cursor-pointer' 77 | alt='' 78 | /> 79 | {showPowerOptions && ( 80 |
81 | toggleAsleep(true)} 84 | > 85 | Lock Screen 86 | 87 | 91 | Shutdown 92 | 93 | 97 | Restart 98 | 99 |
100 | )} 101 |
102 |
103 |
104 | ); 105 | } 106 | 107 | export default Start; 108 | -------------------------------------------------------------------------------- /src/components/Taskbar/Battery.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | function Battery() { 4 | const [charging, setCharging] = useState(false); 5 | const [batteryLevel, setBatteryLevel] = useState(100); 6 | 7 | useEffect(() => { 8 | // @ts-expect-error //this is because most browsers do not support this 9 | navigator.getBattery().then((battery) => { 10 | function updateAllBatteryInfo() { 11 | updateChargeInfo(); 12 | updateLevelInfo(); 13 | } 14 | updateAllBatteryInfo(); 15 | 16 | battery.addEventListener("chargingchange", () => { 17 | updateChargeInfo(); 18 | }); 19 | function updateChargeInfo() { 20 | setCharging(battery.charging); 21 | } 22 | 23 | battery.addEventListener("levelchange", () => { 24 | updateLevelInfo(); 25 | }); 26 | function updateLevelInfo() { 27 | setBatteryLevel(battery.level * 100); 28 | } 29 | }); 30 | }, []); 31 | 32 | return ( 33 | <> 34 |
37 | {charging && ( 38 | 39 | ⚡ 40 | 41 | )} 42 |
48 |
49 | 50 | ); 51 | } 52 | 53 | export default Battery; 54 | -------------------------------------------------------------------------------- /src/components/Taskbar/ItemsList.tsx: -------------------------------------------------------------------------------- 1 | import TaskbarItem from "./TaskbarItem"; 2 | import { useCallback, useRef, useState } from "react"; 3 | import Start from "../Start"; 4 | import { useLocation } from "react-router-dom"; 5 | 6 | function ItemsList() { 7 | const [opened, setOpened] = useState(false); 8 | const startRef = useRef(null); 9 | const { pathname } = useLocation(); 10 | 11 | const toggleStart = useCallback(() => { 12 | setOpened(false); 13 | }, []); 14 | 15 | return ( 16 |
17 | {/* show start component */} 18 | {opened && } 19 | 20 |
setOpened((prev) => !prev)} 22 | ref={startRef} 23 | className='task-item' 24 | > 25 | 29 |
30 | 34 | 38 | 42 | 46 | {pathname.includes("app/notepad") && ( 47 | 51 | )} 52 |
53 | ); 54 | } 55 | 56 | export default ItemsList; 57 | -------------------------------------------------------------------------------- /src/components/Taskbar/Network.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | function Network() { 4 | const [onlineStatus, setOnlineStatus] = useState(navigator.onLine); 5 | 6 | useEffect(() => { 7 | const onlineCheck = () => { 8 | console.log("offline"); 9 | setOnlineStatus(false); 10 | }; 11 | 12 | const offlineCheck = () => { 13 | console.log("online"); 14 | setOnlineStatus(true); 15 | }; 16 | 17 | window.addEventListener("offline", offlineCheck); 18 | 19 | window.addEventListener("online", onlineCheck); 20 | 21 | return () => { 22 | window.removeEventListener("offline", offlineCheck); 23 | 24 | window.removeEventListener("online", onlineCheck); 25 | }; 26 | }, []); 27 | 28 | return ( 29 | <> 30 |
38 | {/*
43 |
48 |
53 |
58 |
*/} 63 | 68 |
69 | 70 | ); 71 | } 72 | 73 | export default Network; 74 | -------------------------------------------------------------------------------- /src/components/Taskbar/StatusBar.tsx: -------------------------------------------------------------------------------- 1 | import Battery from "./Battery"; 2 | import Network from "./Network"; 3 | import Time from "./Time"; 4 | 5 | function StatusBar() { 6 | return ( 7 |
8 |
9 | 10 | 11 |
12 |
14 | ); 15 | } 16 | 17 | export default StatusBar; 18 | -------------------------------------------------------------------------------- /src/components/Taskbar/Taskbar.tsx: -------------------------------------------------------------------------------- 1 | import ItemsList from "./ItemsList"; 2 | import StatusBar from "./StatusBar"; 3 | 4 | function TaskBar() { 5 | return ( 6 |
7 |
8 | 9 | 10 |
11 | ); 12 | } 13 | 14 | export default TaskBar; 15 | -------------------------------------------------------------------------------- /src/components/Taskbar/TaskbarItem.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useLocation } from "react-router-dom"; 2 | 3 | type Props = { imgUrl: string; to: string }; 4 | 5 | function TaskbarItem({ imgUrl, to }: Props) { 6 | const { pathname } = useLocation(); 7 | 8 | //if pathname is the same, => close the app 9 | //else, => open the app 10 | //it's like a toggle button 11 | const redirectProps = { 12 | to: pathname === to ? "/" : to, 13 | replace: pathname === to, 14 | }; 15 | 16 | return ( 17 | 21 | img 22 | 23 | ); 24 | } 25 | 26 | export default TaskbarItem; 27 | -------------------------------------------------------------------------------- /src/components/Taskbar/Time.tsx: -------------------------------------------------------------------------------- 1 | import { formatTime, getFormattedDate } from "@/utils/date"; 2 | import { useCallback, useEffect, useState } from "react"; 3 | 4 | function Time() { 5 | const [time, setTime] = useState(""); 6 | 7 | const updateTime = useCallback(() => { 8 | const formattedTime = formatTime(); 9 | setTime(formattedTime); 10 | }, []); 11 | 12 | useEffect(() => { 13 | updateTime(); 14 | const interval = setInterval(updateTime, 60000); 15 | 16 | return () => clearInterval(interval); 17 | }, [updateTime]); 18 | 19 | return ( 20 |
21 |
22 |

{time}

23 |

{getFormattedDate()}

24 |
25 |
26 | ); 27 | } 28 | 29 | export default Time; 30 | -------------------------------------------------------------------------------- /src/data/notes.ts: -------------------------------------------------------------------------------- 1 | export const notes = [ 2 | { 3 | id: 2, 4 | content: ` 5 |
6 |

Project Title: Task Mate

7 |

Project Description: Task Mate is a task management application designed to help users organize and track their tasks efficiently.

8 |

Status: Completed

9 |

Type: Web Application

10 |

Platform: Web

11 |

Created At: 5/1/2023, 1:15:30 PM

12 |

Updated At: 6/1/2023, 2:00:00 PM

13 |

Technologies Used: HTML, CSS, JavaScript, React, Node.js, MongoDB

14 |

Features:

15 |
    16 |
  • Task creation and management
  • 17 |
  • Due date reminders
  • 18 |
  • Priority setting
  • 19 |
  • Collaboration with team members
  • 20 |
  • Progress tracking
  • 21 |
22 |

Screenshots or Demos:

23 | 28 |

Code Repository: GitHub Repository

29 |

Documentation: Project Documentation

30 |

Contributors:

31 |
    32 |
  • John Doe
  • 33 |
  • Jane Smith
  • 34 |
35 |

License: MIT License

36 |

Challenges:

37 |
    38 |
  • Implementing real-time collaboration features
  • 39 |
  • Ensuring cross-browser compatibility
  • 40 |
41 |

Future Plans:

42 |
    43 |
  • Mobile application development
  • 44 |
  • Integration with other productivity tools
  • 45 |
46 |
47 | 48 | `, 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /src/hooks/useOutsideClick.ts: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect } from "react"; 2 | 3 | type ListItem = HTMLDivElement | null; 4 | 5 | type Props = { 6 | action: () => void; 7 | blackList: ListItem[]; 8 | }; 9 | 10 | /** 11 | * useOutsideClick 12 | * 13 | * This custom hook detects clicks outside of a specified DOM element and triggers an action. It also allows 14 | * certain elements, specified in a blacklist, to be ignored as triggers for the outside click detection. This is 15 | * particularly useful for implementing dropdowns, modals, or any component that needs to close or perform an action 16 | * when a user clicks outside of it. 17 | * 18 | * Props: 19 | * - action: A callback function to be executed when a click is detected outside the specified element and any 20 | * elements in the blacklist. 21 | * - blackList: An array of HTMLDivElement references that should be ignored when detecting outside clicks. This 22 | * prevents the action from being triggered if the click happens within any of these elements. 23 | * 24 | * Returns: 25 | * - An object containing a `handler` ref, which should be attached to the element you want to detect outside clicks for. 26 | * 27 | * Usage: 28 | * const { handler } = useOutsideClick({ action: () => console.log('Clicked outside'), blackList: [triggerRef] }); 29 | *
Content
30 | */ 31 | 32 | export default function useOutsideClick({ action, blackList }: Props) { 33 | const clickRef = React.useRef(null); 34 | 35 | const listener = useCallback( 36 | (e: MouseEvent) => { 37 | if (!clickRef.current) return; 38 | 39 | //if the node is in blacklist, ignore excuting the function 40 | if (blackList.some((element) => element?.contains(e.target as Node))) { 41 | return; 42 | } 43 | 44 | //this executes only outside the div and it's tiggering buttons (which ever div is passed in blacklist array) 45 | if (!clickRef.current.contains(e.target as Node)) { 46 | action(); 47 | } 48 | }, 49 | [action, blackList] 50 | ); 51 | 52 | useEffect(() => { 53 | document.addEventListener("click", listener, true); 54 | return () => { 55 | document.removeEventListener("click", listener, true); 56 | }; 57 | }, [listener]); 58 | 59 | return { handler: clickRef }; 60 | } 61 | -------------------------------------------------------------------------------- /src/hooks/useTabStorage.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | function useTabStorage() { 4 | const [tabId, setTabId] = useState(null); 5 | 6 | useEffect(() => { 7 | const getTabStorage = () => { 8 | const active = JSON.parse(localStorage.active || ""); 9 | setTabId(active); 10 | }; 11 | 12 | window.addEventListener("storage", getTabStorage); 13 | 14 | return () => { 15 | window.removeEventListener("storage", getTabStorage); 16 | }; 17 | }, []); 18 | 19 | return { tabId, setTabId }; 20 | } 21 | 22 | export default useTabStorage; 23 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.cdnfonts.com/css/anurati"); 2 | @import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"); 3 | 4 | @import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"); 5 | 6 | @tailwind base; 7 | @tailwind components; 8 | @tailwind utilities; 9 | 10 | body { 11 | background-image: url("/assets/homepage.jpg"); 12 | background-repeat: no-repeat; 13 | background-attachment: fixed; 14 | background-size: cover; 15 | 16 | @apply font-dmSans bg-stone-800; 17 | } 18 | 19 | /* WebKit browsers (Chrome, Safari) */ 20 | ::-webkit-scrollbar { 21 | width: 8px; 22 | } 23 | 24 | ::-webkit-scrollbar-track { 25 | background: #f1f1f1; 26 | } 27 | 28 | ::-webkit-scrollbar-thumb { 29 | background: #888; 30 | border-radius: 8px; 31 | } 32 | 33 | ::-webkit-scrollbar-thumb:hover { 34 | background: #555; 35 | } 36 | 37 | /* Firefox */ 38 | html { 39 | scrollbar-width: thin; /* "auto" or "thin" */ 40 | scrollbar-color: #890f62 #03071f; /* scroll thumb and track */ 41 | } 42 | 43 | .debug { 44 | @apply border border-red-500; 45 | } 46 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | import TabProvider from "./Provider/TabProvider.tsx"; 6 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 7 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 8 | import { BrowserRouter } from "react-router-dom"; 9 | 10 | const queryClient = new QueryClient({ 11 | defaultOptions: { 12 | queries: { 13 | retry: 3, 14 | retryDelay: 1000, 15 | }, 16 | }, 17 | }); 18 | 19 | ReactDOM.createRoot(document.getElementById("root")!).render( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | -------------------------------------------------------------------------------- /src/react-query/queries/index.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import * as GITHUB_SERVICE from "@/services/github/github"; 3 | import * as WEATHER_SERVICE from "@/services/weather"; 4 | import { Position } from "@/services/weather/types"; 5 | 6 | export function useGetRepos() { 7 | return useQuery({ 8 | queryKey: ["repos"], 9 | queryFn: GITHUB_SERVICE.GET_REPOS, 10 | staleTime: 4_000_000, // for 1 hour, there won't be much data to be changed 11 | }); 12 | } 13 | 14 | export function useGetWeatherData(position: Position | null) { 15 | return useQuery({ 16 | queryKey: ["weather", position], 17 | queryFn: () => WEATHER_SERVICE.GET_WEATHER(position), 18 | staleTime: 4_000_000, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/services/github/github.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from "./types"; 2 | 3 | export async function GET_REPOS(): Promise { 4 | const token = import.meta.env.VITE_GITHUB; 5 | if (!token) { 6 | throw "No token provided"; 7 | } 8 | const response = await fetch("https://api.github.com/users/sukkoth/repos", { 9 | headers: { 10 | Authorization: `token ${token}`, 11 | }, 12 | }); 13 | const data = (await response.json()) as Repository[]; 14 | 15 | return data; 16 | } 17 | -------------------------------------------------------------------------------- /src/services/github/types.ts: -------------------------------------------------------------------------------- 1 | export interface Owner { 2 | login: string; 3 | id: number; 4 | node_id: string; 5 | avatar_url: string; 6 | gravatar_id: string; 7 | url: string; 8 | html_url: string; 9 | followers_url: string; 10 | following_url: string; 11 | gists_url: string; 12 | starred_url: string; 13 | subscriptions_url: string; 14 | organizations_url: string; 15 | repos_url: string; 16 | events_url: string; 17 | received_events_url: string; 18 | type: string; 19 | site_admin: boolean; 20 | } 21 | 22 | export interface License { 23 | key: string; 24 | name: string; 25 | spdx_id: string; 26 | url: string | null; 27 | node_id: string; 28 | } 29 | 30 | export interface Repository { 31 | id: number; 32 | node_id: string; 33 | name: string; 34 | full_name: string; 35 | private: boolean; 36 | owner: Owner; 37 | html_url: string; 38 | description: string | null; 39 | fork: boolean; 40 | url: string; 41 | forks_url: string; 42 | keys_url: string; 43 | collaborators_url: string; 44 | teams_url: string; 45 | hooks_url: string; 46 | issue_events_url: string; 47 | events_url: string; 48 | assignees_url: string; 49 | branches_url: string; 50 | tags_url: string; 51 | blobs_url: string; 52 | git_tags_url: string; 53 | git_refs_url: string; 54 | trees_url: string; 55 | statuses_url: string; 56 | languages_url: string; 57 | stargazers_url: string; 58 | contributors_url: string; 59 | subscribers_url: string; 60 | subscription_url: string; 61 | commits_url: string; 62 | git_commits_url: string; 63 | comments_url: string; 64 | issue_comment_url: string; 65 | contents_url: string; 66 | compare_url: string; 67 | merges_url: string; 68 | archive_url: string; 69 | downloads_url: string; 70 | issues_url: string; 71 | pulls_url: string; 72 | milestones_url: string; 73 | notifications_url: string; 74 | labels_url: string; 75 | releases_url: string; 76 | deployments_url: string; 77 | created_at: string; 78 | updated_at: string; 79 | pushed_at: string; 80 | git_url: string; 81 | ssh_url: string; 82 | clone_url: string; 83 | svn_url: string; 84 | homepage: string | null; 85 | size: number; 86 | stargazers_count: number; 87 | watchers_count: number; 88 | language: string | null; 89 | has_issues: boolean; 90 | has_projects: boolean; 91 | has_downloads: boolean; 92 | has_wiki: boolean; 93 | has_pages: boolean; 94 | has_discussions: boolean; 95 | forks_count: number; 96 | mirror_url: string | null; 97 | archived: boolean; 98 | disabled: boolean; 99 | open_issues_count: number; 100 | license: License | null; 101 | allow_forking: boolean; 102 | is_template: boolean; 103 | web_commit_signoff_required: boolean; 104 | topics: string[]; 105 | visibility: string; 106 | forks: number; 107 | open_issues: number; 108 | watchers: number; 109 | default_branch: string; 110 | } 111 | -------------------------------------------------------------------------------- /src/services/weather/index.ts: -------------------------------------------------------------------------------- 1 | import { Position, Weather } from "./types"; 2 | 3 | export async function GET_WEATHER(position: Position | null) { 4 | if (position === null) throw "Location required"; 5 | 6 | const { latitude, longitude } = position; 7 | 8 | const res = await fetch( 9 | `https://api.weatherapi.com/v1/forecast.json?key=8fcd7cd501fb4f2eb1570427240206&days=7&aqi=no&alerts=no&q=${latitude},${longitude}` 10 | ); 11 | 12 | const data = (await res.json()) as Weather; 13 | 14 | return data; 15 | } 16 | -------------------------------------------------------------------------------- /src/services/weather/types.ts: -------------------------------------------------------------------------------- 1 | export type Position = { 2 | latitude: string | number; 3 | longitude: string | number; 4 | }; 5 | 6 | export interface Weather { 7 | location: Location; 8 | current: Current; 9 | forecast: Forecast; 10 | } 11 | 12 | export interface Location { 13 | name: string; 14 | region: string; 15 | country: string; 16 | lat: number; 17 | lon: number; 18 | tz_id: string; 19 | localtime_epoch: number; 20 | localtime: string; 21 | } 22 | 23 | export interface Current { 24 | last_updated_epoch: number; 25 | last_updated: string; 26 | temp_c: number; 27 | temp_f: number; 28 | is_day: number; 29 | condition: Condition; 30 | wind_mph: number; 31 | wind_kph: number; 32 | wind_degree: number; 33 | wind_dir: string; 34 | pressure_mb: number; 35 | pressure_in: number; 36 | precip_mm: number; 37 | precip_in: number; 38 | humidity: number; 39 | cloud: number; 40 | feelslike_c: number; 41 | feelslike_f: number; 42 | windchill_c: number; 43 | windchill_f: number; 44 | heatindex_c: number; 45 | heatindex_f: number; 46 | dewpoint_c: number; 47 | dewpoint_f: number; 48 | vis_km: number; 49 | vis_miles: number; 50 | uv: number; 51 | gust_mph: number; 52 | gust_kph: number; 53 | } 54 | 55 | export interface Condition { 56 | text: string; 57 | icon: string; 58 | code: number; 59 | } 60 | 61 | export interface Forecast { 62 | forecastday: Forecastday[]; 63 | } 64 | 65 | export interface Forecastday { 66 | date: string; 67 | date_epoch: number; 68 | day: Day; 69 | astro: Astro; 70 | hour: Hour[]; 71 | } 72 | 73 | export interface Day { 74 | maxtemp_c: number; 75 | maxtemp_f: number; 76 | mintemp_c: number; 77 | mintemp_f: number; 78 | avgtemp_c: number; 79 | avgtemp_f: number; 80 | maxwind_mph: number; 81 | maxwind_kph: number; 82 | totalprecip_mm: number; 83 | totalprecip_in: number; 84 | totalsnow_cm: number; 85 | avgvis_km: number; 86 | avgvis_miles: number; 87 | avghumidity: number; 88 | daily_will_it_rain: number; 89 | daily_chance_of_rain: number; 90 | daily_will_it_snow: number; 91 | daily_chance_of_snow: number; 92 | condition: Condition2; 93 | uv: number; 94 | } 95 | 96 | export interface Condition2 { 97 | text: string; 98 | icon: string; 99 | code: number; 100 | } 101 | 102 | export interface Astro { 103 | sunrise: string; 104 | sunset: string; 105 | moonrise: string; 106 | moonset: string; 107 | moon_phase: string; 108 | moon_illumination: number; 109 | is_moon_up: number; 110 | is_sun_up: number; 111 | } 112 | 113 | export interface Hour { 114 | time_epoch: number; 115 | time: string; 116 | temp_c: number; 117 | temp_f: number; 118 | is_day: number; 119 | condition: Condition3; 120 | wind_mph: number; 121 | wind_kph: number; 122 | wind_degree: number; 123 | wind_dir: string; 124 | pressure_mb: number; 125 | pressure_in: number; 126 | precip_mm: number; 127 | precip_in: number; 128 | snow_cm: number; 129 | humidity: number; 130 | cloud: number; 131 | feelslike_c: number; 132 | feelslike_f: number; 133 | windchill_c: number; 134 | windchill_f: number; 135 | heatindex_c: number; 136 | heatindex_f: number; 137 | dewpoint_c: number; 138 | dewpoint_f: number; 139 | will_it_rain: number; 140 | chance_of_rain: number; 141 | will_it_snow: number; 142 | chance_of_snow: number; 143 | vis_km: number; 144 | vis_miles: number; 145 | gust_mph: number; 146 | gust_kph: number; 147 | uv: number; 148 | } 149 | 150 | export interface Condition3 { 151 | text: string; 152 | icon: string; 153 | code: number; 154 | } 155 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | export function getFormattedDate(date: Date = new Date()) { 2 | const month = date.getMonth() + 1; // Months are zero-based, so we need to add 1 3 | const day = date.getDate(); 4 | const year = date.getFullYear(); 5 | 6 | return `${month}/${day}/${year}`; 7 | } 8 | 9 | export function formatTime() { 10 | const date = new Date(); 11 | const hours = date.getHours().toString().padStart(2, "0"); 12 | const minutes = date.getMinutes().toString().padStart(2, "0"); 13 | 14 | return `${hours}:${minutes}`; 15 | } 16 | 17 | export function getDayName(index: number, simple?: boolean) { 18 | const dates = [ 19 | "Sunday", 20 | "Monday", 21 | "Tuesday", 22 | "Wednesday", 23 | "Thursday", 24 | "Friday", 25 | "Saturday", 26 | ]; 27 | if (index >= dates.length) return "N/A"; 28 | return simple ? dates[index].slice(0, 3) : dates[index]; 29 | } 30 | 31 | export function getGreeting() { 32 | const now = new Date(); 33 | const hours = now.getHours(); 34 | let greeting; 35 | 36 | if (hours < 12) { 37 | greeting = "Good Morning"; 38 | } else if (hours < 18) { 39 | greeting = "Good Afternoon"; 40 | } else if (hours < 21) { 41 | greeting = "Good Evening"; 42 | } else { 43 | greeting = "Good Night"; 44 | } 45 | 46 | return greeting; 47 | } 48 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: { 6 | screens: { 7 | "sm-only": { max: "639px" }, 8 | xs: { 9 | min: "420px", 10 | }, 11 | }, 12 | gridTemplateColumns: { 13 | folder: "3fr 1fr repeat(3, 1fr)", 14 | }, 15 | colors: { 16 | "hover-color": "#303032", 17 | }, 18 | fontFamily: { 19 | anurati: ["Anurati", "sans-serif"], 20 | "open-sans": ["Open Sans", "sans-serif"], 21 | dmSans: ["DM Sans", "sans-serif"], 22 | }, 23 | }, 24 | }, 25 | plugins: [ 26 | // eslint-disable-next-line no-undef 27 | require("tailwind-scrollbar-hide"), 28 | function ({ addUtilities }) { 29 | const newUtilities = { 30 | ".custom-full-width": {}, 31 | ".hover-effect": { 32 | content: '""', 33 | position: "absolute", 34 | top: "-4px", 35 | right: "-4px", 36 | bottom: "-4px", 37 | left: "-4px", 38 | zIndex: "-10", 39 | borderRadius: "0.5rem", 40 | backgroundColor: "#303032", 41 | }, 42 | }; 43 | addUtilities(newUtilities, ["hover"]); 44 | }, 45 | ], 46 | }; 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "baseUrl": ".", 23 | "paths": { 24 | "@/*": ["./src/*"] 25 | } 26 | }, 27 | "include": ["src"], 28 | "references": [{ "path": "./tsconfig.node.json" }] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import path from "path"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | }); 14 | --------------------------------------------------------------------------------