├── .husky ├── pre-commit └── pre-push ├── src ├── index.css ├── vite-env.d.ts ├── libs │ ├── utils.ts │ └── date.ts ├── main.tsx ├── contexts │ └── themeContext.ts ├── constants │ └── index.ts ├── helpers │ └── index.ts ├── components │ ├── TechStack.tsx │ ├── Theme.tsx │ ├── Contact.tsx │ ├── Footer.tsx │ ├── Experience.tsx │ ├── SkeletonProject.tsx │ ├── Project.tsx │ ├── Resume.tsx │ ├── ChangeTheme.tsx │ └── TryProjects.tsx ├── hooks │ ├── index.ts │ └── utils.tsx ├── config │ ├── theme.ts │ └── index.tsx ├── types │ └── index.ts └── App.tsx ├── .eslintignore ├── public ├── logo192.png ├── logo512.png ├── pdf │ └── onesine.pdf └── images │ ├── 644888.png │ ├── MyDunya.png │ ├── atompay-app.png │ ├── git-profile.png │ ├── inventor-app.png │ ├── cryptocowrie-app.png │ ├── satis-backoffice.png │ ├── react-headless-accordion.png │ ├── react-tailwindcss-select.png │ └── react-tailwindcss-datepicker.png ├── .prettierignore ├── .idea ├── .gitignore ├── vcs.xml ├── markdown.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml └── git-profile.iml ├── tsconfig.json ├── assets └── img │ ├── dark_Screen_Shot_2022-08-04_at_17.04.09.png │ └── light_Screen_Shot_2022-08-04_at_17.04.09.png ├── .prettierrc ├── vite.config.ts ├── .gitignore ├── tsconfig.node.json ├── tsconfig.app.json ├── .eslintrc.cjs ├── LICENSE ├── index.html ├── package.json └── README.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | # yarn test 2 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Folder 2 | node_modules 3 | dist 4 | .husky 5 | 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/pdf/onesine.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/pdf/onesine.pdf -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | assets/ 3 | build/ 4 | public/ 5 | .next/ 6 | 7 | # Files 8 | README.md 9 | -------------------------------------------------------------------------------- /public/images/644888.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/644888.png -------------------------------------------------------------------------------- /public/images/MyDunya.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/MyDunya.png -------------------------------------------------------------------------------- /public/images/atompay-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/atompay-app.png -------------------------------------------------------------------------------- /public/images/git-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/git-profile.png -------------------------------------------------------------------------------- /public/images/inventor-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/inventor-app.png -------------------------------------------------------------------------------- /public/images/cryptocowrie-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/cryptocowrie-app.png -------------------------------------------------------------------------------- /public/images/satis-backoffice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/satis-backoffice.png -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /public/images/react-headless-accordion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/react-headless-accordion.png -------------------------------------------------------------------------------- /public/images/react-tailwindcss-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/react-tailwindcss-select.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /public/images/react-tailwindcss-datepicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/public/images/react-tailwindcss-datepicker.png -------------------------------------------------------------------------------- /assets/img/dark_Screen_Shot_2022-08-04_at_17.04.09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/assets/img/dark_Screen_Shot_2022-08-04_at_17.04.09.png -------------------------------------------------------------------------------- /assets/img/light_Screen_Shot_2022-08-04_at_17.04.09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/git-portfolio/HEAD/assets/img/light_Screen_Shot_2022-08-04_at_17.04.09.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/libs/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/libs/date.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import relativeTime from "dayjs/plugin/relativeTime"; 3 | 4 | dayjs.extend(relativeTime); 5 | 6 | export function dateTimeFormNow(date: Date) { 7 | return dayjs(date).fromNow(); 8 | } 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 4, 4 | "printWidth": 100, 5 | "singleQuote": false, 6 | "trailingComma": "none", 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "bracketSpacing": true, 10 | "arrowParens": "avoid", 11 | "proseWrap": "always" 12 | } 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import tailwindcss from "@tailwindcss/vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import { defineConfig } from "vite"; 4 | import tsconfigPaths from "vite-tsconfig-paths"; 5 | 6 | // https://vite.dev/config/ 7 | export default defineConfig({ 8 | plugins: [react(), tailwindcss(), tsconfigPaths()] 9 | }); 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import "./index.css"; 5 | import App from "./App.tsx"; 6 | import Theme from "./components/Theme"; 7 | 8 | createRoot(document.getElementById("root")!).render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/contexts/themeContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | import { THEMES } from "@/constants"; 4 | 5 | type ThemeContextType = { 6 | theme: "dark" | "light"; 7 | themes: typeof THEMES; 8 | changeTheme: (choice: "dark" | "light") => void; 9 | }; 10 | 11 | export const ThemeContext = createContext({ 12 | theme: "light", 13 | themes: THEMES, 14 | changeTheme: () => {} 15 | }); 16 | -------------------------------------------------------------------------------- /.idea/git-profile.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const LANGUAGE_COLORS = { 2 | JavaScript: "#f1e05a", 3 | TypeScript: "#3178c6", 4 | HTML: "#e34c26", 5 | Rust: "#dea584", 6 | Python: "#3572A5", 7 | CSS: "#563d7c", 8 | SCSS: "#c6538c", 9 | Vue: "#41b883", 10 | C: "#555555", 11 | Java: "#b07219", 12 | PHP: "#4F5D95" 13 | }; 14 | 15 | export const THEMES: Array<"light" | "dark"> = ["light", "dark"]; 16 | 17 | export const LIGHT = THEMES[1] as "light"; 18 | export const DARK = THEMES[2] as "dark"; 19 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { DARK, LANGUAGE_COLORS, LIGHT, THEMES } from "@/constants"; 2 | import { ThemeType } from "@/types"; 3 | 4 | type LanguageType = keyof typeof LANGUAGE_COLORS; 5 | 6 | export function getLanguageColors(language: LanguageType | string) { 7 | if (language in LANGUAGE_COLORS) { 8 | return LANGUAGE_COLORS[language as LanguageType]; 9 | } 10 | 11 | return LANGUAGE_COLORS["CSS"]; 12 | } 13 | 14 | export function getDefaultTheme() { 15 | const defaultTheme = localStorage.getItem("git-portfolio-theme"); 16 | 17 | if (defaultTheme && THEMES.includes(defaultTheme as ThemeType)) { 18 | return defaultTheme as ThemeType; 19 | } 20 | 21 | const hour = new Date().getHours(); 22 | 23 | if (hour > 5 && hour < 18) { 24 | return LIGHT; 25 | } 26 | return DARK; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/TechStack.tsx: -------------------------------------------------------------------------------- 1 | import { CardContainer, CardTitle } from "../hooks/utils.tsx"; 2 | 3 | import configs from "@/config"; 4 | 5 | const TechStack = () => { 6 | const { profile } = configs; 7 | return ( 8 | 9 | Tech Stack 10 |
11 | {profile.techStack.map((item, index) => ( 12 | 16 | {item} 17 | 18 | ))} 19 |
20 |
21 | ); 22 | }; 23 | 24 | export default TechStack; 25 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true, 24 | 25 | /* Custom config */ 26 | "paths": { 27 | "@/*": ["./src/*"] 28 | } 29 | }, 30 | "include": ["src"] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Theme.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useLayoutEffect, useMemo, useState } from "react"; 2 | 3 | import themeConfig from "@/config/theme.ts"; 4 | import { THEMES } from "@/constants"; 5 | import { ThemeContext } from "@/contexts/themeContext"; 6 | import { getDefaultTheme } from "@/helpers"; 7 | import { ThemeType } from "@/types"; 8 | 9 | const Theme = ({ children }: { children: ReactNode }) => { 10 | const [theme, setTheme] = useState(getDefaultTheme()); 11 | 12 | useLayoutEffect(() => { 13 | document.body.className = themeConfig[theme]["body"]; 14 | }, [theme]); 15 | 16 | const value = useMemo(() => { 17 | return { 18 | theme, 19 | themes: THEMES, 20 | changeTheme: (choice: ThemeType) => { 21 | localStorage.setItem("git-portfolio-theme", choice); 22 | setTheme(choice); 23 | } 24 | }; 25 | }, [theme]); 26 | 27 | return {children}; 28 | }; 29 | 30 | export default Theme; 31 | -------------------------------------------------------------------------------- /src/components/Contact.tsx: -------------------------------------------------------------------------------- 1 | import configs from "@/config"; 2 | import { CardContainer } from "@/hooks/utils.tsx"; 3 | 4 | const Contact = () => { 5 | const { profile } = configs; 6 | 7 | return ( 8 | 9 | {profile.contact.map((item, index) => ( 10 | 17 |
18 | {item.icon} 19 | {item?.data?.text}: 20 |
21 | 22 |

{item?.data?.value}

23 |
24 | ))} 25 |
26 | ); 27 | }; 28 | 29 | export default Contact; 30 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useLayoutEffect, useState } from "react"; 2 | 3 | import themeConfig from "@/config/theme.ts"; 4 | import { ThemeContext } from "@/contexts/themeContext.ts"; 5 | 6 | const Footer = () => { 7 | const { theme } = useContext(ThemeContext); 8 | const [themeSwitcher, setThemeSwitcher] = useState({ 9 | link: "" 10 | }); 11 | 12 | useLayoutEffect(() => { 13 | setThemeSwitcher({ 14 | link: themeConfig[theme].footer["link"] 15 | }); 16 | }, [theme]); 17 | 18 | return ( 19 |
20 | 26 | Inspired by GitProfile and build with ❤️ 27 | 28 |
29 | ); 30 | }; 31 | 32 | export default Footer; 33 | -------------------------------------------------------------------------------- /.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 | "prettier", 9 | "plugin:prettier/recommended" 10 | ], 11 | ignorePatterns: ["dist", ".eslintrc.cjs"], 12 | parser: "@typescript-eslint/parser", 13 | plugins: ["react-refresh", "prettier", "import"], 14 | rules: { 15 | semi: "error", 16 | "import/order": [ 17 | "error", 18 | { 19 | alphabetize: { 20 | order: "asc", 21 | caseInsensitive: true 22 | }, 23 | "newlines-between": "always" 24 | } 25 | ], 26 | "no-console": "warn", 27 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 28 | "prettier/prettier": [ 29 | "error", 30 | { 31 | singleQuote: false 32 | } 33 | ] 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Lewhe Onesine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Lewhe Onesine 9 | 10 | 11 |
12 | 13 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | 4 | export function useFetchData(endpoint: string) { 5 | /* eslint-disable */ 6 | const [data, setData] = useState(null); 7 | /* eslint-enable */ 8 | 9 | const [loading, setLoading] = useState(false); 10 | 11 | useEffect(() => { 12 | setLoading(true); 13 | axios 14 | .get(endpoint) 15 | .then(({ data }) => setData(data)) 16 | .catch(() => { 17 | /* eslint-disable */ 18 | console.error("Something is wrong"); 19 | /* eslint-enable */ 20 | }) 21 | .finally(() => { 22 | setLoading(false); 23 | }); 24 | }, [endpoint]); 25 | 26 | return [data, loading]; 27 | } 28 | 29 | export function useOnClickOutside( 30 | element: HTMLDivElement | null, 31 | handler: (event?: MouseEvent | TouchEvent) => void 32 | ) { 33 | useEffect(() => { 34 | const listener = (event: MouseEvent | TouchEvent) => { 35 | /* eslint-disable */ 36 | // @ts-ignore 37 | if (!element || element.contains(event.target)) { 38 | /* eslint-enable */ 39 | return; 40 | } 41 | 42 | handler(event); 43 | }; 44 | document.addEventListener("mousedown", listener); 45 | document.addEventListener("touchstart", listener); 46 | 47 | return () => { 48 | document.removeEventListener("mousedown", listener); 49 | document.removeEventListener("touchstart", listener); 50 | }; 51 | }, [element, handler]); 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-profile", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "preview": "vite preview", 10 | "lint": "eslint .", 11 | "lint:fix": "eslint --fix .", 12 | "format": "prettier --write .", 13 | "prepare": "husky" 14 | }, 15 | "dependencies": { 16 | "@tailwindcss/vite": "^4.0.3", 17 | "axios": "^1.7.9", 18 | "clsx": "^2.1.1", 19 | "dayjs": "^1.11.13", 20 | "lucide-react": "^0.474.0", 21 | "react": "^18.3.1", 22 | "react-dom": "^18.3.1", 23 | "tailwind-merge": "^3.0.1", 24 | "tailwindcss": "^4.0.3" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^22.13.1", 28 | "@types/react": "^18.3.18", 29 | "@types/react-dom": "^18.3.5", 30 | "@typescript-eslint/eslint-plugin": "^7.13.1", 31 | "@typescript-eslint/parser": "^7.13.1", 32 | "@vitejs/plugin-react": "^4.3.4", 33 | "eslint": "^8.57.0", 34 | "eslint-config-prettier": "^9.1.0", 35 | "eslint-plugin-import": "^2.29.1", 36 | "eslint-plugin-prettier": "^5.1.3", 37 | "eslint-plugin-react-hooks": "^4.6.2", 38 | "eslint-plugin-react-refresh": "^0.4.7", 39 | "globals": "^15.14.0", 40 | "husky": "^9.1.7", 41 | "pinst": "^3.0.0", 42 | "prettier": "^3.4.2", 43 | "typescript": "~5.6.2", 44 | "typescript-eslint": "^8.18.2", 45 | "vite": "^6.0.5", 46 | "vite-tsconfig-paths": "^5.1.4" 47 | }, 48 | "lint-staged": { 49 | "*.{js,jsx,ts,tsx}": [ 50 | "eslint", 51 | "prettier --write" 52 | ], 53 | "*.{html,css,scss,json,md}": [ 54 | "prettier --write" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/Experience.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useLayoutEffect, useState } from "react"; 2 | 3 | import themeConfig from "@/config/theme.ts"; 4 | import { ThemeContext } from "@/contexts/themeContext.ts"; 5 | import { CardContainer, CardTitle } from "@/hooks/utils.tsx"; 6 | 7 | interface Props { 8 | title: string; 9 | data: Array<{ 10 | period: string; 11 | position: string; 12 | institution: string; 13 | }>; 14 | } 15 | 16 | const Experience = (props: Props) => { 17 | const { title, data } = props; 18 | 19 | const { theme } = useContext(ThemeContext); 20 | const [themeSwitcher, setThemeSwitcher] = useState({ 21 | timeLine: "", 22 | dot: "" 23 | }); 24 | 25 | useLayoutEffect(() => { 26 | setThemeSwitcher({ 27 | timeLine: themeConfig[theme].experience.timeLine, 28 | dot: themeConfig[theme].experience.dot 29 | }); 30 | }, [theme]); 31 | 32 | return ( 33 | 34 | {title} 35 | 36 |
37 | {data.map((item, index) => ( 38 |
39 | 42 | 43 |
44 |

{item.period}

45 |
{item.position}
46 |

{item.institution}

47 |
48 |
49 | ))} 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default Experience; 56 | -------------------------------------------------------------------------------- /src/components/SkeletonProject.tsx: -------------------------------------------------------------------------------- 1 | import { CardContainer, ElementSkeleton, TextSkeleton } from "@/hooks/utils"; 2 | 3 | export const SkeletonProject = () => { 4 | return ( 5 | 6 | 10 |
11 |
12 | 13 | Lorem ipsum dolor sit amet, consectetur. 14 |
15 | 16 | 17 | Lorem ipsum dolor sit amet, consectetur adipisicing elit onej. 18 | 19 | 20 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. 21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 | 4457 29 |
30 | 31 |
32 | 33 | 4557 34 |
35 |
36 | 37 |
38 | 39 | JavaScript 40 |
41 |
42 |
43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/config/theme.ts: -------------------------------------------------------------------------------- 1 | const themeConfig = { 2 | dark: { 3 | primaryColor: "bg-indigo-600", 4 | body: "transition-all duration-300 antialiased bg-[#1F242D] text-[#747B88]", 5 | changeThemeButton: { 6 | hover: "hover:bg-[#343A46]", 7 | dropDown: "bg-[#242933] border-[#242933]", 8 | button: "text-white", 9 | buttonHover: "hover:bg-[#2A303C]" 10 | }, 11 | cardContainer: "bg-[#2A303C]", 12 | cardTitle: "text-[#7E8591]", 13 | projectsContainer: "bg-[#232833] border border-[#232833]", 14 | resume: { 15 | avatarBorder: "border-indigo-600" 16 | }, 17 | tryProjects: { 18 | tag: "bg-[#252A35]", 19 | links: "text-indigo-500" 20 | }, 21 | experience: { 22 | timeLine: "border-[#272D37]", 23 | dot: "bg-[#1F242D]" 24 | }, 25 | footer: { 26 | link: "bg-[#2A303C] border-[#2A303C]" 27 | }, 28 | skeleton: { 29 | color: "bg-[#1F242D]" 30 | } 31 | }, 32 | light: { 33 | primaryColor: "bg-indigo-600", 34 | body: "transition-all duration-300 antialiased bg-zinc-200 text-[#797E87]", 35 | changeThemeButton: { 36 | hover: "hover:bg-gray-200", 37 | dropDown: "bg-white border-gray-200/60", 38 | button: "text-white", 39 | buttonHover: "hover:bg-gray-100" 40 | }, 41 | cardContainer: "bg-white", 42 | cardTitle: "text-gray-500", 43 | projectsContainer: "bg-zinc-100 border border-gray-100", 44 | resume: { 45 | avatarBorder: "border-indigo-600" 46 | }, 47 | tryProjects: { 48 | tag: "bg-gray-100", 49 | links: "text-indigo-600" 50 | }, 51 | experience: { 52 | timeLine: "border-gray-100", 53 | dot: "bg-gray-200" 54 | }, 55 | footer: { 56 | link: "bg-white border-gray-300" 57 | }, 58 | skeleton: { 59 | color: "bg-gray-200" 60 | } 61 | } 62 | }; 63 | 64 | export default themeConfig; 65 | -------------------------------------------------------------------------------- /src/components/Project.tsx: -------------------------------------------------------------------------------- 1 | import { FolderClosed, Star } from "lucide-react"; 2 | 3 | import { getLanguageColors } from "@/helpers"; 4 | import { CardContainer, MergeIcon } from "@/hooks/utils.tsx"; 5 | import { GitHubUserType } from "@/types"; 6 | 7 | interface Props { 8 | data: GitHubUserType; 9 | } 10 | 11 | const Project = (props: Props) => { 12 | const { data } = props; 13 | 14 | return ( 15 | 16 | 22 |
23 |
24 | 25 |

{data?.name}

26 |
27 | 28 |

{data?.description}

29 |
30 | 31 |
32 |
33 |
34 | 35 | {data?.stargazers_count} 36 |
37 | 38 |
39 | 40 | {data?.forks_count} 41 |
42 |
43 | 44 |
45 | 49 | {data?.language} 50 |
51 |
52 |
53 |
54 | ); 55 | }; 56 | 57 | export default Project; 58 | -------------------------------------------------------------------------------- /src/components/Resume.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useLayoutEffect, useState } from "react"; 2 | 3 | import configs from "@/config"; 4 | import themeConfig from "@/config/theme.js"; 5 | import { ThemeContext } from "@/contexts/themeContext.ts"; 6 | import { useFetchData } from "@/hooks"; 7 | import { CardContainer, ElementSkeleton, TextSkeleton } from "@/hooks/utils.tsx"; 8 | 9 | const Resume = () => { 10 | const { theme } = useContext(ThemeContext); 11 | const [themeSwitcher, setThemeSwitcher] = useState({ 12 | avatarBorder: "" 13 | }); 14 | 15 | const { profile } = configs; 16 | const [data, loading] = useFetchData(`https://api.github.com/users/${profile.username}`); 17 | 18 | useLayoutEffect(() => { 19 | setThemeSwitcher({ 20 | avatarBorder: themeConfig[theme].resume.avatarBorder 21 | }); 22 | }, [theme]); 23 | 24 | return ( 25 | 26 | {loading ? ( 27 | 28 | ) : ( 29 |
32 | {data && data?.avatar_url ? ( 33 | 38 | ) : ( 39 |
40 | )} 41 |
42 | )} 43 | 44 |
45 | {loading ? ( 46 | Hello World! 47 | ) : ( 48 |

{data?.name || "-"}

49 | )} 50 | 51 | {loading ? ( 52 |
53 | 54 | Lorem ipsum dolor sit amet, consectetur adipisi. 55 | 56 | 57 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. 58 | 59 | Lorem ipsum dolor sit amet. 60 |
61 | ) : ( 62 |

{data?.bio || "-"}

63 | )} 64 | 65 | 70 | Download Resume 71 | 72 |
73 | 74 | ); 75 | }; 76 | 77 | export default Resume; 78 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type ThemeType = "dark" | "light"; 2 | 3 | export interface GitHubUserType { 4 | id: number; 5 | node_id: string; 6 | name: string; 7 | full_name: string; 8 | private: boolean; 9 | owner: Owner; 10 | html_url: string; 11 | description: string; 12 | fork: boolean; 13 | url: string; 14 | forks_url: string; 15 | keys_url: string; 16 | collaborators_url: string; 17 | teams_url: string; 18 | hooks_url: string; 19 | issue_events_url: string; 20 | events_url: string; 21 | assignees_url: string; 22 | branches_url: string; 23 | tags_url: string; 24 | blobs_url: string; 25 | git_tags_url: string; 26 | git_refs_url: string; 27 | trees_url: string; 28 | statuses_url: string; 29 | languages_url: string; 30 | stargazers_url: string; 31 | contributors_url: string; 32 | subscribers_url: string; 33 | subscription_url: string; 34 | commits_url: string; 35 | git_commits_url: string; 36 | comments_url: string; 37 | issue_comment_url: string; 38 | contents_url: string; 39 | compare_url: string; 40 | merges_url: string; 41 | archive_url: string; 42 | downloads_url: string; 43 | issues_url: string; 44 | pulls_url: string; 45 | milestones_url: string; 46 | notifications_url: string; 47 | labels_url: string; 48 | releases_url: string; 49 | deployments_url: string; 50 | created_at: string; 51 | updated_at: string; 52 | pushed_at: string; 53 | git_url: string; 54 | ssh_url: string; 55 | clone_url: string; 56 | svn_url: string; 57 | homepage: string; 58 | size: number; 59 | stargazers_count: number; 60 | watchers_count: number; 61 | language: string; 62 | has_issues: boolean; 63 | has_projects: boolean; 64 | has_downloads: boolean; 65 | has_wiki: boolean; 66 | has_pages: boolean; 67 | has_discussions: boolean; 68 | forks_count: number; 69 | mirror_url: string | null; 70 | archived: boolean; 71 | disabled: boolean; 72 | open_issues_count: number; 73 | license?: License; 74 | allow_forking: boolean; 75 | is_template: boolean; 76 | web_commit_signoff_required: boolean; 77 | topics: string[]; 78 | visibility: string; 79 | forks: number; 80 | open_issues: number; 81 | watchers: number; 82 | default_branch: string; 83 | score: number; 84 | } 85 | 86 | export interface Owner { 87 | login: string; 88 | id: number; 89 | node_id: string; 90 | avatar_url: string; 91 | gravatar_id: string; 92 | url: string; 93 | html_url: string; 94 | followers_url: string; 95 | following_url: string; 96 | gists_url: string; 97 | starred_url: string; 98 | subscriptions_url: string; 99 | organizations_url: string; 100 | repos_url: string; 101 | events_url: string; 102 | received_events_url: string; 103 | type: string; 104 | user_view_type: string; 105 | site_admin: boolean; 106 | } 107 | 108 | export interface License { 109 | key: string; 110 | name: string; 111 | spdx_id: string; 112 | url: string; 113 | node_id: string; 114 | } 115 | -------------------------------------------------------------------------------- /src/components/ChangeTheme.tsx: -------------------------------------------------------------------------------- 1 | import { Palette, ChevronRight } from "lucide-react"; 2 | import { useContext, useLayoutEffect, useRef, useState } from "react"; 3 | 4 | import themeConfig from "@/config/theme.ts"; 5 | import { ThemeContext } from "@/contexts/themeContext.ts"; 6 | import { useOnClickOutside } from "@/hooks"; 7 | import { CardContainer } from "@/hooks/utils.tsx"; 8 | 9 | const ChangeTheme = () => { 10 | const { theme, themes, changeTheme } = useContext(ThemeContext); 11 | const ref = useRef(null); 12 | const [open, setOpen] = useState(false); 13 | const [themeSwitcher, setThemeSwitcher] = useState({ 14 | buttonHover: "", 15 | dropStyle: "", 16 | itemButton: "", 17 | itemButtonHover: "", 18 | primaryColor: "" 19 | }); 20 | 21 | useLayoutEffect(() => { 22 | setThemeSwitcher({ 23 | buttonHover: themeConfig[theme].changeThemeButton.hover, 24 | dropStyle: themeConfig[theme].changeThemeButton.dropDown, 25 | itemButton: themeConfig[theme].changeThemeButton.button, 26 | itemButtonHover: themeConfig[theme].changeThemeButton.buttonHover, 27 | primaryColor: themeConfig[theme].primaryColor 28 | }); 29 | }, [theme]); 30 | 31 | useOnClickOutside(ref?.current, () => setOpen(false)); 32 | 33 | return ( 34 | 35 |
36 |

Theme

37 |

{theme}

38 |
39 | 40 |
41 | 51 | 52 |
55 | {themes.map((item, index) => ( 56 |
{ 59 | changeTheme(item); 60 | }} 61 | className={`transition-all duration-300 px-4 py-2 rounded-md cursor-pointer text-sm ${theme === item ? `${themeSwitcher.itemButton} ${themeSwitcher.primaryColor}` : `${themeSwitcher.itemButtonHover}`}`} 62 | > 63 | {item} 64 |
65 | ))} 66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default ChangeTheme; 73 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import ChangeTheme from "@/components/ChangeTheme.tsx"; 2 | import Contact from "@/components/Contact.tsx"; 3 | import Experience from "@/components/Experience.tsx"; 4 | import Footer from "@/components/Footer.tsx"; 5 | import Project from "@/components/Project.tsx"; 6 | import Resume from "@/components/Resume.tsx"; 7 | import { SkeletonProject } from "@/components/SkeletonProject.tsx"; 8 | import TechStack from "@/components/TechStack.tsx"; 9 | import TryProjects from "@/components/TryProjects.tsx"; 10 | import configs from "@/config"; 11 | import { useFetchData } from "@/hooks"; 12 | import { CardTitle, ProjectsContainer } from "@/hooks/utils.tsx"; 13 | import { GitHubUserType } from "@/types"; 14 | 15 | const App = () => { 16 | const { profile } = configs; 17 | const [data, loading] = useFetchData( 18 | `https://api.github.com/search/repositories?q=user:${profile.username}+fork:true&sort=stars&per_page=6&type=Repositories` 19 | ); 20 | 21 | return ( 22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | My Projects 42 | 52 | See all 53 | 54 |
55 | 56 |
57 | {loading ? ( 58 | <> 59 | {[0, 1, 2, 3, 4, 5].map(item => ( 60 | 61 | ))} 62 | 63 | ) : ( 64 | <> 65 | {data?.items.map((item: GitHubUserType, index: number) => ( 66 | 67 | ))} 68 | 69 | )} 70 |
71 |
72 | 73 | 74 | A recent project that can be tested 75 | 76 |
77 | {profile.tryProjects.map((item, index) => ( 78 | 79 | ))} 80 |
81 |
82 |
83 |
84 | 85 |
86 |
87 | ); 88 | }; 89 | 90 | export default App; 91 | -------------------------------------------------------------------------------- /src/components/TryProjects.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useLayoutEffect, useMemo, useState } from "react"; 2 | 3 | import configs from "@/config"; 4 | import themeConfig from "@/config/theme.js"; 5 | import { ThemeContext } from "@/contexts/themeContext.ts"; 6 | import { CardContainer } from "@/hooks/utils.tsx"; 7 | import { cn } from "@/libs/utils.ts"; 8 | 9 | interface Props { 10 | data: (typeof configs.profile.tryProjects)[0]; 11 | } 12 | 13 | const TryProjects = (props: Props) => { 14 | const { data } = props; 15 | 16 | const { theme } = useContext(ThemeContext); 17 | const [themeSwitcher, setThemeSwitcher] = useState({ 18 | tag: "", 19 | links: "" 20 | }); 21 | 22 | useLayoutEffect(() => { 23 | setThemeSwitcher({ 24 | tag: themeConfig[theme].tryProjects.tag, 25 | links: themeConfig[theme].tryProjects.links 26 | }); 27 | }, [theme]); 28 | 29 | const As = useMemo(() => { 30 | if (data.links) { 31 | return "div" as const; 32 | } 33 | 34 | return "a" as const; 35 | }, [data.links]); 36 | 37 | const attributes = useMemo(() => { 38 | if (As === "div") return {}; 39 | 40 | return { 41 | href: data.link || "#link", 42 | target: "_blank", 43 | rel: "noreferrer" 44 | }; 45 | }, [As, data.link]); 46 | 47 | return ( 48 | 49 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {data?.image ? ( 67 | 73 | ) : ( 74 |
78 | )} 79 |
80 | 81 |
82 |
83 | {data.links && data.links.length > 0 ? ( 84 | 85 | {data.title} 86 | 87 | ) : ( 88 | data.title 89 | )} 90 |
91 |

{data.date}

92 |

{data.description}

93 | 94 | {data.links && data.links.length > 0 && ( 95 |

96 | Links:{" "} 97 | {data.links.map((item, index) => ( 98 | <> 99 | 106 | {item.title} 107 | 108 | {index < data.links.length - 2 109 | ? ", " 110 | : index + 1 === data.links.length 111 | ? "" 112 | : " and "} 113 | 114 | ))} 115 |

116 | )} 117 | 118 |
119 | {(data.techStack || []).map((item, index) => ( 120 | 128 | #{item} 129 | 130 | ))} 131 |
132 |
133 | 134 | 135 | ); 136 | }; 137 | 138 | export default TryProjects; 139 | -------------------------------------------------------------------------------- /src/hooks/utils.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useContext, useLayoutEffect, useState } from "react"; 2 | 3 | import themeConfig from "@/config/theme.ts"; 4 | import { ThemeContext } from "@/contexts/themeContext.ts"; 5 | 6 | interface Props { 7 | children: ReactNode; 8 | className?: string; 9 | } 10 | 11 | export const CardContainer = (props: Props) => { 12 | const { children, className = "" } = props; 13 | 14 | const { theme } = useContext(ThemeContext); 15 | const [themeSwitcher, setThemeSwitcher] = useState(""); 16 | 17 | useLayoutEffect(() => { 18 | setThemeSwitcher(themeConfig[theme].cardContainer); 19 | }, [theme]); 20 | 21 | return ( 22 |
25 | {children} 26 |
27 | ); 28 | }; 29 | 30 | export const CardTitle = ({ children }: { children: ReactNode }) => { 31 | const { theme } = useContext(ThemeContext); 32 | const [themeSwitcher, setThemeSwitcher] = useState(""); 33 | 34 | useLayoutEffect(() => { 35 | setThemeSwitcher(themeConfig[theme].cardTitle); 36 | }, [theme]); 37 | 38 | return ( 39 |

40 | {children} 41 |

42 | ); 43 | }; 44 | 45 | export const ProjectsContainer = ({ children }: { children: ReactNode }) => { 46 | const { theme } = useContext(ThemeContext); 47 | const [themeSwitcher, setThemeSwitcher] = useState(""); 48 | 49 | useLayoutEffect(() => { 50 | setThemeSwitcher(themeConfig[theme].projectsContainer); 51 | }, [theme]); 52 | 53 | return ( 54 |
55 | {children} 56 |
57 | ); 58 | }; 59 | 60 | export const GithubIcon = ({ className = "" }) => { 61 | return ( 62 | 72 | 76 | 77 | ); 78 | }; 79 | 80 | export const TwitterIcon = ({ className = "" }) => { 81 | return ( 82 | 92 | 96 | 97 | ); 98 | }; 99 | 100 | export const LinkedInIcon = ({ className = "" }) => { 101 | return ( 102 | 112 | 116 | 117 | ); 118 | }; 119 | 120 | export const MediumIcon = ({ className = "" }) => { 121 | return ( 122 | 132 | 136 | 137 | ); 138 | }; 139 | 140 | export const DevIcon = ({ className = "" }) => { 141 | return ( 142 | 152 | 156 | 157 | ); 158 | }; 159 | 160 | export const MergeIcon = ({ className = "" }) => { 161 | return ( 162 | 172 | 176 | 177 | ); 178 | }; 179 | 180 | export const TextSkeleton = ({ children }: { children: ReactNode }) => { 181 | const { theme } = useContext(ThemeContext); 182 | const [themeSwitcher, setThemeSwitcher] = useState({ 183 | color: "" 184 | }); 185 | 186 | useLayoutEffect(() => { 187 | setThemeSwitcher({ 188 | color: themeConfig[theme].skeleton.color 189 | }); 190 | }, [theme]); 191 | 192 | return ( 193 |
194 |
197 | {children} 198 |
199 |
200 | ); 201 | }; 202 | 203 | export const ElementSkeleton = ({ className = "" }) => { 204 | const { theme } = useContext(ThemeContext); 205 | const [themeSwitcher, setThemeSwitcher] = useState({ 206 | color: "" 207 | }); 208 | 209 | useLayoutEffect(() => { 210 | setThemeSwitcher({ 211 | color: themeConfig[theme].skeleton.color 212 | }); 213 | }, [theme]); 214 | 215 | return ( 216 |
217 |
218 |
219 | ); 220 | }; 221 | -------------------------------------------------------------------------------- /src/config/index.tsx: -------------------------------------------------------------------------------- 1 | import { MapPin, Building, Mail, Globe } from "lucide-react"; 2 | 3 | import { DevIcon, GithubIcon, LinkedInIcon, MediumIcon, TwitterIcon } from "@/hooks/utils.tsx"; 4 | import { dateTimeFormNow } from "@/libs/date.ts"; 5 | 6 | const configs = { 7 | profile: { 8 | username: "onesine", 9 | contact: [ 10 | { 11 | link: null, 12 | data: { 13 | text: "Based in", 14 | value: "Bénin" 15 | }, 16 | icon: 17 | }, 18 | { 19 | link: "https://paydunya.com/", 20 | data: { 21 | text: "Company", 22 | value: "Paydunya" 23 | }, 24 | icon: 25 | }, 26 | { 27 | link: "https://github.com/onesine", 28 | data: { 29 | text: "GitHub", 30 | value: "onesine" 31 | }, 32 | icon: 33 | }, 34 | { 35 | link: "https://twitter.com/LewheO", 36 | data: { 37 | text: "Twitter", 38 | value: "g2ek" 39 | }, 40 | icon: 41 | }, 42 | { 43 | link: "https://www.linkedin.com/in/onesine-lewhe-63109a171/", 44 | data: { 45 | text: "LinkedIn", 46 | value: "Onesine Lewhe" 47 | }, 48 | icon: 49 | }, 50 | { 51 | link: "https://medium.com/@philemonlewhe", 52 | data: { 53 | text: "Medium", 54 | value: "onesine" 55 | }, 56 | icon: 57 | }, 58 | { 59 | link: "https://dev.to/onesine", 60 | data: { 61 | text: "Dev", 62 | value: "onesine" 63 | }, 64 | icon: 65 | }, 66 | { 67 | link: null, 68 | data: { 69 | text: "Website", 70 | value: "https://onesine.github.io" 71 | }, 72 | icon: 73 | }, 74 | { 75 | link: "mailto:lewheonesine@gmail.com", 76 | data: { 77 | text: "Email", 78 | value: "lewheonesine@gmail.com" 79 | }, 80 | icon: 81 | } 82 | ], 83 | techStack: [ 84 | "PHP", 85 | "Laravel", 86 | "JavaScript", 87 | "TypeScript", 88 | "React.js", 89 | "Next.js", 90 | "Node.js", 91 | "Adonis.js", 92 | "MySQL", 93 | "Git", 94 | "CSS", 95 | "Tailwindcss" 96 | ], 97 | experience: [ 98 | { 99 | period: "April 2023 - Present", 100 | position: "Front-end & Mobile Developer", 101 | institution: "Paydunya | MyDunya" 102 | }, 103 | { 104 | period: "September 2023 - April 2024", 105 | position: "Front-end Developer", 106 | institution: "Freelance" 107 | }, 108 | { 109 | period: "January 2023 - March 2023", 110 | position: "Front-end developer services", 111 | institution: "FASFOX" 112 | }, 113 | { 114 | period: "February 2019 - December 2022", 115 | position: "Full-stack Developer", 116 | institution: "DMD SARL" 117 | } 118 | ], 119 | eduction: [ 120 | { 121 | period: "2015 - 2019", 122 | position: "Licence 3", 123 | institution: "ESGIS" 124 | }, 125 | { 126 | period: "2016", 127 | position: "12th Grade", 128 | institution: "CSS / CED" 129 | } 130 | ], 131 | tryProjects: [ 132 | { 133 | image: "MyDunya.png", 134 | link: "https://mydunya.com/", 135 | links: [ 136 | { 137 | title: "MyDunya on Play Store", 138 | link: "https://play.google.com/store/apps/details?id=com.paydunya.mydunya_live_2&hl=fr" 139 | }, 140 | { 141 | title: "MyDunya on Apple Store", 142 | link: "https://apps.apple.com/fr/app/mydunya/id6475684818" 143 | }, 144 | { 145 | title: "MyDunya on Web", 146 | link: "https://mydunya.com/" 147 | } 148 | ], 149 | title: "MyDunya", 150 | description: 151 | "Development of the MyDunya platform, a wallet-to-wallet and card-to-wallet money transfer application. I worked as a front-end developer on the web application (PWA), mobile application and administration dashboard.", 152 | date: "Still in development", 153 | techStack: [ 154 | "HTML", 155 | "CSS", 156 | "Typescript", 157 | "Tailwind", 158 | "React", 159 | "Redux", 160 | "React Native", 161 | "PWA" 162 | ] 163 | }, 164 | { 165 | image: "cryptocowrie-app.png", 166 | link: "https://app.cryptocowries.io/login", 167 | title: "CryptoCowries app", 168 | description: 169 | "This project is primarily an app for buying, depositing and withdrawing crypto-currencies.", 170 | date: dateTimeFormNow(new Date("2024-04-16")), 171 | techStack: ["HTML", "CSS", "Typescript", "Tailwind", "React", "Redux"] 172 | }, 173 | { 174 | image: "react-tailwindcss-datepicker.png", 175 | link: "https://react-tailwindcss-datepicker.vercel.app/", 176 | title: "React Tailwindcss Datepicker", 177 | description: 178 | "Modern date range picker component for React using Tailwind 3 and dayjs. Alternative to Litepie Datepicker.", 179 | date: dateTimeFormNow(new Date("2022-11-18")), 180 | techStack: ["JavaScript", "TypeScript", "React", "Next", "Tailwindcss"] 181 | }, 182 | { 183 | image: "atompay-app.png", 184 | link: "https://test-paydunya-atompay.vercel.app/", 185 | title: "Atompay app", 186 | description: 187 | "It's an application for transferring money as part of a challenge. All data used in this application is false.", 188 | date: dateTimeFormNow(new Date("2023-02-21")), 189 | techStack: ["HTML", "JavaScript", "React", "Tailwind"] 190 | }, 191 | { 192 | image: "inventor-app.png", 193 | link: "#", 194 | title: "Inventor app", 195 | description: "This project is a property management application for businesses.", 196 | date: dateTimeFormNow(new Date("2022-12-13")), 197 | techStack: ["HTML", "JavaScript", "React", "Bootstrap 4"] 198 | }, 199 | { 200 | image: "git-profile.png", 201 | link: "https://git-profile-red.vercel.app/", 202 | title: "Git Profile", 203 | description: 204 | "This project presents some data from my GitHub and some project you can try.", 205 | date: dateTimeFormNow(new Date("2022-11-03")), 206 | techStack: ["JavaScript", "React", "HTML", "Tailwindcss"] 207 | }, 208 | { 209 | image: "react-headless-accordion.png", 210 | link: "https://react-nested-accordion.vercel.app/", 211 | title: "Example usage react-headless-accordion", 212 | description: 213 | "This project presents some use cases of my react-headless-accordion package.", 214 | date: dateTimeFormNow(new Date("2022-10-17")), 215 | techStack: ["JavaScript", "React", "HTML", "Tailwindcss"] 216 | }, 217 | { 218 | image: "react-tailwindcss-select.png", 219 | link: "https://demo-react-tailwindcss-select.vercel.app/", 220 | title: "Demo of react-tailwindcss-select", 221 | description: 222 | "This project allows you to test the different options available in my react-tailwindcss-select package.", 223 | date: dateTimeFormNow(new Date("2022-07-13")), 224 | techStack: ["JavaScript", "React", "HTML", "Tailwindcss"] 225 | }, 226 | { 227 | image: "satis-backoffice.png", 228 | link: "#", 229 | title: "Satis backoffice", 230 | description: 231 | "This project is a client survey application for microfinance institutions..", 232 | date: dateTimeFormNow(new Date("2022-04-13")), 233 | techStack: ["HTML", "JavaScript", "React", "PHP", "Laravel", "Tailwindcss"] 234 | } 235 | ] 236 | } 237 | }; 238 | 239 | export default configs; 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Portfolio 2 | 3 | This project is a portfolio inspired by [GitProfile](https://github.com/arifszn/gitprofile). You can quickly create your portfolio with the template provided. To do this you just need to provide your github username and some configurations. 4 | 5 | ## Online exemple 6 | 7 | #### Light Mode 8 |

9 | preview react-tailwindcss-select 10 |

11 | 12 | #### Dark Mode 13 |

14 | preview react-tailwindcss-select 15 |

16 | 17 | To view a live example, [click here](https://git-profile-red.vercel.app/). 18 | 19 | ## Installation 20 | If you want to use git portfolio you can: 21 | - Forking this repo 22 | - Setting up locally 23 | 24 | ### Forking this repo 25 | You just have to get a copy of the repo from this [link](https://github.com/onesine/git-portfolio/fork) and then you can configure with your data. 26 | 27 | ### Setting up locally 28 | To do so, you just have to execute these commands. 29 | - Get project 30 | ```bash 31 | git clone https://github.com/onesine/git-portfolio.git 32 | cd git-portfolio 33 | ``` 34 | - Install dependencies. 35 | ```bash 36 | npm install 37 | ``` 38 | - Run dev server. 39 | ```bash 40 | npm run start 41 | ``` 42 | 43 | If everything goes well so far, you can now proceed to the configuration. 44 | 45 | ## Configuration 46 | Everything happens in the `src/config/index.tsx` file. Modify it as you like to get the result you want. 47 | 48 | ```javascript 49 | { 50 | profile: { 51 | username: "", 52 | contact: [ 53 | { 54 | link: null, 55 | data: { 56 | text: "", 57 | value: "" 58 | }, 59 | icon: () 60 | }, 61 | { 62 | link: null, 63 | data: { 64 | text: "", 65 | value: "" 66 | }, 67 | icon: () 68 | }, 69 | { 70 | link: "", 71 | data: { 72 | text: "=", 73 | value: "" 74 | }, 75 | icon: () 76 | }, 77 | { 78 | link: "", 79 | data: { 80 | text: "", 81 | value: "" 82 | }, 83 | icon: () 84 | }, 85 | { 86 | link: "", 87 | data: { 88 | text: "", 89 | value: "" 90 | }, 91 | icon: () 92 | }, 93 | { 94 | link: "", 95 | data: { 96 | text: "", 97 | value: "" 98 | }, 99 | icon: () 100 | }, 101 | { 102 | link: "", 103 | data: { 104 | text: "", 105 | value: "" 106 | }, 107 | icon: () 108 | }, 109 | { 110 | link: null, 111 | data: { 112 | text: "", 113 | value: "" 114 | }, 115 | icon: () 116 | }, 117 | { 118 | link: "", 119 | data: { 120 | text: "", 121 | value: "" 122 | }, 123 | icon: () 124 | }, 125 | ], 126 | techStack: [], 127 | experience: [ 128 | { 129 | period: "", 130 | position: "", 131 | institution: "" 132 | }, 133 | { 134 | period: "", 135 | position: "", 136 | institution: "" 137 | }, 138 | ], 139 | eduction: [ 140 | { 141 | period: "", 142 | position: "", 143 | institution: "" 144 | }, 145 | { 146 | period: "", 147 | position: "", 148 | institution: "" 149 | }, 150 | ], 151 | tryProjects: [ 152 | { 153 | image: "", 154 | link: "", 155 | title: "", 156 | description: "", 157 | date: "", 158 | techStack: [] 159 | }, 160 | { 161 | image: "", 162 | link: "", 163 | title: "", 164 | description: "", 165 | date: "", 166 | techStack: [] 167 | } 168 | ] 169 | } 170 | } 171 | ``` 172 | 173 | ### Avatar, Bio and My Projects 174 | These data are automatically retrieved via GitHub 175 | 176 | > **Warning** 177 | > 178 | > For your CV to be downloadable you must put it in the `public/pdf` folder. The file must have as name your github username. 179 | 180 | ### Contact 181 | ```javascript 182 | { 183 | profile: { 184 | // ... 185 | contact: [ 186 | { 187 | link: null, 188 | data: { 189 | text: "", 190 | value: "" 191 | }, 192 | icon: () 193 | }, 194 | { 195 | link: null, 196 | data: { 197 | text: "", 198 | value: "" 199 | }, 200 | icon: () 201 | }, 202 | { 203 | link: "", 204 | data: { 205 | text: "=", 206 | value: "" 207 | }, 208 | icon: () 209 | }, 210 | { 211 | link: "", 212 | data: { 213 | text: "", 214 | value: "" 215 | }, 216 | icon: () 217 | }, 218 | { 219 | link: "", 220 | data: { 221 | text: "", 222 | value: "" 223 | }, 224 | icon: () 225 | }, 226 | { 227 | link: "", 228 | data: { 229 | text: "", 230 | value: "" 231 | }, 232 | icon: () 233 | }, 234 | { 235 | link: "", 236 | data: { 237 | text: "", 238 | value: "" 239 | }, 240 | icon: () 241 | }, 242 | { 243 | link: null, 244 | data: { 245 | text: "", 246 | value: "" 247 | }, 248 | icon: () 249 | }, 250 | { 251 | link: "", 252 | data: { 253 | text: "", 254 | value: "" 255 | }, 256 | icon: () 257 | }, 258 | ], 259 | // ... 260 | } 261 | } 262 | ``` 263 | 264 | ### Experience 265 | ```javascript 266 | { 267 | profile: { 268 | // ... 269 | experience: [ 270 | { 271 | period: "", 272 | position: "", 273 | institution: "" 274 | }, 275 | { 276 | period: "", 277 | position: "", 278 | institution: "" 279 | }, 280 | ] 281 | // ... 282 | } 283 | } 284 | ``` 285 | 286 | ### Education 287 | ```javascript 288 | { 289 | profile: { 290 | // ... 291 | eduction: [ 292 | { 293 | period: "", 294 | position: "", 295 | institution: "" 296 | }, 297 | { 298 | period: "", 299 | position: "", 300 | institution: "" 301 | }, 302 | ], 303 | // ... 304 | } 305 | } 306 | ``` 307 | 308 | ### A recent project that can be tested 309 | ```javascript 310 | { 311 | profile: { 312 | // ... 313 | tryProjects: [ 314 | { 315 | image: "project_1.png", 316 | link: "", 317 | title: "", 318 | description: "", 319 | date: "", 320 | techStack: [] 321 | }, 322 | { 323 | image: "project_1.png", 324 | link: "", 325 | title: "", 326 | description: "", 327 | date: "", 328 | techStack: [] 329 | } 330 | ] 331 | // ... 332 | } 333 | } 334 | ``` 335 | 336 | > **Warning** 337 | > 338 | >To make your project images visible. You must put them in the `public/images` folder. 339 | 340 | ## Contributing 341 | Got ideas on how to make this better? Open an issue! 342 | 343 | ## Thanks 344 | Thank you, this project would never have seen the light of day without [GitProfile](https://react-select.com/). 345 | It was a pleasure to be inspired by GitProfile's beautiful interface to realize this project. 346 | 347 | ## License 348 | MIT Licensed. Copyright (c) Lewhe Onesine 2022. --------------------------------------------------------------------------------