├── .eslintrc.json ├── public ├── image │ ├── biru.png │ ├── putih.png │ ├── blog-1.webp │ ├── blog1.webp │ ├── favicon.ico │ ├── images.webp │ └── image-about.webp └── js │ └── darkmode.js ├── postcss.config.js ├── lib ├── validate.js ├── checkLogin.js ├── fe │ └── login.js ├── init-middleware.js ├── checkOldImage.js ├── file.js ├── database.js ├── cloudinary.js └── MainScript.js ├── pages ├── _document.js ├── _app.js ├── api │ ├── images │ │ └── [name].js │ ├── category │ │ ├── [id].js │ │ └── index.js │ ├── hidden.js │ ├── portofolio │ │ ├── [id].js │ │ └── index.js │ ├── login.js │ └── blogs │ │ ├── [id].js │ │ └── index.js ├── hidden │ ├── index.js │ ├── category │ │ ├── add.js │ │ ├── edit │ │ │ └── [id].js │ │ └── index.js │ ├── login.js │ ├── portofolio │ │ ├── add.js │ │ ├── edit │ │ │ └── [id].js │ │ └── index.js │ └── blog │ │ ├── add.js │ │ ├── edit │ │ └── [id].js │ │ └── index.js ├── index.js ├── about.js ├── portofolio.js └── blog │ └── [[...slug]].js ├── components ├── front │ ├── BackToTop.js │ ├── HeroHome.js │ ├── BlogHome.js │ ├── PortoFolioHome.js │ ├── Header.js │ ├── AboutHome.js │ └── Footer.js └── admin │ └── header.js ├── next.config.js ├── .gitignore ├── tailwind.config.js ├── .github └── workflows │ └── cd.yml ├── styles ├── globals.css └── Home.module.css ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/image/biru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/biru.png -------------------------------------------------------------------------------- /public/image/putih.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/putih.png -------------------------------------------------------------------------------- /public/image/blog-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/blog-1.webp -------------------------------------------------------------------------------- /public/image/blog1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/blog1.webp -------------------------------------------------------------------------------- /public/image/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/favicon.ico -------------------------------------------------------------------------------- /public/image/images.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/images.webp -------------------------------------------------------------------------------- /public/image/image-about.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusenabdullah-123/myporto-website/HEAD/public/image/image-about.webp -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}), 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /public/js/darkmode.js: -------------------------------------------------------------------------------- 1 | if ( 2 | localStorage.theme === "dark" || 3 | (!("theme" in localStorage) && 4 | window.matchMedia("(prefers-color-scheme: dark)").matches) 5 | ) { 6 | document.documentElement.classList.add("dark"); 7 | } else { 8 | document.documentElement.classList.remove("dark"); 9 | } 10 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | import validator from "fastest-validator"; 2 | 3 | const valid = new validator(); 4 | const schemaLogin = { 5 | username: { type: "string", min: 5 }, 6 | password: { type: "string", min: 5 }, 7 | }; 8 | const validateLogin = valid.compile(schemaLogin); 9 | 10 | export default { validateLogin }; 11 | -------------------------------------------------------------------------------- /lib/checkLogin.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | const checkLogin = (token) => { 3 | return new Promise((resolve, reject) => { 4 | try { 5 | const isLogin = jwt.verify(token, process.env.PRIVATEKEY); 6 | resolve(isLogin); 7 | } catch (error) { 8 | reject(401); 9 | } 10 | }); 11 | }; 12 | export default checkLogin; 13 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /lib/fe/login.js: -------------------------------------------------------------------------------- 1 | import { getCookie } from "cookies-next"; 2 | const isLogin = (req, res) => { 3 | const cookie = getCookie("login", { req, res }); 4 | const token = getCookie("token", { req, res }) || ""; 5 | if (cookie !== true) { 6 | return { status: 401 }; 7 | } else { 8 | return { status: 200, token }; 9 | } 10 | }; 11 | 12 | export default isLogin; 13 | -------------------------------------------------------------------------------- /components/front/BackToTop.js: -------------------------------------------------------------------------------- 1 | const backtoTop = () => { 2 | return ( 3 | 8 | 9 | 10 | ); 11 | }; 12 | export default backtoTop; 13 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | eslint: { 5 | // Warning: This allows production builds to successfully complete even if 6 | // your project has ESLint errors. 7 | ignoreDuringBuilds: true, 8 | }, 9 | images: { 10 | domains: ["localhost", "kusenadev.my.id","res.cloudinary.com"], 11 | }, 12 | }; 13 | 14 | module.exports = nextConfig; 15 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import React from "react"; 3 | import Head from "next/head"; 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | <> 8 | 9 | Kusena Dev Portofolio 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export default MyApp; 18 | -------------------------------------------------------------------------------- /pages/api/images/[name].js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | 4 | export default async function handler(req, res) { 5 | if (req.method == "GET") { 6 | const dest = path.join(__dirname, "../../../../../public/uploads/"); 7 | const { name } = req.query; 8 | const image = fs.readFileSync(`${dest}${name}`); 9 | res.setHeader("Content-Type", "image/webp"); 10 | return res.send(image); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/init-middleware.js: -------------------------------------------------------------------------------- 1 | // Helper method to wait for a middleware to execute before continuing 2 | // And to throw an error when an error happens in a middleware 3 | export default function initMiddleware(middleware) { 4 | return (req, res) => 5 | new Promise((resolve, reject) => { 6 | middleware(req, res, (result) => { 7 | if (result instanceof Error) { 8 | return reject(result); 9 | } 10 | return resolve(result); 11 | }); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /pages/api/category/[id].js: -------------------------------------------------------------------------------- 1 | import querys from "../../../lib/database"; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method == "GET") { 5 | const { id } = req.query; 6 | if (id) { 7 | const result = await querys( 8 | "SELECT `_id`, `kategori` FROM `kategori` where _id = ?", 9 | [id] 10 | ); 11 | if (result.length >= 0) { 12 | return res.status(200).json({ kategori: result }); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | .env 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx}", 4 | "./components/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | darkMode: "class", 7 | theme: { 8 | container: { 9 | center: true, 10 | padding: "16px", 11 | }, 12 | extend: { 13 | colors: { 14 | primary: "#2563eb", 15 | dark: "#0f172a", 16 | }, 17 | screens: { 18 | "2xl": "1320px", 19 | }, 20 | }, 21 | }, 22 | plugins: [], 23 | }; 24 | -------------------------------------------------------------------------------- /lib/checkOldImage.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import querys from "./database"; 4 | const checkOldImage = async (_id, table) => { 5 | const oldImage = await querys(`SELECT image FROM ${table} WHERE _id = ?`, [ 6 | _id, 7 | ]); 8 | const destFile = path.join( 9 | __dirname, 10 | `../../../../public/uploads/${oldImage[0].image}` 11 | ); 12 | const isFile = fs.existsSync(destFile); 13 | 14 | if (isFile) { 15 | fs.unlinkSync(destFile); 16 | } 17 | }; 18 | export default checkOldImage; 19 | -------------------------------------------------------------------------------- /lib/file.js: -------------------------------------------------------------------------------- 1 | import { fileTypeFromFile } from "file-type"; 2 | 3 | const checkFile = (path) => { 4 | return new Promise(async (resolve, reject) => { 5 | try { 6 | const mime = await fileTypeFromFile(path); 7 | 8 | if (mime?.mime !== undefined) { 9 | resolve({ status: 200, message: "File Allowed" }); 10 | } else { 11 | reject({ status: 415, message: "Unsupport Media type" }); 12 | } 13 | } catch (error) { 14 | console.log(error); 15 | } 16 | }); 17 | }; 18 | 19 | export default { checkFile }; 20 | -------------------------------------------------------------------------------- /lib/database.js: -------------------------------------------------------------------------------- 1 | import { createConnection } from "mysql2"; 2 | const connection = createConnection({ 3 | host: process.env.NEXT_PUBLIC_HOST, 4 | user: process.env.NEXT_PUBLIC_USERNAME, 5 | database: process.env.NEXT_PUBLIC_DATABASE, 6 | password: process.env.NEXT_PUBLIC_PASSWORD, 7 | }); 8 | 9 | const querys = (query, data = []) => { 10 | return new Promise(async (resolve, reject) => { 11 | connection.execute(query, data, (err, results) => { 12 | if (err) { 13 | reject(err); 14 | } else { 15 | resolve(results); 16 | } 17 | }); 18 | }); 19 | }; 20 | 21 | export default querys; 22 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | 3 | on: [push] 4 | 5 | jobs: 6 | cd: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Deploy Using ssh 13 | uses: appleboy/ssh-action@master 14 | with: 15 | host: ${{ secrets.SSH_HOST }} 16 | username: ${{ secrets.SSH_USERNAME }} 17 | key: ${{ secrets.SSH_PRIVATE_KEY }} 18 | port: 22 19 | script: | 20 | cd ~/myporto-website 21 | git pull --force 22 | git status 23 | pnpm install 24 | pnpm build 25 | pm2 restart website --update-env 26 | -------------------------------------------------------------------------------- /pages/hidden/index.js: -------------------------------------------------------------------------------- 1 | import Header from "../../components/admin/header"; 2 | import isLogin from "../../lib/fe/login"; 3 | export default function Home() { 4 | return ( 5 |
6 | {/* Header Start */} 7 |
8 | {/* Header End */} 9 |
10 |

Home

11 |
12 |
13 | ); 14 | } 15 | 16 | export const getServerSideProps = ({ req, res }) => { 17 | const { status, token } = isLogin(req, res); 18 | if (status == 401) { 19 | return { 20 | redirect: { 21 | destination: "/hidden/login", 22 | permanent: false, 23 | }, 24 | }; 25 | } 26 | 27 | return { props: { token } }; 28 | }; 29 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .navbar-fixed { 6 | @apply fixed z-[999] bg-white bg-opacity-75 backdrop-blur-sm dark:bg-dark dark:opacity-50; 7 | box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.2); 8 | } 9 | .hamburger-line { 10 | @apply my-2 block h-[2px] w-[30px] bg-dark dark:bg-white; 11 | } 12 | .hamburger-transision { 13 | @apply transition duration-300 ease-in-out; 14 | } 15 | .hamburger-active > span:nth-child(1) { 16 | @apply rotate-45; 17 | } 18 | 19 | .hamburger-active > span:nth-child(2) { 20 | @apply scale-0; 21 | } 22 | .hamburger-active > span:nth-child(3) { 23 | @apply -rotate-45; 24 | } 25 | 26 | #dark-toogle:checked ~ label div.toogle-circle { 27 | @apply translate-x-3; 28 | } 29 | 30 | .paginate-active { 31 | @apply bg-primary text-white; 32 | } 33 | -------------------------------------------------------------------------------- /lib/cloudinary.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require("cloudinary").v2; 2 | cloudinary.config({ 3 | secure: true, 4 | api_key: process.env.CLOUDINARY_KEY, 5 | api_secret: process.env.CLOUDINARY_SECRET, 6 | cloud_name: process.env.CLOUDINARY_NAME, 7 | }); 8 | 9 | const uploadImage = async (imagePath) => { 10 | const options = { 11 | use_filename: true, 12 | unique_filename: false, 13 | overwrite: true, 14 | }; 15 | 16 | try { 17 | // Upload the image 18 | const result = await cloudinary.uploader.upload(imagePath, options); 19 | 20 | return result; 21 | } catch (error) { 22 | console.error(error); 23 | } 24 | }; 25 | 26 | const destroyImage = async (publicId) => { 27 | try { 28 | const result = await cloudinary.uploader.destroy(publicId); 29 | return result; 30 | } catch (error) { 31 | console.error(error); 32 | } 33 | }; 34 | export { uploadImage, destroyImage }; 35 | -------------------------------------------------------------------------------- /pages/api/hidden.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import querys from "../../lib/database"; 3 | import bcrypt from "bcrypt"; 4 | 5 | export default async function handler(req, res) { 6 | if (req.method == "POST") { 7 | const { key } = req.query; 8 | const { username, password } = req.body; 9 | const saltRounds = 5; 10 | if (key == "KusenaDev") { 11 | try { 12 | const pass = await bcrypt.hash(password, saltRounds); 13 | 14 | const { affectedRows } = await querys( 15 | "INSERT INTO `admin`(`nama`, `username`, `password`) VALUES (?,?,?)", 16 | ["KusenaDev", username, pass] 17 | ); 18 | 19 | if (affectedRows > 0) { 20 | res.status(200).json({ status: 200, message: "user admin inserted" }); 21 | } 22 | } catch (error) { 23 | console.log(error); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "NODE_ENV=production next build", 8 | "start": "NODE_ENV=production next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.27.2", 13 | "bcrypt": "^5.0.1", 14 | "cloudinary": "^1.32.0", 15 | "cookies-next": "^2.0.4", 16 | "cors": "^2.8.5", 17 | "fastest-validator": "^1.12.0", 18 | "file-type": "^17.1.1", 19 | "immutable": "^4.1.0", 20 | "jsonwebtoken": "^8.5.1", 21 | "multer": "1.4.4-lts.1", 22 | "mysql2": "^2.3.3", 23 | "next": "12.1.6", 24 | "next-connect": "^0.12.2", 25 | "react": "18.1.0", 26 | "react-dom": "18.1.0", 27 | "react-hook-form": "^7.31.3", 28 | "sharp": "^0.30.6", 29 | "suneditor": "^2.43.8", 30 | "suneditor-react": "^3.4.0" 31 | }, 32 | "devDependencies": { 33 | "autoprefixer": "^10.4.7", 34 | "cssnano": "^5.1.10", 35 | "eslint": "8.16.0", 36 | "eslint-config-next": "12.1.6", 37 | "postcss": "^8.4.14", 38 | "prettier": "^2.6.2", 39 | "prettier-plugin-tailwindcss": "^0.1.11", 40 | "tailwindcss": "^3.0.24" 41 | } 42 | } -------------------------------------------------------------------------------- /pages/api/portofolio/[id].js: -------------------------------------------------------------------------------- 1 | import querys from "../../../lib/database"; 2 | import checkLogin from "../../../lib/checkLogin"; 3 | import initMiddleware from "../../../lib/init-middleware"; 4 | import Cors from "cors"; 5 | const cors = initMiddleware( 6 | // You can read more about the available options here: https://github.com/expressjs/cors#configuration-options 7 | Cors() 8 | ); 9 | export default async function handler(req, res) { 10 | await cors(req, res); 11 | if (req.method == "GET") { 12 | const { id } = req.query; 13 | const result = await querys("select * from portofolio where `_id` = ?", [ 14 | id, 15 | ]); 16 | 17 | return res.status(200).json({ data: result }); 18 | } 19 | if (req.method == "PATCH") { 20 | const { authorization } = req.headers; 21 | if (authorization) { 22 | const token = authorization.split(" ")[1]; 23 | const isLogin = await checkLogin(token); 24 | if (isLogin.login !== true) { 25 | return res.status(401).json({ status: 401, message: "Unauthorized" }); 26 | } 27 | } else { 28 | return res.status(401).json({ status: 401, message: "Unauthorized" }); 29 | } 30 | const { id } = req.query; 31 | const { status } = req.body; 32 | await querys("UPDATE `portofolio` SET `status`= ? WHERE `_id`= ?", [ 33 | status, 34 | id, 35 | ]); 36 | return res.status(200).json({ status: 200, message: "Success Updated" }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pages/api/login.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import querys from "../../lib/database"; 3 | import bcrypt from "bcrypt"; 4 | import jwt from "jsonwebtoken"; 5 | import validate from "../../lib/validate"; 6 | 7 | export default async function handler(req, res) { 8 | if (req.method == "POST") { 9 | const { username, password } = req.body; 10 | const resultCheckLogin = validate.validateLogin({ username, password }); 11 | if (Array.isArray(resultCheckLogin)) { 12 | return res.status(400).json({ status: 400, message: resultCheckLogin }); 13 | } 14 | const result = await querys( 15 | "SELECT admin.username,admin.password FROM admin " 16 | ); 17 | if (result[0].username == username) { 18 | const checkPass = await bcrypt.compare(password, result[0].password); 19 | 20 | if (checkPass) { 21 | const privateKey = process.env.PRIVATEKEY; 22 | try { 23 | const token = jwt.sign( 24 | { nama: "kusenadev", login: true }, 25 | privateKey 26 | ); 27 | return res.status(200).json({ status: 200, token, login: true }); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | } else { 32 | return res.status(401).json({ status: 401, message: "Failed Login" }); 33 | } 34 | } else { 35 | return res.status(401).json({ status: 401, message: "Failed Login" }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /pages/api/blogs/[id].js: -------------------------------------------------------------------------------- 1 | import querys from "../../../lib/database"; 2 | import checkLogin from "../../../lib/checkLogin"; 3 | import initMiddleware from "../../../lib/init-middleware"; 4 | import Cors from "cors"; 5 | const cors = initMiddleware( 6 | // You can read more about the available options here: https://github.com/expressjs/cors#configuration-options 7 | Cors() 8 | ); 9 | export default async function handler(req, res) { 10 | await cors(req, res); 11 | if (req.method == "GET") { 12 | const { id } = req.query; 13 | const result = await querys( 14 | "SELECT `blogs`.`_id`, `blogs`.`title`, `blogs`.`metaKeyword`,`blogs`.`metaDescription`, `blogs`.`image`,`blogs`.`description`,`blogs`.`content`, `status`,`kategori`.`_id` AS category_id,`kategori`.`kategori` FROM `blogs` join `kategori` on `blogs`.`kategori_id` = `kategori`._id where `blogs`.`_id` = ?", 15 | [id] 16 | ); 17 | 18 | return res.status(200).json({ data: result }); 19 | } 20 | if (req.method == "PATCH") { 21 | const { authorization } = req.headers; 22 | if (authorization) { 23 | const token = authorization.split(" ")[1]; 24 | const isLogin = await checkLogin(token); 25 | if (isLogin.login !== true) { 26 | return res.status(401).json({ status: 401, message: "Unauthorized" }); 27 | } 28 | } else { 29 | return res.status(401).json({ status: 401, message: "Unauthorized" }); 30 | } 31 | const { id } = req.query; 32 | const { status } = req.body; 33 | const result = await querys( 34 | "UPDATE `blogs` SET `status`= ? WHERE `_id`= ?", 35 | [status, id] 36 | ); 37 | return res.status(200).json({ status: 200, message: "Success Updated" }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /components/front/HeroHome.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | const heroHome = () => { 3 | return ( 4 |
5 |
6 |
7 |
8 |

9 | Halo All 👋 , Saya 10 | 11 | Kusena Dev 12 | 13 |

14 |

15 | Fullstack Developer 16 |

17 |

18 | Belajar Web Developer 19 |

20 | 24 | Hubungi Saya 25 | 26 |
27 |
28 |
29 |
30 | Kusen Abdullah 37 |
38 |
39 |
40 |
41 |
42 |
43 | ); 44 | }; 45 | export default heroHome; 46 | -------------------------------------------------------------------------------- /components/admin/header.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { removeCookies } from "cookies-next"; 3 | import { useRouter } from "next/router"; 4 | const Header = () => { 5 | const router = useRouter(); 6 | const onLogout = () => { 7 | removeCookies("login"); 8 | removeCookies("token"); 9 | router.push("/hidden/login"); 10 | }; 11 | return ( 12 | <> 13 | {/* Header Start */} 14 |
15 |
16 | 17 | Kusena Dev 18 | 19 | 20 | 57 |
58 |
59 | {/* Header End */} 60 | 61 | ); 62 | }; 63 | export default Header; 64 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pages/hidden/category/add.js: -------------------------------------------------------------------------------- 1 | import Header from "../../../components/admin/header"; 2 | import { useForm } from "react-hook-form"; 3 | import axios from "axios"; 4 | import { getCookie } from "cookies-next"; 5 | import { useRouter } from "next/router"; 6 | import isLogin from "../../../lib/fe/login"; 7 | const AddCategory = ({ url }) => { 8 | const router = useRouter(); 9 | const { register, handleSubmit } = useForm(); 10 | const onSubmit = async (data) => { 11 | console.log(data); 12 | try { 13 | const token = getCookie("token") || ""; 14 | const response = await axios({ 15 | method: "POST", 16 | url: `${url}api/category/`, 17 | data, 18 | headers: { 19 | authorization: `Bearer ${token}`, 20 | }, 21 | }); 22 | if (response.status == 200) { 23 | router.push("/hidden/category"); 24 | } 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | }; 29 | 30 | return ( 31 |
32 | {/* Header Start */} 33 |
34 | {/* Header End */} 35 |
36 |

Add Category

37 |
38 |
39 | 48 | 49 | 55 |
56 |
57 |
58 |
59 | ); 60 | }; 61 | 62 | export default AddCategory; 63 | 64 | export const getServerSideProps = async ({ req, res }) => { 65 | const url = process.env.NEXT_PUBLIC_URL; 66 | const { status } = isLogin(req, res); 67 | if (status == 401) { 68 | return { 69 | redirect: { 70 | destination: "/hidden/login", 71 | permanent: false, 72 | }, 73 | }; 74 | } 75 | 76 | return { props: { url } }; 77 | }; 78 | -------------------------------------------------------------------------------- /lib/MainScript.js: -------------------------------------------------------------------------------- 1 | const mainScript = () => { 2 | // function change color icon 3 | const changeIcons = (icons, color) => { 4 | icons.forEach((el) => { 5 | el.children[0].children[0].attributes[0].value = color; 6 | }); 7 | }; 8 | 9 | // Navbar Fixed 10 | window.onscroll = () => { 11 | const header = document.querySelector("header"); 12 | const fixednav = header.offsetTop; 13 | const toTop = document.querySelector("#to-top"); 14 | if (window.pageYOffset > fixednav) { 15 | header.classList.add("navbar-fixed"); 16 | toTop.classList.remove("hidden"); 17 | toTop.classList.add("flex"); 18 | return; 19 | } 20 | header.classList.remove("navbar-fixed"); 21 | toTop.classList.remove("flex"); 22 | toTop.classList.add("hidden"); 23 | }; 24 | 25 | // Hamburger 26 | const hamburger = document.querySelector("#hamburger"); 27 | const navMenu = document.querySelector("#nav-menu"); 28 | 29 | hamburger.addEventListener("click", () => { 30 | hamburger.classList.toggle("hamburger-active"); 31 | navMenu.classList.toggle("hidden"); 32 | }); 33 | 34 | window.addEventListener("click", (e) => { 35 | if (e.target != hamburger && e.target != navMenu) { 36 | hamburger.classList.remove("hamburger-active"); 37 | navMenu.classList.add("hidden"); 38 | } 39 | }); 40 | 41 | // Dark Mode 42 | const darkToogle = document.querySelector("#dark-toogle"); 43 | const html = document.querySelector("html"); 44 | const icon = document.querySelectorAll("span#icon-blog"); 45 | const imgLogo = document.querySelector("header img#logo-header"); 46 | 47 | darkToogle.addEventListener("click", (e) => { 48 | if (darkToogle.checked) { 49 | imgLogo.src = "/image/putih.png"; 50 | html.classList.add("dark"); 51 | localStorage.theme = "dark"; 52 | changeIcons(icon, "white"); 53 | return; 54 | } 55 | imgLogo.src = "/image/biru.png"; 56 | html.classList.remove("dark"); 57 | localStorage.theme = "light"; 58 | changeIcons(icon, "black"); 59 | }); 60 | 61 | // toogle dark mode and toogle color icon 62 | 63 | if ( 64 | localStorage.theme === "dark" || 65 | (!("theme" in localStorage) && 66 | window.matchMedia("(prefers-color-scheme: dark)").matches) 67 | ) { 68 | imgLogo.src = "/image/putih.png"; 69 | darkToogle.checked = true; 70 | changeIcons(icon, "white"); 71 | } else { 72 | imgLogo.src = "/image/biru.png"; 73 | darkToogle.checked = false; 74 | changeIcons(icon, "black"); 75 | } 76 | }; 77 | export default mainScript; 78 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Header from "../components/front/Header"; 2 | import HeroHome from "../components/front/HeroHome"; 3 | import AboutHome from "../components/front/AboutHome"; 4 | import PortoFolioHome from "../components/front/PortoFolioHome"; 5 | import BlogHome from "../components/front/BlogHome"; 6 | import Footer from "../components/front/Footer"; 7 | import BackToTop from "../components/front/BackToTop"; 8 | import { useEffect } from "react"; 9 | import mainScript from "../lib/MainScript"; 10 | import Head from "next/head"; 11 | import Script from "next/script"; 12 | import axios from "axios"; 13 | export default function Home({ url, portofolio, blogs }) { 14 | useEffect(() => { 15 | mainScript(); 16 | }, []); 17 | return ( 18 |
19 | 20 | 21 | 22 | 26 | 27 | {/*