├── .env.sample ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── biome.json ├── components.json ├── content ├── blog │ ├── 2-tahun-dan-masih-berlanjut.mdx │ ├── berkenalan-dengan-javascript.mdx │ ├── berkenalan-dengan-react-basic.mdx │ ├── berkenalan-dengan-react-memahami-useeffect.mdx │ ├── berkenalan-dengan-react-memoisasi.mdx │ ├── berkenalan-dengan-react-props-and-route.mdx │ ├── cerita-dibalik-gilbot.mdx │ ├── deployment-dengan-docker.mdx │ ├── git-tips-cherry-pick.mdx │ ├── headless-ui-vs-component-library.mdx │ ├── kehidupan-setelah-smk.mdx │ ├── kilas-balik-2024.mdx │ ├── memilih-rem-daripada-px-dalam-css.mdx │ ├── mencoba-next-14.mdx │ ├── mengenal-clean-code.mdx │ ├── migrasi-ke-biome.mdx │ ├── migrasi-ke-velite.mdx │ ├── panduan-css-bem.mdx │ └── pengalaman-menggunakan-prisma-orm.mdx ├── data │ ├── about.json │ ├── hardware.json │ └── software.json └── project │ ├── 01-e-voting.mdx │ ├── 02-gift-store-landing-page.mdx │ ├── 03-pengaduan-sekolah.mdx │ ├── 04-smkn2kra-website.mdx │ ├── 05-next-chakra-starter.mdx │ ├── 06-bed-covid-rs-indonesia.mdx │ ├── 07-rpl-skandakra-website.mdx │ ├── 08-rpl-skandakra-bot.mdx │ ├── 09-vlr-api.mdx │ ├── 10-fitcells.mdx │ ├── 11-tuut.mdx │ ├── 12-gelora.mdx │ ├── 13-gilbot.mdx │ ├── 14-project-showcase-wpu.mdx │ └── 15-elevaite.mdx ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── assets │ ├── blog │ │ ├── 2-tahun-dan-masih-berlanjut │ │ │ └── thumbnail.png │ │ ├── berkenalan-dengan-javascript │ │ │ ├── survey-2020.png │ │ │ └── thumbnail.png │ │ ├── berkenalan-dengan-react-basic │ │ │ ├── created-react-app.png │ │ │ ├── demo-setcount-value.gif │ │ │ ├── name-value-in-jsx.png │ │ │ ├── react-new-project-interface.png │ │ │ ├── react.png │ │ │ └── thumbnail.png │ │ ├── berkenalan-dengan-react-memahami-useeffect │ │ │ ├── project-view.png │ │ │ ├── re-render.gif │ │ │ ├── render-once.gif │ │ │ ├── thumbnail.png │ │ │ ├── update-title.gif │ │ │ └── update-title.png │ │ ├── berkenalan-dengan-react-memoisasi │ │ │ └── thumbnail.png │ │ ├── berkenalan-dengan-react-props-and-route │ │ │ ├── console-props.png │ │ │ ├── correct-about-page.png │ │ │ ├── show-props.png │ │ │ ├── thumbnail.png │ │ │ └── wrong-about-page.png │ │ ├── cerita-dibalik-gilbot │ │ │ ├── analytics.jpg │ │ │ ├── thumbnail.png │ │ │ └── warning-whatsapp.jpg │ │ ├── deployment-dengan-docker │ │ │ └── thumbnail.png │ │ ├── git-tips-cherry-pick │ │ │ ├── add-new-file.png │ │ │ ├── check-first-commit.png │ │ │ ├── cherry-pick-docs.png │ │ │ ├── example-project.png │ │ │ ├── git-log-oneline.png │ │ │ └── thumbnail.png │ │ ├── headless-ui-vs-component-library │ │ │ └── thumbnail.png │ │ ├── kehidupan-setelah-smk │ │ │ └── thumbnail.png │ │ ├── kilas-balik-2024 │ │ │ ├── 2024-in-1.png │ │ │ └── thumbnail.png │ │ ├── memilih-rem-daripada-px-dalam-css │ │ │ ├── font-size-firefox.png │ │ │ ├── font-size-px.png │ │ │ ├── font-size-rem.png │ │ │ └── thumbnail.png │ │ ├── mencoba-next-14 │ │ │ └── thumbnail.png │ │ ├── mengenal-clean-code │ │ │ ├── thumbnail.png │ │ │ └── wtfm.jpg │ │ ├── migrasi-ke-biome │ │ │ ├── biome-run.png │ │ │ └── thumbnail.png │ │ ├── migrasi-ke-velite │ │ │ ├── thumbnail.png │ │ │ └── velite-flow.png │ │ ├── panduan-css-bem │ │ │ └── thumbnail.png │ │ └── pengalaman-menggunakan-prisma-orm │ │ │ ├── prisma-landing-page.png │ │ │ └── thumbnail.png │ ├── main │ │ ├── avatar.png │ │ ├── logo-rounded.png │ │ ├── placeholder-content.png │ │ └── spotify.png │ └── project │ │ ├── bed-covid-rs-indonesia.png │ │ ├── e-voting.png │ │ ├── elevaite.png │ │ ├── fitcells.png │ │ ├── gelora.png │ │ ├── gift-store-landing-page.png │ │ ├── gilbot.png │ │ ├── image-gallery.png │ │ ├── next-chakra-starter.png │ │ ├── pengaduan-sekolah.png │ │ ├── project-showcase-wpu.png │ │ ├── rpl-skandakra-bot.png │ │ ├── rpl-skandakra-dev-website.png │ │ ├── smkn2kra-website.png │ │ ├── todo-list.png │ │ ├── tuut.png │ │ └── vlr-api.png ├── fonts │ ├── Gabarito-Bold.ttf │ └── Gabarito-Regular.ttf ├── icons │ ├── android-chrome-192x192.png │ └── android-chrome-512x512.png ├── next.svg └── vercel.svg ├── src ├── app │ ├── about │ │ └── page.tsx │ ├── api │ │ └── spotify │ │ │ └── now-playing │ │ │ └── route.ts │ ├── apple-icon.png │ ├── blog │ │ ├── [slug] │ │ │ └── page.tsx │ │ └── page.tsx │ ├── dashboard │ │ └── page.tsx │ ├── equipments │ │ └── page.tsx │ ├── favicon.ico │ ├── icon1.png │ ├── icon2.png │ ├── layout.tsx │ ├── loading.tsx │ ├── manifest.ts │ ├── not-found.tsx │ ├── og │ │ ├── content │ │ │ └── route.tsx │ │ └── main │ │ │ └── route.tsx │ ├── opengraph-image.png │ ├── page.tsx │ ├── projects │ │ ├── [slug] │ │ │ └── page.tsx │ │ └── page.tsx │ ├── robots.ts │ └── sitemap.ts ├── collections │ ├── about.ts │ ├── hardware.ts │ ├── index.ts │ ├── post.ts │ ├── project.ts │ └── software.ts ├── components │ ├── blog │ │ ├── card.tsx │ │ ├── comment.tsx │ │ ├── index.ts │ │ └── published-time.tsx │ ├── dashboard │ │ ├── card.tsx │ │ ├── index.ts │ │ ├── spotify │ │ │ ├── index.ts │ │ │ ├── now-playing.tsx │ │ │ └── top-tracks.tsx │ │ └── stats.tsx │ ├── home │ │ ├── index.ts │ │ └── section-container.tsx │ ├── layout │ │ ├── article-container.tsx │ │ ├── footer.tsx │ │ ├── index.ts │ │ ├── navigation │ │ │ ├── index.ts │ │ │ ├── items.ts │ │ │ ├── link.tsx │ │ │ └── navigation.tsx │ │ └── page-container.tsx │ ├── project │ │ ├── card.tsx │ │ └── index.ts │ ├── provider │ │ ├── index.ts │ │ └── theme.tsx │ ├── theme │ │ ├── index.ts │ │ └── toggle.tsx │ └── ui │ │ ├── button.tsx │ │ ├── dropdown-menu.tsx │ │ ├── heading.tsx │ │ ├── image-blur.tsx │ │ ├── index.ts │ │ ├── link-preview.tsx │ │ ├── link.tsx │ │ ├── mdx-content.tsx │ │ ├── sheet.tsx │ │ └── tooltip.tsx ├── hooks │ ├── index.ts │ ├── use-media-query.ts │ └── use-navigation-state.ts ├── lib │ ├── constants.ts │ ├── format.ts │ ├── seo.ts │ ├── server │ │ └── stats │ │ │ ├── github.ts │ │ │ ├── index.ts │ │ │ ├── spotify.ts │ │ │ ├── wakatime.ts │ │ │ ├── website.ts │ │ │ └── youtube.ts │ ├── transform.tsx │ └── utils.ts ├── styles │ └── globals.css └── types │ ├── github.ts │ ├── spotify.ts │ ├── wakatime.ts │ └── website.ts ├── tailwind.config.js ├── tsconfig.json └── velite.config.ts /.env.sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SITE_URL="http://localhost:3000" 2 | 3 | SPOTIFY_CLIENT_ID="" 4 | SPOTIFY_CLIENT_SECRET="" 5 | SPOTIFY_REFRESH_TOKEN="" 6 | 7 | GOOGLE_CLIENT_EMAIL="" 8 | GOOGLE_PRIVATE_KEY="" 9 | 10 | WEBSITE_STATS_ENDPOINT="" 11 | WEBSITE_STATS_TOKEN="" 12 | 13 | GITHUB_TOKEN="" 14 | WAKATIME_URL="" 15 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # velite 20 | .velite 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "quickfix.biome": "always" 4 | }, 5 | "[css]": { 6 | "editor.defaultFormatter": "biomejs.biome" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "biomejs.biome" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "biomejs.biome" 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hendraaagil.dev 2 | 3 | [![Open Graph Image](src/app/opengraph-image.png)](https://hendraaagil.dev) 4 | 5 | ## Stack 6 | 7 | - [Next.js 14 (App router)](https://nextjs.org/) - Full-stack React framework for the web. 8 | - [Velite](https://velite.js.org/) - Tool for building type-safe data layer, turns Markdown / MDX, YAML, JSON, or other files into app's data layer with Zod schema. 9 | - [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework. 10 | - [shadcn/ui](https://ui.shadcn.com/) - Beautifully designed components built with Radix UI and Tailwind CSS. 11 | 12 | ## Local development setup 13 | 14 | Node.js `>=22.x` is required and setup with [pnpm](https://pnpm.io/) is recommended. 15 | 16 | ```sh 17 | # duplicate & fill environment file 18 | cp .env.sample .env.local 19 | 20 | # install dependencies 21 | pnpm i 22 | 23 | # serve with hot reload at http://localhost:3000 24 | pnpm dev 25 | 26 | # build for production 27 | pnpm build 28 | 29 | # serve for production 30 | pnpm start 31 | ``` 32 | 33 | ## License 34 | 35 | This source code is under the [GPL-3.0 License](LICENSE). 36 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", 3 | "formatter": { 4 | "enabled": true, 5 | "formatWithErrors": false, 6 | "indentStyle": "space", 7 | "indentWidth": 2, 8 | "lineEnding": "lf", 9 | "lineWidth": 80, 10 | "attributePosition": "auto" 11 | }, 12 | "organizeImports": { "enabled": true }, 13 | "linter": { 14 | "enabled": true, 15 | "rules": { 16 | "recommended": false, 17 | "nursery": { 18 | "useSortedClasses": { 19 | "level": "warn", 20 | "options": { "functions": ["cva"] } 21 | } 22 | } 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "jsxQuoteStyle": "double", 28 | "quoteProperties": "asNeeded", 29 | "trailingCommas": "all", 30 | "semicolons": "asNeeded", 31 | "arrowParentheses": "always", 32 | "bracketSpacing": true, 33 | "bracketSameLine": false, 34 | "quoteStyle": "single", 35 | "attributePosition": "auto" 36 | } 37 | }, 38 | "vcs": { 39 | "enabled": true, 40 | "clientKind": "git", 41 | "useIgnoreFile": true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": false 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /content/blog/2-tahun-dan-masih-berlanjut.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2 Tahun dan Masih Berlanjut 3 | thumbnail: /assets/blog/2-tahun-dan-masih-berlanjut/thumbnail.png 4 | thumbnailCredit: Photo by Saulo Mohana on Unsplash 5 | summary: Cerita singkat tentang karir saya selama 2 tahun ke belakang. 6 | tags: 7 | - career 8 | - story 9 | - life 10 | author: Hendra Agil 11 | createdAt: 2023-08-20T22:00:00+07:00 12 | updatedAt: 2023-08-20T22:00:00+07:00 13 | --- 14 | 15 | **Disclaimer!** Tulisan / cerita ini bukan bertujuan untuk memotivasi, saya hanya ingin bercerita sebagaimana faktanya saja. Tapi kalau memberikan motivasi ya Alhamdulillah, kalau tidak ya jangan berharap 😅 16 | 17 | --- 18 | 19 | ## Awal Mula Menjadi Programmer 20 | 21 | Aku lulus dari SMK pada bulan Juni 2021 (kalau tidak salah ingat), beberapa waktu setelah aku publish salah satu blog yang berjudul [Kehidupan Setelah SMK](https://hendraaagil.dev/blog/kehidupan-setelah-smk). Jujur saja setelah lulus itu aku belum ada plan / rencana sama sekali. Apa yang bisa aku lakukan, aku lakukan saja pada saat itu. Mulai dari ikut tes beasiswa sampai apply kerjaan. Singkat cerita pada bulan Juli (lupa tanggalnya), aku dan salah satu teman sekelasku mengikuti internship. Pada saat itu kita tidak expect untuk dapat gaji, yang penting dapat pengalaman dulu 😂 22 | 23 | Setelah beberapa minggu berjalan, aku sempat diprotes sama ibu aku yang bilang “kerja kok ngga dibayar”. Namanya juga orang tua, tidak terlalu tau apa itu intern, dkk. karena beda generasi. Saat intern berjalan, aku juga coba apply ke beberapa lowongan pekerjaan lewat platform seperti linkedin, glints, jobstreet, kalibrr, dan masih banyak lagi. Aku apply semua yang levelnya intern sampai ke junior, karena kalau mid / senior tidak mungkin karena belum ada pengalaman sama sekali wkwk. 24 | 25 | Pada akhirnya, tanggal 2 Agustus ada satu panggilan interview sebagai internship dan keesokan harinya yaitu tanggal 3 Agustus ada tes live coding. Sebelum tes itu aku belum ada persiapan yang baik, hanya membaca ulang beberapa kode JavaScript yang sudah aku tulis sebelumnya. Saat waktu tes tiba aku cukup kaget wkwk, karena tesnya lebih ke tes logika menggunakan platform [codewars](https://www.codewars.com/). Saat itu aku kerjakan sebisanya saja dan hasilnya langsung dikirimkan ke HR. Waktu itu aku tidak terlalu berharap untuk diterima, karena menurutku aku tidak begitu lancar saat mengerjakan tesnya. Tapi takdir berkata lain, tanggal 6 Agustus aku dihubungi lagi dan diterima sebagai intern pada saat itu. Perasaanku saat itu memang senang, tapi disisi lain ada bingungnya karena masih belum mengundurkan diri dari intern yang masih berjalan. Tapi ya pada akhirnya aku sudah mengundurkan diri dengan template surat yang aku minta ke teman yang sudah mengundurkan diri sebelumnya wkwk. 26 | 27 | Saat itu adalah pertama kalinya aku membaca sebuah surat perjanjian kontrak kerja, ada banyak hal yang tertulis didalamnya seperti gaji, jam kerja, dan beberapa peraturan lain yang juga mengacu langsung ke undang-undang. Tanggal 9 Agustus adalah hari pertamaku kerja sebagai intern. Oh iya intern ini ada kontraknya yaitu selama 3 bulan, tapi ada kemungkinan di akhir akan ada offer sebagai full-time jika berhasil memenuhi kebutuhan perusahaan dan programmer lain yang lebih senior. Saat intern aku mengerjakan backend dengan Node.js, walau saat SMK aku belajarnya lebih banyak di frontend dengan React. Waktu itu aku terima saja karena aku juga ingin belajar lebih banyak terkait Node.js. 28 | 29 | --- 30 | 31 | ## Menjadi Pekerja Full-time 32 | 33 | Singkat cerita setelah 3 bulan intern, aku mendapatkan offer sebagai full-time Software Developer dengan kontrak 2 tahun yang terhitung mulai dari tanggal 1 November 2021. Alhamdulillah, tentu saja aku sangat senang pada saat itu wkwk. Saat aku memberitahu hal itu kepada orang tua aku, mereka seakan tidak percaya. Apalagi ini pekerjaan remote, jadi kerjanya bisa dari rumah. Orang tua aku dulu juga sempat meragukan saat aku mengambil jurusan RPL di SMK karena takut setelah lulus sulit untuk mendapat pekerjaan. 34 | 35 | Selama bekerja aku belajar banyak hal yang belum pernah aku temui sebelumnya. Seperti komunikasi sesama tim dan sesama programmer, manajemen task, CI / CD, monitoring, analytics, dan masih banyak lagi. Selain itu aku juga belajar untuk bekerja dibawah tekanan wkwk, contohnya kejar-kejaran dengan deadline. Sejak mulai di dunia kerja, aku sangat merasa kalau ilmu di sekolah itu masih belum seberapa. Masih banyak sekali hal yang perlu dipelajari / di explore lebih jauh lagi. 36 | 37 | --- 38 | 39 | ## Kerja Sambil Kuliah 40 | 41 | Setelah hampir 2 tahun bekerja, aku mulai memikirkan untuk melanjutkan kuliah. Karena merasa kegiatan belajar aku sudah tidak serajin dulu waktu sekolah lagi. Jadi aku berpikir untuk masuk kuliah agar ada kewajiban / tuntutan untuk belajar lagi. Ya selain itu tentu saja untuk mendapatkan ijazah 😅 42 | 43 | Karena pekerjaanku full-time tentu saja aku tidak bisa mengambil kuliah reguler, kalau melepas pekerjaan lantas siapa yang membiayai kuliah? wkwk. Akhirnya aku memutuskan untuk mengambil kuliah online, tentu saja dengan persetujuan orang tuaku dulu. Aku mulai kuliah pada awal Maret 2023 ini dengan jurusan Teknik Informatika dan sudah selesai semester 1. Semoga saja bisa lancar sampai lulus nanti, aamiin. 44 | 45 | Kalau ada dari kalian yang bertanya, kuliah online itu worth it atau tidak. Menurutku itu tergantung orangnya, kalau lebih suka / lebih paham kalau diajar langsung berarti kurang worth it. Karena di online sendiri pasti kebanyakan self learning, ada juga beberapa kali vicon (video conference) dalam satu semester. Well, keputusan ada di tangan kalian masing-masing. Setiap keputusan pasti ada resikonya, jadi pastikan sudah siap dengan resiko tersebut. 46 | 47 | --- 48 | 49 | ## What’s Next ? 50 | 51 | 2 tahun bukanlah waktu yang sebentar, tapi juga perjalanan masih panjang kalau masih diberi kesempatan. Dibalik semua yang aku tulis di atas sebenarnya masih ada banyak entah itu target / wishlist untuk ke depannya. Jadi, apa selanjutnya? Yaa dijalani aja sebagaimana alurnya. Yang pasti di dunia ini tidak ada yang pasti, apapun bisa terjadi (asek). 52 | 53 | Mungkin sedikit tips aja, jangan bikin target yang terlalu jauh misalnya 5 / 10 tahun ke depan. Karena kita juga tidak tau 5 / 10 tahun ke depan masih diberi kesempatan atau tidak. Jangan takut untuk mengambil resiko, karena tidak ada keputusan yang tanpa resiko. 54 | 55 | Cya next time 😎 56 | -------------------------------------------------------------------------------- /content/blog/berkenalan-dengan-javascript.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Berkenalan Dengan JavaScript 3 | thumbnail: /assets/blog/berkenalan-dengan-javascript/thumbnail.png 4 | thumbnailCredit: Photo by Pankaj Patel on Unsplash 5 | summary: Apa itu JavaScript? Kenapa harus belajar JavaScript? Apa perbedaan JavaScript dengan Java? Saya akan sedikit membahas tentang JavaScript pada blog ini. 6 | tags: 7 | - development 8 | - javascript 9 | author: Hendra Agil 10 | createdAt: 2021-05-29T12:46:34+07:00 11 | updatedAt: 2021-05-29T12:49:08+07:00 12 | --- 13 | 14 | ## Apa Itu JavaScript? 15 | 16 | JavaScript adalah bahasa pemrograman yang digunakan dalam pengembangan website agar lebih dinamis dan interaktif. Jika di dalam sebuah website sudah ada HTML dan CSS, dengan menambahkan JavaScript dapat meningkatkan fungsionalitas pada halaman web. (Sumber: [Dicoding](https://www.dicoding.com/blog/apa-itu-javascript-fungsi-dan-contohnya/)) 17 | 18 | ### Sejarah JavaScript 19 | 20 | Pada tahun 1994 JavaScript mulai dikenal, pada saat itu web dan internet sudah mulai berkembang. JavaScript didesain oleh **[Brendan Eich](https://en.wikipedia.org/wiki/Brendan_Eich)** yang merupakan karyawan Netscape. Transformasi nama JavaScript, dimulai dari Mocha, Mona, LiveScript, hingga akhirnya resmi bernama JavaScript. 21 | 22 | Versi awal bahasa JS hanya dipakai di kalangan Netscape beserta dengan fungsionalitas pun yang masih terbatas. Singkat cerita pada tahun 1996 JavaScript secara resmi dinamakan sebagai ECMAScript. ECMAScript 2 dikembangkan pada tahun 1998 yang dilanjutkan dengan ECMAScript 3 setahun kemudian. ECMAScript terus dikembangkan sampai akhirnya menjadi JavaScript atau JS hingga saat ini. Pada tahun 2016, 92% web diketahui telah menggunakan JavaScript. Itulah mengapa JavaScript atau JS terus berkembang. (Sumber: [Dicoding](https://www.dicoding.com/blog/apa-itu-javascript-fungsi-dan-contohnya/)) 23 | 24 | ### Kenapa Harus Belajar JavaScript 25 | 26 | Ada beberapa alasan kenapa kita harus belajar JavaScript: 27 | 28 | 1. Membuat website menjadi lebih dinamis dan interaktif. 29 | 2. Bisa digunakan di sisi server atau aplikasi mobile. 30 | 3. Sumber belajar sudah cukup banyak. 31 | 32 | ### Perbedaan JavaScript Dengan Java 33 | 34 | Bagi pemula, cukup banyak yang menganggap kalau JavaScript & Java itu sama. Padahal mereka sangat berbeda. Java sendiri dikembangkan oleh **[James Gosling](https://en.wikipedia.org/wiki/James_Gosling)**, sedangkan JavaScript dikembangkan oleh **[Brendan Eich](https://en.wikipedia.org/wiki/Brendan_Eich)**. Untuk menjalankan program Java kita memerlukan sebuah JVM, sedangkan untuk JavaScript dapat langsung kita buka di browser. 35 | 36 | Contoh program hello world dengan Java: 37 | 38 | ```java 39 | class Program { 40 | public static void main(String args[]){ 41 | System.out.println("Hello World"); 42 | } 43 | } 44 | ``` 45 | 46 | Contoh program hello world dengan JavaScript: 47 | 48 | ```js 49 | console.log('Hello World') 50 | ``` 51 | 52 | Berbeda kan? 53 | 54 | ### Popularitas JavaScript 55 | 56 | Berdasarkan [Stack Overflow Survey 2020](https://insights.stackoverflow.com/survey/2020#most-popular-technologies), JavaScript menjadi yang paling banyak dipilih. 57 | 58 | ![Stack Overflow Survey 2020](/assets/blog/berkenalan-dengan-javascript/survey-2020.png) 59 | 60 | Popularitas JavaScript juga didukung dengan munculnya banyak framework dan library yang baru. Fitur JavaScript modern juga sangat membantu para developer untuk mengembangkan suatu program. Library lama seperti jQuery sudah mulai ditinggalkan karena fitur JavaScript modern yang dirasa sudah sangat membantu pengembangan. Ditambah dengan munculnya JavaScript flavors seperti [TypeScript](https://www.typescriptlang.org/) yang cukup populer saat ini. 61 | 62 | Ada beberapa framework frontend yang cukup terkenal pada saat ini, yaitu [React](https://reactjs.org/), [Vue](https://vuejs.org/), [Svelte](https://svelte.dev/) dan [Angular](https://angular.io/). Kita tidak perlu memperdebatkan mana yang terbaik, karena framework sebenarnya digunakan untuk mempermudah dan mempercepat proses pengembangan **bukan untuk bahan debat** 😆. 63 | 64 | Saya sendiri memilih React karena di React saya belajar banyak sintaks modern JavaScript. Bisa jadi nanti saya akan mencoba beberapa framework lain kalau sudah bosan di React, hehe. Tapi, untuk React sendiri menurut saya ekosistemnya cukup luas. Munculnya beberapa framework yang lebih mempermudah pengembangan dengan base React seperti [Gatsby](https://www.gatsbyjs.com/) dan [Next.js](https://nextjs.org/) membuat React menjadi cukup populer saat ini. 65 | 66 | ### Sumber Belajar 67 | 68 | Saya akan membagikan beberapa sumber belajar yang pernah saya ikuti. Tentu saja yang pertama adalah dari Web Programming UNPAS 😄. 69 | 70 | - [Dasar Pemrograman JavaScript](https://www.youtube.com/playlist?list=PLFIM0718LjIWXagluzROrA-iBY9eeUt4w) 71 | - [JavaScript dan DOM](https://www.youtube.com/playlist?list=PLFIM0718LjIWB3YRoQbQh82ZewAGtE2-3) 72 | - [JavaScript Lanjutan](https://www.youtube.com/playlist?list=PLFIM0718LjIUGpY8wmE41W7rTJo_3Y46-) 73 | 74 | Selain itu, ada beberapa website yang menurut saya membantu saat belajar JavaScript. 75 | 76 | - [Modern JavaScript Tutorial](https://javascript.info/) 77 | - [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/javascript) 78 | 79 | ### Kesimpulan 80 | 81 | Menurut saya, JavaScript cocok bagi kamu yang ingin masuk ke dunia pemrograman. Setelah belajar JavaScript mungkin kamu ingin mencoba bahasa pemrograman yang lain ataupun lanjut ke framework JavaScript itu tidak masalah. Yang penting adalah di awal jangan langsung mencoba banyak bahasa pemrograman, tapi cobalah fokus ke satu bahasa pemrograman dulu. 82 | 83 | Itu saja sepertinya yang bisa saya tuliskan. Terima kasih. 84 | -------------------------------------------------------------------------------- /content/blog/berkenalan-dengan-react-memahami-useeffect.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Berkenalan Dengan React: Memahami useEffect' 3 | thumbnail: /assets/blog/berkenalan-dengan-react-memahami-useeffect/thumbnail.png 4 | thumbnailCredit: Photo by Lautaro Andreani on Unsplash 5 | summary: Kita akan lanjut berkenalan lagi dengan React, yaitu tentang penggunaan useEffect di blog ini. 6 | tags: 7 | - development 8 | - react 9 | author: Hendra Agil 10 | createdAt: 2021-12-17T23:52:29+07:00 11 | updatedAt: 2021-12-18T12:49:30+07:00 12 | --- 13 | 14 | ## Apa Itu useEffect 15 | 16 | **useEffect** adalah salah satu hooks yang mulai diperkenalkan di React versi `>= 16.8`. Jika kalian pernah belajar class component di React, `useEffect` ini memiliki fungsi yang mirip atau bisa dibilang sama dengan `componentDidMount`, `componentDidUpdate` dan `componentWillUnmount`. 17 | 18 | ## Contoh Penggunaan 19 | 20 | Hooks `useEffect` ini akan berjalan saat pertama render dan saat terdapat sebuah re-render / pembaruan di komponennya. Sebagai contoh, kita akan melanjutkan project di blog [ini](/blog/berkenalan-dengan-react-basic) kemarin. 21 | 22 | Saat ini memiliki tampilan seperti berikut: 23 | 24 | ![Project View](/assets/blog/berkenalan-dengan-react-memahami-useeffect/project-view.png) 25 | 26 | Kita bisa coba menambahkan kode `useEffect` untuk mengubah title dari website tadi: 27 | 28 | ```js 29 | import { useEffect, useState } from 'react'; 30 | ... 31 | 32 | function App() { 33 | const [count, setCount] = useState(0); 34 | const name = 'Hendra Agil'; 35 | 36 | useEffect(() => { 37 | document.title = `You clicked ${count} times`; 38 | }); 39 | 40 | return ( 41 | ... 42 | ); 43 | } 44 | 45 | export default App; 46 | ``` 47 | 48 | Dari kode tersebut, kita memanggil sebuah hooks `useEffect` dan memasukkan sebuah anonymous function di dalamnya yang akan dieksekusi oleh `useEffect` tadi. Function tersebut akan mengubah title dari halaman website kita melalui [Document API](https://developer.mozilla.org/en-US/docs/Web/API/Document). 49 | 50 | Hasilnya akan seperti berikut: 51 | 52 | ![Update Title Using useEffect](/assets/blog/berkenalan-dengan-react-memahami-useeffect/update-title.gif) 53 | 54 | Contoh sederhana penggunaan `useEffect` adalah seperti tadi. Namun, bagaimana jika kita hanya ingin menggunakan `useEffect` saat pertama kali komponen itu dirender saja? 🤔 55 | 56 | Sebagai contoh kasus, kita ingin mengambil data dari sebuah API. Tentu saja kita hanya akan mengambil data itu satu kali saja saat halaman pertama kali dibuka. 57 | 58 | Tapi di contoh kali ini kita akan coba hal sederhana, yaitu untuk update title dari website kita tadi. Kita coba ubah kode `useEffect` tadi menjadi seperti berikut: 59 | 60 | ```js 61 | ... 62 | useEffect(() => { 63 | document.title = `Halo Hendra`; 64 | }); 65 | ... 66 | ``` 67 | 68 | Jika kita cek, sudah benar dia mengupdate title dari website kita: 69 | 70 | ![Update Title Using useEffect](/assets/blog/berkenalan-dengan-react-memahami-useeffect/update-title.png) 71 | 72 | Namun, jika kita coba tambahkan kode `console.log` seperti ini: 73 | 74 | ```js 75 | ... 76 | useEffect(() => { 77 | console.log('Re-Render'); 78 | document.title = `Halo Hendra`; 79 | }); 80 | ... 81 | ``` 82 | 83 | Kita coba cek di console browser saat kita mengklik button: 84 | 85 | ![Re-Render](/assets/blog/berkenalan-dengan-react-memahami-useeffect/re-render.gif) 86 | 87 | Tulisan **Re-Render** tadi muncul di console sebanyak kita mengklik button tadi. 88 | 89 | Itu terjadi karena setiap kita mengklik button tadi, dia mengupdate state `count` dan melakukan render ulang / re-render di halaman yang kita akses. Sebagai solusinya kita bisa memberikan params kedua yang berupa array di `useEffect` tadi. 90 | 91 | Jika kita hanya ingin mengeksekusinya saat pertama kali render, kita bisa menggunakan array kosong sebagai params kedua: 92 | 93 | ```js 94 | ... 95 | useEffect(() => { 96 | console.log('Re-Render'); 97 | document.title = `Halo Hendra`; 98 | }, []); 99 | ... 100 | ``` 101 | 102 | Kita coba jalankan: 103 | 104 | ![Render Once](/assets/blog/berkenalan-dengan-react-memahami-useeffect/render-once.gif) 105 | 106 | Tulisan **Re-Render** hanya muncul sekali di console browser kita. Berarti fungsi di `useEffect` tadi hanya dijalankan ketika halaman pertama kali dibuka. Yeay! 🥳 107 | 108 | ### Penggunaan Lanjutan 109 | 110 | Penggunaan `useEffect` ini sebenarnya bisa lebih sakti lagi, bukan hanya untuk update elemen DOM seperti yang kita lakukan tadi. Kita bisa melakukan fetch API di dalam fungsi `useEffect` dan mengupdate data state untuk nantinya dirender ke halaman website kita. 111 | 112 | Di penggunaan yang lebih lanjut lagi, kita bisa menggunakan `useEffect` sebagai pengganti `componentWillUnmount` di class component dengan menggunakan fungsi `cleanup()` untuk melakukan pembersihan. 113 | 114 | ## Penutup 115 | 116 | Mungkin cukup itu sedikit perkenalan tentang `useEffect` di React. Jika kalian ingin mempelajari lebih lanjut bisa lewat sumber berikut: 117 | 118 | - [Dokumentasi React Tentang useEffect](https://reactjs.org/docs/hooks-effect.html) 119 | 120 | Terima kasih sudah membaca blog ini, tetap semangat belajar! Semoga bermanfaat. 121 | -------------------------------------------------------------------------------- /content/blog/berkenalan-dengan-react-memoisasi.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Berkenalan Dengan React: Memoisasi' 3 | thumbnail: /assets/blog/berkenalan-dengan-react-memoisasi/thumbnail.png 4 | thumbnailCredit: Photo by Fredy Jacob on Unsplash 5 | summary: Mari lanjut berkenalan dengan React, yaitu tentang memoisasi dengan memo, useMemo dan useCallback. 6 | tags: 7 | - development 8 | - react 9 | author: Hendra Agil 10 | createdAt: 2024-03-31T21:10:00+07:00 11 | updatedAt: 2024-03-31T21:20:00+07:00 12 | --- 13 | 14 | Ini adalah lanjutan dari seri beberapa blog berikut: 15 | 16 | 17 | 18 | 19 | 20 | --- 21 | 22 | ## Apa itu Memoisasi? 23 | 24 | Memoisasi adalah suatu teknik dalam pemrograman komputer yang memungkinkan kita untuk menyimpan hasil dari pemanggilan fungsi dan disimpan dalam memori cache. Sehingga jika kita memanggil fungsi tersebut dengan nilai argumen yang sama, maka hasilnya dapat langsung diperoleh dari cache tanpa melakukan komputasi kembali. Teknik ini membantu meningkatkan efisiensi kinerja aplikasi dengan menghindari pengulangan komputasi yang sama. 25 | 26 | --- 27 | 28 | ## Fungsi memo 29 | 30 | Fungsi `memo` adalah salah satu cara untuk menerapkan memoisasi dalam React. Ini dapat digunakan untuk mengoptimalkan kinerja komponen fungsional dengan menyimpan versi terakhir dari outputnya. Ketika komponen diberikan properti yang sama, React akan menghindari pengulangan rendering dengan menggunakan hasil memoisasi yang disimpan sebelumnya. 31 | 32 | ### Contoh penggunaan 33 | 34 | ```jsx 35 | import { memo } from 'react' 36 | 37 | const MyComponent = memo((props) => { 38 | // Komputasi atau rendering komponen 39 | }) 40 | 41 | export default MyComponent 42 | ``` 43 | 44 | --- 45 | 46 | ## Hook useMemo 47 | 48 | Hook `useMemo` adalah hooks yang digunakan untuk melakukan memoisasi dalam fungsi komponen. Ini memungkinkan kita untuk menghitung nilai dengan argumen yang diberikan dan menyimpannya untuk digunakan kembali nanti. `useMemo` menerima sebuah fungsi dan sebuah array dependensi. Nilai yang dihitung oleh fungsi akan hanya diupdate saat salah satu dari dependensi berubah. 49 | 50 | ### Contoh penggunaan 51 | 52 | ```jsx 53 | import { useMemo } from 'react' 54 | 55 | const MyComponent = ({ data }) => { 56 | const memoizedValue = useMemo(() => { 57 | // Komputasi yang akan dimemoisasi 58 | }, [data]) // Daftar dependensi 59 | 60 | return ( 61 | // JSX untuk menampilkan komponen 62 | ) 63 | } 64 | 65 | export default MyComponent 66 | ``` 67 | 68 | --- 69 | 70 | ## Hook useCallback 71 | 72 | Hook `useCallback` adalah hooks yang digunakan untuk memoisasi fungsi callback dalam komponen React. Ini memungkinkan kita untuk menyimpan versi terakhir dari fungsi callback, sehingga komponen yang bergantung pada callback tersebut tidak perlu dirender ulang setiap kali komponen tersebut di-render kembali. 73 | 74 | ### Contoh penggunaan 75 | 76 | ```jsx 77 | import { useCallback } from 'react' 78 | 79 | const MyComponent = ({ onClick }) => { 80 | const handleClick = useCallback(() => { 81 | // Logika handler 82 | }, []) // Daftar dependensi 83 | 84 | return 85 | } 86 | 87 | export default MyComponent 88 | ``` 89 | 90 | --- 91 | 92 | Penting untuk dicatat perbedaan antara `useCallback` dan `useMemo`: 93 | 94 | - `useCallback` digunakan untuk memoisasi fungsi, sedangkan `useMemo` digunakan untuk memoisasi nilai yang dihitung secara dinamis. 95 | - `useCallback` mengembalikan kembali versi terakhir dari fungsi callback yang dimemoisasi, sedangkan `useMemo` mengembalikan nilai yang dihitung oleh fungsi yang dimemoisasi. 96 | - Kedua hooks dapat menggunakan array dependensi untuk menentukan kapan mereka harus dijalankan ulang. Jika daftar dependensi berubah, kedua hooks tersebut akan menghitung ulang nilai yang dimemoisasi atau fungsi callback yang dimemoisasi. 97 | 98 | --- 99 | 100 | ## Penutup 101 | 102 | Dengan menggunakan memoisasi melalui fungsi `memo`, hook `useMemo`, dan hook `useCallback`, kita dapat meningkatkan kinerja aplikasi React dengan menghindari pengulangan komputasi yang sama dan rendering yang tidak perlu. Ini merupakan teknik yang sangat berguna terutama saat kita memiliki komponen yang sering dirender dengan argumen yang sama atau bergantung pada callback yang sering berubah. 103 | 104 | Kalian bisa pelajari lebih lanjut tentang ketiga fungsi tersebut melalui dokumentasi dari React berikut: 105 | 106 | - [memo - React](https://react.dev/reference/react/memo) 107 | - [useMemo - React](https://react.dev/reference/react/useMemo) 108 | - [useCallback - React](https://react.dev/reference/react/useCallback) 109 | 110 | Thank you. 111 | -------------------------------------------------------------------------------- /content/blog/cerita-dibalik-gilbot.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cerita Dibalik GilBot 3 | thumbnail: /assets/blog/cerita-dibalik-gilbot/thumbnail.png 4 | thumbnailCredit: Photo by Dimitri Karastelev on Unsplash 5 | summary: Apa itu GilBot? Siapa dia? 6 | tags: 7 | - development 8 | - node 9 | - story 10 | author: Hendra Agil 11 | createdAt: 2024-02-29T21:20:00+07:00 12 | updatedAt: 2024-02-29T21:20:00+07:00 13 | --- 14 | 15 | GilBot adalah sebuah bot WhatsApp yang saya buat ketika ada waktu luang dan ingin digunakan untuk mengetik kode. Kebetulan pada saat itu ada kartu SIM yang tidak terpakai, jadi saya daftarkan nomornya ke WhatsApp dan mencoba untuk membuat bot tersebut. Bot ini awalnya hanya saya bagikan ke status WhatsApp dan story Instagram sekitar tanggal 30 Mei 2023 (berdasarkan tanggal post story Instagram). 16 | 17 | --- 18 | 19 | ## Atensi 20 | 21 | Semakin hari ternyata semakin banyak orang yang menggunakannya dan nomor WhatsApp tersebut berpotensi terkena **banned** karena terlalu banyak aktivitas diluar aplikasi WhatsApp resmi itu sendiri. Berikut adalah screenshot-nya: 22 | 23 |
24 | ![Warning WhatsApp](/assets/blog/cerita-dibalik-gilbot/warning-whatsapp.jpg) 25 |
26 | 27 | Banyaknya orang yang menggunakan kemungkinan karena ada seseorang yang membagikan link dari bot tersebut ke Twitter (X) berdasarkan data dari analytics website GilBot pada tanggal 30 Januari 2024: 28 | 29 | [![Analytics 30 Januari 2024](/assets/blog/cerita-dibalik-gilbot/analytics.jpg)](https://twitter.com/hendraaagil/status/1752312421174350154) 30 | 31 | Mulai dari situ, ada yang sampai mengirim email ke saya untuk meminta sebuah fitur. Ada juga yang mengirim pesan ke saya melalui Telegram dengan tujuan menanyakan teknologi apa yang dipakai untuk membuat bot tersebut. 32 | 33 | --- 34 | 35 | ## Teknologi dan Deployment 36 | 37 | Untuk teknologi yang saya gunakan adalah seperti yang saya sebutkan pada halaman [project](/projects/gilbot). Ada 2 repository, untuk bot-nya sendiri berada di private repository. Sedangkan untuk website dari bot-nya berada di public repository, kalian bisa mengaksesnya pada link berikut: 38 | 39 | 40 | 41 | ### Website 42 | 43 | Pada awalnya saya inisialisasi project website-nya menggunakan vite & react dengan rencana menggunakan vite-plugin-ssr (sekarang berubah menjadi [Vike](https://vike.dev/)) agar halamannya bisa pre-rendered. Namun cukup kesulitan saat implementasi dan pada akhirnya saya ganti ke Next.js dengan app router, sekalian mencoba fitur barunya sebelum saya putuskan migrasi website personal saya ke app router juga 😁 44 | 45 | Data yang ditampilkan di website tersebut bersifat statically generated, artinya data-nya hanya diambil ketika build. Ketika nantinya ada tambahan command di bot-nya, itu tentu perlu deploy ulang ke server dan di script deploy-nya saya tambahkan untuk trigger build website-nya melalui [netlify build hooks](https://docs.netlify.com/configure-builds/build-hooks/). Untuk pengambilan data-nya langsung dengan menggunakan [library supabase-js](https://github.com/supabase/supabase-js), jadi tidak perlu membuat API terlebih dahulu. 46 | 47 | ### Bot 48 | 49 | Untuk bot-nya sendiri menggunakan Node.js dan [whatsapp-web.js](https://wwebjs.dev/) yang dibelakang layar menggunakan puppeteer, sehingga cukup memberatkan server karena harus menjalankan sebuah browser. Logikanya sendiri cukup simpel, awalnya hanya mengandalkan pengkondisian. Tapi itu menjadi masalah ketika command / perintah sudah menjadi cukup banyak. Akhirnya saya ubah dengan memanfaatkan object `command[key]`, di mana `key` adalah nama dari perintah-nya. Sehingga ketika ada command baru saya tinggal menambahkan 1 key baru ke dalam object-nya. 50 | 51 | Database di sini berfungsi untuk menyimpan perintah, credits, list user, dan log pesan. Juga saya pakai untuk membuat limit penggunaan command, sehingga command hanya bisa digunakan setiap **X** menit sekali (misalnya). Saya menggunakan Prisma sebagai ORM dan Supabase untuk hosting database PostgreSQL. 52 | 53 | Untuk deployment-nya, saya menggunakan VPS $5 per bulan yang dipakai bersama dengan [RPL Skandakra bot discord](/projects/rpl-skandakra-bot) dan menggunakan pm2 untuk menjalankan keduanya secara bersamaan. Di sini saya juga menggunakan GitHub Actions untuk proses deployment secara otomatis. 54 | 55 | --- 56 | 57 | ## Masa Depan 58 | 59 | Bot ini mungkin akan tetap saya maintenance, namun bisa saja nanti berhenti jika nomornya terbanned dan tidak ada penggantinya. Atau bisa juga karena saya sudah tidak bisa membayar server-nya lagi 😆 60 | 61 | Bicara soal server, karena memakai puppeteer yang cukup memberatkan, kemarin saya ada menemukan library alternatif yaitu [Baileys](https://github.com/WhiskeySockets/Baileys) yang di belakang layar menggunakan websocket. Bisa jadi itu lebih ringan, tapi saya belum sempat untuk mencoba eksplor lebih lanjut mengenai library tersebut. 62 | 63 | Jika memang sudah tidak memungkinkan untuk dijadikan bot WhatsApp lagi, sepertinya akan saya menjadikan ini sebuah API / website sehingga resource di dalamnya masih bisa dipakai oleh banyak orang. Karena sebenarnya bot ini hanya mengumpulkan beberapa API menjadi satu dan semuanya dapat diakses dengan mudah melalui WhatsApp. 64 | 65 | --- 66 | 67 | ## Penutup 68 | 69 | Itu saja yang bisa saya ceritakan tentang GilBot. Oh iya, untuk namanya sendiri itu hanya karena sekilas terpintas Agil & Bot dan jadilah GilBot 🤣 70 | 71 | Jika kalian penasaran dengan bot-nya silahkan kunjungi link berikut ini: 72 | 73 | 74 | 75 | Terima kasih. 76 | -------------------------------------------------------------------------------- /content/blog/git-tips-cherry-pick.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Git Tips: Cherry Pick' 3 | thumbnail: /assets/blog/git-tips-cherry-pick/thumbnail.png 4 | thumbnailCredit: Photo by Oleksander Tomin on Unsplash 5 | summary: Cherry pick adalah sebuah perintah dari Git yang menurut saya sangat berguna di lingkup version control system. 6 | tags: 7 | - development 8 | - git 9 | author: Hendra Agil 10 | createdAt: 2022-08-17T19:16:10+07:00 11 | updatedAt: 2022-08-17T19:16:10+07:00 12 | --- 13 | 14 | ## Apa Itu Git? 15 | 16 | Bagi kalian yang masih belum tau, **Git** adalah salah satu jenis dari VCS (Version Control System). VCS sendiri berguna untuk mengontrol versi dari sebuah sistem / project, sesuai dengan namanya. Git adalah yang paling populer, sehingga untuk saat ini menjadi VCS yang paling banyak digunakan. Jika kalian ingin mengetahui lebih lanjut tentang Git, bisa kunjungi websitenya di [git-scm.com](https://git-scm.com/). 17 | 18 | ## Apa Itu Cherry Pick? 19 | 20 | Cherry pick adalah salah satu perintah Git yang bisa kita gunakan. Caranya dengan mengetikkan: 21 | 22 | ```bash 23 | git cherry-pick ... 24 | ``` 25 | 26 | Jika kalian ingin mendapatkan bantuan tentang perintah `cherry-pick`, kalian bisa menjalankan perintah: 27 | 28 | ```bash 29 | git cherry-pick --help 30 | ``` 31 | 32 | Maka, nanti akan otomatis membuka browser dan menampilkan dokumentasi dari perintah `cherry-pick`. 33 | 34 | ![Cherry Pick Docs](/assets/blog/git-tips-cherry-pick/cherry-pick-docs.png) 35 | 36 | ### Contoh Penggunaan 37 | 38 | Contoh sederhana penggunaan perintah `cherry-pick` yaitu kalian bisa _pick_ sebuah commit dari branch lain ke branch yang sedang kalian kerjakan. 39 | 40 | Misalnya, saya mempunyai project dengan 1 file di branch `main` seperti berikut: 41 | 42 | ![Example Project](/assets/blog/git-tips-cherry-pick/example-project.png) 43 | 44 | Di project tersebut saya sudah mempunyai 1 commit, bisa dicek dengan perintah `git log`. 45 | 46 | ![Check First Commit](/assets/blog/git-tips-cherry-pick/check-first-commit.png) 47 | 48 | --- 49 | 50 | Selanjutnya, saya checkout ke branch `add-feature` lalu menambahkan 2 file dan 2 commit. 51 | 52 | ![Add New File](/assets/blog/git-tips-cherry-pick/add-new-file.png) 53 | 54 | Jika kita cek log commit di branch `add-feature` maka akan menampilkan 3 commit, 1 dari branch `main` tadi dan 2 yang baru kita tambahkan. Kalian bisa menggunakan perintah `git log` dengan tambahan flag `--oneline` agar lebih ringkas. 55 | 56 | ![Git Log Oneline](/assets/blog/git-tips-cherry-pick/git-log-oneline.png) 57 | 58 | Nah, jika kita merge branch `add-feature` ke branch `main` maka semua commit akan terbawa. Bagaimana jika kita cuma ingin membawa salah satu commit saja? Di kasus ini kita bisa menggunakan `cherry-pick` tadi. Kita bisa kirim kode SHA dari commit yang ingin kita pilih untuk dibawa ke branch `main`. 59 | 60 | --- 61 | 62 | Pertama, kita perlu checkout dulu ke branch `main`. Baru kita pilih commit mana yang ingin kita bawa. Misal kita ingin bawa commit `'Fitur 3'` dengan kode SHA `1d29122`: 63 | 64 | ```bash 65 | git checkout main 66 | git cherry-pick 1d29122 67 | ``` 68 | 69 | Commit tersebut sudah terbawa ke branch `main` dengan kode SHA yang baru. Jadi, dengan `cherry-pick` kita bisa memilih fitur mana yang mau kita bawa ke branch utama / `main`. 70 | 71 | ## Penutup 72 | 73 | Sepertinya itu saja sedikit tips dari saya tentang penggunaan perintah cherry pick. Jika kalian ingin mempelajari lebih lanjut bisa kunjungi website berikut: 74 | 75 | - [Git Cherry Pick Docs](https://git-scm.com/docs/git-cherry-pick) 76 | 77 | Atau bisa lewat perintah `git cherry-pick --help` tadi. 78 | 79 | Kalau kalian ada pertanyaan bisa kirim komentar di bawah. Terima kasih, semoga bermanfaat! 80 | -------------------------------------------------------------------------------- /content/blog/headless-ui-vs-component-library.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Headless UI vs Component Library 3 | thumbnail: /assets/blog/headless-ui-vs-component-library/thumbnail.png 4 | thumbnailCredit: Photo by Ryunosuke Kikuno on Unsplash 5 | summary: Kapan kita harus memilih untuk menggunakan Headless UI daripada Component Library? 6 | tags: 7 | - development 8 | - react 9 | author: Hendra Agil 10 | createdAt: 2023-02-09T12:21:00+07:00 11 | updatedAt: 2023-02-09T12:21:00+07:00 12 | --- 13 | 14 | Karena saya cukup aktif di twitter, saya melihat ada developer yang membuat sebuah UI library: [shadcn/ui](https://ui.shadcn.com/). Pas saya cek ke repository-nya, dia pakai tailwind dan juga Radix UI. Dari situ saya jadi penasaran untuk tau lebih lanjut mengenai [Radix UI](https://www.radix-ui.com/). 15 | 16 | Di kepala saya muncul pertanyaan, kenapa sudah pakai Tailwind tapi masih pakai Radix UI. Setelah saya cari, ternyata Radix UI itu adalah sebuah Headless UI. Artinya, dia cuma punya logic bagaimana sebuah komponen berinteraksi. Untuk styling dari component, kita bisa custom sesuka hati. Entah itu pakai Emotion, styled-components, Tailwind, dsb. 17 | 18 | Ini pernah saya lihat sebelumnya di [headless-ui](https://headlessui.com/). headless-ui sendiri itu buatan Tailwind, jadi kalau sudah pakai Tailwind saya kira paling cocok pakai itu. Namun setelah saya coba cari referensi artikel yang membahas tentang library headless-ui ternyata dia tidak selengkap Radix UI yang sempat saya mention di awal tadi. 19 | 20 | --- 21 | 22 | ## Headless UI vs Component Library 23 | 24 | 28 | 29 | Pada artikel di atas, dia menulis kalau kita pakai component library yang sudah jadi dan siap pakai semacam Material UI, Chakra, dsb. Itu akan kompleks jika kita perlu custom style-nya. Berbeda jika pakai Headless UI, kita bisa fokus membuat style sesuai kebutuhan dan menyerahkan logic interaktifitas komponen ke library Headless UI. 30 | 31 | Lalu, apakah Headless UI akan lebih worth untuk dipakai daripada component library? 32 | 33 | Jawabannya belum tentu, kembali lagi ke kebutuhan kita. Kalau kita perlu membuat tampilan yang cepat dan tampilannya tidak terlalu “ribet”, kita bisa pakai component library. Tapi kalau perlu tampilan yang lumayan custom dan tidak bisa di cover dengan component library, lebih baik pakai Headless UI daripada mengeluarkan banyak effort untuk tweak konfigurasi si component library. 34 | 35 | Beberapa pilihan untuk component library: 36 | 37 | - [Material UI](https://mui.com/material-ui/getting-started/overview/) 38 | - [Chakra UI](https://chakra-ui.com/) 39 | - [Ant Design](https://ant.design/) 40 | - [React Bootstrap](https://react-bootstrap.github.io/) 41 | 42 | Sedangkan pilihan untuk Headless UI: 43 | 44 | - [Radix UI](https://www.radix-ui.com/) 45 | - [Reach UI](https://reach.tech/) 46 | - [Downshift](https://www.downshift-js.com/) 47 | - [Headless UI](https://headlessui.com/) 48 | 49 | --- 50 | 51 | ## Kebutuhan 52 | 53 | Mungkin saya akan sedikit mundur untuk membahas kebutuhan apa yang diperlukan sebelum memilih tool yang akan kita gunakan. Dari artikel yang sempat saya mention sebelumnya, si penulis mengatakan dalam membuat sebuah design system itu harus menjawab beberapa kebutuhan: 54 | 55 | 1. Accessibility, Acessibility (aksesibilitas) ini memang penting, tapi terkadang banyak orang yang tidak peduli tentang ini. 56 | 2. Theming, Tema tampilan seperti light / dark mode. 57 | 3. Uniqueness, Tampilan software yang kita buat harus unik. Kita tidak ingin terlihat kalau kita pakai Material UI, dsb. yang sangat terlihat generic. 58 | 4. Browser support, Support digunakan di banyak jenis browser. Tapi untuk saat ini setau saya ada 3 engine yang sering digunakan: chromium, webkit dan gecko (kalau tidak salah dipakai oleh firefox). 59 | 5. Functionality, Fungsionalitas yang artinya kita punya kontrol terhadap apa yang akan dilakukan oleh komponen. 60 | 6. Responsiveness, Responsif, karena pengguna kita tidak hanya memakai desktop. 61 | 7. Maintainability. Mudah untuk di maintain dan di modifikasi. 62 | 63 | --- 64 | 65 | ### Tradeoffs 66 | 67 | Kalau menurut saya jika kita benar benar membutuhkan 7 poin di atas, component library bukanlah pilihan tepat. Karena kita perlu untuk custom konfigurasinya. Seperti yang disebutkan pada artikel sebelumnya, ketika si penulis memakai Material UI dan seiring berjalannya waktu ternyata dia membutuhkan komponen yang lebih _complicated_. 68 | 69 | Dan tentu saja component library akan sangat sulit untuk memenuhi hal tersebut. Pada akhirnya dia menyadari kalau memakai component library bukan pilihan yang tepat dan memutuskan untuk menggunakan Headless UI. 70 | 71 | Mungkin di sini saya akan tuliskan beberapa _tradeoffs_ dari Headless UI dan component library: 72 | 73 | - Headless UI 74 | 1. ✅ Kontrol tampilan lebih bebas 75 | 2. ✅ Bisa dipakai dengan berbagai tool styling seperti Emotion, styled-components, Tailwind, dsb. 76 | 3. ❌ Membutuhkan setup yang lebih banyak 77 | - Component library 78 | 1. ✅ Tidak membutuhkan setup yang banyak 79 | 2. ❌ Kontrol terhadap tampilan lebih sedikit 80 | 3. ❌ Biasanya perlu menggabungkan dengan styling kita sendiri 81 | 82 | --- 83 | 84 | ## Penutup 85 | 86 | Untuk sekarang, Headless UI bisa dibilang adalah tool yang mainstream. Seperti tim dari Material sekarang sedang membuat Headless UI yang bernama [MUI Base](https://mui.com/base/getting-started/overview/). MUI Base adalah versi Headless dari component library Material UI. Tapi masih versi alpha dan tentu saja belum direkomendasikan untuk dipakai di production. 87 | 88 | Mungkin cukup itu saja yang bisa saya tuliskan. Menurut saya dengan Headless UI kita bisa membuat komponen yang accessible dan tampilan yang sesuai kebutuhan. Component library pun sama, tapi kita tidak bisa cukup leluasa untuk mengatur tampilannya. Mungkin di post selanjutnya saya akan berbagi pengalaman saya mencoba menggunakan Headless UI. 89 | 90 | Beberapa referensi yang saya ambil: 91 | 92 | - [youtube.com/watch?v=BVLBNqW4ves](https://www.youtube.com/watch?v=BVLBNqW4ves) 93 | - [medium.com/@nirbenyair/headless-components-in-react-and-why-i-stopped-using-ui-libraries](https://medium.com/@nirbenyair/headless-components-in-react-and-why-i-stopped-using-ui-libraries-a8208197c268) 94 | 95 | Terima kasih. 96 | -------------------------------------------------------------------------------- /content/blog/kehidupan-setelah-smk.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Kehidupan Setelah SMK 3 | thumbnail: /assets/blog/kehidupan-setelah-smk/thumbnail.png 4 | thumbnailCredit: Photo by Dose Media on Unsplash 5 | summary: Apa yang akan kita hadapi setelah masa SMK? Yaitu sebuah kehidupan yang sebenarnya :) 6 | tags: 7 | - life 8 | - story 9 | author: Hendra Agil 10 | createdAt: 2021-05-25T11:17:21+07:00 11 | updatedAt: 2021-05-27T13:55:48+07:00 12 | --- 13 | 14 | Masa SMK/SMA adalah masa terakhir dalam lingkup sekolah. 3 tahun terakhir yang mungkin kurang berkesan dikarenakan datangnya pandemi yang tidak terduga selama hampir 2 tahun terakhir. Eh, saya tidak akan membahas pandemi kali ini hehe. 15 | 16 | ### Apa yang akan kamu lakukan setelah SMK? Apa yang kamu akan hadapi? 17 | 18 | Tidak tahu. Bisa jadi kehidupanmu akan lebih baik. Bisa jadi kehidupanmu akan lebih buruk. Tergantung bagaimana skenarionya nanti. Yang bisa kita lakukan hanyalah berusaha untuk menjadi lebih baik dari hari sebelumnya. Jika disaat SMK kita pernah berpikir untuk melakukan suatu hal di esok hari. Mungkin esok hari yang kita maksud sudah datang dan kita harus mulai sekarang. 19 | 20 | Ada yang memilih kerja, ada juga yang memilih kuliah. Kamu harus bisa memilih dengan tepat, yang sesuai dengan kondisi kamu saat ini. Mana yang kamu butuhkan maka pilihlah. Jangan terlalu lama berpikir, berencana dan mengkhayal. Jika kamu punya mimpi maka waktunya untuk diwujudkan. 21 | 22 | Menikah? Janganlah menikah jika dirimu belum siap. Itu hanya akan menambah beban kehidupanmu. Itu bukan menjadi prioritas saat ini. Jadikan yang lebih dekat denganmu untuk menjadi prioritasmu saat ini. Jangan jadikan menikah sebagai prioritas jika belum punya calonnya, wkwk. 23 | 24 | #### Bagaimana targetmu disaat SMK dulu? 25 | 26 | Disaat SMK, aku punya beberapa target untuk aku capai. Setelah aku lihat, ada yang tercapai ada juga yang belum (bukan tidak). Tapi aku tetap bangga dengan progress diriku saat ini. Mungkin aku akan memperbarui targetku nanti. Target dekat saat ini adalah segera kuliah / bekerja / kuliah & bekerja. Belajar juga harus setiap hari tentunya. Bidang IT bukanlah bidang yang mudah, teknologi berkembang setiap hari bahkan setiap detik. Tapi, jangan selalu dan memaksa untuk mengikuti tren IT. Karena itu akan sangat melelahkan. 27 | 28 | **"Pelajari fundamental programmingnya"**, sebuah kalimat yang selalu aku ingat. Dulu aku pertama belajar PHP, mulai dari basic - oop - mvc. Dan setelah mencoba ke JavaScript tidak banyak berbeda, struktur percabangan, perulangan, oop, mvc juga gitu gitu aja. Lalu, awal tahun ini juga diberi kesempatan untuk mengikuti LKS tingkat Provinsi Jawa Tengah dan diikutkan di bidang IT Software Solution for Business (ITSSB) mewakili Kabupaten Karanganyar. ITSSB sendiri untuk tingkat provinsi ada 2 modul, yaitu desktop dan mobile. Aku sendiri belum pernah belajar C# (untuk desktop) dan Java (untuk mobile). Java sebenarnya sudah diajarkan di sekolah, cuma kurang suka karena sintaksnya yang menurutku cukup rumit dan panjang XD. Walaupun tidak juara, tapi dari situ aku belajar kalau sintaks setiap bahasa memang tidak jauh berbeda. 29 | 30 | Akhirnya, aku memilih untuk belajar JavaScript untuk saat ini. Menurutku menarik untuk dipelajari apalagi dengan adanya TypeScript sekarang. Untuk saat ini masih fokus di frontend dengan React dan Next.js. Semoga saja kedepannya bisa jadi fullstack XD. 31 | 32 | Mungkin itu saja yang bisa aku tuliskan. Ini adalah tulisan pertamaku di blog ini. Semoga kedepannya bisa menuliskan beberapa cerita lagi hehe. Terima kasih sudah mau membaca tulisanku yang cukup random ini wkwk. Semangat buat kalian, kehidupan sebenarnya akan datang dan kalian harus siap dengan hal itu. Persiapkan dari sekarang dan mulai lakukan! 33 | 34 | > Gracias. Thank you. Terima kasih. Matur nuwun. 35 | -------------------------------------------------------------------------------- /content/blog/kilas-balik-2024.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Kilas Balik 2024 3 | thumbnail: /assets/blog/kilas-balik-2024/thumbnail.png 4 | thumbnailCredit: Photo by Yoel J Gonzalez on Unsplash 5 | summary: Sebuah cerita kehidupan selama satu tahun terakhir. 6 | tags: 7 | - life 8 | - story 9 | - career 10 | author: Hendra Agil 11 | createdAt: 2024-12-31T22:35:00+07:00 12 | updatedAt: 2024-12-31T23:35:00+07:00 13 | --- 14 | 15 | Tulisan ini masih sejenis dengan edisi [sebelumnya](/blog/2-tahun-dan-masih-berlanjut) dengan disclaimer yang masih sama. Jika belum tau, silakan baca terlebih dahulu tulisan tersebut baru ke sini lagi 😅 16 | 17 | --- 18 | 19 | ## Istirahat dari Pekerjaan Full-time 20 | 21 | Sebenarnya ini tidak terhitung pada tahun 2024. Namun, jika tidak ada pengantar ini rasanya kurang hehe. Singkat cerita setelah aku selesai dari kontrak kerja pada bulan Oktober, aku memutuskan untuk tidak memperpanjang kontrak tersebut. Lalu aku menjadi pengangguran selama 2 bulan dan pada saat itu juga aku mulai mencari pekerjaan baru. 22 | 23 | Setelah 2 bulan, aku memutuskan untuk menerima kontrak pada lowongan kerja terakhir yang aku lamar. Pada lamaran terakhir ini aku tidak menyangka jika akan diterima, jadi aku cukup senang pada saat itu. 24 | 25 | --- 26 | 27 | ## Mencoba Hal Baru 28 | 29 | Pada kontrak kerja yang baru aku terima pada saat itu mewajibkan untuk onsite. Ini juga menjadi salah satu alasan aku menerima kontrak tersebut. Karena masih muda jadi aku ingin mencoba hal yang belum pernah aku alami, yaitu merantau. Tempat kerja yang aku terima ini adalah Dicoding, yang dulu sewaktu SMK aku hanya belajar di platform-nya dan sekarang berkesempatan untuk berkontribusi langsung. 30 | 31 | Pada 7 bulan pertama aku ditempatkan di Tangsel dan setelah itu pindah ke Bandung. Banyak hal yang baru yang aku dapatkan ketika merantau, juga lebih banyak teman ngobrol daripada saat kerja remote. Tentu saja semua ada sisi positif dan negatifnya. 32 | 33 | Dari sisi teknikal, aku juga belajar banyak dari kompleksitas codebase Dicoding. Seperti yang sudah dibahas pada [DevTalk Dicoding](https://youtu.be/n78c-w-a3sI?si=ghKD_e1Zhj-qluXb), codebase-nya sendiri menggunakan Laravel yang mana sebenarnya aku sudah lama tidak memegang teknologi tersebut. Tapi karena urusan pekerjaan, ya mau tidak mau harus mempelajarinya kembali. Dari situ aku juga teringat beberapa waktu lalu ada yang pernah bilang kalau bekerja dengan tech stack favorit adalah sebuah privilege 😅 34 | 35 | Pada intinya banyak pengalaman baru yang aku dapatkan ketika memutuskan untuk keluar dari zona nyaman (kerja remote) ke kerja onsite dan merantau. Namun, terkadang juga terpikirkan untuk kembali ke masa kerja remote karena suasana yang lebih tentram dan lebih dekat dengan keluarga. 36 | 37 | --- 38 | 39 | ## Menghadiri Event Offline 40 | 41 | Salah satu hal yang tidak bisa aku dapatkan ketika kerja remote adalah menghadiri suatu event offline. Mungkin bukan tidak bisa, tapi selama kerja remote aku tidak menganggap sebuah event itu cukup bermanfaat bagiku. Dan saat kerja onsite aku diberi kesempatan untuk menghadiri beberapa event. 42 | 43 | Yang pertama adalah [BDD Bandung 2024](https://www.dicoding.com/events/7928) yang diselenggarakan oleh Dicoding. Di situ aku pertama kali bertemu dengan pemilik [server discord](https://discord.gg/wpu) yang selama ini aku bantu untuk maintain yaitu Pak Sandhika Galih. Lalu menjelang akhir tahun ada 2 event yang aku hadiri, yaitu [IDSW](https://idsw.dev/) dan juga [DevFest Bandung](https://gdg.community.dev/events/details/google-gdg-bandung-presents-devfest-bandung-2024-1/). 44 | 45 | Dari beberapa event tersebut, aku bertemu dengan beberapa teman yang sebelumnya hanya kenal secara online. Juga bertemu dengan rekan kerja remote sebelumnya. Aku juga mendapatkan beberapa insight baru melalui speaker-speaker yang mengisi pada event tersebut. Dari sini aku jadi cukup tertarik untuk menghadiri event (offline) atau mungkin menjadi speaker event nantinya 🫣 46 | 47 | --- 48 | 49 | ## 1 Tahun dalam 1 Gambar 50 | 51 | Ini adalah beberapa gambar yang sempat aku simpan selama 2024 😁 52 | 53 | ![2024 dalam 1 Gambar](/assets/blog/kilas-balik-2024/2024-in-1.png) 54 | 55 | --- 56 | 57 | ## Apa Selanjutnya 58 | 59 | Tentu dengan beberapa poin yang cukup "menyenangkan" tadi, bukan berarti tidak ada target atau hal yang belum tercapai di tahun 2024. Salah satunya adalah alokasi waktu untuk mengerjakan side project yang memungkinkan aku eksplor hal baru diluar pekerjaan. Di 2024 aku tidak melewatkan hal itu, namun hanya sedikit yang aku kerjakan. Padahal sudah cukup banyak list yang ingin aku kerjakan dan coba-coba hehe. 60 | 61 | Dengan keadaan seperti itu, tentu aku berharap bisa lebih baik ke depannya dengan pembagian waktu yang lebih efisien. Mungkin tahun ini menjadi adaptasi bagi aku yang merasa kerja onsite sedikit lebih melelahkan daripada kerja remote. Tentu masa depan adalah misteri dan keputusan apapun bisa terjadi. 62 | 63 | Sampai bertemu tahun depan (yang lebih baik, _semoga_)! 👋 64 | -------------------------------------------------------------------------------- /content/blog/memilih-rem-daripada-px-dalam-css.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Memilih rem Daripada px dalam CSS 3 | thumbnail: /assets/blog/memilih-rem-daripada-px-dalam-css/thumbnail.png 4 | thumbnailCredit: Photo by Mark Wilkinson Hughes on Unsplash 5 | summary: Kapan kita menggunakan rem daripada px ketika menuliskan kode CSS? 6 | tags: 7 | - development 8 | - css 9 | - practice 10 | author: Hendra Agil 11 | createdAt: 2024-07-31T21:40:00+07:00 12 | updatedAt: 2024-07-31T21:40:00+07:00 13 | --- 14 | 15 | Unit yang cukup populer ketika berbicara mengenai ukuran yaitu pixel atau `px`: 16 | 17 | ```css 18 | .container { 19 | width: 1024px; 20 | padding: 16px; 21 | } 22 | ``` 23 | 24 | Tapi, apakah `px` adalah unit yang cukup bagus ketika kita menggunakannya dalam CSS? 25 | 26 | --- 27 | 28 | ## px vs rem 29 | 30 | Ketika kita berurusan dengan CSS dan aksesibilitas web, unit `px` adalah pilihan yang kurang tepat dalam beberapa skenario. Sebagai contoh, dalam sebuah web browser kita dapat mengatur ukuran teks seperti berikut: 31 | 32 | ![Mengatur ukuran teks di browser 33 | Firefox](/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-firefox.png) 34 | 35 | Secara default, browser akan mengatur ukuran teks ke 16px. 36 | 37 | Jika menggunakan unit `px` di kode CSS, maka ukuran teks tidak akan berubah mengikuti ukuran teks yang sudah diatur dari browser. 38 | 39 | ```css 40 | p { 41 | font-size: 16px; 42 | } 43 | ``` 44 | 45 | ![Ukuran teks menggunakan px](/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-px.png) 46 | 47 | Berbeda jika menggunakan `rem`. 48 | 49 | ```css 50 | p { 51 | font-size: 1rem; 52 | } 53 | ``` 54 | 55 | ![Ukuran teks menggunakan rem](/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-rem.png) 56 | 57 | Selain pada properti `font-size`, direkomendasikan juga menggunakan unit `rem` untuk properti lain seperti `width` dan `height` jika diperlukan. 58 | 59 | > **Bagaimana jika menggunakan `em`?** 60 | > 61 | > Sudah pasti hasilnya akan sama saja ketika tidak ada parent element yang meng-_override_ ukuran teks yang sudah diatur oleh browser. 62 | 63 | > **Bagaimana dengan `padding` dan `margin`?** 64 | > 65 | > Sebaiknya kita hanya gunakan `px` ketika mengatur `padding` atau `margin`. Karena jika kita menggunakan unit `rem` maka akan terlalu banyak whitespace yang tidak perlu ketika ukuran teks pada browser diperbesar. 66 | 67 | --- 68 | 69 | Praktik ini juga sudah diterapkan oleh framework CSS yang cukup populer seperti [Tailwind](https://tailwindcss.com/) dan [Bootstrap](https://getbootstrap.com/). Sehingga jika kita sudah menggunakan salah satu framework tersebut dan menggunakan class-class yang sudah disediakan, maka tidak perlu khawatir dengan masalah yang saya sebutkan sebelumnya. 70 | 71 | Namun, jika kita membuat styling yang kustom maka kita perlu memperhatikan yang mungkin disebutnya "scalable size" dalam CSS yang kita tulis. Dengan begitu, kita tidak hanya mengikuti desain yang sudah disepakati. Namun juga memperhatikan aksesibilitas web yang akan diakses oleh berbagai macam pengguna di luar sana. 72 | 73 | --- 74 | 75 | ## Kesimpulan 76 | 77 | Itu saja yang dapat saya bagikan, tulisan yang singkat. Mungkin bisa disebut ini adalah catatan dari TIL (Today I Learn), karena saya juga baru mengetahui ini beberapa hari sebelum saya menuliskannya di sini. Sepertinya saya perlu membuat halaman TIL / snippet sendiri agar tulisan-tulisan singkat seperti ini dapat saya simpan di sana, sehingga blog ini akan khusus membahas hal yang cukup kompleks. Mari kita lihat~ 😆 78 | 79 | Jika kalian ingin mempelajari lebih lanjut terkait apa yang saya tuliskan sebelumnya, dapat mengunjungi tautan berikut: 80 | 81 | - [YouTube: Please stop using px for font-size](https://youtu.be/xCSw6bPXZks?si=4myvbgvTJvSHK8O1) 82 | - [Artikel: The Surprising Truth About Pixels and Accessibility](https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility) 83 | 84 | Terima kasih dan sampai jumpa! 👋 85 | -------------------------------------------------------------------------------- /content/blog/migrasi-ke-biome.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Migrasi ke Biome 3 | thumbnail: /assets/blog/migrasi-ke-biome/thumbnail.png 4 | thumbnailCredit: Photo by Thomas Chan on Unsplash 5 | summary: Menuliskan sedikit tentang proses migrasi dari ESLint & Prettier ke Biome. 6 | tags: 7 | - development 8 | - practice 9 | - javascript 10 | author: Hendra Agil 11 | createdAt: 2024-09-04T03:45:00+07:00 12 | updatedAt: 2024-09-04T03:45:00+07:00 13 | --- 14 | 15 | Kita pasti sudah sering mendengar / menggunakan [ESLint](https://eslint.org/) sebagai linter dan [Prettier](https://prettier.io/) sebagai formatter dalam sebuah project JavaScript. Seiring berjalannya waktu pasti ada library baru terutama dalam ekosistem JavaScript, salah satunya adalah [Biome](https://biomejs.dev/) sebagai tool yang menggabungkan formatter dan linter. 16 | 17 | --- 18 | 19 | ## Apa itu Biome? 20 | 21 | **Biome** adalah suatu library untuk menerapkan linter dan formatter untuk project JavaScript kita. Dulunya library ini bernama [rome](https://github.com/rome/tools) dan sekarang sudah tidak di*maintain* lagi, sehingga dilanjutkan oleh komunitas dan diberi nama Biome. Jika ingin melihat cerita lengkapnya, bisa melalui tautan berikut: 22 | 23 | 24 | 25 | ## Memulai Migrasi 26 | 27 | Untuk proses migrasi dari ESLint dan Prettier sendiri sudah dituliskan pada [dokumentasi Biome](https://biomejs.dev/guides/migrate-eslint-prettier/). Yang terlihat berbeda antara ESLint dan Biome adalah penulisan rule-nya. Jika di ESLint menggunakan `kebab-case`, di Biome sendiri menggunakan `camelCase` untuk penulisan rule-nya. 28 | 29 | Untuk instalasinya dapat menjalankan perintah: 30 | 31 | ```bash 32 | pnpm add --save-dev --save-exact @biomejs/biome 33 | pnpm biome init 34 | ``` 35 | 36 | Lalu, jalankan perintah untuk memindahkan konfigurasi dari ESLint ke Biome: 37 | 38 | ```bash 39 | pnpm biome migrate eslint --write 40 | ``` 41 | 42 | Dan perintah untuk memindahkan konfigurasi Prettier: 43 | 44 | ```bash 45 | pnpm biome migrate prettier --write 46 | ``` 47 | 48 | Pada kasus saya, ada penggunaan plugin Prettier untuk Tailwind. Di Biome sendiri sudah membuatkan rule sendiri untuk menerapkan sorted class untuk utility framework seperti Tailwind: 49 | 50 | 51 | 52 | Akhirnya, kita bisa menghapus ESLint dan Prettier serta config / plugin tambahannya jika ada: 53 | 54 | ```bash 55 | pnpm remove eslint prettier eslint-config-next prettier-plugin-tailwindcss 56 | ``` 57 | 58 | Kita juga bisa menambahkan script baru pada file package.json untuk menjalankan linter dan formatter dari Biome: 59 | 60 | ```json 61 | "scripts": { 62 | "lint": "biome lint", 63 | "lint:write": "biome lint --write", 64 | "lint:write:unsafe": "biome lint --write --unsafe", 65 | "format": "biome format", 66 | "format:write": "biome format --write" 67 | }, 68 | ``` 69 | 70 | --- 71 | 72 | ## Kesimpulan 73 | 74 | Proses migrasi cukup smooth. Mungkin ada beberapa rules yang masih _work in progress_ seperti `useSortedClasses` tadi, sehingga ada beberapa algoritma yang belum diadaptasi. Proses pengecekan dari Biome juga cukup cepat, seperti klaim pada website-nya: 75 | 76 | ![Menjalankan perintah Biome](/assets/blog/migrasi-ke-biome/biome-run.png) 77 | 78 | Kita juga bisa melihat progress development dari Biome melalui issue berikut: 79 | 80 | 81 | 82 | Jika kalian ingin tahu kode lebih lengkap terkait proses migrasi pada post ini dapat mengunjungi pull request berikut: 83 | 84 | 89 | 90 | Sekian dan terima kasih. 91 | -------------------------------------------------------------------------------- /content/blog/migrasi-ke-velite.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Migrasi ke Velite 3 | thumbnail: /assets/blog/migrasi-ke-velite/thumbnail.png 4 | thumbnailCredit: Photo by Patrick Tomasso on Unsplash 5 | summary: Pindah dari Contentlayer ke Velite, no CMS-CMS club. 6 | tags: 7 | - development 8 | - practice 9 | author: Hendra Agil 10 | createdAt: 2025-02-22T21:40:00+07:00 11 | updatedAt: 2025-02-22T21:40:00+07:00 12 | --- 13 | 14 | Siapa yang tidak kenal dengan Wordpress? Dia adalah salah satu CMS yang cukup populer. Kita juga bisa menggunakannya sebagai headless CMS, yang artinya proses rendering kontennya bisa kita atur sesuka hati. Bahasan kali ini berawal dari saya yang menggunakan headless CMS untuk manajemen postingan blog pada website ini. Namun, pada akhirnya beralih ke markdown file-based. 15 | 16 | --- 17 | 18 | ## Apa itu Markdown? 19 | 20 | Markdown adalah sebuah bahasa markup yang dapat memudahkan kita dalam hal text formatting dan pada akhirnya akan ditampilkan dalam bentuk HTML. Standar markdown ini bisa berbeda-beda, seperti contoh yang diterapkan [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) mungkin tidak akan berjalan sama persis ketika dipakai di platform lain. 21 | 22 | ## Markdown dan Next.js 23 | 24 | Dari awal saya sudah membuat website ini menggunakan Next.js dan sudah menyediakan fitur untuk menangani file markdown. Lebih spesifiknya yang ditangani di sini bukan file markdown biasa, tapi file MDX atau markdown & JSX. Jadi kita bisa menuliskan kode markdown dan juga kode JSX pada satu file yang sama. 25 | 26 | Jika kita mengikuti dokumentasi yang sudah ada, kita sudah bisa menuliskan kode MDX dan akan di-compile ke halaman HTML oleh Next.js dengan lancar. Lalu, kenapa masih membutuhkan library seperti Contentlayer / Velite? 27 | 28 | --- 29 | 30 | ## Content SDK 31 | 32 |
33 | ![Velite content flow](/assets/blog/migrasi-ke-velite/velite-flow.png) 34 |
35 | 36 | [Contentlayer](https://contentlayer.dev/) & [Velite](https://velite.js.org/) ini bisa disebut sebagai content SDK karena memudahkan kita dalam manajemen konten. Konsep keduanya juga kurang lebih sama, yaitu mengubah file source (json, markdown, yaml) menjadi file js, ts, dan json sebagai output datanya. Sehingga nanti semua file source yang kita tambahkan akan mengikuti tipe data yang sudah kita definisikan (di schema) sebelumnya. 37 | 38 | Awalnya saya menggunakan Contentlayer, namun status project-nya saat ini sudah tidak dilanjutkan lagi seperti yang dijelaskan pada issue: [contentlayer/issues/429](https://github.com/contentlayerdev/contentlayer/issues/429#issuecomment-1731298319). Akhirnya saya coba mencari alternatif lain dan bertemu dengan Velite. 39 | 40 | Salah satu hal yang saya suka dari Velite adalah dia menggunakan [Zod](https://zod.dev/) untuk pembuatan schema kontennya. Ada beberapa [schema tambahan](https://velite.js.org/guide/velite-schemas) juga yang mempermudah komputasi seperti mengubah gambar menjadi base64 untuk dijadikan placeholder. Ketika menggunakan Contentlayer sebelumnya, hal itu harus saya lakukan manual dengan bantuan [plaiceholder](https://plaiceholder.co/). 41 | 42 | Beberapa schema lain yang saya pakai ada seperti `metadata` yang mengasilkan output `readingTime` untuk artikel yang saya tulis, jadi tidak perlu hitung manual lagi. Penggunaan code highlighting juga menurut saya lebih mudah dengan bantuan library [shiki](https://shiki.matsu.io/) dan bisa ditambahkan library lain yang dapat mempercantik tampilan snippet kode pada artikel yang kita tulis. 43 | 44 | --- 45 | 46 | Pembuat Velite ini juga menyebutkan kalau dia [terinspirasi dari Contentlayer](https://velite.js.org/guide/introduction#why-not-contentlayer), jadi secara konsep sama. Tapi dengan kebutuhan yang lebih lengkap. Tidak heran juga ketika saya migrasi dari Contentlayer ke Velite ini prosesnya cukup seamless dan mudah, juga lebih dimudahkan dengan beberapa fitur yang sudah ada. 47 | 48 | Jika kalian ingin melihat seberapa banyak kode yang berubah bisa dilihat pada pull request berikut: 49 | 50 | 51 | 52 | Lalu, ada juga pull request lain yang spesifik untuk melakukan refactor proses komputasi gambar: 53 | 54 | 55 | 56 | --- 57 | 58 | ## Penutup 59 | 60 | Cukup itu saja yang bisa saya bagikan, saat ini markdown adalah pilihan pertama ketika saya ingin menuliskan teks ataupun diagram yang dapat menggunakan [mermaid](https://mermaid.js.org/). Karena menurut saya lebih predictable daripada menggunakan WYSIWYG editor seperti yang digunakan CMS pada umumnya 😄 61 | 62 | Sampai bertemu pada tulisan lainnya. Terima kasih 👋 63 | -------------------------------------------------------------------------------- /content/data/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "avatar": "/assets/main/avatar.png", 3 | "name": "Hendra Agil", 4 | "username": "hendraaagil", 5 | "summary": "I'm Hendra Agil — a frontend-focused software engineer with a full-stack mindset and a passion for crafting digital experiences that truly solve problems. With hands-on experience across companies and communities, I've led product development on both the backend and frontend. I enjoy building intuitive UIs, improving developer experience, and exploring what's next in the web ecosystem.", 6 | "description": [ 7 | "👋 Hello! My name is Hendra Agil Syaputra, usually called Hendra / Agil. Born and lives in Karanganyar Regency, Central Java, Indonesia. Graduated from SMKN 2 Karanganyar in 2021 as a Software Engineering student. Currently studying at the Binus Online Learning.", 8 | "I started learn about web development since 2019 with PHP language from the school. For now I spend more time to explore about software engineering using JavaScript, Node.js and React. I also still learn some other topics like accessibility, testing, CI/CD, etc." 9 | ], 10 | "skills": [ 11 | { 12 | "name": "Languages", 13 | "items": [ 14 | "Bash", 15 | "C#", 16 | "CSS & HTML", 17 | "Java", 18 | "JavaScript", 19 | "Markdown", 20 | "PHP", 21 | "TypeScript" 22 | ] 23 | }, 24 | { 25 | "name": "Frontend", 26 | "items": [ 27 | "Emotion", 28 | "Next.js", 29 | "React", 30 | "Scss", 31 | "Webpack", 32 | "Tailwind", 33 | "Vite", 34 | "Vue" 35 | ] 36 | }, 37 | { 38 | "name": "Backend", 39 | "items": [ 40 | "Docker", 41 | "Firebase", 42 | "MongoDB", 43 | "MySQL", 44 | "Nest.js", 45 | "Node.js", 46 | "PostgreSQL" 47 | ] 48 | } 49 | ], 50 | "social": { 51 | "description": "You can react me out via email at: hi@hendraaagil.dev or via socials below:", 52 | "links": [ 53 | { "name": "GitHub", "url": "https://github.com/hendraaagil" }, 54 | { "name": "LinkedIn", "url": "https://linkedin.com/in/hendraaagil" }, 55 | { "name": "Telegram", "url": "https://t.me/hendraaagil" }, 56 | { "name": "Twitter", "url": "https://twitter.com/hendraaagil" }, 57 | { "name": "Others", "url": "https://hendraaagil.dev/links" } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /content/data/hardware.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "name": "Laptop", 5 | "items": [ 6 | { "name": "Lenovo Idepad Gaming 3 15ACH6" }, 7 | { 8 | "name": "Specs:", 9 | "subitems": [ 10 | "Processor: AMD Ryzen 5 5600H", 11 | "Graphics: NVIDIA GeForce RTX 3050 4GB GDDR6", 12 | "RAM: 2 x 8GB DDR4-3200", 13 | "Storage[0]: 512GB SSD M.2 2280 PCIe", 14 | "Storage[1]: 256GB SSD M.2 2242 PCIe", 15 | "OS: Windows 11 Home 64-bit" 16 | ] 17 | } 18 | ] 19 | }, 20 | { 21 | "name": "Peripherals", 22 | "items": [ 23 | { "name": "Monitor[0]: Lenovo R27q-30" }, 24 | { "name": "Monitor[1]: Samsung S24R350" }, 25 | { "name": "Keyboard: Rexus Daiva Max D68SF" }, 26 | { "name": "Mouse: VortexSeries Oni R1" }, 27 | { "name": "Gamepad: Fantech Nova WGP14" }, 28 | { "name": "Headphone: dbE GM500" }, 29 | { "name": "IEM: Kinera Celest Wyvern Pro" }, 30 | { "name": "TWS: Tozo Aerosound 3" }, 31 | { "name": "Phone[0]: Google Pixel 6a" }, 32 | { "name": "Phone[1]: Realme 9" } 33 | ] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /content/data/software.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "name": "Editor", 5 | "items": [ 6 | { 7 | "name": "Visual Studio Code", 8 | "link": "https://code.visualstudio.com" 9 | }, 10 | { 11 | "name": "Theme: GitHub Theme", 12 | "link": "https://marketplace.visualstudio.com/items?itemName=GitHub.github-vscode-theme" 13 | }, 14 | { 15 | "name": "Editor Font: Iosevka Mayukai Serif", 16 | "link": "https://github.com/Iosevka-Mayukai/Iosevka-Mayukai" 17 | }, 18 | { 19 | "name": "Terminal Font: Geist Mono Nerd Font", 20 | "link": "https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/GeistMono" 21 | } 22 | ] 23 | }, 24 | { 25 | "name": "Terminal", 26 | "items": [ 27 | { 28 | "name": "Windows Terminal", 29 | "link": "https://github.com/microsoft/terminal" 30 | }, 31 | { 32 | "name": "oh-my-posh", 33 | "link": "https://ohmyposh.dev" 34 | }, 35 | { 36 | "name": "Scoop", 37 | "link": "https://scoop.sh" 38 | }, 39 | { 40 | "name": "Chocolatey", 41 | "link": "https://chocolatey.org" 42 | } 43 | ] 44 | }, 45 | { 46 | "name": "Design", 47 | "items": [ 48 | { 49 | "name": "GIMP", 50 | "link": "https://gimp.org" 51 | }, 52 | { 53 | "name": "Figma", 54 | "link": "https://figma.com" 55 | } 56 | ] 57 | }, 58 | { 59 | "name": "Video", 60 | "items": [ 61 | { 62 | "name": "OBS Studio", 63 | "link": "https://obsproject.com" 64 | }, 65 | { 66 | "name": "HandBrake", 67 | "link": "https://handbrake.fr" 68 | }, 69 | { 70 | "name": "Kdenlive", 71 | "link": "https://kdenlive.org" 72 | } 73 | ] 74 | }, 75 | { 76 | "name": "Miscellaneous", 77 | "items": [ 78 | { 79 | "name": "Git", 80 | "link": "https://git-scm.com" 81 | }, 82 | { 83 | "name": "Notion", 84 | "link": "https://notion.so" 85 | }, 86 | { 87 | "name": "Postman", 88 | "link": "https://postman.com" 89 | }, 90 | { 91 | "name": "DBeaver", 92 | "link": "https://dbeaver.io" 93 | }, 94 | { 95 | "name": "Docker", 96 | "link": "https://docker.com" 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /content/project/01-e-voting.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: E-Voting 3 | description: E-Voting app to choose your choice. 4 | thumbnail: /assets/project/e-voting.png 5 | github: https://github.com/hendraaagil/vote-client 6 | demo: https://demo-vote.netlify.app 7 | --- 8 | 9 | A voting app used for one of my school task and for practicing React and Node. 10 | 11 | This app is having 2 interfaces, one is for the user to vote and the other one is for the admin to manage the candidates and the election. 12 | 13 | Stack used: 14 | 15 | - React 16 | - React Router 17 | - Tailwind CSS 18 | - Node.js 19 | - Express.js 20 | - MongoDB 21 | - AdminJS (formerly AdminBro) 22 | -------------------------------------------------------------------------------- /content/project/02-gift-store-landing-page.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gift Store Landing Page 3 | description: Simple landing page sliced from figma design. 4 | thumbnail: /assets/project/gift-store-landing-page.png 5 | github: https://github.com/hendraaagil/gift-store-landing-page 6 | demo: https://gift-store-landing-page.netlify.app 7 | --- 8 | 9 | A simple landing page sliced from figma design for practicing React and Framer Motion. 10 | 11 | Stack used: 12 | 13 | - React 14 | - Framer Motion 15 | -------------------------------------------------------------------------------- /content/project/03-pengaduan-sekolah.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pengaduan Sekolah 3 | description: Pengaduan sekolah is an app for sent complaint to your school. 4 | thumbnail: /assets/project/pengaduan-sekolah.png 5 | github: https://github.com/erpeel-a/Sistem-Pengaduan-Sekolah-SMKN2Karanganyar-Frontend 6 | demo: https://pengaduan-smkn2kra.netlify.app 7 | --- 8 | 9 | Pengaduan sekolah (english: school complaint) is used for one of my school task. 10 | 11 | This is a group project, and I'm in charge of the frontend. So, my main task is creating the interface and integrate it with the backend. 12 | 13 | Stack used: 14 | 15 | - React 16 | - React Router 17 | - Chakra UI 18 | -------------------------------------------------------------------------------- /content/project/04-smkn2kra-website.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SMKN 2 Karanganyar Website 3 | description: An official website for SMKN 2 Karanganyar. 4 | thumbnail: /assets/project/smkn2kra-website.png 5 | demo: https://smkn2kra.sch.id 6 | --- 7 | 8 | This website is used for the high school where I studied (SMKN 2 Karanganyar). 9 | 10 | I made this before my graduation and by collaborating with my classmates, you can check it out at [https://smkn2kra.sch.id/teams](https://smkn2kra.sch.id/teams). I'm working on the frontend side and my classmates working on the backend, design, also the 3D. I really appreciate and proud of our work. 11 | 12 | Stack used: 13 | 14 | - Next.js 15 | - Chakra UI 16 | - Laravel (for the backend) 17 | -------------------------------------------------------------------------------- /content/project/05-next-chakra-starter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Next.js Chakra Starter 3 | description: An opinionated simple starter / template project with Next.js and Chakra UI. 4 | thumbnail: /assets/project/next-chakra-starter.png 5 | github: https://github.com/hendraaagil/next-chakra-starter 6 | --- 7 | 8 | A simple starter / template project to explore Next.js, Chakra UI, and some other libraries. 9 | 10 | Stack used: 11 | 12 | - Next.js 13 | - Chakra UI 14 | -------------------------------------------------------------------------------- /content/project/06-bed-covid-rs-indonesia.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bed Covid RS Indonesia 3 | description: A website that provides information regarding the availability of hospitals and hospital beds for covid-19 or non-covid patients in Indonesia. 4 | thumbnail: /assets/project/bed-covid-rs-indonesia.png 5 | github: https://github.com/hendraaagil/bed-covid-rs-indo 6 | --- 7 | 8 | A simple app for checking the availability of hospitals and hospital beds based on open source API. 9 | 10 | Stack used: 11 | 12 | - Next.js 13 | - Chakra UI 14 | - SWR 15 | -------------------------------------------------------------------------------- /content/project/07-rpl-skandakra-website.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: RPL Skandakra Dev Website 3 | description: An official website for RPL Skandakra Dev Community. 4 | thumbnail: /assets/project/rpl-skandakra-dev-website.png 5 | github: https://github.com/rpl-skandakra/website 6 | demo: https://rplskandakra.netlify.app 7 | --- 8 | 9 | A website for my high school's programming community. The content is managed using Contentful CMS. 10 | 11 | Stack used: 12 | 13 | - Next.js 14 | - Chakra UI 15 | - Contentful 16 | -------------------------------------------------------------------------------- /content/project/08-rpl-skandakra-bot.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: RPL Skandakra Bot 3 | description: A bot for help moderation in RPL Skandakra Discord Server. 4 | thumbnail: /assets/project/rpl-skandakra-bot.png 5 | github: https://github.com/rpl-skandakra/bot 6 | --- 7 | 8 | A discord bot to be used as moderation tool in RPL Skandakra Discord Server. Deployed automatically using GitHub Actions and Docker. 9 | 10 | Stack used: 11 | 12 | - Node.js 13 | - Discord.js 14 | -------------------------------------------------------------------------------- /content/project/09-vlr-api.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vlr API 3 | description: Unofficial Valorant match API from https://vlr.gg site. 4 | thumbnail: /assets/project/vlr-api.png 5 | github: https://github.com/hendraaagil/vlr-api 6 | demo: https://vlr-api.hendraaagil.dev 7 | --- 8 | 9 | An open source API for Valorant match data from [vlr.gg](https://vlr.gg) site, and scraped using cheerio. 10 | 11 | Stack used: 12 | 13 | - Node.js 14 | - Express.js 15 | - Cheerio 16 | -------------------------------------------------------------------------------- /content/project/10-fitcells.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fit.Cells 3 | description: An app for booking gym, class, and trainer. 4 | thumbnail: /assets/project/fitcells.png 5 | demo: https://play.google.com/store/apps/details?id=com.fitcells 6 | --- 7 | 8 | This is one of my company project. Mainly, I'm working as a backend developer for this project for about 1 and a half years (August 2021 - Early 2023). 9 | 10 | I'm responsible for the backend development of the app, including the API, database, deployment, and the integration with the 3rd party (Twilio, SendGrid, Stripe, Sentry, OneSignal). 11 | 12 | Stack used in the backend: 13 | 14 | - Node.js 15 | - Express.js 16 | - Socket.io 17 | - Sequelize 18 | - PostgreSQL 19 | -------------------------------------------------------------------------------- /content/project/11-tuut.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tuut 3 | description: An app for booking class and tutor. 4 | thumbnail: /assets/project/tuut.png 5 | demo: https://play.google.com/store/apps/details?hl=en&id=com.tuut 6 | --- 7 | 8 | This is one of my company project. Mainly, I'm working as a backend developer for this project for about 4 months (March 2023 - June 2023). 9 | 10 | I'm responsible for the backend development of the app, including the API, database, deployment, and the integration with the 3rd party (SendGrid, Stripe, Sentry, OneSignal). 11 | 12 | Stack used in the backend: 13 | 14 | - Node.js 15 | - TypeScript 16 | - Nest.js 17 | - Socket.io 18 | - TypeORM 19 | - PostgreSQL 20 | -------------------------------------------------------------------------------- /content/project/12-gelora.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gelora 3 | description: An app for booking field and activity. 4 | thumbnail: /assets/project/gelora.png 5 | demo: https://play.google.com/store/apps/details?id=com.gelora 6 | --- 7 | 8 | This is one of my company project. I'm working as a team lead for this project for about 4 months (July 2023 - October 2023). 9 | 10 | I'm responsible for identifying all the features to be developed based on the client's needs, managing the developers workload, and deciding on releases / updates for the application. Also, discuss with the client about the API that will be developed. 11 | 12 | Stack used in the application: 13 | 14 | - React Native 15 | - React Query 16 | - React Hook Form 17 | - TypeScript 18 | - MobX 19 | - i18n 20 | - AppCenter 21 | -------------------------------------------------------------------------------- /content/project/13-gilbot.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: GilBot 3 | description: WhatsApp bot for any needs. 4 | thumbnail: /assets/project/gilbot.png 5 | github: https://gilbot.hendraaagil.dev/source 6 | demo: https://gilbot.hendraaagil.dev 7 | --- 8 | 9 | A WhatsApp bot to accompany your loneliness. I made this because have an unused number and I want to make something useful with it. But for now is already inactive because it break the rules of WhatsApp itself. So, I make the source code open source in here: 10 | 11 | 16 | 17 | Then I create a new bot in Telegram with improvement on the deployment to use Docker. The bot is developed using [grammy](https://grammy.dev/) library with same functionality on the WhatsApp before. You can try it at: [t.me/gilchatbot](https://t.me/gilchatbot). 18 | 19 | --- 20 | 21 | Stack used for the Telegram bot: 22 | 23 | - Node.js 24 | - Grammy 25 | 26 | Stack used for the WhatsApp bot: 27 | 28 | - Node.js 29 | - whatsapp-web.js 30 | - Prisma 31 | - Supabase 32 | 33 | Stack used for the web: 34 | 35 | - Next.js 36 | - Tailwind CSS 37 | -------------------------------------------------------------------------------- /content/project/14-project-showcase-wpu.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Project Showcase WPU 3 | description: Contains list of projects in WPU's showcase. 4 | thumbnail: /assets/project/project-showcase-wpu.png 5 | github: https://github.com/hendraaagil/project-showcase-wpu 6 | demo: https://project-showcase-wpu.netlify.app/ 7 | --- 8 | 9 | This project contains list of projects in WPU's showcase based on open source API data. You can submit your project via [WPU's Discord](https://discord.gg/wpu). 10 | 11 | Stack used: 12 | 13 | - Next.js 14 | - Tailwind CSS 15 | - shadcn/ui 16 | -------------------------------------------------------------------------------- /content/project/15-elevaite.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: elevAIte with Dicoding 3 | description: Website for elevAIte with Dicoding campaign. 4 | thumbnail: /assets/project/elevaite.png 5 | demo: https://elevaite.id/dicoding 6 | --- 7 | 8 | This is a company project where I am responsible for the entire website development. 9 | 10 | The website consists of static pages, along with a list of articles and FAQs that are dynamically fetched from a WordPress CMS. Referral and registrant data are stored in Firestore, while the registration form is managed using the Tally platform, developed by another team. 11 | 12 | To handle registrant data, a webhook is triggered upon form submission. This webhook processes the data by storing it in Firestore and sending a confirmation email automatically. 13 | 14 | Stack used: 15 | 16 | - Next.js 17 | - Tailwind CSS 18 | - shadcn/ui 19 | - Firebase (Firestore) 20 | - react.email 21 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | const isDev = process.argv.indexOf('dev') !== -1 2 | const isBuild = process.argv.indexOf('build') !== -1 3 | if (!process.env.VELITE_STARTED && (isDev || isBuild)) { 4 | process.env.VELITE_STARTED = '1' 5 | const { build } = await import('velite') 6 | await build({ watch: isDev, clean: !isDev }) 7 | } 8 | 9 | /** @type {import('next').NextConfig} */ 10 | export default { 11 | redirects: async () => { 12 | // https://twitter.com/LiamHammett/status/1260984553570570240 13 | return [ 14 | { 15 | source: '/.env', 16 | destination: 'https://www.youtube.com/watch?v=V4MF2s6MLxY', 17 | permanent: false, 18 | }, 19 | { 20 | source: '/wp-login.php', 21 | destination: 'https://www.youtube.com/watch?v=V4MF2s6MLxY', 22 | permanent: false, 23 | }, 24 | { 25 | source: '/wp-admin', 26 | destination: 'https://www.youtube.com/watch?v=V4MF2s6MLxY', 27 | permanent: false, 28 | }, 29 | { 30 | source: '/github', 31 | destination: 'https://github.com/hendraaagil', 32 | permanent: false, 33 | }, 34 | { 35 | source: '/linkedin', 36 | destination: 'https://linkedin.com/in/hendraaagil', 37 | permanent: false, 38 | }, 39 | { 40 | source: '/spotify', 41 | destination: 42 | 'https://open.spotify.com/user/31c2l7t3xscqcrmfz6nvinn645nu', 43 | permanent: false, 44 | }, 45 | { 46 | source: '/source', 47 | destination: 'https://github.com/hendraaagil/website', 48 | permanent: false, 49 | }, 50 | { 51 | source: '/analytics', 52 | destination: 'https://analytics.hendraaagil.dev/share/uYc4Zzl2', 53 | permanent: false, 54 | }, 55 | { 56 | source: '/links', 57 | destination: 'https://links.hendraaagil.dev', 58 | permanent: false, 59 | }, 60 | ] 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "personal-website", 3 | "version": "4.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "biome lint", 10 | "lint:write": "biome lint --write", 11 | "lint:write:unsafe": "biome lint --write --unsafe", 12 | "format": "biome format", 13 | "format:write": "biome format --write" 14 | }, 15 | "engines": { 16 | "node": ">=22" 17 | }, 18 | "packageManager": "pnpm@10.4.1", 19 | "dependencies": { 20 | "@giscus/react": "^3.0.0", 21 | "@googleapis/youtube": "^20.0.0", 22 | "@radix-ui/react-dialog": "^1.1.2", 23 | "@radix-ui/react-dropdown-menu": "^2.1.2", 24 | "@radix-ui/react-slot": "^1.1.0", 25 | "@radix-ui/react-tooltip": "^1.1.3", 26 | "@shikijs/rehype": "^3.0.0", 27 | "@shikijs/transformers": "^3.0.0", 28 | "class-variance-authority": "^0.7.0", 29 | "clsx": "^2.1.1", 30 | "date-fns": "^4.1.0", 31 | "htmr": "^1.0.2", 32 | "jsdom": "^25.0.1", 33 | "lucide-react": "^0.451.0", 34 | "next": "14.2.21", 35 | "next-themes": "^0.3.0", 36 | "next-view-transitions": "^0.3.2", 37 | "react": "^18.3.1", 38 | "react-countup": "^6.5.3", 39 | "react-dom": "^18.3.1", 40 | "react-swipeable": "^7.0.1", 41 | "rehype-unwrap-images": "^1.0.0", 42 | "sharp": "^0.33.5", 43 | "swr": "^2.2.5", 44 | "tailwind-merge": "^2.5.3", 45 | "tailwindcss-animate": "^1.0.7" 46 | }, 47 | "devDependencies": { 48 | "@biomejs/biome": "1.9.3", 49 | "@types/jsdom": "^21.1.7", 50 | "@types/mdx": "^2.0.13", 51 | "@types/node": "^22.7.5", 52 | "@types/react": "^18.3.11", 53 | "@types/react-dom": "^18.3.0", 54 | "autoprefixer": "^10.4.20", 55 | "postcss": "^8.4.47", 56 | "tailwindcss": "^3.4.13", 57 | "typescript": "^5.6.3", 58 | "velite": "^0.2.2" 59 | }, 60 | "pnpm": { 61 | "onlyBuiltDependencies": ["@biomejs/biome", "esbuild", "sharp"] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/blog/2-tahun-dan-masih-berlanjut/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/2-tahun-dan-masih-berlanjut/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-javascript/survey-2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-javascript/survey-2020.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-javascript/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-javascript/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-basic/created-react-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-basic/created-react-app.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-basic/demo-setcount-value.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-basic/demo-setcount-value.gif -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-basic/name-value-in-jsx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-basic/name-value-in-jsx.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-basic/react-new-project-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-basic/react-new-project-interface.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-basic/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-basic/react.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-basic/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-basic/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memahami-useeffect/project-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memahami-useeffect/project-view.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memahami-useeffect/re-render.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memahami-useeffect/re-render.gif -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memahami-useeffect/render-once.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memahami-useeffect/render-once.gif -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memahami-useeffect/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memahami-useeffect/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memahami-useeffect/update-title.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memahami-useeffect/update-title.gif -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memahami-useeffect/update-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memahami-useeffect/update-title.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-memoisasi/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-memoisasi/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-props-and-route/console-props.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-props-and-route/console-props.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-props-and-route/correct-about-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-props-and-route/correct-about-page.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-props-and-route/show-props.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-props-and-route/show-props.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-props-and-route/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-props-and-route/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/berkenalan-dengan-react-props-and-route/wrong-about-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/berkenalan-dengan-react-props-and-route/wrong-about-page.png -------------------------------------------------------------------------------- /public/assets/blog/cerita-dibalik-gilbot/analytics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/cerita-dibalik-gilbot/analytics.jpg -------------------------------------------------------------------------------- /public/assets/blog/cerita-dibalik-gilbot/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/cerita-dibalik-gilbot/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/cerita-dibalik-gilbot/warning-whatsapp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/cerita-dibalik-gilbot/warning-whatsapp.jpg -------------------------------------------------------------------------------- /public/assets/blog/deployment-dengan-docker/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/deployment-dengan-docker/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/git-tips-cherry-pick/add-new-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/git-tips-cherry-pick/add-new-file.png -------------------------------------------------------------------------------- /public/assets/blog/git-tips-cherry-pick/check-first-commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/git-tips-cherry-pick/check-first-commit.png -------------------------------------------------------------------------------- /public/assets/blog/git-tips-cherry-pick/cherry-pick-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/git-tips-cherry-pick/cherry-pick-docs.png -------------------------------------------------------------------------------- /public/assets/blog/git-tips-cherry-pick/example-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/git-tips-cherry-pick/example-project.png -------------------------------------------------------------------------------- /public/assets/blog/git-tips-cherry-pick/git-log-oneline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/git-tips-cherry-pick/git-log-oneline.png -------------------------------------------------------------------------------- /public/assets/blog/git-tips-cherry-pick/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/git-tips-cherry-pick/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/headless-ui-vs-component-library/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/headless-ui-vs-component-library/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/kehidupan-setelah-smk/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/kehidupan-setelah-smk/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/kilas-balik-2024/2024-in-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/kilas-balik-2024/2024-in-1.png -------------------------------------------------------------------------------- /public/assets/blog/kilas-balik-2024/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/kilas-balik-2024/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-firefox.png -------------------------------------------------------------------------------- /public/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-px.png -------------------------------------------------------------------------------- /public/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-rem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/memilih-rem-daripada-px-dalam-css/font-size-rem.png -------------------------------------------------------------------------------- /public/assets/blog/memilih-rem-daripada-px-dalam-css/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/memilih-rem-daripada-px-dalam-css/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/mencoba-next-14/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/mencoba-next-14/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/mengenal-clean-code/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/mengenal-clean-code/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/mengenal-clean-code/wtfm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/mengenal-clean-code/wtfm.jpg -------------------------------------------------------------------------------- /public/assets/blog/migrasi-ke-biome/biome-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/migrasi-ke-biome/biome-run.png -------------------------------------------------------------------------------- /public/assets/blog/migrasi-ke-biome/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/migrasi-ke-biome/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/migrasi-ke-velite/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/migrasi-ke-velite/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/migrasi-ke-velite/velite-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/migrasi-ke-velite/velite-flow.png -------------------------------------------------------------------------------- /public/assets/blog/panduan-css-bem/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/panduan-css-bem/thumbnail.png -------------------------------------------------------------------------------- /public/assets/blog/pengalaman-menggunakan-prisma-orm/prisma-landing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/pengalaman-menggunakan-prisma-orm/prisma-landing-page.png -------------------------------------------------------------------------------- /public/assets/blog/pengalaman-menggunakan-prisma-orm/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/blog/pengalaman-menggunakan-prisma-orm/thumbnail.png -------------------------------------------------------------------------------- /public/assets/main/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/main/avatar.png -------------------------------------------------------------------------------- /public/assets/main/logo-rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/main/logo-rounded.png -------------------------------------------------------------------------------- /public/assets/main/placeholder-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/main/placeholder-content.png -------------------------------------------------------------------------------- /public/assets/main/spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/main/spotify.png -------------------------------------------------------------------------------- /public/assets/project/bed-covid-rs-indonesia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/bed-covid-rs-indonesia.png -------------------------------------------------------------------------------- /public/assets/project/e-voting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/e-voting.png -------------------------------------------------------------------------------- /public/assets/project/elevaite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/elevaite.png -------------------------------------------------------------------------------- /public/assets/project/fitcells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/fitcells.png -------------------------------------------------------------------------------- /public/assets/project/gelora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/gelora.png -------------------------------------------------------------------------------- /public/assets/project/gift-store-landing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/gift-store-landing-page.png -------------------------------------------------------------------------------- /public/assets/project/gilbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/gilbot.png -------------------------------------------------------------------------------- /public/assets/project/image-gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/image-gallery.png -------------------------------------------------------------------------------- /public/assets/project/next-chakra-starter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/next-chakra-starter.png -------------------------------------------------------------------------------- /public/assets/project/pengaduan-sekolah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/pengaduan-sekolah.png -------------------------------------------------------------------------------- /public/assets/project/project-showcase-wpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/project-showcase-wpu.png -------------------------------------------------------------------------------- /public/assets/project/rpl-skandakra-bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/rpl-skandakra-bot.png -------------------------------------------------------------------------------- /public/assets/project/rpl-skandakra-dev-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/rpl-skandakra-dev-website.png -------------------------------------------------------------------------------- /public/assets/project/smkn2kra-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/smkn2kra-website.png -------------------------------------------------------------------------------- /public/assets/project/todo-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/todo-list.png -------------------------------------------------------------------------------- /public/assets/project/tuut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/tuut.png -------------------------------------------------------------------------------- /public/assets/project/vlr-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/assets/project/vlr-api.png -------------------------------------------------------------------------------- /public/fonts/Gabarito-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/fonts/Gabarito-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Gabarito-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/fonts/Gabarito-Regular.ttf -------------------------------------------------------------------------------- /public/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/public/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | import React from 'react' 4 | import { about } from '@/.velite' 5 | 6 | import { env } from '@/lib/constants' 7 | import { htmr } from '@/lib/transform' 8 | import { generateSeoMeta } from '@/lib/seo' 9 | import { PageContainer } from '@/components/layout' 10 | import { ExternalLink, Heading } from '@/components/ui' 11 | 12 | const SectionContainer = ({ 13 | title, 14 | children, 15 | }: { 16 | title: string 17 | children?: React.ReactNode 18 | }) => ( 19 |
20 | {title} 21 | {children} 22 |
23 | ) 24 | 25 | export const generateMetadata = async (): Promise => { 26 | const url = new URL(env.url.website + '/about') 27 | 28 | return { 29 | ...generateSeoMeta({ 30 | title: 'About', 31 | alternates: { canonical: url.toString() }, 32 | newOg: { url: url }, 33 | }), 34 | } 35 | } 36 | 37 | export default function Page() { 38 | const { description, skills, social } = about 39 | 40 | return ( 41 | 47 |
48 | {description.map((desc, i) => ( 49 |

{htmr(desc)}

50 | ))} 51 |
52 | 53 |
54 | {skills.map((skill) => ( 55 |
59 | {skill.name} 60 |
    61 | {skill.items.map((item) => ( 62 |
  • {item}
  • 63 | ))} 64 |
65 |
66 | ))} 67 |
68 |
69 | 70 |
71 |

{htmr(social.description)}

72 |
    73 | {social.links.map((link) => ( 74 |
  • 75 | {link.name} 76 | {' - '} 77 | {link.url} 78 |
  • 79 | ))} 80 |
81 |
82 |
83 |
84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /src/app/api/spotify/now-playing/route.ts: -------------------------------------------------------------------------------- 1 | import { fetchNowPlaying } from '@/lib/server/stats' 2 | 3 | export const dynamic = 'force-dynamic' 4 | export async function GET() { 5 | try { 6 | const nowPlaying = await fetchNowPlaying() 7 | return Response.json(nowPlaying) 8 | } catch (error) { 9 | console.error(error) 10 | return Response.json({ isPlaying: false }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/src/app/apple-icon.png -------------------------------------------------------------------------------- /src/app/blog/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | import { post } from '@/.velite' 4 | import { notFound } from 'next/navigation' 5 | 6 | import { env } from '@/lib/constants' 7 | import { generateSeoMeta } from '@/lib/seo' 8 | import { toTitleCase } from '@/lib/format' 9 | import { Heading, ImageBlur, MDXContent } from '@/components/ui' 10 | import { ArticleContainer } from '@/components/layout' 11 | import { Comment, PublishedTime } from '@/components/blog' 12 | 13 | const getPost = (slug: string) => post.find((post) => post.slug === slug) 14 | 15 | export async function generateStaticParams() { 16 | return post.map((post) => ({ 17 | slug: post.slug, 18 | })) 19 | } 20 | 21 | export const generateMetadata = async ({ 22 | params, 23 | }: { 24 | params: { slug: string } 25 | }): Promise => { 26 | const post = getPost(params.slug) 27 | if (!post) return {} 28 | 29 | const postUrl = new URL(env.url.website + '/blog/' + params.slug) 30 | const imageUrl = new URL(env.url.website + post.thumbnail.src) 31 | return { 32 | ...generateSeoMeta({ 33 | title: post.title, 34 | description: post.summary, 35 | alternates: { canonical: postUrl.toString() }, 36 | openGraph: { 37 | title: post.title, 38 | description: post.summary, 39 | url: postUrl, 40 | type: 'article', 41 | publishedTime: post.createdAt, 42 | modifiedTime: post.updatedAt, 43 | authors: ['Hendra Agil'], 44 | tags: post.tags, 45 | siteName: 'Hendra Agil', 46 | images: [ 47 | { 48 | url: `/og/content?title=${encodeURIComponent( 49 | post.title, 50 | )}&link=${encodeURIComponent( 51 | postUrl.toString(), 52 | )}&image=${encodeURIComponent(imageUrl.toString())}`, 53 | width: 1200, 54 | height: 630, 55 | alt: post.title, 56 | }, 57 | ], 58 | }, 59 | }), 60 | } 61 | } 62 | 63 | export default function Page({ params }: { params: { slug: string } }) { 64 | const post = getPost(params.slug) 65 | if (!post) return notFound() 66 | 67 | return ( 68 | 69 |
70 | 77 |
{post.thumbnailCredit}
78 |
79 |
80 | 85 | ・{post.metadata.readingTime} minute(s) read 86 |
87 | {post.title} 88 |

{post.summary}

89 |
90 | {post.tags.map((tag) => ( 91 | 92 | {toTitleCase(tag)} 93 | 94 | ))} 95 |
96 |
97 | 98 |
99 | 100 |
101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /src/app/blog/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | import { post } from '@/.velite' 4 | import { compareDesc } from 'date-fns' 5 | 6 | import { env } from '@/lib/constants' 7 | import { generateSeoMeta } from '@/lib/seo' 8 | import { PageContainer } from '@/components/layout' 9 | import { PostCard } from '@/components/blog' 10 | 11 | export const generateMetadata = async (): Promise => { 12 | const url = new URL(env.url.website + '/blog') 13 | 14 | return { 15 | ...generateSeoMeta({ 16 | title: 'Blog', 17 | alternates: { canonical: url.toString() }, 18 | newOg: { url: url }, 19 | }), 20 | } 21 | } 22 | 23 | export default function Page() { 24 | const posts = post.sort((a, b) => 25 | compareDesc(new Date(a.createdAt), new Date(b.createdAt)), 26 | ) 27 | 28 | return ( 29 | 35 |
36 | {posts.map((post) => ( 37 | 38 | ))} 39 |
40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import React from 'react' 3 | 4 | import { env } from '@/lib/constants' 5 | import { generateSeoMeta } from '@/lib/seo' 6 | import { PageContainer } from '@/components/layout' 7 | import { Heading } from '@/components/ui' 8 | import { 9 | CodingHours, 10 | DashboardCardSkeleton, 11 | GithubStars, 12 | WebsiteViews, 13 | WebsiteVisitors, 14 | YoutubeVideos, 15 | YoutubeViews, 16 | } from '@/components/dashboard' 17 | import { 18 | NowPlaying, 19 | NowPlayingSkeleton, 20 | TopTracks, 21 | TopTracksSkeleton, 22 | } from '@/components/dashboard/spotify' 23 | 24 | const statistics = [ 25 | GithubStars, 26 | WebsiteViews, 27 | WebsiteVisitors, 28 | CodingHours, 29 | YoutubeViews, 30 | YoutubeVideos, 31 | ] 32 | 33 | const SectionContainer = ({ children }: { children?: React.ReactNode }) => ( 34 |
{children}
35 | ) 36 | 37 | export const generateMetadata = async (): Promise => { 38 | const url = new URL(env.url.website + '/dashboard') 39 | 40 | return { 41 | ...generateSeoMeta({ 42 | title: 'Dashboard', 43 | alternates: { canonical: url.toString() }, 44 | newOg: { url: url }, 45 | }), 46 | } 47 | } 48 | 49 | export const dynamic = 'force-dynamic' 50 | 51 | export default async function Page() { 52 | return ( 53 | 59 |
60 | {statistics.map((Item, index) => ( 61 | }> 62 | 63 | 64 | ))} 65 |
66 | 67 | Music 68 | 69 | 70 | 71 | Top tracks 72 | }> 73 | 74 | 75 | 76 |
77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /src/app/equipments/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { hardware, software } from '@/.velite' 3 | 4 | import { env } from '@/lib/constants' 5 | import { htmr } from '@/lib/transform' 6 | import { generateSeoMeta } from '@/lib/seo' 7 | import { PageContainer } from '@/components/layout' 8 | import { ExternalLink, Heading } from '@/components/ui' 9 | 10 | const SectionContainer = ({ 11 | title, 12 | children, 13 | }: { 14 | title: string 15 | children?: React.ReactNode 16 | }) => ( 17 |
18 | 19 | {title} 20 | 21 | {children} 22 |
23 | ) 24 | 25 | export const generateMetadata = async (): Promise => { 26 | const url = new URL(env.url.website + '/equipments') 27 | 28 | return { 29 | ...generateSeoMeta({ 30 | title: 'Equipments', 31 | alternates: { canonical: url.toString() }, 32 | newOg: { url: url }, 33 | }), 34 | } 35 | } 36 | 37 | export default function Page() { 38 | return ( 39 | 46 | 47 | {software.list.map((detail) => ( 48 |
49 | {detail.name} 50 |
    51 | {detail.items.map((item) => ( 52 |
  • 53 | 57 | {htmr(item.name)} 58 | 59 |
  • 60 | ))} 61 |
62 |
63 | ))} 64 |
65 | 66 | {hardware.list.map((detail) => ( 67 |
68 | {detail.name} 69 |
    70 | {detail.items.map((item) => ( 71 |
  • 72 | {htmr(item.name)} 73 | {item.subitems && ( 74 |
      75 | {item.subitems.map((subitem) => ( 76 |
    • {htmr(subitem)}
    • 77 | ))} 78 |
    79 | )} 80 |
  • 81 | ))} 82 |
83 |
84 | ))} 85 |
86 |
87 | ) 88 | } 89 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/src/app/icon1.png -------------------------------------------------------------------------------- /src/app/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendraaagil/website/01732b926e04d2a8cb6b1449bf4e702c4361286c/src/app/icon2.png -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata, Viewport } from 'next' 2 | 3 | import Script from 'next/script' 4 | import { 5 | Gabarito as FontSans, 6 | JetBrains_Mono as FontMono, 7 | } from 'next/font/google' 8 | import { ViewTransitions } from 'next-view-transitions' 9 | import { generateSeoMeta } from '@/lib/seo' 10 | import { cn } from '@/lib/utils' 11 | 12 | import '@/styles/globals.css' 13 | import { ThemeProvider } from '@/components/provider' 14 | import { Navigation } from '@/components/layout' 15 | 16 | const fontSans = FontSans({ 17 | subsets: ['latin'], 18 | display: 'swap', 19 | adjustFontFallback: false, 20 | variable: '--font-sans', 21 | }) 22 | const fontMono = FontMono({ subsets: ['latin'], variable: '--font-mono' }) 23 | 24 | export const generateMetadata = async (): Promise => generateSeoMeta() 25 | 26 | export const viewport: Viewport = { 27 | width: 'device-width', 28 | initialScale: 1, 29 | themeColor: [ 30 | { media: '(prefers-color-scheme: light)', color: '#f8fafc' }, 31 | { media: '(prefers-color-scheme: dark)', color: '#0f172a' }, 32 | ], 33 | colorScheme: 'dark light', 34 | } 35 | 36 | export default function RootLayout({ 37 | children, 38 | }: { 39 | children: React.ReactNode 40 | }) { 41 | return ( 42 | 43 | 44 | 45 | 53 |
54 | 60 | 61 | {children} 62 | 63 |
64 | {process.env.NODE_ENV === 'production' && ( 65 |