├── .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 |
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 |
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 |
21 |
22 |
23 |
24 | Home
25 |
26 |
27 |
28 |
29 | Portofolio
30 |
31 |
32 |
33 |
34 | Blogs
35 |
36 |
37 |
38 |
39 | Category
40 |
41 |
42 |
43 | {
45 | onLogout();
46 | }}
47 | className="text-sm px-3 font-normal"
48 | >
49 | Logout
50 |
51 | {/*
52 | Logout
53 | */}
54 |
55 |
56 |
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 |
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 | {/* */}
28 |
29 |
30 | {/* Header Start */}
31 |
32 | {/* Header End */}
33 | {/* Hero Section Start */}
34 |
35 | {/* Hero Section End */}
36 | {/* About Section Start */}
37 |
38 | {/* About Section End */}
39 | {/* Portfolio Section Start */}
40 |
41 | {/* Portfolio Section End */}
42 | {/* Blog Section */}
43 |
44 | {/* Blog Section End */}
45 | {/* Footer */}
46 |
47 | {/* Footer End */}
48 | {/* Back To Top */}
49 |
50 | {/* Back To Top */}
51 |
55 |
56 | );
57 | }
58 |
59 | export async function getServerSideProps({ req, res }) {
60 | const url = process.env.NEXT_PUBLIC_URL;
61 | const blogs = await axios.get(`${url}api/blogs/?limit=3`);
62 |
63 | const portfolio = await axios.get(`${url}api/portofolio/?limit=2`);
64 | // res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
65 |
66 | return {
67 | props: {
68 | blogs: blogs.data.blogs,
69 | portofolio: portfolio.data.portofolio,
70 | url,
71 | },
72 | };
73 | }
74 |
--------------------------------------------------------------------------------
/components/front/BlogHome.js:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | const BlogHome = ({ blogs }) => {
3 | return (
4 |
5 |
6 |
7 |
8 |
Blog
9 |
10 | Tulisan Terkini
11 |
12 |
Artikel Up to Date
13 |
14 |
15 |
16 | {blogs.map((item, index) => {
17 | return (
18 |
19 |
20 |
21 |
28 |
29 |
30 |
55 |
56 |
57 | );
58 | })}
59 |
60 |
61 |
62 | );
63 | };
64 | export default BlogHome;
65 |
--------------------------------------------------------------------------------
/pages/about.js:
--------------------------------------------------------------------------------
1 | import Header from "../components/front/Header";
2 | import Footer from "../components/front/Footer";
3 | import BackToTop from "../components/front/BackToTop";
4 | import { useEffect } from "react";
5 | import mainScript from "../lib/MainScript";
6 | import Script from "next/script";
7 | import Image from "next/image";
8 | const About = () => {
9 | useEffect(() => {
10 | mainScript();
11 | }, []);
12 | return (
13 |
14 | {/* Header Start */}
15 |
16 |
17 | {/* Header End */}
18 | {/* About Section Start */}
19 |
23 |
24 |
25 | Tentang Saya
26 |
27 |
28 |
29 |
30 |
31 |
37 |
38 | {/*
*/}
39 |
40 |
41 |
42 | M Kusen Abdullah
43 |
44 |
45 | Halo! Saya Kusen Abdullah fullstack developer, saya mulai
46 | tertarik menjadi Programmer sejak lulus smk, dan dari situ
47 | saya terus belajar mengasah skill Programmer dari berbagai
48 | sumber terutama media youtube. Semenjak saya belajar awal
49 | sampai sekarang banyak pengalaman yang saya peroleh mulai dari
50 | bagaimana cara menganangi error sampai optimasi sebuah
51 | aplikasi / website
52 |
53 |
54 |
55 |
56 |
57 |
58 | {/* About Section End */}
59 | {/* Footer */}
60 |
61 | {/* Footer End */}
62 | {/* Back To Top */}
63 |
64 | {/* Back To Top */}
65 |
66 | );
67 | };
68 | export default About;
69 |
--------------------------------------------------------------------------------
/pages/hidden/category/edit/[id].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 EditCategory = ({ category, url }) => {
8 | const router = useRouter();
9 | const { register, handleSubmit } = useForm();
10 | const onSubmit = async (data) => {
11 | try {
12 | const token = getCookie("token") || "";
13 | const response = await axios({
14 | method: "PUT",
15 | url: `${url}api/category/`,
16 | data: { category: data.category, _id: category._id },
17 | headers: {
18 | authorization: `Bearer ${token}`,
19 | },
20 | });
21 | if (response.status == 200) {
22 | router.push("/hidden/category");
23 | }
24 | } catch (error) {
25 | console.log(error);
26 | }
27 | };
28 |
29 | return (
30 |
31 | {/* Header Start */}
32 |
33 | {/* Header End */}
34 |
35 | Add Portofolio
36 |
37 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default EditCategory;
66 |
67 | export const getServerSideProps = async ({ req, res, params }) => {
68 | const url = process.env.NEXT_PUBLIC_URL;
69 | const { status } = isLogin(req, res);
70 | if (status == 401) {
71 | return {
72 | redirect: {
73 | destination: "/hidden/login",
74 | permanent: false,
75 | },
76 | };
77 | }
78 | const result = await axios.get(`${url}api/category/${params.id}`);
79 |
80 | return { props: { category: result.data.kategori[0], url } };
81 | };
82 |
--------------------------------------------------------------------------------
/pages/api/category/index.js:
--------------------------------------------------------------------------------
1 | import querys from "../../../lib/database";
2 | import checkLogin from "../../../lib/checkLogin";
3 | export default async function handler(req, res) {
4 | if (req.method == "GET") {
5 | try {
6 | const result = await querys("SELECT `_id`, `kategori` FROM `kategori`");
7 | if (result.length >= 0) {
8 | return res.status(200).json({ kategori: result });
9 | }
10 | } catch (error) {
11 | return res.status(500).json({ message: "server error" });
12 | }
13 | } else if (req.method == "POST") {
14 | const { authorization } = req.headers;
15 | if (authorization) {
16 | const token = authorization.split(" ")[1];
17 | const isLogin = await checkLogin(token);
18 | if (isLogin.login !== true) {
19 | return res.status(401).json({ status: 401, message: "Unauthorized" });
20 | }
21 | } else {
22 | return res.status(401).json({ status: 401, message: "Unauthorized" });
23 | }
24 | const { category } = req.body;
25 | const result = await querys(
26 | "INSERT INTO `kategori`(`kategori`) VALUES (?)",
27 | [category]
28 | );
29 | if (result.affectedRows >= 1) {
30 | return res.status(200).json({ status: 200, message: "success" });
31 | }
32 | } else if (req.method == "PUT") {
33 | const { authorization } = req.headers;
34 | if (authorization) {
35 | const token = authorization.split(" ")[1];
36 | const isLogin = await checkLogin(token);
37 | if (isLogin.login !== true) {
38 | return res.status(401).json({ status: 401, message: "Unauthorized" });
39 | }
40 | } else {
41 | return res.status(401).json({ status: 401, message: "Unauthorized" });
42 | }
43 | const { _id, category } = req.body;
44 | console.log(req.body);
45 | const result = await querys(
46 | "UPDATE `kategori` SET `_id`=?,`kategori`=? WHERE `kategori`.`_id` = ?",
47 | [_id, category, _id]
48 | );
49 | console.log(result);
50 | if (result.affectedRows >= 1) {
51 | return res.status(200).json({ status: 200, message: "success" });
52 | }
53 | } else if (req.method == "DELETE") {
54 | const { authorization } = req.headers;
55 | if (authorization) {
56 | const token = authorization.split(" ")[1];
57 | const isLogin = await checkLogin(token);
58 | if (isLogin.login !== true) {
59 | return res.status(401).json({ status: 401, message: "Unauthorized" });
60 | }
61 | } else {
62 | return res.status(401).json({ status: 401, message: "Unauthorized" });
63 | }
64 | const { _id } = req.body;
65 | const result = await querys("DELETE FROM `kategori` WHERE _id = ?", [_id]);
66 | if (result.affectedRows >= 1) {
67 | return res.status(200).json({ status: 200, message: "success" });
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/pages/hidden/login.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import axios from "axios";
3 | import { setCookies, getCookie } from "cookies-next";
4 | import { useRouter } from "next/router";
5 | const Login = ({ url }) => {
6 | const router = useRouter();
7 | const [username, setUsername] = useState("");
8 | const [password, setPassword] = useState("");
9 | const handleSubmit = async (e) => {
10 | e.preventDefault();
11 | try {
12 | const response = await axios.post(`${url}api/login/`, {
13 | username,
14 | password,
15 | });
16 | setCookies("login", response.data.login);
17 | setCookies("token", response.data.token);
18 | router.push("/hidden");
19 | } catch (error) {
20 | console.log(error);
21 | }
22 | };
23 | return (
24 |
70 | );
71 | };
72 | export default Login;
73 |
74 | export const getServerSideProps = ({ req, res }) => {
75 | const url = process.env.NEXT_PUBLIC_URL;
76 | const cookie = getCookie("login", { req, res });
77 | if (cookie == true) {
78 | return {
79 | redirect: {
80 | destination: "/hidden",
81 | permanent: false,
82 | // statusCode: 301,
83 | },
84 | };
85 | }
86 |
87 | return { props: { url } };
88 | };
89 |
--------------------------------------------------------------------------------
/pages/hidden/portofolio/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 AddPortofolio = ({ url }) => {
8 | const router = useRouter();
9 | const { register, handleSubmit } = useForm();
10 | const onSubmit = async (data) => {
11 | try {
12 | console.log(data);
13 | const formData = new FormData();
14 | formData.append("title", data.title);
15 | formData.append("description", data.description);
16 | formData.append("github", data.github);
17 | formData.append("link", data.link);
18 | formData.append("image", data.image[0]);
19 |
20 | const token = getCookie("token") || "";
21 | const response = await axios({
22 | method: "POST",
23 | url: `${url}api/portofolio/`,
24 | data: formData,
25 | headers: {
26 | "Content-Type": "multipart/form-data",
27 | authorization: `Bearer ${token}`,
28 | },
29 | });
30 | if (response.status == 200) {
31 | router.push("/hidden/portofolio");
32 | }
33 | } catch (error) {
34 | console.log(error);
35 | }
36 | };
37 |
38 | return (
39 |
40 | {/* Header Start */}
41 |
42 | {/* Header End */}
43 |
44 | Add Portofolio
45 |
46 |
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default AddPortofolio;
107 |
108 | export const getServerSideProps = async ({ req, res }) => {
109 | const url = process.env.NEXT_PUBLIC_URL;
110 | const { status } = isLogin(req, res);
111 | if (status == 401) {
112 | return {
113 | redirect: {
114 | destination: "/hidden/login",
115 | permanent: false,
116 | },
117 | };
118 | }
119 |
120 | return { props: { url } };
121 | };
122 |
--------------------------------------------------------------------------------
/components/front/PortoFolioHome.js:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | const PortoFolioHome = ({ portofolio, url }) => {
4 | return (
5 |
9 |
10 |
11 |
12 |
Portfolio
13 |
14 | Project Terbaru
15 |
16 |
17 | Project Terbaru Yang Saya Buat
18 |
19 |
20 |
21 |
22 | {portofolio.map((item, index) => {
23 | return (
24 |
25 |
26 |
33 |
34 |
35 | {item.title}
36 |
37 |
38 | {item.description}
39 |
40 |
79 |
80 | );
81 | })}
82 |
83 |
84 |
85 | );
86 | };
87 | export default PortoFolioHome;
88 |
--------------------------------------------------------------------------------
/pages/hidden/portofolio/edit/[id].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 EditPortofolio = ({ portofolio, url }) => {
8 | const router = useRouter();
9 | const { register, handleSubmit } = useForm();
10 | const onSubmit = async (data) => {
11 | try {
12 | const formData = new FormData();
13 | formData.append("_id", portofolio._id);
14 | formData.append("title", data.title);
15 | formData.append("github", data.github);
16 | formData.append("link", data.link);
17 | formData.append("description", data.description);
18 | if (data.image.length > 0) {
19 | formData.append("image", data.image[0]);
20 | }
21 |
22 | const token = getCookie("token") || "";
23 | const response = await axios({
24 | method: "PUT",
25 | url: `${url}api/portofolio/`,
26 | data: formData,
27 | headers: {
28 | "Content-Type": "multipart/form-data",
29 | authorization: `Bearer ${token}`,
30 | },
31 | });
32 | if (response.status == 200) {
33 | router.push("/hidden/portofolio");
34 | }
35 | } catch (error) {
36 | console.log(error);
37 | }
38 | };
39 |
40 | return (
41 |
108 | );
109 | };
110 |
111 | export default EditPortofolio;
112 |
113 | export const getServerSideProps = async ({ req, res, params }) => {
114 | const url = process.env.NEXT_PUBLIC_URL;
115 | const { status } = isLogin(req, res);
116 | if (status == 401) {
117 | return {
118 | redirect: {
119 | destination: "/hidden/login",
120 | permanent: false,
121 | },
122 | };
123 | }
124 | const result = await axios.get(`${url}api/portofolio/${params.id}`);
125 |
126 | return { props: { portofolio: result.data.data[0], url } };
127 | };
128 |
--------------------------------------------------------------------------------
/pages/portofolio.js:
--------------------------------------------------------------------------------
1 | import Header from "../components/front/Header";
2 | import Footer from "../components/front/Footer";
3 | import BackToTop from "../components/front/BackToTop";
4 | import { useEffect } from "react";
5 | import mainScript from "../lib/MainScript";
6 | import Script from "next/script";
7 | import Image from "next/image";
8 | import axios from "axios";
9 | import Link from "next/link";
10 | const Portofolio = ({ portofolio }) => {
11 | useEffect(() => {
12 | mainScript();
13 | }, []);
14 | return (
15 |
16 |
17 | {/* Header Start */}
18 |
19 | {/* Header End */}
20 | {/* Portofolio Section Start */}
21 |
22 |
23 |
24 | Portofolio
25 |
26 |
27 |
28 | {portofolio.map((item, index) => {
29 | return (
30 |
31 |
40 |
41 | {item.title}
42 |
43 |
44 | {item.description}
45 |
46 |
85 |
86 | );
87 | })}
88 |
89 |
90 |
91 |
92 | {/* Portofolio Section End */}
93 | {/* Footer */}
94 |
95 | {/* Footer End */}
96 | {/* Back To Top */}
97 |
98 | {/* Back To Top */}
99 |
100 | );
101 | };
102 |
103 | export async function getServerSideProps({ req, res }) {
104 | const url = process.env.NEXT_PUBLIC_URL;
105 | const porto = await axios.get(`${url}api/portofolio/`);
106 | const portofolio = porto.data.portofolio.filter((item) => item.status == "1");
107 | return {
108 | props: {
109 | portofolio,
110 | },
111 | };
112 | }
113 |
114 | export default Portofolio;
115 |
--------------------------------------------------------------------------------
/components/front/Header.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | const header = () => {
4 | return (
5 |
6 |
7 |
8 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
117 |
118 |
119 |
120 |
121 | );
122 | };
123 |
124 | export default header;
125 |
--------------------------------------------------------------------------------
/pages/hidden/category/index.js:
--------------------------------------------------------------------------------
1 | import Header from "../../../components/admin/header";
2 | import Link from "next/link";
3 | import { getCookie, removeCookies } from "cookies-next";
4 | import axios from "axios";
5 | import { useRouter } from "next/router";
6 | import isLogin from "../../../lib/fe/login";
7 |
8 | export default function HomeCategory({ data, url }) {
9 | const router = useRouter();
10 |
11 | const onDelete = async (_id) => {
12 | const token = getCookie("token") || "";
13 |
14 | const response = await axios({
15 | method: "DELETE",
16 | url: `${url}api/category/`,
17 | data: { _id },
18 | headers: {
19 | authorization: `Bearer ${token}`,
20 | },
21 | });
22 | if (response.status == 200) {
23 | router.push("/hidden/category");
24 | }
25 | };
26 | return (
27 |
28 |
29 |
30 | Portofolio
31 |
32 |
33 |
34 |
35 |
44 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Category
58 |
59 | Actions
60 |
61 |
62 |
63 | {data.map((item, index) => {
64 | return (
65 |
69 | {item.kategori}
70 |
71 |
72 |
73 |
75 | router.push(`/hidden/category/edit/${item._id}`)
76 | }
77 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
78 | aria-label="Edit"
79 | >
80 |
86 |
87 |
88 |
89 |
{
91 | onDelete(item._id);
92 | }}
93 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
94 | aria-label="Delete"
95 | >
96 |
102 |
107 |
108 |
109 |
110 |
111 |
112 | );
113 | })}
114 |
115 |
116 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | export const getServerSideProps = async ({ req, res }) => {
124 | const url = process.env.NEXT_PUBLIC_URL;
125 | const { status, token } = isLogin(req, res);
126 | if (status == 401) {
127 | return {
128 | redirect: {
129 | destination: "/hidden/login",
130 | permanent: false,
131 | },
132 | };
133 | }
134 | const response = await axios.get(`${url}api/category/`, {
135 | headers: {
136 | authorization: `Bearer ${token}`,
137 | },
138 | });
139 |
140 | if (response.status == 401) {
141 | removeCookies("login", { req, res });
142 | removeCookies("token", { req, res });
143 | return {
144 | redirect: {
145 | destination: "/hidden/login",
146 | permanent: false,
147 | // statusCode: 301,
148 | },
149 | };
150 | }
151 |
152 | return { props: { data: response.data.kategori, url } };
153 | };
154 |
--------------------------------------------------------------------------------
/pages/api/portofolio/index.js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import multer from "multer";
3 | import path from "path";
4 | import file from "../../../lib/file";
5 | import fs from "fs";
6 | import querys from "../../../lib/database";
7 | import checkLogin from "../../../lib/checkLogin";
8 | // import checkOldImage from "../../../lib/checkOldImage";
9 | import Cors from "cors";
10 | import { uploadImage, destroyImage } from "../../../lib/cloudinary";
11 | const upload = multer({
12 | storage: multer.diskStorage({
13 | destination: "./public/uploads",
14 | filename: (req, file, cb) => cb(null, file.originalname),
15 | }),
16 | });
17 |
18 | const apiRoute = nextConnect({
19 | onError(error, req, res) {
20 | res
21 | .status(501)
22 | .json({ error: `Sorry something Happened! ${error.message}` });
23 | },
24 | onNoMatch(req, res, next) {
25 | res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
26 | },
27 | });
28 |
29 | apiRoute.use(Cors());
30 | apiRoute.use(upload.single("image"));
31 |
32 | apiRoute.get(async (req, res) => {
33 | try {
34 | if (req.query.limit) {
35 | const result = await querys(
36 | "SELECT `_id`, `title`, `description`,`status`,`github`,`link` ,`image` FROM `portofolio` ORDER BY `_id` DESC LIMIT ?",
37 | [req.query.limit]
38 | );
39 | if (result.length >= 0) {
40 | return res.status(200).json({ portofolio: result });
41 | }
42 | }
43 | const result = await querys(
44 | "SELECT `_id`, `title`, `description`,`github`,`link`,`status`, `image` FROM `portofolio`"
45 | );
46 | if (result.length >= 0) {
47 | return res.status(200).json({ portofolio: result });
48 | }
49 | } catch (error) {
50 | res.status(500).json({ message: "server error" });
51 | }
52 | });
53 |
54 | apiRoute.post(async (req, res) => {
55 | try {
56 | const { authorization } = req.headers;
57 | if (authorization) {
58 | const token = authorization.split(" ")[1];
59 | const isLogin = await checkLogin(token);
60 | if (isLogin.login !== true) {
61 | return res.status(401).json({ status: 401, message: "Unauthorized" });
62 | }
63 | } else {
64 | return res.status(401).json({ status: 401, message: "Unauthorized" });
65 | }
66 | const { title, description, github, link } = req.body;
67 |
68 | const dest = path.join(__dirname, `../../../../${req.file.path}`);
69 | const { status } = await file.checkFile(dest);
70 | if (status == 200) {
71 | const { public_id, secure_url } = await uploadImage(dest);
72 | if (public_id) {
73 | const result = await querys(
74 | "insert into portofolio (`title`,`description`,`publicId`,`status`,`image`,github,link) VALUES (?,?,?,?,?,?,?)",
75 | [title, description, public_id, "0", secure_url, github, link]
76 | );
77 | if (result.affectedRows >= 1) {
78 | fs.unlinkSync(req.file.path);
79 | return res.status(200).json({ status: 200, message: "success" });
80 | }
81 | } else {
82 | return res
83 | .status(500)
84 | .json({ status: 500, message: "Upload Image Error" });
85 | }
86 | }
87 | } catch (error) {
88 | if (error == 401) {
89 | return res.status(401).json({ status: 401, message: "Unauthorized" });
90 | }
91 | fs.unlinkSync(req.file.path);
92 | return res.status(415).json(error);
93 | }
94 | });
95 |
96 | apiRoute.put(async (req, res) => {
97 | try {
98 | const { authorization } = req.headers;
99 | if (authorization) {
100 | const token = authorization.split(" ")[1];
101 | const isLogin = await checkLogin(token);
102 | if (isLogin.login !== true) {
103 | return res.status(401).json({ status: 401, message: "Unauthorized" });
104 | }
105 | } else {
106 | return res.status(401).json({ status: 401, message: "Unauthorized" });
107 | }
108 |
109 | const { _id, title, github, link, description } = req.body;
110 |
111 | if (req.file) {
112 | const dest = path.join(__dirname, `../../../../${req.file.path}`);
113 | const { status } = await file.checkFile(dest);
114 |
115 | if (status == 200) {
116 | const { public_id, secure_url } = await uploadImage(dest);
117 | if (public_id) {
118 | const result = await querys(
119 | "UPDATE `portofolio` SET `_id`=?,`title`=?,`description`=?,`publicId`=?,`status`=?,`image`= ?,`github`=?,`link`=? WHERE `_id` = ?",
120 | [
121 | _id,
122 | title,
123 | description,
124 | public_id,
125 | "0",
126 | secure_url,
127 | github,
128 | link,
129 | _id,
130 | ]
131 | );
132 |
133 | if (result.affectedRows >= 1) {
134 | fs.unlinkSync(req.file.path);
135 | return res.status(200).json({ status: 200, message: "success" });
136 | }
137 | }
138 | }
139 | } else {
140 | const result = await querys(
141 | "UPDATE `portofolio` SET `_id`=?,`title`=?,`description`=?,`status`=?,`github`=?,`link`= ? WHERE `_id` = ?",
142 | [_id, title, description, "0", github, link, _id]
143 | );
144 | if (result.affectedRows >= 1) {
145 | fs.unlinkSync(req.file.path);
146 | return res.status(200).json({ status: 200, message: "success" });
147 | }
148 | }
149 | } catch (error) {
150 | if (error == 401) {
151 | return res.status(401).json({ status: 401, message: "Unauthorized" });
152 | }
153 | fs.unlinkSync(req.file.path);
154 | return res.status(415).json(error);
155 | }
156 | });
157 |
158 | apiRoute.delete(async (req, res) => {
159 | try {
160 | const { _id } = req.body;
161 |
162 | const publicId = await querys(
163 | "SELECT `publicId` FROM `portofolio` WHERE `_id` = ?",
164 | [_id]
165 | );
166 |
167 | const { result } = await destroyImage(publicId[0].publicId);
168 | if (result == "ok") {
169 | await querys("DELETE FROM `portofolio` WHERE `_id` = ? ", [_id]);
170 | return res.status(200).json({ status: 200, message: "success" });
171 | } else {
172 | return res.status(400).json({ status: 400, message: "Data Not Found" });
173 | }
174 | } catch (error) {
175 | console.log(error);
176 | res.status(500).json({ status: 500, message: "server error" });
177 | }
178 | });
179 | export default apiRoute;
180 |
181 | export const config = {
182 | api: {
183 | bodyParser: false,
184 | },
185 | };
186 |
--------------------------------------------------------------------------------
/components/front/AboutHome.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | const AboutHome = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 | Tentang Saya
11 |
12 |
13 | Belajar Menjadi Developer Expert
14 |
15 |
16 | Terus belajar Sampai Mencapai harapan Yang Di Inginkan
17 |
18 |
19 |
20 |
21 | Mari Berteman
22 |
23 |
24 | Mari Berteman Dan Sharing Informasi
25 |
26 |
91 |
92 |
93 |
94 |
95 | );
96 | };
97 | export default AboutHome;
98 |
--------------------------------------------------------------------------------
/pages/hidden/blog/add.js:
--------------------------------------------------------------------------------
1 | import Header from "../../../components/admin/header";
2 | import { useState } from "react";
3 | import { useForm } from "react-hook-form";
4 | import dynamic from "next/dynamic";
5 | import "suneditor/dist/css/suneditor.min.css";
6 | import axios from "axios";
7 | import { getCookie } from "cookies-next";
8 | import { useRouter } from "next/router";
9 | import isLogin from "../../../lib/fe/login";
10 |
11 | const SunEditor = dynamic(() => import("suneditor-react"), {
12 | ssr: false,
13 | });
14 | const AddBlog = ({ url, category }) => {
15 | const router = useRouter();
16 | const { register, handleSubmit } = useForm();
17 | const [content, setContent] = useState("");
18 | const onSubmit = async (data) => {
19 | const formData = new FormData();
20 | formData.append("title", data.title);
21 | formData.append("metaDescription", data.metaDescription);
22 | formData.append("metaKeyword", data.metaKeyword);
23 | formData.append("description", data.description);
24 | formData.append("date", data.date);
25 | formData.append("image", data.image[0]);
26 | formData.append("content", content);
27 | formData.append("category_id", data.category_id);
28 | const token = getCookie("token") || "";
29 | const response = await axios({
30 | method: "POST",
31 | url: `${url}api/blogs/`,
32 | data: formData,
33 | headers: {
34 | "Content-Type": "multipart/form-data",
35 | authorization: `Bearer ${token}`,
36 | },
37 | });
38 |
39 | if (response.status == 200) {
40 | router.push("/hidden/blog");
41 | }
42 | };
43 | return (
44 |
45 | {/* Header Start */}
46 |
47 | {/* Header End */}
48 |
49 | Add Blogs
50 |
51 |
52 |
53 | Title
54 |
60 |
61 |
62 | Meta Destription
63 |
69 |
70 |
71 | Meta Keyword
72 |
78 |
79 |
80 | Description
81 |
87 |
88 |
89 | Date
90 |
96 |
97 |
98 |
104 |
105 |
106 |
110 | Select Category
111 |
112 |
113 |
117 | Select...
118 | {category.map((item, index) => {
119 | return (
120 |
121 | {item.kategori}
122 |
123 | );
124 | })}
125 |
126 |
135 |
136 |
137 |
138 | {
141 | setContent(text);
142 | }}
143 | setOptions={{
144 | height: 200,
145 | buttonList: [
146 | [
147 | "formatBlock",
148 | "font",
149 | "fontSize",
150 | "fontColor",
151 | "align",
152 | "paragraphStyle",
153 | "blockquote",
154 | ],
155 | [
156 | "bold",
157 | "underline",
158 | "italic",
159 | "strike",
160 | "subscript",
161 | "superscript",
162 | ],
163 | ["removeFormat"],
164 | ["outdent", "indent"],
165 | ["table", "list"],
166 | ["link", "image", "video"],
167 | ],
168 | }}
169 | />
170 |
171 |
175 | Submit
176 |
177 |
178 |
179 |
180 |
181 | );
182 | };
183 |
184 | export default AddBlog;
185 |
186 | export const getServerSideProps = async ({ req, res }) => {
187 | const url = process.env.NEXT_PUBLIC_URL;
188 | const { status } = isLogin(req, res);
189 | if (status == 401) {
190 | return {
191 | redirect: {
192 | destination: "/hidden/login",
193 | permanent: false,
194 | },
195 | };
196 | }
197 | const category = await axios.get(`${url}api/category`);
198 |
199 | return { props: { url, category: category.data.kategori } };
200 | };
201 |
--------------------------------------------------------------------------------
/pages/api/blogs/index.js:
--------------------------------------------------------------------------------
1 | import Cors from "cors";
2 | import nextConnect from "next-connect";
3 | import multer from "multer";
4 | import path from "path";
5 | import file from "../../../lib/file";
6 | import fs from "fs";
7 | import querys from "../../../lib/database";
8 | import checkLogin from "../../../lib/checkLogin";
9 | // import checkOldImage from "../../../lib/checkOldImage";
10 | import { uploadImage, destroyImage } from "../../../lib/cloudinary";
11 |
12 | const upload = multer({
13 | storage: multer.diskStorage({
14 | destination: "./public/uploads",
15 | filename: (req, file, cb) => cb(null, file.originalname),
16 | }),
17 | });
18 |
19 | const apiRoute = nextConnect({
20 | onError(error, req, res) {
21 | res
22 | .status(501)
23 | .json({ error: `Sorry something Happened! ${error.message}` });
24 | },
25 | onNoMatch(req, res, next) {
26 | res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
27 | },
28 | });
29 |
30 | apiRoute.use(Cors());
31 | apiRoute.use(upload.single("image"));
32 |
33 | apiRoute.get(async (req, res) => {
34 | try {
35 | if (req.query.limit) {
36 | const result = await querys(
37 | "SELECT `blogs`.`_id`,`blogs`.`title`,`blogs`.`description`,`blogs`.`date`,`blogs`.`image`,`blogs`.`status`,`kategori`.`kategori` FROM blogs JOIN kategori ON `blogs`.`kategori_id` = `kategori`.`_id` ORDER BY `blogs`.`_id` DESC LIMIT ?;",
38 | [req.query.limit]
39 | );
40 | if (result.length >= 0) {
41 | return res.status(200).json({ blogs: result });
42 | }
43 | }
44 | const result = await querys(
45 | "SELECT `blogs`.`_id`,`blogs`.`title`,`blogs`.`description`,`blogs`.`date`,`blogs`.`image`,`blogs`.`status`,`kategori`.`kategori` FROM blogs JOIN kategori ON `blogs`.`kategori_id` = `kategori`.`_id`"
46 | );
47 | if (result.length >= 0) {
48 | return res.status(200).json({ blogs: result });
49 | }
50 | } catch (error) {
51 | res.status(500).json({ message: "server error" });
52 | }
53 | });
54 |
55 | apiRoute.post(async (req, res) => {
56 | try {
57 | const { authorization } = req.headers;
58 | if (authorization) {
59 | const token = authorization.split(" ")[1];
60 | const isLogin = await checkLogin(token);
61 | if (isLogin.login !== true) {
62 | return res.status(401).json({ status: 401, message: "Unauthorized" });
63 | }
64 | } else {
65 | return res.status(401).json({ status: 401, message: "Unauthorized" });
66 | }
67 | console.log(req.body);
68 | const {
69 | title,
70 | metaDescription,
71 | metaKeyword,
72 | description,
73 | date,
74 | content,
75 | category_id,
76 | } = req.body;
77 |
78 | const dest = path.join(__dirname, `../../../../${req.file.path}`);
79 | const { status } = await file.checkFile(dest);
80 | if (status == 200) {
81 | const { public_id, secure_url } = await uploadImage(dest);
82 | if (public_id) {
83 | const result = await querys(
84 | "INSERT INTO `blogs`(`title`, `metaKeyword`, `metaDescription`, `description`, `date`, `publicId`, `image`, `content`, `status`,`kategori_id`) VALUES (?,?,?,?,?,?,?,?,?,?)",
85 | [
86 | title,
87 | metaKeyword,
88 | metaDescription,
89 | description,
90 | date,
91 | public_id,
92 | secure_url,
93 | content,
94 | "0",
95 | category_id,
96 | ]
97 | );
98 | if (result.affectedRows >= 1) {
99 | fs.unlinkSync(req.file.path);
100 | return res.status(200).json({ status: 200, message: "success" });
101 | }
102 | } else {
103 | return res
104 | .status(500)
105 | .json({ status: 500, message: "Upload Image Error" });
106 | }
107 | }
108 | } catch (error) {
109 | if (error == 401) {
110 | return res.status(401).json({ status: 401, message: "Unauthorized" });
111 | }
112 | fs.unlinkSync(req.file.path);
113 | return res.status(415).json(error);
114 | }
115 | });
116 |
117 | apiRoute.put(async (req, res) => {
118 | try {
119 | const { authorization } = req.headers;
120 | if (authorization) {
121 | const token = authorization.split(" ")[1];
122 | const isLogin = await checkLogin(token);
123 | if (isLogin.login !== true) {
124 | return res.status(401).json({ status: 401, message: "Unauthorized" });
125 | }
126 | } else {
127 | return res.status(401).json({ status: 401, message: "Unauthorized" });
128 | }
129 |
130 | const {
131 | _id,
132 | title,
133 | metaDescription,
134 | metaKeyword,
135 | description,
136 | date,
137 | content,
138 | category_id,
139 | } = req.body;
140 |
141 | if (req.file) {
142 | const dest = path.join(__dirname, `../../../../${req.file.path}`);
143 | const { status } = await file.checkFile(dest);
144 |
145 | if (status == 200) {
146 | const { public_id, secure_url } = await uploadImage(dest);
147 | if (public_id) {
148 | const result = await querys(
149 | "UPDATE `blogs` SET `_id`=?,`title`=?,`metaKeyword`=?,`metaDescription`=?,`description`=?,`date`=?, `publicId`=?,`image`=?,`content`=?,`status`=?,`kategori_id` = ? WHERE `_id` = ?",
150 | [
151 | _id,
152 | title,
153 | metaKeyword,
154 | metaDescription,
155 | description,
156 | date,
157 | public_id,
158 | secure_url,
159 | content,
160 | "0",
161 | category_id,
162 | _id,
163 | ]
164 | );
165 |
166 | if (result.affectedRows >= 1) {
167 | fs.unlinkSync(req.file.path);
168 | return res.status(200).json({ status: 200, message: "success" });
169 | }
170 | } else {
171 | return res
172 | .status(500)
173 | .json({ status: 500, message: "Upload Image Error" });
174 | }
175 | }
176 | } else {
177 | const result = await querys(
178 | "UPDATE `blogs` SET `_id`=?,`title`=?,`metaKeyword`=?,`metaDescription`=?,`description`=?,`date`=?,`content`=?,`status`=?,`kategori_id` = ? WHERE `_id` = ?",
179 | [
180 | _id,
181 | title,
182 | metaKeyword,
183 | metaDescription,
184 | description,
185 | date,
186 | content,
187 | "0",
188 | category_id,
189 | _id,
190 | ]
191 | );
192 | if (result.affectedRows >= 1) {
193 | return res.status(200).json({ status: 200, message: "success" });
194 | }
195 | }
196 | } catch (error) {
197 | if (error == 401) {
198 | return res.status(401).json({ status: 401, message: "Unauthorized" });
199 | }
200 | fs.unlinkSync(req.file.path);
201 | return res.status(415).json(error);
202 | }
203 | });
204 |
205 | apiRoute.delete(async (req, res) => {
206 | try {
207 | const { _id } = req.body;
208 | const publicId = await querys(
209 | "SELECT `publicId` FROM `blogs` WHERE `_id` = ?",
210 | [_id]
211 | );
212 |
213 | const { result } = await destroyImage(publicId[0].publicId);
214 |
215 | if (result == "ok") {
216 | await querys("DELETE FROM `blogs` WHERE `_id` = ? ", [_id]);
217 | return res.status(200).json({ status: 200, message: "success" });
218 | } else {
219 | return res.status(400).json({ status: 400, message: "Data Not Found" });
220 | }
221 | } catch (error) {
222 | res.status(500).json({ status: 500, message: "server error" });
223 | }
224 | });
225 | export default apiRoute;
226 |
227 | export const config = {
228 | api: {
229 | bodyParser: false,
230 | },
231 | };
232 |
--------------------------------------------------------------------------------
/pages/hidden/blog/edit/[id].js:
--------------------------------------------------------------------------------
1 | import Header from "../../../../components/admin/header";
2 | import { useState } from "react";
3 | import { useForm } from "react-hook-form";
4 | import dynamic from "next/dynamic";
5 | import "suneditor/dist/css/suneditor.min.css";
6 | import axios from "axios";
7 | import { getCookie } from "cookies-next";
8 | import { useRouter } from "next/router";
9 | import isLogin from "../../../../lib/fe/login";
10 | const SunEditor = dynamic(() => import("suneditor-react"), {
11 | ssr: false,
12 | });
13 | const EditBlog = ({ blogs, category, url }) => {
14 | const router = useRouter();
15 | const { register, handleSubmit } = useForm();
16 | const [content, setContent] = useState("");
17 | const onSubmit = async (data) => {
18 | const formData = new FormData();
19 | formData.append("_id", blogs._id);
20 | formData.append("title", data.title);
21 | formData.append("metaDescription", data.metaDescription);
22 | formData.append("metaKeyword", data.metaKeyword);
23 | formData.append("description", data.description);
24 | formData.append("date", data.date);
25 | formData.append("image", data.image[0]);
26 | formData.append("content", content);
27 | formData.append("category_id", data.category_id);
28 | const token = getCookie("token") || "";
29 | const response = await axios({
30 | method: "PUT",
31 | url: `${url}api/blogs/`,
32 | data: formData,
33 | headers: {
34 | "Content-Type": "multipart/form-data",
35 | authorization: `Bearer ${token}`,
36 | },
37 | });
38 |
39 | if (response.status == 200) {
40 | router.push("/hidden/blog");
41 | }
42 | };
43 | return (
44 |
45 | {/* Header Start */}
46 |
47 | {/* Header End */}
48 |
49 | Edit Blogs
50 |
51 |
52 |
53 | Title
54 |
61 |
62 |
63 | Meta Destription
64 |
71 |
72 |
73 | Meta Keyword
74 |
81 |
82 |
83 | Description
84 |
91 |
92 |
93 | Date
94 |
101 |
102 |
103 |
109 |
110 |
111 |
115 | Select Category
116 |
117 |
118 |
123 | Select...
124 | {category.map((item, index) => {
125 | return (
126 |
127 | {item.kategori}
128 |
129 | );
130 | })}
131 |
132 |
141 |
142 |
143 | {
146 | setContent(text);
147 | }}
148 | defaultValue={`${blogs.content}`}
149 | setOptions={{
150 | height: 200,
151 | buttonList: [
152 | [
153 | "formatBlock",
154 | "font",
155 | "fontSize",
156 | "fontColor",
157 | "align",
158 | "paragraphStyle",
159 | "blockquote",
160 | ],
161 | [
162 | "bold",
163 | "underline",
164 | "italic",
165 | "strike",
166 | "subscript",
167 | "superscript",
168 | ],
169 | ["removeFormat"],
170 | ["outdent", "indent"],
171 | ["table", "list"],
172 | ["link", "image", "video"],
173 | ],
174 | }}
175 | />
176 |
177 |
181 | Submit
182 |
183 |
184 |
185 |
186 |
187 | );
188 | };
189 |
190 | export default EditBlog;
191 |
192 | export const getServerSideProps = async ({ req, res, params }) => {
193 | const url = process.env.NEXT_PUBLIC_URL;
194 | const { status } = isLogin(req, res);
195 | if (status == 401) {
196 | return {
197 | redirect: {
198 | destination: "/hidden/login",
199 | permanent: false,
200 | },
201 | };
202 | }
203 | const category = await axios.get(`${url}api/category`);
204 | const result = await axios.get(`${url}api/blogs/${params.id}`);
205 |
206 | return {
207 | props: {
208 | blogs: result.data.data[0],
209 | url,
210 | category: category.data.kategori,
211 | },
212 | };
213 | };
214 |
--------------------------------------------------------------------------------
/components/front/Footer.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | const Footer = () => {
3 | return (
4 |
5 |
6 |
7 |
8 |
Kusena Dev
9 |
Hubungi Saya
10 |
kusena322@gmail.com
11 |
Banyuwangi
12 |
13 |
14 |
15 | Kategori Tulisan
16 |
17 |
40 |
41 |
74 |
75 |
162 |
163 |
164 | );
165 | };
166 | export default Footer;
167 |
--------------------------------------------------------------------------------
/pages/hidden/portofolio/index.js:
--------------------------------------------------------------------------------
1 | import Header from "../../../components/admin/header";
2 | import Link from "next/link";
3 | import { getCookie, removeCookies } from "cookies-next";
4 | import axios from "axios";
5 | import { useRouter } from "next/router";
6 | import isLogin from "../../../lib/fe/login";
7 |
8 | export default function HomePortofolio({ data, url }) {
9 | const router = useRouter();
10 | const onUpdateStatus = async (_id, status) => {
11 | const token = getCookie("token") || "";
12 | const response = await axios({
13 | method: "PATCH",
14 | url: `${url}api/portofolio/${_id}`,
15 | data: { status },
16 | headers: {
17 | authorization: `Bearer ${token}`,
18 | },
19 | });
20 | if (response.status == 200) {
21 | router.push("/hidden/portofolio");
22 | }
23 | };
24 | const onDelete = async (_id) => {
25 | const token = getCookie("token") || "";
26 | const formData = new FormData();
27 | formData.append("_id", _id);
28 | const response = await axios({
29 | method: "DELETE",
30 | url: `${url}api/portofolio/`,
31 | data: formData,
32 | headers: {
33 | "Content-Type": "multipart/form-data",
34 | authorization: `Bearer ${token}`,
35 | },
36 | });
37 | if (response.status == 200) {
38 | router.push("/hidden/portofolio");
39 | }
40 | };
41 | return (
42 |
43 |
44 |
45 | Portofolio
46 |
47 |
48 |
49 |
50 |
59 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Image
73 | Title
74 | Status
75 |
76 | Actions
77 |
78 |
79 |
80 | {data.map((item, index) => {
81 | return (
82 |
86 |
87 |
88 | {/* Avatar with inset shadow */}
89 |
90 |
95 |
99 |
100 |
101 |
102 | {item.title}
103 |
104 | {item.status == "0" ? (
105 |
106 | pending
107 |
108 | ) : (
109 |
110 | Approved
111 |
112 | )}
113 |
114 |
115 |
116 |
117 |
119 | router.push(`/hidden/portofolio/edit/${item._id}`)
120 | }
121 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
122 | aria-label="Edit"
123 | >
124 |
130 |
131 |
132 |
133 |
{
135 | onDelete(item._id);
136 | }}
137 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
138 | aria-label="Delete"
139 | >
140 |
146 |
151 |
152 |
153 | {item.status == "0" ? (
154 |
{
156 | onUpdateStatus(item._id, "1");
157 | }}
158 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
159 | aria-label="Delete"
160 | >
161 |
170 |
175 |
176 |
177 |
178 | ) : (
179 |
{
181 | onUpdateStatus(item._id, "0");
182 | }}
183 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
184 | aria-label="Delete"
185 | >
186 |
195 |
199 |
200 |
201 | )}
202 |
203 |
204 |
205 | );
206 | })}
207 |
208 |
209 |
210 |
211 |
212 |
213 | );
214 | }
215 |
216 | export const getServerSideProps = async ({ req, res }) => {
217 | const url = process.env.NEXT_PUBLIC_URL;
218 | const { status, token } = isLogin(req, res);
219 | if (status == 401) {
220 | return {
221 | redirect: {
222 | destination: "/hidden/login",
223 | permanent: false,
224 | },
225 | };
226 | }
227 | const response = await axios.get(`${url}api/portofolio/`, {
228 | headers: {
229 | authorization: `Bearer ${token}`,
230 | },
231 | });
232 |
233 | if (response.status == 401) {
234 | removeCookies("login", { req, res });
235 | removeCookies("token", { req, res });
236 | return {
237 | redirect: {
238 | destination: "/hidden/login",
239 | permanent: false,
240 | // statusCode: 301,
241 | },
242 | };
243 | }
244 |
245 | return { props: { data: response.data.portofolio, url } };
246 | };
247 |
--------------------------------------------------------------------------------
/pages/hidden/blog/index.js:
--------------------------------------------------------------------------------
1 | import Header from "../../../components/admin/header";
2 | import Link from "next/link";
3 | import { getCookie, removeCookies } from "cookies-next";
4 | import axios from "axios";
5 | import { useRouter } from "next/router";
6 | import isLogin from "../../../lib/fe/login";
7 |
8 | export default function HomeBlogs({ data, url }) {
9 | const router = useRouter();
10 | const onUpdateStatus = async (_id, status) => {
11 | const token = getCookie("token") || "";
12 | const response = await axios({
13 | method: "PATCH",
14 | url: `${url}api/blogs/${_id}`,
15 | data: { status },
16 | headers: {
17 | authorization: `Bearer ${token}`,
18 | },
19 | });
20 | if (response.status == 200) {
21 | router.push("/hidden/blog");
22 | }
23 | };
24 | const onDelete = async (_id) => {
25 | const token = getCookie("token") || "";
26 | const formData = new FormData();
27 | formData.append("_id", _id);
28 | const response = await axios({
29 | method: "DELETE",
30 | url: `${url}api/blogs/`,
31 | data: formData,
32 | headers: {
33 | "Content-Type": "multipart/form-data",
34 | authorization: `Bearer ${token}`,
35 | },
36 | });
37 | if (response.status == 200) {
38 | router.push("/hidden/blog");
39 | }
40 | };
41 | return (
42 |
43 |
44 |
45 | Portofolio
46 |
47 |
48 |
49 |
50 |
59 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Image
73 | Title
74 | Category
75 | Status
76 | Actions
77 |
78 |
79 |
80 | {data.map((item, index) => {
81 | return (
82 |
86 |
87 |
88 | {/* Avatar with inset shadow */}
89 |
90 |
95 |
99 |
100 |
101 |
102 | {item.title}
103 | {item.kategori}
104 |
105 | {item.status == "0" ? (
106 |
107 | pending
108 |
109 | ) : (
110 |
111 | Approved
112 |
113 | )}
114 |
115 |
116 |
117 |
118 |
120 | router.push(`/hidden/blog/edit/${item._id}`)
121 | }
122 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
123 | aria-label="Edit"
124 | >
125 |
131 |
132 |
133 |
134 |
{
136 | onDelete(item._id);
137 | }}
138 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
139 | aria-label="Delete"
140 | >
141 |
147 |
152 |
153 |
154 | {item.status == "0" ? (
155 |
{
157 | onUpdateStatus(item._id, "1");
158 | }}
159 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
160 | aria-label="Delete"
161 | >
162 |
171 |
176 |
177 |
178 |
179 | ) : (
180 |
{
182 | onUpdateStatus(item._id, "0");
183 | }}
184 | className="flex items-center justify-between px-2 py-2 text-sm font-medium leading-5 text-primary rounded-lg dark:text-gray-400 focus:outline-none focus:shadow-outline-gray"
185 | aria-label="Delete"
186 | >
187 |
196 |
200 |
201 |
202 | )}
203 |
204 |
205 |
206 | );
207 | })}
208 |
209 |
210 |
211 |
212 |
213 |
214 | );
215 | }
216 |
217 | export const getServerSideProps = async ({ req, res }) => {
218 | const url = process.env.NEXT_PUBLIC_URL;
219 | const { status, token } = isLogin(req, res);
220 | if (status == 401) {
221 | return {
222 | redirect: {
223 | destination: "/hidden/login",
224 | permanent: false,
225 | },
226 | };
227 | }
228 | const response = await axios.get(`${url}api/blogs/`, {
229 | headers: {
230 | authorization: `Bearer ${token}`,
231 | },
232 | });
233 |
234 | if (response.status == 401) {
235 | removeCookies("login", { req, res });
236 | removeCookies("token", { req, res });
237 | return {
238 | redirect: {
239 | destination: "/login",
240 | permanent: false,
241 | // statusCode: 301,
242 | },
243 | };
244 | }
245 |
246 | return { props: { data: response.data.blogs, url } };
247 | };
248 |
--------------------------------------------------------------------------------
/pages/blog/[[...slug]].js:
--------------------------------------------------------------------------------
1 | import Header from "../../components/front/Header";
2 | import Footer from "../../components/front/Footer";
3 | import BackToTop from "../../components/front/BackToTop";
4 | import { useEffect } from "react";
5 | import mainScript from "../../lib/MainScript";
6 | import { useRouter } from "next/router";
7 | import Link from "next/link";
8 |
9 | import Image from "next/image";
10 | import Script from "next/script";
11 | export default function Blogs({ data, type, url }) {
12 | const router = useRouter();
13 | useEffect(() => {
14 | mainScript();
15 | }, []);
16 | return (
17 | <>
18 |
19 |
20 | {type == "short" ? (
21 |
22 |
23 |
24 | BLog
25 |
26 |
27 |
28 | {data?.map((item, index) => {
29 | return (
30 |
34 |
35 |
36 |
37 |
61 |
62 | Kusena Dev
63 |
67 |
77 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | {item.date}
88 |
92 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | 1.2M Views
113 |
117 |
127 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | {/* */}
140 |
141 |
147 |
148 |
149 |
158 |
159 | {item.description}
160 |
161 |
{
163 | router.push(
164 | `/blog/${item.kategori}/${item.title.replaceAll(
165 | " ",
166 | "-"
167 | )}-${item._id}`
168 | );
169 | }}
170 | className="h-10 w-40 rounded-md bg-primary text-white hover:border-2 hover:border-primary hover:bg-inherit hover:text-black dark:text-white dark:hover:text-white"
171 | >
172 | View More
173 |
174 |
175 |
176 | );
177 | })}
178 |
179 |
180 |
238 |
239 |
240 | ) : (
241 |
242 |
243 |
244 | {data.title}
245 |
246 |
256 |
257 |
258 |
259 | )}
260 |
261 |
262 | >
263 | );
264 | }
265 |
266 | export async function getServerSideProps({ req, res, params }) {
267 | const url = process.env.NEXT_PUBLIC_URL;
268 | const response = await fetch(`${url}api/blogs/`);
269 | const blogs = await response.json();
270 | let type = !params?.slug ? "short" : "";
271 | let data = !params?.slug ? blogs.blogs : [];
272 |
273 | if (params?.slug) {
274 | const { length } = params.slug;
275 | if (length == 1) {
276 | type = "short";
277 | data = blogs.blogs.filter((item) => {
278 | return item.kategori == params.slug[0];
279 | });
280 | } else if (length == 2) {
281 | type = "long";
282 | const _id = params.slug.at(-1).split("-").at(-1);
283 | const response = await fetch(`${url}api/blogs/${_id}`);
284 | const result = await response.json();
285 | data = result.data[0];
286 | } else {
287 | return {
288 | notFound: true,
289 | };
290 | }
291 | }
292 | res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
293 | return {
294 | props: {
295 | type,
296 | data,
297 | url,
298 | },
299 | };
300 | }
301 |
--------------------------------------------------------------------------------