From 27af573ddcec350d0e8c478d76dbedaa292b2074 Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Tue, 26 Mar 2024 22:26:09 -0700 Subject: [PATCH 01/22] =?UTF-8?q?add:=20user=20model=F0=9F=8E=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/models/index.ts | 3 +- src/database/models/user.ts | 106 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/database/models/user.ts diff --git a/src/database/models/index.ts b/src/database/models/index.ts index 71c654e..cebf496 100644 --- a/src/database/models/index.ts +++ b/src/database/models/index.ts @@ -10,8 +10,9 @@ import { Sequelize } from 'sequelize'; import { getConfig } from '@/config'; // import models for tables of your database +import userModel from '@/database/models/user'; -const dataModels: Array = []; +const dataModels: Array = [userModel]; const databaseModules = { postgres: pg, diff --git a/src/database/models/user.ts b/src/database/models/user.ts new file mode 100644 index 0000000..0dac68f --- /dev/null +++ b/src/database/models/user.ts @@ -0,0 +1,106 @@ +import { DataTypes } from 'sequelize'; + +export default function model(sequelize) { + const user = sequelize.define( + 'user', + { + id: { + type: DataTypes.BIGINT.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + fullName: { + type: DataTypes.STRING(255), + allowNull: true, + validate: { + len: [0, 255], + }, + }, + firstName: { + type: DataTypes.STRING(80), + allowNull: true, + validate: { + len: [0, 80], + }, + }, + lastName: { + type: DataTypes.STRING(175), + allowNull: true, + validate: { + len: [0, 175], + }, + }, + password: { + type: DataTypes.STRING(255), + allowNull: true, + validate: { + len: [0, 255], + }, + get() { + return undefined; + }, + }, + email: { + type: DataTypes.STRING(255), + allowNull: false, + validate: { + isEmail: true, + notEmpty: true, + len: [0, 255], + }, + }, + }, + { + indexes: [ + { + unique: true, + fields: ['email'], + where: { + deletedAt: null, + }, + }, + ], + timestamps: true, + }, + ); + + user.associate = (models) => { + models.user.belongsTo(models.user, { + as: 'createdBy', + }); + + models.user.belongsTo(models.user, { + as: 'updatedBy', + }); + }; + + user.beforeCreate((user, options) => { + user = trimStringFields(user); + user.fullName = buildFullName(user.firstName, user.lastName); + }); + + user.beforeUpdate((user, options) => { + user = trimStringFields(user); + user.fullName = buildFullName(user.firstName, user.lastName); + }); + + return user; +} + +function buildFullName(firstName, lastName) { + if (!firstName && !lastName) { + return null; + } + + return [firstName, lastName].filter(Boolean).join(' ').trim(); +} + +function trimStringFields(user) { + user.email = user.email.trim(); + + user.firstName = user.firstName ? user.firstName.trim() : null; + + user.lastName = user.lastName ? user.lastName.trim() : null; + + return user; +} From c29bc54993c8d0d5fe36c41d6bac72d11b8d14a7 Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Thu, 28 Mar 2024 01:50:43 -0700 Subject: [PATCH 02/22] =?UTF-8?q?adopt:=20dotenv=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 7b830fd..2f38757 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/react-vertical-timeline-component": "^3.3.6", "bcrypt": "^5.1.1", "cli-highlight": "^2.1.11", + "dotenv": "^16.4.5", "lodash": "^4.17.21", "mariadb": "^3.3.0", "mysql2": "^3.9.2", diff --git a/yarn.lock b/yarn.lock index 008ef47..f2dce56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1660,6 +1660,11 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + dottie@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.6.tgz#34564ebfc6ec5e5772272d466424ad5b696484d4" From e9721c6096b5b0574818180e8315294fc7250de2 Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Thu, 28 Mar 2024 01:58:46 -0700 Subject: [PATCH 03/22] =?UTF-8?q?config:=20google=20verification=20&=20gtm?= =?UTF-8?q?=20id=E2=9C=8F=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 25 ++++++++++++++----------- src/app/layout.tsx | 2 +- src/components/layouts/RootLayout.tsx | 5 ++++- src/config/index.ts | 8 ++++++++ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index 6d63feb..135b773 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,15 @@ -DATABASE_DIALECT = "postgres" -DATABASE_HOST = "127.0.0.1" -DATABASE_PORT = "5432" -DATABASE_DATABASE = "defaultdb" -DATABASE_USERNAME = "postgres" -DATABASE_PASSWORD = "postgres" -DATABASE_SSL = "false" -DATABASE_SSL_CERT = "" -DATABASE_LOGGING = "false" +DATABASE_DIALECT = "postgres" +DATABASE_HOST = "127.0.0.1" +DATABASE_PORT = "5432" +DATABASE_DATABASE = "defaultdb" +DATABASE_USERNAME = "postgres" +DATABASE_PASSWORD = "postgres" +DATABASE_SSL = "false" +DATABASE_SSL_CERT = "" +DATABASE_LOGGING = "false" -HOST_URL = "http://localhost:3000" -BASE_PATH = "" +HOST_URL = "http://localhost:3000" +BASE_PATH = "" + +GOOGLE_VERIFICATION = "" +GOOGLE_TAG_MANAGER_ID = "" diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 23f5f9a..daa21de 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -18,7 +18,7 @@ export const metadata: Metadata = { images: '/avatar.webp', }, verification: { - google: 'vSy6aBobFApUM2bc6BgZd2XYJQ8P3sFadIdTcEtClwY', + google: config.google.verification, }, icons: { icon: '/favicon.ico', diff --git a/src/components/layouts/RootLayout.tsx b/src/components/layouts/RootLayout.tsx index f691521..0c5dff4 100644 --- a/src/components/layouts/RootLayout.tsx +++ b/src/components/layouts/RootLayout.tsx @@ -1,4 +1,5 @@ import '@/app/globals.css'; +import { getConfig } from '@/config'; import { GoogleTagManager } from '@next/third-parties/google'; import { Raleway } from 'next/font/google'; import { Suspense } from 'react'; @@ -9,6 +10,8 @@ import PreImage from '@/app/assets/pre.svg'; import ReactTooltip from '@/components/clients/ReactTooltip'; import Stars from '@/components/clients/backgrounds/Stars'; +const config = getConfig(); + const Loading = () => (
Loading... @@ -38,7 +41,7 @@ export default function RootLayout({ - + {Boolean(config.google.tag_manager_id) && } ); } diff --git a/src/config/index.ts b/src/config/index.ts index 05a66c8..fe727d2 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -22,6 +22,10 @@ type TConfig = { node: { env: string; }; + google: { + verification: string; + tag_manager_id: string; + }; }; const parseBoolean = (value) => ['true', '1'].includes((value || '').toLowerCase()); @@ -36,6 +40,10 @@ export function getConfig(): TConfig { node: { env: env.NODE_ENV || 'production', }, + google: { + verification: env.GOOGLE_VERIFICATION || '', + tag_manager_id: env.GOOGLE_TAG_MANAGER_ID || '', + }, }; if (env.DATABASE_HOST && env.DATABASE_DIALECT) { From 171f49f4cb6550e2f524330c50a31ecc0b130a35 Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Thu, 28 Mar 2024 04:59:12 -0700 Subject: [PATCH 04/22] =?UTF-8?q?adopt:=20`flowbite`,=20`flowbite-react`?= =?UTF-8?q?=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 ++ yarn.lock | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f38757..b568d52 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "bcrypt": "^5.1.1", "cli-highlight": "^2.1.11", "dotenv": "^16.4.5", + "flowbite": "^2.3.0", + "flowbite-react": "^0.7.5", "lodash": "^4.17.21", "mariadb": "^3.3.0", "mysql2": "^3.9.2", diff --git a/yarn.lock b/yarn.lock index f2dce56..fffb2fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,6 +41,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" + integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== + dependencies: + regenerator-runtime "^0.14.0" + "@commitlint/cli@^19.0.3": version "19.0.3" resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-19.0.3.tgz#a415069099864b75dc65bb22c703c11a3837f258" @@ -248,6 +255,22 @@ "@floating-ui/core" "^1.0.0" "@floating-ui/utils" "^0.2.0" +"@floating-ui/react-dom@^2.0.0": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + +"@floating-ui/react@^0.26.2": + version "0.26.10" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.10.tgz#d4a4878bcfaed70963ec0eaa67a71bead5924ee5" + integrity sha512-sh6f9gVvWQdEzLObrWbJ97c0clJObiALsFe0LiR/kb3tDRKwEhObASEH2QyfdoO/ZBPzwxa9j+nYFo+sqgbioA== + dependencies: + "@floating-ui/react-dom" "^2.0.0" + "@floating-ui/utils" "^0.2.0" + tabbable "^6.0.0" + "@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" @@ -506,6 +529,11 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ== +"@popperjs/core@^2.9.3": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@rushstack/eslint-patch@^1.3.3": version "1.7.2" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz#2d4260033e199b3032a08b41348ac10de21c47e9" @@ -1354,7 +1382,7 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -classnames@^2.2.6, classnames@^2.3.0: +classnames@^2.2.6, classnames@^2.3.0, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -1565,6 +1593,11 @@ debounce@^1.2.1: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== +debounce@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-2.0.0.tgz#b2f914518a1481466f4edaee0b063e4d473ad549" + integrity sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA== + debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -2206,6 +2239,26 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +flowbite-react@^0.7.5: + version "0.7.5" + resolved "https://registry.yarnpkg.com/flowbite-react/-/flowbite-react-0.7.5.tgz#a4ef968eb652cde0181aaa1e4ed0dbffa371cb35" + integrity sha512-Zt2joKS29xLfsmOpMjpSVkHo3qwYrneGui78prJ97LbelFmK4WvAEIhjp5DWLgCkw94hvv5qFxQpZmksh6re5g== + dependencies: + "@floating-ui/react" "^0.26.2" + classnames "^2.5.1" + debounce "^2.0.0" + flowbite "^2.0.0" + react-icons "^4.11.0" + tailwind-merge "^2.0.0" + +flowbite@^2.0.0, flowbite@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/flowbite/-/flowbite-2.3.0.tgz#0730e35d8b0d1dcdea26bb27d848bd9c0141cde1" + integrity sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ== + dependencies: + "@popperjs/core" "^2.9.3" + mini-svg-data-uri "^1.4.3" + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -3119,6 +3172,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mini-svg-data-uri@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939" + integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== + minimatch@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" @@ -3766,6 +3824,11 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-icons@^4.11.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.12.0.tgz#54806159a966961bfd5cdb26e492f4dafd6a8d78" + integrity sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw== + react-intersection-observer@^8.26.2: version "8.34.0" resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz#6f6e67831c52e6233f6b6cc7eb55814820137c42" @@ -4244,6 +4307,18 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + +tailwind-merge@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.2.2.tgz#87341e7604f0e20499939e152cd2841f41f7a3df" + integrity sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw== + dependencies: + "@babel/runtime" "^7.24.0" + tailwindcss@^3.3.0: version "3.4.1" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" From cbae099d103131fb4699900312183cee9ee9caf9 Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Thu, 28 Mar 2024 05:00:43 -0700 Subject: [PATCH 05/22] =?UTF-8?q?config:=20tailwind=20for=20flowbite?= =?UTF-8?q?=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tailwind.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tailwind.config.ts b/tailwind.config.ts index b48a652..9db67cf 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -2,6 +2,7 @@ import type { Config } from 'tailwindcss'; const config: Config = { content: [ + './node_modules/flowbite-react/lib/**/*.js', './src/pages/**/*.{js,ts,jsx,tsx,mdx}', './src/components/**/*.{js,ts,jsx,tsx,mdx}', './src/app/**/*.{js,ts,jsx,tsx,mdx}', @@ -9,6 +10,6 @@ const config: Config = { theme: { extend: {}, }, - plugins: [], + plugins: [require('flowbite/plugin')], }; export default config; From facf6e820040614e795746ca785e0d8ea0847c3f Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Thu, 28 Mar 2024 05:01:22 -0700 Subject: [PATCH 06/22] =?UTF-8?q?style:=20reset=20global=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/assets/background.jpg | Bin 22939 -> 0 bytes src/app/assets/background.webp | Bin 5094 -> 0 bytes src/app/globals.css | 44 --------------------------------- 3 files changed, 44 deletions(-) delete mode 100644 src/app/assets/background.jpg delete mode 100644 src/app/assets/background.webp diff --git a/src/app/assets/background.jpg b/src/app/assets/background.jpg deleted file mode 100644 index 45bf2d54f51abab253fb73fee006b87b464fe726..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22939 zcma)^eb8Lhec#W$x>sCX34B-D<;}5k*a+sb0f7V(MmEh^*kxpmmD!c|u?eO#;zd~C zAju#KcH5bw8=P4dG9ypLOp1q@>jgZ^W0JuQPF;7>iVn_X(u9gJbn;SKCv95Fq-2uG zAMIcC^F4Q;M>1j3o@e*m^LFn&zqjx2ch0^0eDFsf{F%9Q`1k|&8}r%E_Dp8Zr4Rnn zv<8pf`<1Vm$Rsp>#+VQOY{O+oPMkQt_qywjKCvrCJ^Ub=(u3gtM;@U^P zcJ##Ek6(NAk)K<;!r<^D_dfi<@e>amJ8E^syYD&n=!sjp%(Nllq2mV*tY7#ex%JRP zqAQGkC&>}z6b8xyKCpJ8+PyBdDEc3YtPP|!{MiP-n6U#sa-qw-1wsCn?Y`-@ zn|ALR4sN||*KId5<$ia0&VA=m`~N)WI&OZQ#CV=q*P75371x!N%DS2#K8kKXj1qtF zzPTuF-ej%*_fsqH*Ms*Ior4~+q@;uH|P15VoONHmWw`CeC(nv z+qSja+qV78$3On@pZVGK6kTw^1s86)@Y15V^zu%tbNPD5|8L=gGp3cr|N8>JA!?bp z6>VrmAG~h9XrhqL*}9414M|3$*$|!AYN8EE8W(E+snm*EA8sccrE?e9g4S?a#Nz(eRRF z?Ann&BSVidqiED_O*RZoJ2E5HOpzA-WY`+Fhpn%spZsER*C$ts*E+4^!@&_Z z9E>}y@Ex_|S~-q~WjznU%TY;_jTr}aFab*t&T*PX`G z#jIOXsu#0-y4WcL_gDGE_a$+KWz<)aXUd;uqirX>aMTjv+3mGll@_Dv+Im{ zy=v~i-=06yc`zN8-yQYJ*?9k~`%EY8l@*6_-kVI?i$!mIu&%x{dv?qFvv1A6^DCd8 zf2TUS`djrIi)Vj+@AG>%t=_=j%kMq?-jktp!)kN9YVpP@-{09(Kl``4;QVwRKRADI zIxqc$)5U|U+02x_jN=kUZTQ@w*qFJAJu+;L^yNJ}Kk4handjxD`4zLWjLRKm9j}_Y zj4NN~@ywW>&+9l|^*%RcR&oW7x=zleteNF;x8ja7vKhBtezRGZvcA&{Ww5!OzIyR= zbmvJozc#K-o~BV;y}5t0t6$A$ujZS_UpU!+@apD6dHjX>>3lXhI6r;y{KaAKYCdBs zlSQXfzBt@Jqx90SymEN|>{~Sqd-tUuZh3$8JMZ4`!%g$w`r-V+r{AB!y65+92@DhC zo_>#3axt#^{NBI4ite7K+nr{MXF1xu`|WyWn!muZ9~TDxnYf15GBy^LOesF)A@?+q_lseox`s0x9icJX}WkZqVyBl5_xC0TXQ#JPDPQWBFTY`ShxaU!}_nxM{2(`V>e|q(* z_nv-N8i#K2_w3)^fPOvw?p42&J>O%_aLXg*2cKNEnEm2N_gAa(NY`7)MbmD4-NaK1 z?J%J7@Y6u~=2^VMg~m^DM&u+A6z8>w6gpCP*9a9-IYV+q(zJxZMGXFC3dO zs9{nwG|_L{!kK8&Zv$;@V+yil6uFj5Q`3OjaD~x3hv{o(CF)Gl0w|k|`|Ytay@e^! zwOJ{%GQD`_(L&khMmvXh)A|Q7z$`LhIJ9O_zTjw<)_zs>)=VCY=mL%zgqdh*(9HxM%!eU(zc4GXOefMZ(`Np zoz8P-54^qY#1|T#4d5X4FFZCmAip49iGeHICWi+U4g`0wn__>ryp?O+wo;Juw`~G@ zU|a;>j2RUsEvQEWn&4?PXiricC5~ax9*v@vwi&g@=B}{=^gHl(WL8Fi{YrvcOxs1r zrEW|K2ZC?mDlTogK7M90M4NB{sgR!1#dGuOaBwGT)qPXtTdTKjeWHBIEuL~RNfa+W ziG9YfC`G^W-q{ki{zhECdQiO6XFtFuHmM*TtFcL}^~yGA*4rdp%kc-&G_b6H)9}FC zTW4>)i#g&JR<8oy=0|@enCJVz*QZxssssLmUtrsls|}pZGr`&A*}TkVU9Ufk5d1

>Es_h)(6bw{p0JCs-OL915TP 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 Date: Thu, 28 Mar 2024 05:02:24 -0700 Subject: [PATCH 07/22] =?UTF-8?q?add:=20i18n=F0=9F=93=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/en.ts | 6 +++++- src/i18n/index.ts | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 20335d2..ea49624 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -53,7 +53,11 @@ const en = { }, }, - entities: {}, + entities: { + user: { + menu: 'User', + }, + }, }; export default en; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index b2b89b0..750c99d 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -26,8 +26,8 @@ function format(message, args) { /** * Checks if the key exists on the language. */ -export const i18nExists = (languageCode, key) => { - const dictionary = languages[languageCode] || languages[defaultLanguage]; +export const i18nExists = (key) => { + const dictionary = languages[defaultLanguage]; const message = _get(dictionary, key); return Boolean(message); }; @@ -35,8 +35,8 @@ export const i18nExists = (languageCode, key) => { /** * Returns the translation based on the key. */ -export const i18n = (languageCode, key, ...args) => { - const dictionary = languages[languageCode] || languages[defaultLanguage]; +export const i18n = (key, ...args) => { + const dictionary = languages[defaultLanguage]; const message = _get(dictionary, key); if (!message) { From 78d3f4486add13b23dd7b7f77d6f7e7a2eee909a Mon Sep 17 00:00:00 2001 From: james-gates-0212 Date: Thu, 28 Mar 2024 05:03:17 -0700 Subject: [PATCH 08/22] =?UTF-8?q?fix:=20frontend=20components=20&=20pages?= =?UTF-8?q?=E2=99=BB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/about/page.tsx | 99 +++++++-------- src/app/experience/page.tsx | 25 ++-- src/app/layout.tsx | 31 ++++- src/app/page.tsx | 23 ++-- src/components/clients/Experience.tsx | 2 +- src/components/clients/partials/NavBar.tsx | 136 ++++----------------- src/components/layouts/RootLayout.tsx | 43 ++----- 7 files changed, 135 insertions(+), 224 deletions(-) diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 87d4df0..e4f17bd 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,8 +1,9 @@ import { SNLightBulb } from '@icongo/sn'; +import aboutImage from '@/app/assets/about.svg'; import Image from 'next/image'; +import RootLayout from '@/components/layouts/RootLayout'; import Skills from '@/components/Skills'; import type { Metadata } from 'next'; -import aboutImage from '@/app/assets/about.svg'; export const metadata: Metadata = { title: "James Gates | I'm glad to meet you", @@ -20,58 +21,60 @@ export const metadata: Metadata = { export default function Page() { return ( -

-
-
-

- I'm James Gates, really glad to meet{' '} - you -

+ +
+
+
+

+ I'm James Gates, really glad to meet{' '} + you +

-

- Hi, everyone, I'm James Gates.
- I'm Senior Full Stack Developer and{' '} - SEO Expert. -
-
I have 5+ years' experience for web development. During last years, I had earned many skills to - develop and manage a website and it now helps for a new project to develop in a high quality, rapidly. -
-
I like to work with a simple communication, a clean and optimized code convention, a high quality - development for collaboration and maintenance, and a keeping schedules for a project. I also love learning a - new thing from analyzing, discussing and resolving variable claims. -

-
+

+ Hi, everyone, I'm James Gates.
+ I'm Senior Full Stack Developer and{' '} + SEO Expert. +
+
I have 5+ years' experience for web development. During last years, I had earned many skills to + develop and manage a website and it now helps for a new project to develop in a high quality, rapidly. +
+
I like to work with a simple communication, a clean and optimized code convention, a high quality + development for collaboration and maintenance, and a keeping schedules for a project. I also love learning + a new thing from analyzing, discussing and resolving variable claims. +

+
-
- About +
+ About +
-
-
- - Skills -
+
+ + Skills +
- + -
-

- For many aspects, such as Requirement Analysis, Architect Design, Database Modeling, Front-end/Back-end Coding - and Maintenance, I support reliable and qualified service. -

-
    -
  • Fast Progress, Best Quality and Constant Report
  • -
  • Cooperative Idea Support and so on
  • -
-
-
+
+

+ For many aspects, such as Requirement Analysis, Architect Design, Database Modeling, Front-end/Back-end + Coding and Maintenance, I support reliable and qualified service. +

+
    +
  • Fast Progress, Best Quality and Constant Report
  • +
  • Cooperative Idea Support and so on
  • +
+
+ + ); } diff --git a/src/app/experience/page.tsx b/src/app/experience/page.tsx index 4a18864..53b9ba7 100644 --- a/src/app/experience/page.tsx +++ b/src/app/experience/page.tsx @@ -1,5 +1,6 @@ -import type { Metadata } from 'next'; import Experience from '@/components/clients/Experience'; +import RootLayout from '@/components/layouts/RootLayout'; +import type { Metadata } from 'next'; export const metadata: Metadata = { title: 'James Gates | Experience & Education', @@ -16,15 +17,17 @@ export const metadata: Metadata = { export default function Page() { return ( -
-

- Experience - & - Education -

-
- -
-
+ +
+

+ Experience + & + Education +

+
+ +
+
+
); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index daa21de..177b342 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,11 @@ +import '@/app/globals.css'; import { getConfig } from '@/config'; -import RootLayout from '@/components/layouts/RootLayout'; +import { GoogleTagManager } from '@next/third-parties/google'; +import { Suspense } from 'react'; +import { ThemeModeScript } from 'flowbite-react'; +import Image from 'next/image'; +import PreImage from '@/app/assets/pre.svg'; +import ReactTooltip from '@/components/clients/ReactTooltip'; import type { Metadata, Viewport } from 'next'; const config = getConfig(); @@ -44,10 +50,29 @@ export const viewport: Viewport = { userScalable: true, }; -export default function Layout({ +const Loading = () => ( +
+ Loading... +
+); + +export default function BaseLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { - return {children}; + return ( + + + + + + }> + {children} + + + + {Boolean(config.google.tag_manager_id) && } + + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 0477f75..3011d3a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,12 @@ import { classNames } from '@/components/Commons'; +import avatarImage from '@/app/assets/avatar.svg'; +import homeImage from '@/app/assets/home.svg'; import Image from 'next/image'; +import Link from 'next/link'; import LinkInfos from '@/infos/Links'; +import RootLayout from '@/components/layouts/RootLayout'; import style from './style.module.css'; import type { Metadata } from 'next'; -import Link from 'next/link'; -import homeImage from '@/app/assets/home.svg'; -import avatarImage from '@/app/assets/avatar.svg'; export const metadata: Metadata = { title: 'James Gates | Senior Full Stack Developer & SEO Expert', @@ -15,7 +16,7 @@ export const metadata: Metadata = { export default function Page() { return ( - <> +
@@ -30,19 +31,19 @@ export default function Page() {

My name is - James Gates + James Gates

  • - Senior Full Stack Developer + Senior Full Stack Developer
  • - SEO Expert + SEO Expert
  • - Continuously Learning + Continuously Learning
@@ -65,7 +66,7 @@ export default function Page() {

- Let me Introduce myself + Let me Introduce myself

As an accomplished Senior Full Stack Developer specializing in SEO and MERN Stack development, I offer a @@ -127,7 +128,7 @@ export default function Page() {

FIND ME ON

- Feel free to connect with me + Feel free to connect with me

    {LinkInfos.map(({ href, icon: { dark: DarkIcon }, label }, idx) => ( @@ -147,6 +148,6 @@ export default function Page() {
- +
); } diff --git a/src/components/clients/Experience.tsx b/src/components/clients/Experience.tsx index fab1851..ef9e4b7 100644 --- a/src/components/clients/Experience.tsx +++ b/src/components/clients/Experience.tsx @@ -17,7 +17,7 @@ export default function Experience() { visible={true} >

{title}

-

+

{company} diff --git a/src/components/clients/partials/NavBar.tsx b/src/components/clients/partials/NavBar.tsx index 5bec4f9..a1cf891 100644 --- a/src/components/clients/partials/NavBar.tsx +++ b/src/components/clients/partials/NavBar.tsx @@ -1,126 +1,32 @@ 'use client'; -import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'; -import { classNames } from '@/components/Commons'; -import { Disclosure, Menu, Transition } from '@headlessui/react'; -import { Fragment } from 'react'; + +import { DarkThemeToggle, Navbar, NavbarBrand, NavbarCollapse, NavbarLink, NavbarToggle } from 'flowbite-react'; +import { i18n } from '@/i18n'; import { usePathname } from 'next/navigation'; +import { VLLinkedin } from '@icongo/vl'; import Link from 'next/link'; -import LinkInfos from '@/infos/Links'; import NavItems from '@/infos/NavItems'; -import { VLLinkedin } from '@icongo/vl'; -import { SNUserMale } from '@icongo/sn'; export default function NavBar() { const current = usePathname(); return ( - - {({ open }) => ( - <> -
-
-
- - - Open main menu - {open ? ( - -
-
-
- -
-
-
- {NavItems.map((item) => ( - - {item.name} - - ))} -
-
-
-
- -
- - - Open user menu - - -
- - - {LinkInfos.map(({ href, icon: { dark: DarkIcon }, label }, idx) => ( - - {({ active }) => ( - - {label} - - )} - - ))} - - -
-
-
-
- - -
- {NavItems.map((item) => ( - - {item.name} - - ))} -
-
- - )} -
+ + + + {i18n('app.title')} + +
+ + +
+ + {NavItems.map((item) => ( + + {item.name} + + ))} + +
); } diff --git a/src/components/layouts/RootLayout.tsx b/src/components/layouts/RootLayout.tsx index 0c5dff4..678d6b1 100644 --- a/src/components/layouts/RootLayout.tsx +++ b/src/components/layouts/RootLayout.tsx @@ -1,27 +1,9 @@ -import '@/app/globals.css'; -import { getConfig } from '@/config'; -import { GoogleTagManager } from '@next/third-parties/google'; -import { Raleway } from 'next/font/google'; -import { Suspense } from 'react'; +'use client'; + import Footer from '@/components/Footer'; -import Image from 'next/image'; -import NavBar from '@/components/clients/partials/NavBar'; -import PreImage from '@/app/assets/pre.svg'; import ReactTooltip from '@/components/clients/ReactTooltip'; import Stars from '@/components/clients/backgrounds/Stars'; - -const config = getConfig(); - -const Loading = () => ( -
- Loading... -
-); - -const raleway = Raleway({ - subsets: ['latin'], - display: 'swap', -}); +import NavBar from '@/components/clients/partials/NavBar'; export default function RootLayout({ children, @@ -29,19 +11,10 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - }> - -
- - {children} -
-
- -
- - {Boolean(config.google.tag_manager_id) && } - + <> + + {children} +