├── .browserslistrc
├── .editorconfig
├── .env.example
├── .eslintrc.js
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── app
├── [username]
│ ├── [slug]
│ │ ├── favorite-button.tsx
│ │ ├── page.tsx
│ │ ├── post-views.tsx
│ │ └── related-posts.tsx
│ ├── aside.tsx
│ ├── components
│ │ └── tab-link.tsx
│ ├── favorites
│ │ ├── page.tsx
│ │ └── post-list.tsx
│ ├── layout.tsx
│ ├── page.tsx
│ ├── post-list.tsx
│ └── statistics.tsx
├── api
│ ├── auth
│ │ ├── callback
│ │ │ └── route.ts
│ │ └── confirm
│ │ │ └── route.ts
│ ├── cron
│ │ └── daily-reset-posts
│ │ │ └── route.ts
│ ├── ip
│ │ └── route.ts
│ ├── revalidate
│ │ └── route.ts
│ ├── v1
│ │ ├── email
│ │ │ ├── list
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ └── verify
│ │ │ │ └── route.ts
│ │ ├── favorite
│ │ │ └── route.ts
│ │ ├── notification
│ │ │ └── route.ts
│ │ ├── notify
│ │ │ └── route.ts
│ │ ├── post
│ │ │ ├── count
│ │ │ │ └── route.ts
│ │ │ ├── list
│ │ │ │ └── route.ts
│ │ │ ├── rank
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── statistic
│ │ │ └── list
│ │ │ │ └── route.ts
│ │ ├── tag
│ │ │ ├── list
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ └── user
│ │ │ ├── list
│ │ │ └── route.ts
│ │ │ └── route.ts
│ └── verify
│ │ └── email
│ │ └── route.ts
├── auth
│ ├── auth-code-error
│ │ └── page.tsx
│ ├── blocked
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── forgot-password
│ │ ├── forgot-password-form.tsx
│ │ └── page.tsx
│ ├── page.tsx
│ ├── reset-password
│ │ ├── page.tsx
│ │ └── reset-password-form.tsx
│ ├── signin
│ │ ├── page.tsx
│ │ └── signin-form.tsx
│ └── signup
│ │ ├── page.tsx
│ │ ├── policy.tsx
│ │ └── signup-form.tsx
├── dashboard
│ ├── admin
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── appearance
│ │ ├── change-language-form.tsx
│ │ ├── change-theme-form.tsx
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components
│ │ ├── app-bar.tsx
│ │ ├── app-panel.tsx
│ │ ├── demo-site-warning-notification.tsx
│ │ ├── navigation.tsx
│ │ └── notify.tsx
│ ├── dashboard
│ │ ├── index.tsx
│ │ ├── latest-posts.tsx
│ │ └── post-ranks.tsx
│ ├── layout.tsx
│ ├── page.tsx
│ ├── posts
│ │ ├── components
│ │ │ ├── add-dummy-post.tsx
│ │ │ ├── add-post.tsx
│ │ │ ├── bulk-actions
│ │ │ │ ├── bulk-actions-provider.tsx
│ │ │ │ ├── bulk-actions.tsx
│ │ │ │ └── index.ts
│ │ │ ├── head-link.tsx
│ │ │ ├── quick-links
│ │ │ │ ├── index.ts
│ │ │ │ ├── quick-delete.tsx
│ │ │ │ ├── quick-draft.tsx
│ │ │ │ ├── quick-edit.tsx
│ │ │ │ ├── quick-private.tsx
│ │ │ │ ├── quick-public.tsx
│ │ │ │ ├── quick-publish.tsx
│ │ │ │ ├── quick-restore.tsx
│ │ │ │ ├── quick-trash.tsx
│ │ │ │ └── quick-view.tsx
│ │ │ └── search-form.tsx
│ │ ├── edit
│ │ │ ├── components
│ │ │ │ ├── back-link.tsx
│ │ │ │ ├── ckeditor5
│ │ │ │ │ ├── editor.tsx
│ │ │ │ │ ├── style.css
│ │ │ │ │ └── supabase-upload-adapter.ts
│ │ │ │ ├── fields
│ │ │ │ │ ├── field-meta.tsx
│ │ │ │ │ ├── field-title.tsx
│ │ │ │ │ ├── field-user-id.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── metaboxes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── metabox-description.tsx
│ │ │ │ │ ├── metabox-future-date.tsx
│ │ │ │ │ ├── metabox-keywords.tsx
│ │ │ │ │ ├── metabox-permalink.tsx
│ │ │ │ │ ├── metabox-publish.tsx
│ │ │ │ │ ├── metabox-restriction.tsx
│ │ │ │ │ ├── metabox-revisions.tsx
│ │ │ │ │ ├── metabox-slug.tsx
│ │ │ │ │ ├── metabox-tags.tsx
│ │ │ │ │ └── metabox-thumbnail.tsx
│ │ │ ├── context
│ │ │ │ └── post-form-provider.tsx
│ │ │ ├── page.tsx
│ │ │ └── post-form.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── post-list.tsx
│ ├── settings
│ │ ├── account
│ │ │ ├── change-username-form.tsx
│ │ │ ├── deactivate-user-form.tsx
│ │ │ ├── delete-user-form.tsx
│ │ │ └── page.tsx
│ │ ├── emails
│ │ │ ├── components
│ │ │ │ ├── add-email.tsx
│ │ │ │ ├── delete-email.tsx
│ │ │ │ ├── edit-primary-email.tsx
│ │ │ │ └── resend-verify-email.tsx
│ │ │ ├── email-list.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── notifications
│ │ │ ├── notifications-form.tsx
│ │ │ └── page.tsx
│ │ ├── page.tsx
│ │ ├── security
│ │ │ ├── change-password-form.tsx
│ │ │ ├── manage-2fa-form.tsx
│ │ │ └── page.tsx
│ │ └── sessions
│ │ │ ├── page.tsx
│ │ │ └── sessions-form.tsx
│ ├── tags
│ │ ├── components
│ │ │ ├── add-tag.tsx
│ │ │ ├── bulk-actions
│ │ │ │ ├── bulk-actions-provider.tsx
│ │ │ │ ├── bulk-actions.tsx
│ │ │ │ └── index.ts
│ │ │ ├── quick-links
│ │ │ │ ├── index.ts
│ │ │ │ ├── quick-delete.tsx
│ │ │ │ ├── quick-edit.tsx
│ │ │ │ └── quick-view.tsx
│ │ │ └── search-form.tsx
│ │ ├── edit
│ │ │ ├── components
│ │ │ │ ├── back-link.tsx
│ │ │ │ ├── fields
│ │ │ │ │ ├── field-meta.tsx
│ │ │ │ │ ├── field-name.tsx
│ │ │ │ │ ├── field-post-tags.tsx
│ │ │ │ │ ├── field-user-id.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── metaboxes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── metabox-description.tsx
│ │ │ │ │ ├── metabox-publish.tsx
│ │ │ │ │ └── metabox-slug.tsx
│ │ │ ├── context
│ │ │ │ └── tag-form-provider.tsx
│ │ │ ├── page.tsx
│ │ │ └── tag-form.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── tag-list.tsx
│ └── users
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── profile
│ │ ├── page.tsx
│ │ └── profile-form.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
├── not-found.tsx
├── page.tsx
├── policy
│ ├── privacy
│ │ └── page.tsx
│ └── terms
│ │ └── page.tsx
├── posts
│ ├── page.tsx
│ └── sitemap.ts
├── robots.ts
├── search
│ └── page.tsx
└── sitemap.ts
├── components.json
├── components
├── account-menu.tsx
├── banners
│ ├── index.ts
│ └── upgrade-pro-banner.tsx
├── button-link.tsx
├── copyright.tsx
├── country-flag-button.tsx
├── description.tsx
├── error.tsx
├── footer.tsx
├── header.tsx
├── hentry
│ ├── entry-author.tsx
│ ├── entry-published.tsx
│ ├── entry-summary.tsx
│ ├── entry-tags.tsx
│ ├── entry-title.tsx
│ ├── entry-updated.tsx
│ └── index.ts
├── hero.tsx
├── language-combobox.tsx
├── latest-posts
│ ├── index.tsx
│ └── latest-posts.tsx
├── mobile-navigation.tsx
├── navigation.tsx
├── paging
│ ├── index.tsx
│ ├── paging-provider.tsx
│ └── paging.tsx
├── search-form-dialog.tsx
├── search-form.tsx
├── signin-with-github.tsx
├── signin-with-google.tsx
├── signin-with.tsx
├── signout-button.tsx
├── site-brand.tsx
├── site-logo.tsx
├── tailwind-indicator.tsx
├── text-link.tsx
├── theme-toggle.tsx
├── time-picker.tsx
├── title.tsx
├── ui-custom
│ ├── accordion.tsx
│ ├── command.tsx
│ ├── pagination.tsx
│ └── time-picker-input
│ │ ├── index.tsx
│ │ └── time-picker-utils.tsx
└── ui
│ ├── accordion.tsx
│ ├── alert-dialog.tsx
│ ├── alert.tsx
│ ├── aspect-ratio.tsx
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── breadcrumb.tsx
│ ├── button.tsx
│ ├── calendar.tsx
│ ├── card.tsx
│ ├── carousel.tsx
│ ├── chart.tsx
│ ├── checkbox.tsx
│ ├── collapsible.tsx
│ ├── command.tsx
│ ├── context-menu.tsx
│ ├── dialog.tsx
│ ├── drawer.tsx
│ ├── dropdown-menu.tsx
│ ├── form.tsx
│ ├── hover-card.tsx
│ ├── input-otp.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── menubar.tsx
│ ├── navigation-menu.tsx
│ ├── pagination.tsx
│ ├── popover.tsx
│ ├── progress.tsx
│ ├── radio-group.tsx
│ ├── resizable.tsx
│ ├── scroll-area.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── sheet.tsx
│ ├── skeleton.tsx
│ ├── slider.tsx
│ ├── sonner.tsx
│ ├── switch.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ ├── textarea.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ ├── toggle-group.tsx
│ ├── toggle.tsx
│ ├── tooltip.tsx
│ └── use-toast.ts
├── config
├── dashboard.ts
├── middleware.ts
└── site.ts
├── context
├── app-provider.tsx
├── auth-provider.tsx
├── i18n-provider.tsx
├── swr-provider.tsx
└── theme-provider.tsx
├── docs
├── CONFIGURATION.md
├── DEPLOYING.md
├── EXAMPLES.md
├── INSTALLATION.md
└── LINTER.md
├── hooks
├── headers
│ ├── index.ts
│ └── url.ts
├── i18next
│ ├── get-translation.ts
│ ├── index.ts
│ └── use-trans.tsx
├── url
│ ├── index.ts
│ └── use-query-string.ts
└── use-auth.ts
├── i18next.config.ts
├── lib
├── country-flag-icons.tsx
├── dayjs.ts
├── emblor.ts
├── i18next.ts
├── jsonwebtoken.ts
├── lucide-icon.tsx
├── nodemailer.ts
├── redux
│ ├── hooks.ts
│ ├── persist-provider.tsx
│ ├── redux-provider.tsx
│ ├── storage.ts
│ ├── store-provider.tsx
│ └── store.ts
├── slugify.ts
└── utils
│ ├── cache.ts
│ ├── cookie.ts
│ ├── dummy-text.ts
│ ├── error.ts
│ ├── fetcher.ts
│ ├── functions.ts
│ ├── http-status-codes.ts
│ ├── index.ts
│ ├── tailwind.ts
│ └── url.ts
├── middleware.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── assets
│ ├── icons
│ │ ├── icon-192x192.png
│ │ ├── icon-256x256.png
│ │ ├── icon-384x384.png
│ │ ├── icon-512x512.png
│ │ └── icon.svg
│ └── images
│ │ └── main
│ │ ├── photo-1481277542470-605612bd2d61.jpg
│ │ ├── photo-1516455207990-7a41ce80f7ee.jpg
│ │ ├── photo-1517487881594-2787fef5ebf7.jpg
│ │ ├── photo-1519710164239-da123dc03ef4.jpg
│ │ ├── photo-1523413651479-597eb2da0ad6.jpg
│ │ ├── photo-1525097487452-6278ff080c31.jpg
│ │ ├── photo-1530731141654-5993c3016c77.jpg
│ │ ├── photo-1549388604-817d15aa0110.jpg
│ │ ├── photo-1563298723-dcfebaa392e3.jpg
│ │ ├── photo-1574180045827-681f8a1a9622.jpg
│ │ ├── photo-1588436706487-9d55d73a39e3.jpg
│ │ └── photo-1597262975002-c5c3b14bbd62.jpg
├── data
│ ├── countries
│ │ ├── countries.json
│ │ ├── languages-all.json
│ │ └── languages.json
│ └── country-flag-icons
│ │ ├── AC.svg
│ │ ├── AD.svg
│ │ ├── AE.svg
│ │ ├── AF.svg
│ │ ├── AG.svg
│ │ ├── AI.svg
│ │ ├── AL.svg
│ │ ├── AM.svg
│ │ ├── AO.svg
│ │ ├── AQ.svg
│ │ ├── AR.svg
│ │ ├── AS.svg
│ │ ├── AT.svg
│ │ ├── AU.svg
│ │ ├── AW.svg
│ │ ├── AX.svg
│ │ ├── AZ.svg
│ │ ├── BA.svg
│ │ ├── BB.svg
│ │ ├── BD.svg
│ │ ├── BE.svg
│ │ ├── BF.svg
│ │ ├── BG.svg
│ │ ├── BH.svg
│ │ ├── BI.svg
│ │ ├── BJ.svg
│ │ ├── BL.svg
│ │ ├── BM.svg
│ │ ├── BN.svg
│ │ ├── BO.svg
│ │ ├── BQ.svg
│ │ ├── BR.svg
│ │ ├── BS.svg
│ │ ├── BT.svg
│ │ ├── BV.svg
│ │ ├── BW.svg
│ │ ├── BY.svg
│ │ ├── BZ.svg
│ │ ├── CA.svg
│ │ ├── CC.svg
│ │ ├── CD.svg
│ │ ├── CF.svg
│ │ ├── CG.svg
│ │ ├── CH.svg
│ │ ├── CI.svg
│ │ ├── CK.svg
│ │ ├── CL.svg
│ │ ├── CM.svg
│ │ ├── CN.svg
│ │ ├── CO.svg
│ │ ├── CR.svg
│ │ ├── CU.svg
│ │ ├── CV.svg
│ │ ├── CW.svg
│ │ ├── CX.svg
│ │ ├── CY.svg
│ │ ├── CZ.svg
│ │ ├── DE.svg
│ │ ├── DJ.svg
│ │ ├── DK.svg
│ │ ├── DM.svg
│ │ ├── DO.svg
│ │ ├── DZ.svg
│ │ ├── EC.svg
│ │ ├── EE.svg
│ │ ├── EG.svg
│ │ ├── EH.svg
│ │ ├── ER.svg
│ │ ├── ES.svg
│ │ ├── ET.svg
│ │ ├── EU.svg
│ │ ├── FI.svg
│ │ ├── FJ.svg
│ │ ├── FK.svg
│ │ ├── FM.svg
│ │ ├── FO.svg
│ │ ├── FR.svg
│ │ ├── GA.svg
│ │ ├── GB.svg
│ │ ├── GD.svg
│ │ ├── GE-AB.svg
│ │ ├── GE-OS.svg
│ │ ├── GE.svg
│ │ ├── GF.svg
│ │ ├── GG.svg
│ │ ├── GH.svg
│ │ ├── GI.svg
│ │ ├── GL.svg
│ │ ├── GM.svg
│ │ ├── GN.svg
│ │ ├── GP.svg
│ │ ├── GQ.svg
│ │ ├── GR.svg
│ │ ├── GS.svg
│ │ ├── GT.svg
│ │ ├── GU.svg
│ │ ├── GW.svg
│ │ ├── GY.svg
│ │ ├── HK.svg
│ │ ├── HM.svg
│ │ ├── HN.svg
│ │ ├── HR.svg
│ │ ├── HT.svg
│ │ ├── HU.svg
│ │ ├── IC.svg
│ │ ├── ID.svg
│ │ ├── IE.svg
│ │ ├── IL.svg
│ │ ├── IM.svg
│ │ ├── IN.svg
│ │ ├── IO.svg
│ │ ├── IQ.svg
│ │ ├── IR.svg
│ │ ├── IS.svg
│ │ ├── IT.svg
│ │ ├── JE.svg
│ │ ├── JM.svg
│ │ ├── JO.svg
│ │ ├── JP.svg
│ │ ├── KE.svg
│ │ ├── KG.svg
│ │ ├── KH.svg
│ │ ├── KI.svg
│ │ ├── KM.svg
│ │ ├── KN.svg
│ │ ├── KP.svg
│ │ ├── KR.svg
│ │ ├── KW.svg
│ │ ├── KY.svg
│ │ ├── KZ.svg
│ │ ├── LA.svg
│ │ ├── LB.svg
│ │ ├── LC.svg
│ │ ├── LI.svg
│ │ ├── LK.svg
│ │ ├── LR.svg
│ │ ├── LS.svg
│ │ ├── LT.svg
│ │ ├── LU.svg
│ │ ├── LV.svg
│ │ ├── LY.svg
│ │ ├── MA.svg
│ │ ├── MC.svg
│ │ ├── MD.svg
│ │ ├── ME.svg
│ │ ├── MF.svg
│ │ ├── MG.svg
│ │ ├── MH.svg
│ │ ├── MK.svg
│ │ ├── ML.svg
│ │ ├── MM.svg
│ │ ├── MN.svg
│ │ ├── MO.svg
│ │ ├── MP.svg
│ │ ├── MQ.svg
│ │ ├── MR.svg
│ │ ├── MS.svg
│ │ ├── MT.svg
│ │ ├── MU.svg
│ │ ├── MV.svg
│ │ ├── MW.svg
│ │ ├── MX.svg
│ │ ├── MY.svg
│ │ ├── MZ.svg
│ │ ├── NA.svg
│ │ ├── NC.svg
│ │ ├── NE.svg
│ │ ├── NF.svg
│ │ ├── NG.svg
│ │ ├── NI.svg
│ │ ├── NL.svg
│ │ ├── NO.svg
│ │ ├── NP.svg
│ │ ├── NR.svg
│ │ ├── NU.svg
│ │ ├── NZ.svg
│ │ ├── OM.svg
│ │ ├── PA.svg
│ │ ├── PE.svg
│ │ ├── PF.svg
│ │ ├── PG.svg
│ │ ├── PH.svg
│ │ ├── PK.svg
│ │ ├── PL.svg
│ │ ├── PM.svg
│ │ ├── PN.svg
│ │ ├── PR.svg
│ │ ├── PS.svg
│ │ ├── PT.svg
│ │ ├── PW.svg
│ │ ├── PY.svg
│ │ ├── QA.svg
│ │ ├── RE.svg
│ │ ├── RO.svg
│ │ ├── RS.svg
│ │ ├── RU.svg
│ │ ├── RW.svg
│ │ ├── SA.svg
│ │ ├── SB.svg
│ │ ├── SC.svg
│ │ ├── SD.svg
│ │ ├── SE.svg
│ │ ├── SG.svg
│ │ ├── SH.svg
│ │ ├── SI.svg
│ │ ├── SJ.svg
│ │ ├── SK.svg
│ │ ├── SL.svg
│ │ ├── SM.svg
│ │ ├── SN.svg
│ │ ├── SO.svg
│ │ ├── SR.svg
│ │ ├── SS.svg
│ │ ├── ST.svg
│ │ ├── SV.svg
│ │ ├── SX.svg
│ │ ├── SY.svg
│ │ ├── SZ.svg
│ │ ├── TA.svg
│ │ ├── TC.svg
│ │ ├── TD.svg
│ │ ├── TF.svg
│ │ ├── TG.svg
│ │ ├── TH.svg
│ │ ├── TJ.svg
│ │ ├── TK.svg
│ │ ├── TL.svg
│ │ ├── TM.svg
│ │ ├── TN.svg
│ │ ├── TO.svg
│ │ ├── TR.svg
│ │ ├── TT.svg
│ │ ├── TV.svg
│ │ ├── TW.svg
│ │ ├── TZ.svg
│ │ ├── UA.svg
│ │ ├── UG.svg
│ │ ├── UM.svg
│ │ ├── US.svg
│ │ ├── UY.svg
│ │ ├── UZ.svg
│ │ ├── VA.svg
│ │ ├── VC.svg
│ │ ├── VE.svg
│ │ ├── VG.svg
│ │ ├── VI.svg
│ │ ├── VN.svg
│ │ ├── VU.svg
│ │ ├── WF.svg
│ │ ├── WS.svg
│ │ ├── XK.svg
│ │ ├── YE.svg
│ │ ├── YT.svg
│ │ ├── ZA.svg
│ │ ├── ZM.svg
│ │ ├── ZW.svg
│ │ ├── flags.css
│ │ └── index.html
├── locales
│ ├── en
│ │ ├── components.json
│ │ ├── httpstatuscode.json
│ │ ├── translation.json
│ │ ├── zod-custom.json
│ │ └── zod.json
│ └── ko
│ │ ├── components.json
│ │ ├── httpstatuscode.json
│ │ ├── translation.json
│ │ ├── zod-custom.json
│ │ └── zod.json
├── manifest.json
├── next.svg
└── vercel.svg
├── queries
├── client
│ ├── emails.ts
│ ├── favorites.ts
│ ├── notifications.ts
│ ├── posts.ts
│ ├── statistics.ts
│ ├── tags.ts
│ └── users.ts
└── server
│ ├── auth.ts
│ ├── posts.ts
│ └── users.ts
├── screenshot.png
├── screenshots
├── 01.main.png
├── 02.signin.png
├── 03.signup.png
├── 04.forgot-password.png
├── 05.reset-password.png
├── 06.profile.png
├── 07.favorites.png
├── 08.posts.png
├── 09.post.png
├── 10.search.png
├── 20.dashboard.png
├── 30.posts.png
├── 31.post.png
├── 32.tags.png
├── 33.tag.png
├── 40.appearance.png
├── 50.profile.png
├── 60.account.png
├── 61.notifications.png
├── 62.emails.png
└── 63.security.png
├── store
├── reducers
│ └── app-reducer.ts
└── root-reducer.ts
├── supabase
├── .gitignore
├── client.ts
├── middleware.ts
├── schemas
│ ├── auth
│ │ └── users.sql
│ ├── cron
│ │ ├── job_scheduling.sql
│ │ └── pg_cron.sql
│ ├── public
│ │ ├── emails.sql
│ │ ├── favorites.sql
│ │ ├── notifications.sql
│ │ ├── post_tags.sql
│ │ ├── postmeta.sql
│ │ ├── posts.sql
│ │ ├── role_permissions.sql
│ │ ├── statistics.sql
│ │ ├── tagmeta.sql
│ │ ├── tags.sql
│ │ ├── usermeta.sql
│ │ ├── users.sql
│ │ └── votes.sql
│ └── storage
│ │ └── buckets.sql
├── seed.sql
└── server.ts
├── tailwind.config.js
├── tsconfig.json
├── types
├── api.ts
├── ckeditor5-react
│ └── index.d.ts
├── database.ts
├── index.d.ts
├── supabase.ts
└── token.ts
└── vercel.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # Browsers that we support
2 |
3 | defaults and fully supports es6-module
4 | maintained node versions
5 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Application
2 | NEXT_PUBLIC_APP_NAME=App
3 | NEXT_PUBLIC_APP_URL=http://localhost:3000
4 |
5 | # Application Private Keys
6 | # Terminal Command Line: openssl rand -hex 64
7 | SECRET_KEY=secret
8 |
9 | # Supabse Keys
10 | NEXT_PUBLIC_SUPABASE_PROJECT_ID=your_supabase_project_id
11 | NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
12 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
13 | NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET=your_supabase_storage_bucket
14 |
15 | # Supabse Private Keys
16 | SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
17 |
18 | # Supabse Database
19 | #SUPABASE_DATABASE_HOST=
20 | #SUPABASE_DATABASE_POST=5432
21 | #SUPABASE_DATABASE_NAME=postgres
22 | #SUPABASE_DATABASE_USER=
23 | #SUPABASE_DATABASE_PASSWORD=
24 |
25 | # SMTP Configure
26 | #SMTP_SENDER_EMAIL=noreply@example.com
27 | #SMTP_SENDER_NAME=
28 | #SMTP_BREVO_USER=
29 | #SMTP_BREVO_PASS=
30 | #SMTP_GMAIL_USER=
31 | #SMTP_GMAIL_PASS=
32 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.18.2
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | out/
2 | dist/
3 | node_modules/
4 | components/ui/
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["prettier-plugin-tailwindcss"],
3 | "trailingComma": "es5",
4 | "printWidth": 80,
5 | "tabWidth": 2,
6 | "useTabs": false,
7 | "semi": false,
8 | "singleQuote": true,
9 | "quoteProps": "as-needed",
10 | "jsxSingleQuote": false,
11 | "bracketSpacing": true,
12 | "bracketSameLine": false,
13 | "arrowParens": "always",
14 | "requirePragma": false,
15 | "insertPragma": false,
16 | "proseWrap": "preserve",
17 | "htmlWhitespaceSensitivity": "css",
18 | "vueIndentScriptAndStyle": false,
19 | "endOfLine": "lf",
20 | "embeddedLanguageFormatting": "auto",
21 | "singleAttributePerLine": false
22 | }
23 |
--------------------------------------------------------------------------------
/app/[username]/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Statistics } from './statistics'
3 |
4 | export default function UsernameLayout({
5 | children,
6 | }: {
7 | children?: React.ReactNode
8 | }) {
9 | return (
10 | <>
11 | {children}
12 |
13 | >
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/app/api/ip/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, type NextRequest } from 'next/server'
2 |
3 | /**
4 | * IP Address
5 | *
6 | * @link https://nextjs.org/docs/app/api-reference/functions/headers#ip-address
7 | */
8 |
9 | export async function GET(request: NextRequest) {
10 | const FALLBACK_IP_ADDRESS = '127.0.0.1'
11 | const xForwardedFor = request.headers.get('X-Forwarded-For')
12 |
13 | let ip = request.ip
14 |
15 | if (!ip && xForwardedFor) {
16 | ip = xForwardedFor.split(',')[0] ?? FALLBACK_IP_ADDRESS
17 | } else if (!ip) {
18 | ip = request.headers.get('x-real-ip') ?? FALLBACK_IP_ADDRESS
19 | }
20 |
21 | return new Response(ip, { status: 200 })
22 | }
23 |
--------------------------------------------------------------------------------
/app/api/revalidate/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, type NextRequest } from 'next/server'
2 | import { revalidatePath } from 'next/cache'
3 |
4 | export async function GET(request: NextRequest) {
5 | const path = request.nextUrl.searchParams.get('path')
6 |
7 | if (path) {
8 | revalidatePath(path)
9 | return Response.json({ revalidated: true, now: Date.now() })
10 | }
11 |
12 | return Response.json({
13 | revalidated: false,
14 | now: Date.now(),
15 | message: 'Missing path to revalidate',
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/app/api/v1/email/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, type NextRequest } from 'next/server'
2 | import { createClient } from '@/supabase/server'
3 | import { ApiError, revalidates } from '@/lib/utils'
4 | import { authorize } from '@/queries/server/auth'
5 |
6 | export async function GET(request: NextRequest) {
7 | const searchParams = request.nextUrl.searchParams
8 | const userId = searchParams.get('userId') as string
9 |
10 | const { authorized } = await authorize(userId)
11 |
12 | if (!authorized) {
13 | return NextResponse.json(
14 | { data: null, error: new ApiError(401) },
15 | { status: 401 }
16 | )
17 | }
18 |
19 | const supabase = createClient()
20 | const { data: emails, error } = await supabase
21 | .from('emails')
22 | .select('*')
23 | .eq('user_id', userId)
24 |
25 | if (error) {
26 | return NextResponse.json({ data: null, error }, { status: 400 })
27 | }
28 |
29 | return NextResponse.json({ data: emails, error: null })
30 | }
31 |
--------------------------------------------------------------------------------
/app/api/v1/notify/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, type NextRequest } from 'next/server'
2 |
3 | export async function GET(request: NextRequest) {
4 | return NextResponse.json({ data, count: data.length })
5 | }
6 |
7 | const data = [
8 | {
9 | id: 1,
10 | title: 'Your call has been confirmed.',
11 | description: '1 hour ago',
12 | },
13 | {
14 | id: 2,
15 | title: 'You have a new message!',
16 | description: '1 hour ago',
17 | },
18 | {
19 | id: 3,
20 | title: 'Your subscription is expiring soon!',
21 | description: '2 hours ago',
22 | },
23 | ]
24 |
--------------------------------------------------------------------------------
/app/auth/auth-code-error/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Error } from '@/components/error'
4 | import { ButtonLink } from '@/components/button-link'
5 |
6 | export default function AuthCodeError() {
7 | return (
8 |
9 |
15 | signin
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/auth/blocked/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { getUserAPI } from '@/queries/server/users'
5 |
6 | export default async function BlockedLayout({
7 | children,
8 | }: {
9 | children?: React.ReactNode
10 | }) {
11 | const { user } = await getUserAPI()
12 |
13 | if (!user) redirect('/auth/signin')
14 | if (!user?.is_ban) redirect('/dashboard')
15 |
16 | return <>{children}>
17 | }
18 |
--------------------------------------------------------------------------------
/app/auth/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default function AuthPage() {
4 | redirect('/auth/signin')
5 | }
6 |
--------------------------------------------------------------------------------
/app/auth/signup/policy.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Link from 'next/link'
5 | import { useTrans } from '@/hooks/i18next'
6 |
7 | const Policy = () => {
8 | const { trans } = useTrans()
9 |
10 | return (
11 |
12 | {trans(
13 | 'by_clicking_sign_up_you_agree_to_our_terms_of_service_and_privacy_policy',
14 | {
15 | components: {
16 | link1: (
17 |
21 | ),
22 | link2: (
23 |
27 | ),
28 | },
29 | }
30 | )}
31 |
32 | )
33 | }
34 |
35 | export { Policy }
36 |
--------------------------------------------------------------------------------
/app/dashboard/admin/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { AppBar } from '@/app/dashboard/components/app-bar'
5 | import { AppPanel } from '@/app/dashboard/components/app-panel'
6 |
7 | import { getUserAPI } from '@/queries/server/users'
8 |
9 | export default async function AdminLayout({
10 | children,
11 | }: {
12 | children?: React.ReactNode
13 | }) {
14 | const { user } = await getUserAPI()
15 | const isAdmin = user?.role === 'admin' || user?.role === 'superadmin'
16 |
17 | if (!user) redirect('/auth/signin')
18 | if (!isAdmin) return Unauthorized
19 |
20 | return (
21 |
22 |
23 |
24 | {children}
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/dashboard/admin/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Title } from '@/components/title'
4 | import { Description } from '@/components/description'
5 | import { Separator } from '@/components/ui/separator'
6 |
7 | export default function AdminPage() {
8 | return (
9 |
10 |
11 |
admin
12 |
13 |
14 | ...
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/app/dashboard/appearance/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { AppBar } from '@/app/dashboard/components/app-bar'
5 | import { AppPanel } from '@/app/dashboard/components/app-panel'
6 |
7 | import { getUserAPI } from '@/queries/server/users'
8 |
9 | export default async function AppearanceLayout({
10 | children,
11 | }: {
12 | children?: React.ReactNode
13 | }) {
14 | const { user } = await getUserAPI()
15 |
16 | if (!user) redirect('/auth/signin')
17 |
18 | return (
19 |
20 |
21 |
22 | {children}
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/dashboard/components/app-bar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 |
5 | import { AccountMenu } from '@/components/account-menu'
6 | import { SiteBrand } from '@/components/site-brand'
7 | import { Notify } from '@/app/dashboard/components/notify'
8 |
9 | import { cn } from '@/lib/utils'
10 |
11 | interface AppBarProps extends React.HTMLAttributes {}
12 |
13 | const AppBar = ({ children, className, ...props }: AppBarProps) => {
14 | return (
15 |
23 |
24 | {children}
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export { AppBar, type AppBarProps }
33 |
--------------------------------------------------------------------------------
/app/dashboard/components/demo-site-warning-notification.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useTranslation } from 'react-i18next'
5 |
6 | import { Terminal } from 'lucide-react'
7 | import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
8 |
9 | const DemoSiteWarningNotification = () => {
10 | const { t } = useTranslation()
11 |
12 | return (
13 |
17 |
18 | {/* {t('heads_up')} */}
19 |
20 | {t('heads_up')}{' '}
21 | {t('data_stored_on_the_demo_site_is_reset_periodically')}
22 |
23 |
24 | )
25 | }
26 |
27 | export { DemoSiteWarningNotification }
28 |
--------------------------------------------------------------------------------
/app/dashboard/dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | export { LatestPosts } from './latest-posts'
2 | export { PostRanks } from './post-ranks'
3 |
--------------------------------------------------------------------------------
/app/dashboard/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { DemoSiteWarningNotification } from './components/demo-site-warning-notification'
5 | import { getUserAPI } from '@/queries/server/users'
6 |
7 | export default async function DashboardLayout({
8 | children,
9 | }: {
10 | children?: React.ReactNode
11 | }) {
12 | const { user } = await getUserAPI()
13 |
14 | if (!user) redirect('/auth/signin')
15 | if (user?.is_ban) redirect('/auth/blocked')
16 | // if (user?.deleted_at) redirect('/auth/deactivated')
17 |
18 | return (
19 | <>
20 | {process.env.NODE_ENV === 'production' ? (
21 |
22 | ) : null}
23 | {children}
24 | >
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/dashboard/posts/components/bulk-actions/index.ts:
--------------------------------------------------------------------------------
1 | export { BulkActions, type BulkActionsProps } from './bulk-actions'
2 | export { BulkActionsProvider, useBulkActions } from './bulk-actions-provider'
3 |
--------------------------------------------------------------------------------
/app/dashboard/posts/components/quick-links/index.ts:
--------------------------------------------------------------------------------
1 | export { QuickEdit } from './quick-edit'
2 | export { QuickView } from './quick-view'
3 | export { QuickTrash } from './quick-trash'
4 | export { QuickRestore } from './quick-restore'
5 | export { QuickDelete } from './quick-delete'
6 | export { QuickPublish } from './quick-publish'
7 | export { QuickPublic } from './quick-public'
8 | export { QuickPrivate } from './quick-private'
9 | export { QuickDraft } from './quick-draft'
10 |
--------------------------------------------------------------------------------
/app/dashboard/posts/components/quick-links/quick-edit.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Link from 'next/link'
5 | import { useTranslation } from 'react-i18next'
6 | import { type Post } from '@/types/database'
7 |
8 | interface QuickEditProps {
9 | post: Post
10 | }
11 |
12 | const QuickEdit = ({ post }: QuickEditProps) => {
13 | const { t } = useTranslation()
14 |
15 | return (
16 |
20 | {t('edit')}
21 |
22 | )
23 | }
24 |
25 | export { QuickEdit, type QuickEditProps }
26 |
--------------------------------------------------------------------------------
/app/dashboard/posts/components/quick-links/quick-view.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Link from 'next/link'
5 | import { useTranslation } from 'react-i18next'
6 | import { type Post } from '@/types/database'
7 |
8 | interface QuickViewProps {
9 | post: Post
10 | }
11 |
12 | const QuickView = ({ post }: QuickViewProps) => {
13 | const { t } = useTranslation()
14 |
15 | return (
16 |
20 | {t('view')}
21 |
22 | )
23 | }
24 |
25 | export { QuickView, type QuickViewProps }
26 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/components/back-link.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Link from 'next/link'
3 | import { LucideIcon } from '@/lib/lucide-icon'
4 |
5 | const BackLink = () => {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
13 | export { BackLink }
14 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/components/fields/field-meta.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useFormContext } from 'react-hook-form'
5 |
6 | import {
7 | Form,
8 | FormControl,
9 | FormDescription,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 |
16 | const FieldMeta = () => {
17 | const { control } = useFormContext()
18 |
19 | return (
20 | (
24 |
25 |
26 |
27 |
28 |
29 | )}
30 | />
31 | )
32 | }
33 |
34 | export { FieldMeta }
35 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/components/fields/field-title.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useTranslation } from 'react-i18next'
5 | import { useFormContext } from 'react-hook-form'
6 |
7 | import {
8 | Form,
9 | FormControl,
10 | FormDescription,
11 | FormField,
12 | FormItem,
13 | FormLabel,
14 | FormMessage,
15 | } from '@/components/ui/form'
16 | import { Input } from '@/components/ui/input'
17 |
18 | const FieldTitle = () => {
19 | const { t } = useTranslation()
20 | const { control } = useFormContext()
21 |
22 | return (
23 | (
27 |
28 |
29 |
30 |
31 |
32 |
33 | )}
34 | />
35 | )
36 | }
37 |
38 | export { FieldTitle }
39 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/components/fields/field-user-id.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useFormContext } from 'react-hook-form'
5 |
6 | import {
7 | Form,
8 | FormControl,
9 | FormDescription,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 |
16 | const FieldUserId = () => {
17 | const { control } = useFormContext()
18 |
19 | return (
20 | (
24 |
25 |
26 |
27 |
28 |
29 | )}
30 | />
31 | )
32 | }
33 |
34 | export { FieldUserId }
35 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/components/fields/index.ts:
--------------------------------------------------------------------------------
1 | export { FieldUserId } from './field-user-id'
2 | export { FieldTitle } from './field-title'
3 | export { FieldMeta } from './field-meta'
4 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/components/metaboxes/index.ts:
--------------------------------------------------------------------------------
1 | export { MetaboxSlug } from './metabox-slug'
2 | export { MetaboxDescription } from './metabox-description'
3 | export { MetaboxKeywords } from './metabox-keywords'
4 | export { MetaboxRevisions } from './metabox-revisions'
5 | export { MetaboxPublish } from './metabox-publish'
6 | export { MetaboxThumbnail } from './metabox-thumbnail'
7 | export { MetaboxRectriction } from './metabox-restriction'
8 | export { MetaboxFutureDate } from './metabox-future-date'
9 | export { MetaboxTags } from './metabox-tags'
10 | export { MetaboxPermalink } from './metabox-permalink'
11 |
--------------------------------------------------------------------------------
/app/dashboard/posts/edit/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Title } from '@/components/title'
4 | import { AddPost } from '../components/add-post'
5 | import { BackLink } from './components/back-link'
6 | import { PostForm } from './post-form'
7 |
8 | export default function PostEditPage({
9 | searchParams: { id },
10 | }: {
11 | searchParams: { id: string }
12 | }) {
13 | return (
14 |
15 |
16 |
17 |
edit_post
18 |
19 | add_post
20 |
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/dashboard/posts/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { AppBar } from '@/app/dashboard/components/app-bar'
5 | import { AppPanel } from '@/app/dashboard/components/app-panel'
6 |
7 | import { getUserAPI } from '@/queries/server/users'
8 |
9 | export default async function PostsLayout({
10 | children,
11 | }: {
12 | children?: React.ReactNode
13 | }) {
14 | const { user } = await getUserAPI()
15 |
16 | if (!user) redirect('/auth/signin')
17 |
18 | return (
19 |
20 |
21 |
22 | {children}
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/dashboard/settings/emails/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Title } from '@/components/title'
4 | import { Description } from '@/components/description'
5 | import { Separator } from '@/components/ui/separator'
6 |
7 | import { EmailList } from './email-list'
8 | import { AddEmail } from './components/add-email'
9 | import { EditPrimaryEmail } from './components/edit-primary-email'
10 |
11 | export default function EmailsPage() {
12 | return (
13 |
14 |
15 |
emails
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/dashboard/settings/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { AppBar } from '@/app/dashboard/components/app-bar'
5 | import { AppPanel } from '@/app/dashboard/components/app-panel'
6 |
7 | import { getUserAPI } from '@/queries/server/users'
8 |
9 | export default async function SettingsLayout({
10 | children,
11 | }: {
12 | children?: React.ReactNode
13 | }) {
14 | const { user } = await getUserAPI()
15 |
16 | if (!user) redirect('/auth/signin')
17 |
18 | return (
19 |
20 |
21 |
22 | {children}
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/dashboard/settings/notifications/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Title } from '@/components/title'
4 | import { Description } from '@/components/description'
5 | import { Separator } from '@/components/ui/separator'
6 |
7 | import { NotificationsForm } from './notifications-form'
8 |
9 | export default function NotificationsPage() {
10 | return (
11 |
12 |
13 |
notifications
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/dashboard/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default function SettingsPage() {
4 | redirect('/dashboard/settings/account')
5 | }
6 |
--------------------------------------------------------------------------------
/app/dashboard/settings/security/manage-2fa-form.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useTranslation } from 'react-i18next'
5 |
6 | import { Button } from '@/components/ui/button'
7 | import { type User } from '@/types/database'
8 |
9 | interface Manage2FAFormProps {
10 | user: User
11 | }
12 |
13 | const Manage2FAForm = ({ user }: Manage2FAFormProps) => {
14 | const { t } = useTranslation()
15 |
16 | return (
17 |
24 | )
25 | }
26 |
27 | export { Manage2FAForm }
28 |
--------------------------------------------------------------------------------
/app/dashboard/settings/sessions/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Separator } from '@/components/ui/separator'
4 | import { Title } from '@/components/title'
5 | import { Description } from '@/components/description'
6 |
7 | import { SessionsForm } from './sessions-form'
8 |
9 | export default function SessionsPage() {
10 | return (
11 |
12 |
13 |
session
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/dashboard/settings/sessions/sessions-form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | const SessionsForm = () => {
4 | return <>...>
5 | }
6 |
7 | export { SessionsForm }
8 |
--------------------------------------------------------------------------------
/app/dashboard/tags/components/bulk-actions/index.ts:
--------------------------------------------------------------------------------
1 | export { BulkActions, type BulkActionsProps } from './bulk-actions'
2 | export { BulkActionsProvider, useBulkActions } from './bulk-actions-provider'
3 |
--------------------------------------------------------------------------------
/app/dashboard/tags/components/quick-links/index.ts:
--------------------------------------------------------------------------------
1 | export { QuickEdit } from './quick-edit'
2 | export { QuickDelete } from './quick-delete'
3 | export { QuickView } from './quick-view'
4 |
--------------------------------------------------------------------------------
/app/dashboard/tags/components/quick-links/quick-edit.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Link from 'next/link'
5 | import { useTranslation } from 'react-i18next'
6 | import { type Tag } from '@/types/database'
7 |
8 | interface QuickEditProps {
9 | tag: Tag
10 | }
11 |
12 | const QuickEdit = ({ tag }: QuickEditProps) => {
13 | const { t } = useTranslation()
14 |
15 | return (
16 |
20 | {t('edit')}
21 |
22 | )
23 | }
24 |
25 | export { QuickEdit, type QuickEditProps }
26 |
--------------------------------------------------------------------------------
/app/dashboard/tags/components/quick-links/quick-view.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Link from 'next/link'
5 | import { useTranslation } from 'react-i18next'
6 | import { type Tag } from '@/types/database'
7 |
8 | interface QuickViewProps {
9 | tag: Tag
10 | }
11 |
12 | const QuickView = ({ tag }: QuickViewProps) => {
13 | const { t } = useTranslation()
14 |
15 | return (
16 |
20 | {t('view')}
21 |
22 | )
23 | }
24 |
25 | export { QuickView, type QuickViewProps }
26 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/back-link.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Link from 'next/link'
3 | import { LucideIcon } from '@/lib/lucide-icon'
4 |
5 | const BackLink = () => {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
13 | export { BackLink }
14 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/fields/field-meta.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useFormContext } from 'react-hook-form'
5 |
6 | import {
7 | Form,
8 | FormControl,
9 | FormDescription,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 |
16 | const FieldMeta = () => {
17 | const { control } = useFormContext()
18 |
19 | return (
20 | (
24 |
25 |
26 |
27 |
28 |
29 | )}
30 | />
31 | )
32 | }
33 |
34 | export { FieldMeta }
35 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/fields/field-post-tags.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useFormContext } from 'react-hook-form'
5 |
6 | import {
7 | Form,
8 | FormControl,
9 | FormDescription,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 |
16 | const FieldPostTags = () => {
17 | const { control } = useFormContext()
18 |
19 | return (
20 | (
24 |
25 |
26 |
27 |
28 |
29 | )}
30 | />
31 | )
32 | }
33 |
34 | export { FieldPostTags }
35 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/fields/field-user-id.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useFormContext } from 'react-hook-form'
5 |
6 | import {
7 | Form,
8 | FormControl,
9 | FormDescription,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 |
16 | const FieldUserId = () => {
17 | const { control } = useFormContext()
18 |
19 | return (
20 | (
24 |
25 |
26 |
27 |
28 |
29 | )}
30 | />
31 | )
32 | }
33 |
34 | export { FieldUserId }
35 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/fields/index.ts:
--------------------------------------------------------------------------------
1 | export { FieldName } from './field-name'
2 | export { FieldUserId } from './field-user-id'
3 | export { FieldMeta } from './field-meta'
4 | export { FieldPostTags } from './field-post-tags'
5 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/metaboxes/index.ts:
--------------------------------------------------------------------------------
1 | export { MetaboxSlug } from './metabox-slug'
2 | export { MetaboxDescription } from './metabox-description'
3 | export { MetaboxPublish } from './metabox-publish'
4 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/components/metaboxes/metabox-description.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useTranslation } from 'react-i18next'
5 | import { useFormContext } from 'react-hook-form'
6 |
7 | import { FormMessage } from '@/components/ui/form'
8 | import { Textarea } from '@/components/ui/textarea'
9 |
10 | const MetaboxDescription = () => {
11 | const { t } = useTranslation()
12 | const { register, getFieldState, formState } = useFormContext()
13 | const fieldState = getFieldState('description', formState)
14 |
15 | return (
16 |
17 |
{t('description')}
18 |
19 |
24 | {fieldState?.error?.message}
25 |
26 |
27 | )
28 | }
29 |
30 | export { MetaboxDescription }
31 |
--------------------------------------------------------------------------------
/app/dashboard/tags/edit/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Title } from '@/components/title'
4 | import { AddTag } from '../components/add-tag'
5 | import { BackLink } from './components/back-link'
6 | import { TagForm } from './tag-form'
7 |
8 | export default function PostEditPage({
9 | searchParams: { id },
10 | }: {
11 | searchParams: { id: string }
12 | }) {
13 | return (
14 |
15 |
16 |
17 |
edit_tag
18 |
19 | add_tag
20 |
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/dashboard/tags/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { AppBar } from '@/app/dashboard/components/app-bar'
5 | import { AppPanel } from '@/app/dashboard/components/app-panel'
6 |
7 | import { getUserAPI } from '@/queries/server/users'
8 |
9 | export default async function TagsLayout({
10 | children,
11 | }: {
12 | children?: React.ReactNode
13 | }) {
14 | const { user } = await getUserAPI()
15 |
16 | if (!user) redirect('/auth/signin')
17 |
18 | return (
19 |
20 |
21 |
22 | {children}
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/dashboard/tags/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Separator } from '@/components/ui/separator'
4 | import { Title } from '@/components/title'
5 | import { Description } from '@/components/description'
6 |
7 | import { AddTag } from './components/add-tag'
8 | import { TagList } from './tag-list'
9 |
10 | export default function TagsPage() {
11 | return (
12 |
13 |
14 |
15 |
tag_list
16 | create_and_manage_tags
17 |
18 |
19 |
20 | new_tag
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/dashboard/users/layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { redirect } from 'next/navigation'
3 |
4 | import { AppBar } from '@/app/dashboard/components/app-bar'
5 | import { AppPanel } from '@/app/dashboard/components/app-panel'
6 |
7 | import { getUserAPI } from '@/queries/server/users'
8 |
9 | export default async function UsersLayout({
10 | children,
11 | }: {
12 | children?: React.ReactNode
13 | }) {
14 | const { user } = await getUserAPI()
15 |
16 | if (!user) redirect('/auth/signin')
17 |
18 | return (
19 |
20 |
21 |
22 | {children}
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/dashboard/users/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default function UsersPage() {
4 | redirect('/dashboard/users/profile')
5 | }
6 |
--------------------------------------------------------------------------------
/app/dashboard/users/profile/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Title } from '@/components/title'
4 | import { Description } from '@/components/description'
5 | import { Separator } from '@/components/ui/separator'
6 |
7 | import { ProfileForm } from './profile-form'
8 |
9 | export default function ProfilePage() {
10 | return (
11 |
12 |
13 |
profile
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/app/favicon.ico
--------------------------------------------------------------------------------
/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Error } from '@/components/error'
4 | import { ButtonLink } from '@/components/button-link'
5 |
6 | export default function NotFound() {
7 | return (
8 |
9 |
15 | home
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/posts/sitemap.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from 'next'
2 | import { getPostsAPI } from '@/queries/server/posts'
3 |
4 | export async function generateSitemaps() {
5 | const length = Math.ceil(1 / 10000)
6 |
7 | // Fetch the total number of posts and calculate the number of sitemaps needed
8 | return Array.from({ length }).map((_, i) => ({ id: i + 1 }))
9 | }
10 |
11 | export default async function sitemap({
12 | id,
13 | }: {
14 | id: number
15 | }): Promise {
16 | const routes: MetadataRoute.Sitemap = []
17 |
18 | // Google's limit is 50,000 URLs per sitemap
19 | const { posts } = await getPostsAPI(null, {
20 | page: id,
21 | perPage: 10000,
22 | postType: 'post',
23 | status: 'publish',
24 | })
25 |
26 | if (Array.isArray(posts) && posts?.length > 0) {
27 | for (let i = 0; i < posts.length; i++) {
28 | const { permalink, date } = posts[i]
29 | routes.push({ url: permalink ?? '', lastModified: date ?? '' })
30 | }
31 | }
32 |
33 | return routes
34 | }
35 |
--------------------------------------------------------------------------------
/app/robots.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from 'next'
2 |
3 | export default function robots(): MetadataRoute.Robots {
4 | const BASE_URL = process.env.NEXT_PUBLIC_APP_URL!
5 |
6 | return {
7 | rules: {
8 | userAgent: '*',
9 | allow: '/',
10 | disallow: ['/api/', '/auth/', '/policy/', '/dashboard/', 'robots.txt'],
11 | },
12 | sitemap: [
13 | BASE_URL + '/sitemap.xml',
14 | process.env.NODE_ENV === 'production'
15 | ? BASE_URL + '/posts/sitemap/1.xml'
16 | : BASE_URL + '/posts/sitemap.xml/1',
17 | ],
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/sitemap.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from 'next'
2 |
3 | export default function sitemap(): MetadataRoute.Sitemap {
4 | const BASE_URL = process.env.NEXT_PUBLIC_APP_URL!
5 |
6 | return [
7 | {
8 | url: BASE_URL,
9 | lastModified: new Date().toISOString(),
10 | changeFrequency: 'yearly',
11 | priority: 1,
12 | },
13 | {
14 | url: BASE_URL + '/posts',
15 | lastModified: new Date().toISOString(),
16 | changeFrequency: 'weekly',
17 | priority: 0.8,
18 | },
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/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": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/banners/index.ts:
--------------------------------------------------------------------------------
1 | export { UpgradeProBanner } from './upgrade-pro-banner'
2 |
--------------------------------------------------------------------------------
/components/copyright.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | interface CopyrightProps extends React.HTMLAttributes {}
4 |
5 | const Copyright = (props: CopyrightProps) => {
6 | return © {' 2024. '}
7 | }
8 |
9 | export { Copyright, type CopyrightProps }
10 |
--------------------------------------------------------------------------------
/components/description.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useTranslation } from 'react-i18next'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | interface DescriptionProps extends React.HTMLAttributes {
9 | text?: string
10 | ns?: string
11 | }
12 |
13 | const Description = ({
14 | children,
15 | className,
16 | translate,
17 | text,
18 | ns,
19 | ...props
20 | }: DescriptionProps) => {
21 | const { t } = useTranslation()
22 |
23 | return (
24 |
25 | {text && translate === 'yes' ? t(text, { ns }) : text}
26 | {children && typeof children === 'string' && translate === 'yes'
27 | ? t(children, { ns })
28 | : children}
29 |
30 | )
31 | }
32 |
33 | export { Description, type DescriptionProps }
34 |
--------------------------------------------------------------------------------
/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Copyright } from '@/components/copyright'
4 | import { ThemeToggle } from '@/components/theme-toggle'
5 | import { LanguageCombobox } from '@/components/language-combobox'
6 | import { cn } from '@/lib/utils'
7 |
8 | interface FooterProps extends React.HTMLAttributes {}
9 |
10 | const Footer = ({ className, ...props }: FooterProps) => {
11 | return (
12 |
27 | )
28 | }
29 |
30 | export { Footer, type FooterProps }
31 |
--------------------------------------------------------------------------------
/components/hentry/entry-author.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Link, { type LinkProps } from 'next/link'
3 |
4 | import { cn } from '@/lib/utils'
5 | import { type Author } from '@/types/database'
6 |
7 | interface EntryAuthorProps
8 | extends LinkProps,
9 | Omit, 'href'> {
10 | author: Author | null
11 | }
12 |
13 | const EntryAuthor = ({ className, author, ...props }: EntryAuthorProps) => {
14 | return (
15 |
16 | {author?.full_name ?? author?.username}
17 |
18 | )
19 | }
20 |
21 | export { EntryAuthor, type EntryAuthorProps }
22 |
--------------------------------------------------------------------------------
/components/hentry/entry-published.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import dayjs from 'dayjs'
3 |
4 | interface EntryPublishedProps
5 | extends React.TimeHTMLAttributes {}
6 |
7 | const EntryPublished = ({ dateTime, ...props }: EntryPublishedProps) => {
8 | if (!dateTime) return null
9 |
10 | return (
11 |
14 | )
15 | }
16 |
17 | export { EntryPublished, type EntryPublishedProps }
18 |
--------------------------------------------------------------------------------
/components/hentry/entry-summary.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { cn } from '@/lib/utils'
3 |
4 | interface EntrySummaryProps extends React.HTMLAttributes {
5 | text: string | null
6 | }
7 |
8 | const EntrySummary = ({ className, text, ...props }: EntrySummaryProps) => {
9 | return (
10 |
11 | {text}
12 |
13 | )
14 | }
15 |
16 | export { EntrySummary, type EntrySummaryProps }
17 |
--------------------------------------------------------------------------------
/components/hentry/entry-title.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Link from 'next/link'
3 | import { cn } from '@/lib/utils'
4 |
5 | interface EntryTitleProps extends React.HTMLAttributes {
6 | text: string | null
7 | href?: string
8 | }
9 |
10 | const EntryTitle = ({ className, href, text, ...props }: EntryTitleProps) => {
11 | return (
12 |
20 | {href ? {text} : text}
21 |
22 | )
23 | }
24 |
25 | export { EntryTitle, type EntryTitleProps }
26 |
--------------------------------------------------------------------------------
/components/hentry/entry-updated.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import dayjs from 'dayjs'
3 |
4 | interface EntryUpdatedProps extends React.TimeHTMLAttributes {}
5 |
6 | const EntryUpdated = ({ dateTime, ...props }: EntryUpdatedProps) => {
7 | if (!dateTime) return null
8 |
9 | return (
10 |
13 | )
14 | }
15 |
16 | export { EntryUpdated, type EntryUpdatedProps }
17 |
--------------------------------------------------------------------------------
/components/hentry/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * h-entry
3 | *
4 | * @description
5 | * h-entry is a simple, open format for episodic or datestamped content on the web.
6 | * h-entry is often used with content intended to be syndicated, e.g. blog posts.
7 | * h-entry is one of several open microformat standards suitable for embedding data in HTML.
8 | *
9 | * @link https://microformats.org/wiki/h-entry
10 | * @link https://microformats.org/wiki/hentry
11 | */
12 | export { EntryTitle, type EntryTitleProps } from './entry-title'
13 | export { EntrySummary, type EntrySummaryProps } from './entry-summary'
14 | export { EntryUpdated, type EntryUpdatedProps } from './entry-updated'
15 | export { EntryPublished, type EntryPublishedProps } from './entry-published'
16 | export { EntryAuthor, type EntryAuthorProps } from './entry-author'
17 | export { EntryTags, type EntryTagsProps } from './entry-tags'
18 |
--------------------------------------------------------------------------------
/components/latest-posts/index.tsx:
--------------------------------------------------------------------------------
1 | export { LatestPosts, type LatestPostsProps } from './latest-posts'
2 |
--------------------------------------------------------------------------------
/components/mobile-navigation.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Link from 'next/link'
3 |
4 | import { absoluteUrl } from '@/lib/utils'
5 | import { siteConfig } from '@/config/site'
6 | import { SiteLogo } from '@/components/site-logo'
7 |
8 | const MobileNavigation = () => {
9 | return (
10 |
11 |
12 |
13 | {siteConfig?.title}
14 |
15 |
23 |
24 | )
25 | }
26 |
27 | export { MobileNavigation }
28 |
--------------------------------------------------------------------------------
/components/navigation.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Link from 'next/link'
5 |
6 | import {
7 | NavigationMenuLink,
8 | NavigationMenuTrigger,
9 | NavigationMenuContent,
10 | NavigationMenuItem,
11 | NavigationMenuList,
12 | NavigationMenu,
13 | } from '@/components/ui/navigation-menu'
14 | import { absoluteUrl } from '@/lib/utils'
15 |
16 | const Navigation = () => {
17 | return (
18 |
19 |
20 |
21 |
25 | Posts
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export { Navigation }
34 |
--------------------------------------------------------------------------------
/components/paging/index.tsx:
--------------------------------------------------------------------------------
1 | export { Paging, type PagingProps } from './paging'
2 | export {
3 | PagingProvider,
4 | usePaging,
5 | type PagingContextProps,
6 | } from './paging-provider'
7 |
--------------------------------------------------------------------------------
/components/signin-with.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { SignInWithGoogle } from '@/components/signin-with-google'
4 | // import { SignInWithGithub } from '@/components/auth/signin-with-github'
5 |
6 | const SignInWith = () => {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
13 |
14 | Or
15 |
16 |
17 |
18 | {/* */}
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export { SignInWith }
26 |
--------------------------------------------------------------------------------
/components/site-brand.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Link from 'next/link'
3 |
4 | import { absoluteUrl } from '@/lib/utils'
5 | import { siteConfig } from '@/config/site'
6 | import { SiteLogo } from '@/components/site-logo'
7 |
8 | interface SiteBrandProps {
9 | className?: string
10 | }
11 |
12 | const SiteBrand = ({ className }: SiteBrandProps) => {
13 | return (
14 |
15 |
16 | {siteConfig?.name}
17 |
18 | )
19 | }
20 |
21 | export { SiteBrand, type SiteBrandProps }
22 |
--------------------------------------------------------------------------------
/components/tailwind-indicator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | const TailwindIndicator = () => {
4 | if (process.env.NODE_ENV === 'production') return null
5 |
6 | return (
7 |
8 |
xs
9 |
sm
10 |
md
11 |
lg
12 |
xl
13 |
2xl
14 |
15 | )
16 | }
17 |
18 | export { TailwindIndicator }
19 |
--------------------------------------------------------------------------------
/components/title.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useTranslation } from 'react-i18next'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | interface TitleProps extends React.HTMLAttributes {
9 | text?: string
10 | ns?: string
11 | }
12 |
13 | const Title = ({
14 | children,
15 | className,
16 | translate,
17 | text,
18 | ns,
19 | ...props
20 | }: TitleProps) => {
21 | const { t } = useTranslation()
22 |
23 | return (
24 |
28 | {text && translate === 'yes' ? t(text, { ns }) : text}
29 | {children && typeof children === 'string' && translate === 'yes'
30 | ? t(children, { ns })
31 | : children}
32 |
33 | )
34 | }
35 |
36 | export { Title, type TitleProps }
37 |
--------------------------------------------------------------------------------
/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useTheme } from "next-themes"
4 | import { Toaster as Sonner } from "sonner"
5 |
6 | type ToasterProps = React.ComponentProps
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = "system" } = useTheme()
10 |
11 | return (
12 |
28 | )
29 | }
30 |
31 | export { Toaster }
32 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/config/site.ts:
--------------------------------------------------------------------------------
1 | import { type LucideIconName } from '@/lib/lucide-icon'
2 |
3 | export interface SiteConfig {
4 | name: string
5 | title: string
6 | symbol: LucideIconName
7 | description: string
8 | }
9 |
10 | export const siteConfig: SiteConfig = {
11 | name: 'NextJS supabase dashboard',
12 | title: 'NextJS supabase dashboard',
13 | description:
14 | 'This is a dashboard starter template for the NextJS 14 app router using supabase based on shadcn-ui.',
15 | symbol: 'Activity', // LucideIcon
16 | }
17 |
18 | export interface PricingPlan {
19 | name: string
20 | post: number
21 | }
22 |
23 | export const pricingPlans: PricingPlan[] = [
24 | { name: 'free', post: 3 },
25 | { name: 'basic', post: -1 },
26 | { name: 'standard', post: -1 },
27 | { name: 'premium', post: -1 },
28 | ]
29 |
--------------------------------------------------------------------------------
/context/i18n-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { I18nextProvider } from 'react-i18next'
5 | import i18n from '@/lib/i18next'
6 | import { defaultNS } from '@/i18next.config'
7 |
8 | import { useAppDispatch } from '@/lib/redux/hooks'
9 | import { setAppLanguage } from '@/store/reducers/app-reducer'
10 |
11 | interface I18nProviderProps {
12 | children?: React.ReactNode
13 | value: { language: string }
14 | }
15 |
16 | const I18nProvider = ({ children, value }: I18nProviderProps) => {
17 | const dispatch = useAppDispatch()
18 |
19 | React.useEffect(() => {
20 | i18n.changeLanguage(value?.language)
21 | dispatch(setAppLanguage(value?.language))
22 | }, [value?.language, dispatch])
23 |
24 | return (
25 |
26 | {children}
27 |
28 | )
29 | }
30 |
31 | export { I18nProvider, type I18nProviderProps }
32 |
--------------------------------------------------------------------------------
/context/swr-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { SWRConfig } from 'swr'
5 | import { fetcher } from '@/lib/utils'
6 |
7 | const SWRProvider = ({ children }: { children?: React.ReactNode }) => {
8 | return {children}
9 | }
10 |
11 | export { SWRProvider }
12 |
--------------------------------------------------------------------------------
/context/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { ThemeProvider as NextThemeProvider } from 'next-themes'
5 |
6 | interface ThemeProviderProps {
7 | children?: React.ReactNode
8 | value: { theme: string }
9 | }
10 |
11 | const ThemeProvider = ({ children, value }: ThemeProviderProps) => {
12 | return (
13 |
19 | {children}
20 |
21 | )
22 | }
23 |
24 | export { ThemeProvider, type ThemeProviderProps }
25 |
--------------------------------------------------------------------------------
/docs/CONFIGURATION.md:
--------------------------------------------------------------------------------
1 | # CONFIGURATION
2 |
3 | Edit `next.config.js`:
4 |
5 | ```javascript
6 | module.exports = {
7 | reactStrictMode: true,
8 | swcMinify: true,
9 | }
10 | ```
11 |
12 | To enable Turbopack. Edit `packages.json`:
13 |
14 | ```json
15 | {
16 | "scripts": {
17 | "dev": "next dev --turbo"
18 | }
19 | }
20 | ```
21 |
22 | Edit `packages.json`:
23 |
24 | ```json
25 | {
26 | "scripts": {
27 | "clean:files": "rm -rf node_modules && rm -rf .next && rm -rf package-lock.json",
28 | "clean:cache": "npm cache clean --force && npm cache verify",
29 | "clean": "npm run clean:files && npm run clean:cache",
30 | "upgrade:latest": "npm run clean && npx npm-check-updates -u && npm install",
31 | "reinstall": "npm run clean && npm install",
32 | },
33 | }
34 | ```
35 |
36 | After cleaning the directories and cache, install the dependency packages.
37 |
38 | ```shell
39 | npm run reinstall
40 | ```
41 |
--------------------------------------------------------------------------------
/hooks/headers/index.ts:
--------------------------------------------------------------------------------
1 | export { getUrl, getOrigin, getPathname } from './url'
2 |
--------------------------------------------------------------------------------
/hooks/headers/url.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { headers } from 'next/headers'
4 |
5 | export const getUrl = (): string => headers().get('x-url') as string
6 |
7 | export const getOrigin = (): string => headers().get('x-origin') as string
8 |
9 | export const getPathname = (): string => headers().get('x-pathname') as string
10 |
--------------------------------------------------------------------------------
/hooks/i18next/get-translation.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { promises as fs } from 'fs'
4 | import path from 'path'
5 | import { cookies } from 'next/headers'
6 | import { defaultLng } from '@/i18next.config'
7 |
8 | export interface Translation {
9 | t: (key: string) => string
10 | }
11 |
12 | export async function getTranslation(
13 | ns: string = 'translation'
14 | ): Promise {
15 | const lng = cookies().get('app:language')?.value ?? defaultLng
16 |
17 | const filePath = path.join(process.cwd(), `/public/locales/${lng}/${ns}.json`)
18 | const file = await fs.readFile(filePath, 'utf8')
19 |
20 | const t = (key: string): string => {
21 | const obj: Record =
22 | file && typeof file === 'string' ? JSON.parse(file) : ({} as JSON)
23 | return obj[key] ?? ''
24 | }
25 |
26 | return { t }
27 | }
28 |
--------------------------------------------------------------------------------
/hooks/i18next/index.ts:
--------------------------------------------------------------------------------
1 | export { useTrans } from './use-trans'
2 | export { getTranslation, type Translation } from './get-translation'
3 |
--------------------------------------------------------------------------------
/hooks/url/index.ts:
--------------------------------------------------------------------------------
1 | export { useQueryString } from './use-query-string'
2 |
--------------------------------------------------------------------------------
/hooks/url/use-query-string.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useSearchParams } from 'next/navigation'
5 |
6 | const useQueryString = () => {
7 | const searchParams = useSearchParams()
8 |
9 | const qs = React.useCallback(
10 | >(object?: T): string => {
11 | const params = new URLSearchParams(searchParams.toString())
12 |
13 | if (object) {
14 | Object.keys(object).forEach((k: string) => {
15 | if (object[k] === undefined || object[k] === null) {
16 | params.delete(k)
17 | return
18 | }
19 | params.set(k, object[k])
20 | })
21 | }
22 |
23 | return params.toString()
24 | },
25 | [searchParams]
26 | )
27 |
28 | return { qs }
29 | }
30 |
31 | export { useQueryString }
32 |
--------------------------------------------------------------------------------
/hooks/use-auth.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { AuthContext, AuthContextProps } from '@/context/auth-provider'
5 |
6 | const useAuth = () => {
7 | const context = React.useContext(AuthContext)
8 |
9 | if (context === undefined) {
10 | throw new Error('useAuth must be used within an AuthProvider')
11 | }
12 |
13 | return context
14 | }
15 |
16 | export { useAuth }
17 |
--------------------------------------------------------------------------------
/lib/country-flag-icons.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Flags from 'country-flag-icons/react/3x2'
3 |
4 | export interface FlagProps {
5 | code: string
6 | className?: string
7 | }
8 |
9 | export function Flag({ code, className }: FlagProps) {
10 | const FlagComponent = Flags[code.toUpperCase() as keyof typeof Flags]
11 | return
12 | }
13 |
--------------------------------------------------------------------------------
/lib/dayjs.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 | import utc from 'dayjs/plugin/utc'
3 | import timezone from 'dayjs/plugin/timezone'
4 | // import 'dayjs/locale/fr'
5 |
6 | dayjs.extend(utc)
7 | dayjs.extend(timezone)
8 |
9 | // dayjs.locale('fr')
10 | dayjs.tz.setDefault('Asia/Seoul')
11 |
12 | // const timezonedDayjs = (...args: any[]) => {
13 | // return dayjs(...args).tz()
14 | // }
15 |
16 | // const timezonedUnix = (value: number) => {
17 | // return dayjs.unix(value).tz()
18 | // }
19 |
20 | // timezonedDayjs.unix = timezonedUnix
21 | // timezonedDayjs.duration = dayjs.duration
22 |
23 | // export default timezonedDayjs
24 |
--------------------------------------------------------------------------------
/lib/emblor.ts:
--------------------------------------------------------------------------------
1 | import { Tag as EmblorTag } from 'emblor'
2 |
3 | export type Tag = EmblorTag & {
4 | slug?: string
5 | }
6 |
7 | export function generateTagId() {
8 | return crypto.getRandomValues(new Uint32Array(1))[0].toString()
9 | }
10 |
--------------------------------------------------------------------------------
/lib/jsonwebtoken.ts:
--------------------------------------------------------------------------------
1 | import * as jwt from 'jsonwebtoken'
2 | import {
3 | Secret,
4 | SignOptions,
5 | JwtPayload,
6 | VerifyOptions,
7 | VerifyErrors,
8 | } from 'jsonwebtoken'
9 |
10 | export const secret: Secret = process.env.SECRET_KEY!
11 |
12 | export function jwtSign(
13 | payload: string | object | Buffer,
14 | options?: SignOptions
15 | ): string {
16 | return jwt.sign(payload, secret, { algorithm: 'HS256', ...options })
17 | }
18 |
19 | export type JwtVerify =
20 | | { payload: string | JwtPayload; error: null }
21 | | { payload: null; error: VerifyErrors }
22 |
23 | export function jwtVerify(
24 | token: string,
25 | options?: VerifyOptions & { complete?: false }
26 | ): JwtVerify {
27 | try {
28 | const decoded = jwt.verify(token, secret, options)
29 | return { payload: decoded, error: null }
30 | } catch (e: unknown) {
31 | return { payload: null, error: e as VerifyErrors }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/lucide-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { icons, LucideProps } from 'lucide-react'
3 |
4 | export type LucideIconName = keyof typeof icons
5 |
6 | export type LucideIconProps = LucideProps & {
7 | name: LucideIconName
8 | }
9 |
10 | export function LucideIcon({ name, ...props }: LucideIconProps) {
11 | const Icon = icons[name]
12 |
13 | return
14 | }
15 |
--------------------------------------------------------------------------------
/lib/nodemailer.ts:
--------------------------------------------------------------------------------
1 | import * as nodemailer from 'nodemailer'
2 |
3 | export const sender = {
4 | name: process.env.SMTP_SENDER_NAME!,
5 | email: process.env.SMTP_SENDER_EMAIL!,
6 | }
7 |
8 | export const brevoTransporter = nodemailer.createTransport({
9 | host: 'smtp-relay.brevo.com',
10 | port: 587,
11 | secure: false, // true for 465, false for other ports
12 | auth: {
13 | user: process.env.SMTP_BREVO_USER!,
14 | pass: process.env.SMTP_BREVO_PASS!,
15 | },
16 | })
17 |
18 | export const gmailTransporter = nodemailer.createTransport({
19 | host: 'smtp.gmail.com',
20 | port: 465,
21 | secure: true, // true for 465, false for other ports
22 | auth: {
23 | user: process.env.SMTP_GMAIL_USER!,
24 | pass: process.env.SMTP_GMAIL_PASS!,
25 | },
26 | })
27 |
28 | export const transporter = brevoTransporter
29 |
--------------------------------------------------------------------------------
/lib/redux/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector, useStore } from 'react-redux'
2 | import type { TypedUseSelectorHook } from 'react-redux'
3 | import type { RootState, AppDispatch, AppStore } from '@/lib/redux/store'
4 |
5 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
6 | export const useAppDispatch: () => AppDispatch = useDispatch
7 | export const useAppSelector: TypedUseSelectorHook = useSelector
8 | export const useAppStore: () => AppStore = useStore
9 |
--------------------------------------------------------------------------------
/lib/redux/persist-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { PersistGate } from 'redux-persist/integration/react'
5 | import { persistor } from '@/lib/redux/store'
6 |
7 | const PersistProvider = ({ children }: { children?: React.ReactNode }) => {
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
15 | export { PersistProvider }
16 |
--------------------------------------------------------------------------------
/lib/redux/redux-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { StoreProvider } from '@/lib/redux/store-provider'
5 | import { PersistProvider } from '@/lib/redux/persist-provider'
6 |
7 | const ReduxProvider = ({ children }: { children?: React.ReactNode }) => {
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
15 | export { ReduxProvider }
16 |
--------------------------------------------------------------------------------
/lib/redux/storage.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | /**
4 | * redux-persist failed to create sync storage.
5 | * falling back to noop storage. #15687
6 | *
7 | * @link https://github.com/vercel/next.js/discussions/15687
8 | */
9 | import createWebStorage from 'redux-persist/lib/storage/createWebStorage'
10 | import { WebStorage } from 'redux-persist/lib/types'
11 |
12 | const createNoopStorage = (): WebStorage => {
13 | return {
14 | getItem(_key: any) {
15 | return Promise.resolve(null)
16 | },
17 | setItem(_key: any, value: any) {
18 | return Promise.resolve(value)
19 | },
20 | removeItem(_key: any) {
21 | return Promise.resolve()
22 | },
23 | }
24 | }
25 |
26 | const storage =
27 | typeof window !== 'undefined'
28 | ? createWebStorage('local')
29 | : createNoopStorage()
30 |
31 | export default storage
32 |
--------------------------------------------------------------------------------
/lib/redux/store-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { Provider } from 'react-redux'
5 | import { makeStore, AppStore } from '@/lib/redux/store'
6 |
7 | const StoreProvider = ({ children }: { children?: React.ReactNode }) => {
8 | const storeRef = React.useRef()
9 | if (!storeRef.current) {
10 | // Create the store instance the first time this renders
11 | storeRef.current = makeStore()
12 | }
13 |
14 | return {children}
15 | }
16 |
17 | export { StoreProvider }
18 |
--------------------------------------------------------------------------------
/lib/utils/cookie.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | export function setCookie(key: string, value: string): void {
4 | document.cookie = key + '=' + value + '; path=/;'
5 | }
6 |
7 | export function getCookie(key: string): string | null {
8 | const cookies = document.cookie.split(';') ?? []
9 | for (let i = 0; i < cookies.length; i++) {
10 | const v = cookies[i].split('=')
11 | if (v[0].trim() === key) return v[1]
12 | }
13 | return null
14 | }
15 |
16 | export function deleteCookie(key: string): void {
17 | document.cookie =
18 | key + '=; Max-Age=0; path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'
19 | }
20 |
--------------------------------------------------------------------------------
/lib/utils/error.ts:
--------------------------------------------------------------------------------
1 | import { httpStatusText } from '@/lib/utils'
2 |
3 | export class ApiError extends Error {
4 | constructor(status: number, message?: string) {
5 | super()
6 | this.name = this.constructor.name
7 | this.message = message ?? httpStatusText(status)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/utils/fetcher.ts:
--------------------------------------------------------------------------------
1 | import { absoluteUrl } from '@/lib/utils'
2 |
3 | export function fetcher(
4 | input: string,
5 | init?: RequestInit
6 | ): Promise {
7 | if (/^\//.test(input)) input = absoluteUrl(input)
8 | return fetch(input, init).then((response: Response) =>
9 | response.headers.get('content-type')?.includes('application/json')
10 | ? response.json()
11 | : response.text()
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/lib/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { cn } from './tailwind'
2 | export { fetcher } from './fetcher'
3 | export { revalidates, revalidatePaths, revalidateTags } from './cache'
4 | export { setCookie, getCookie, deleteCookie } from './cookie'
5 | export {
6 | httpStatusCodes,
7 | type HttpStatusCode,
8 | httpUnknownStatusCode,
9 | httpStatusCode,
10 | httpStatusText,
11 | httpStatusMessage,
12 | } from './http-status-codes'
13 | export { ApiError } from './error'
14 | export {
15 | absoluteUrl,
16 | isAbsoluteUrl,
17 | relativeUrl,
18 | setUrn,
19 | getQueryString,
20 | setQueryString,
21 | } from './url'
22 | export {
23 | setMeta,
24 | getMeta,
25 | getMetaValue,
26 | compareMetaValue,
27 | compareTags,
28 | } from './functions'
29 | export { generateRecentPosts } from './dummy-text'
30 |
--------------------------------------------------------------------------------
/lib/utils/tailwind.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export function cn(...inputs: ClassValue[]): string {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/assets/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/icons/icon-192x192.png
--------------------------------------------------------------------------------
/public/assets/icons/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/icons/icon-256x256.png
--------------------------------------------------------------------------------
/public/assets/icons/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/icons/icon-384x384.png
--------------------------------------------------------------------------------
/public/assets/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/icons/icon-512x512.png
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1481277542470-605612bd2d61.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1481277542470-605612bd2d61.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1516455207990-7a41ce80f7ee.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1516455207990-7a41ce80f7ee.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1517487881594-2787fef5ebf7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1517487881594-2787fef5ebf7.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1519710164239-da123dc03ef4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1519710164239-da123dc03ef4.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1523413651479-597eb2da0ad6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1523413651479-597eb2da0ad6.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1525097487452-6278ff080c31.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1525097487452-6278ff080c31.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1530731141654-5993c3016c77.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1530731141654-5993c3016c77.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1549388604-817d15aa0110.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1549388604-817d15aa0110.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1563298723-dcfebaa392e3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1563298723-dcfebaa392e3.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1574180045827-681f8a1a9622.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1574180045827-681f8a1a9622.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1588436706487-9d55d73a39e3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1588436706487-9d55d73a39e3.jpg
--------------------------------------------------------------------------------
/public/assets/images/main/photo-1597262975002-c5c3b14bbd62.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/public/assets/images/main/photo-1597262975002-c5c3b14bbd62.jpg
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AE.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AF.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AG.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AM.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AQ.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AR.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AS.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AT.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AW.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AX.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/AZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BB.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BD.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BF.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BG.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BH.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BJ.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BO.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BQ.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BR.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BS.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BT.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BV.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/BZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CA.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CD.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CF.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CG.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CH.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CI.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CL.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CM.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CN.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CR.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CU.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CW.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/CZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/DE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/DJ.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/DK.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/DO.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/DZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/EC.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/EE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/EG.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/EH.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ER.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ES.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/FI.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/FM.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/FO.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/FR.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GA.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GE-OS.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GE.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GF.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GG.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GH.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GL.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GM.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GN.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GP.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GR.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GT.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/GY.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/HT.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/HU.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IC.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ID.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IL.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IN.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IQ.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IS.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/IT.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/JE.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/JM.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/JO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/JP.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/KH.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/KN.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/KP.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/KW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LA.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LC.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LI.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LR.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LS.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LT.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LU.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LV.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/LY.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MA.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MC.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MD.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MF.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MG.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MH.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ML.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MM.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MN.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MP.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MQ.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MR.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MT.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MU.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MV.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MX.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MY.svg:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/MZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NA.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NF.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NG.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NI.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NL.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/NR.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/OM.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PA.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PF.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PL.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PM.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PR.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PS.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PT.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/PY.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/QA.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/RE.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/RO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/RU.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/RW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SC.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SD.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SI.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SJ.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SL.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SN.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SR.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SS.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ST.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SV.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SY.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/SZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TD.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TF.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TG.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TH.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TL.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TN.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TR.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TT.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/TZ.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/UA.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/VC.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/VN.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/WF.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/YE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ZA.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/public/data/country-flag-icons/ZM.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/public/locales/en/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "Pagination": {
3 | "firstText": "First",
4 | "firstAriaLabel": "Go to first page",
5 | "previousText": "Previous",
6 | "previousAriaLabel": "Go to previous page",
7 | "nextText": "Next",
8 | "nextAriaLabel": "Go to next page",
9 | "lastText": "Last",
10 | "lastAriaLabel": "Go to last page",
11 | "ellipsisText": "More pages"
12 | }
13 | }
--------------------------------------------------------------------------------
/public/locales/en/zod-custom.json:
--------------------------------------------------------------------------------
1 | {
2 | "invalid_confirm_password": "Passwords does not match.",
3 | "invalid_confirmation_phrase": "Confirmation text does not match.",
4 | "invalid_username": "Username can only contain english, numbers, underscores, and hyphens."
5 | }
--------------------------------------------------------------------------------
/public/locales/ko/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "Pagination": {
3 | "firstText": "처음",
4 | "firstAriaLabel": "처음 페이지로 이동",
5 | "previousText": "이전",
6 | "previousAriaLabel": "이전 페이지로 이동",
7 | "nextText": "다음",
8 | "nextAriaLabel": "다음 페이지로 이동",
9 | "lastText": "마지막",
10 | "lastAriaLabel": "마지막 페이지로 이동",
11 | "ellipsisText": "더 많은 페이지"
12 | }
13 | }
--------------------------------------------------------------------------------
/public/locales/ko/zod-custom.json:
--------------------------------------------------------------------------------
1 | {
2 | "invalid_confirm_password": "비밀번호가 일치하지 않습니다.",
3 | "invalid_confirmation_phrase": "확인 텍스트가 일치하지 않습니다.",
4 | "invalid_username": "사용자 이름에는 영어, 숫자, 밑줄, 하이픈만 사용할 수 있습니다."
5 | }
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/queries/client/emails.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import useSWR from 'swr'
4 | import { type EmailsAPI } from '@/types/api'
5 |
6 | export function useEmailsAPI(userId: string | null) {
7 | const url = userId ? `/api/v1/email/list?userId=${userId}` : null
8 |
9 | const { data, error, isLoading, isValidating, mutate } = useSWR<
10 | EmailsAPI,
11 | Error
12 | >(url)
13 |
14 | return {
15 | emails: data?.data ?? null,
16 | error: error ?? data?.error ?? null,
17 | isLoading,
18 | isValidating,
19 | mutate,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/queries/client/favorites.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import useSWR from 'swr'
4 | import { setQueryString } from '@/lib/utils'
5 | import { type FavoriteAPI } from '@/types/api'
6 |
7 | export function useFavoriteAPI(
8 | id: number | null,
9 | params?: { postId?: number; userId?: string }
10 | ) {
11 | const query = setQueryString({ id, ...params })
12 | const url = query ? `/api/v1/favorite?${query}` : null
13 |
14 | const { data, error, isLoading, isValidating, mutate } = useSWR<
15 | FavoriteAPI,
16 | Error
17 | >(url)
18 |
19 | return {
20 | favorite: data?.data ?? null,
21 | error: error ?? data?.error ?? null,
22 | isLoading,
23 | isValidating,
24 | mutate,
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/queries/client/notifications.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import useSWR from 'swr'
4 | import { type NotificationAPI } from '@/types/api'
5 |
6 | export function useNotificationAPI(userId: string | null) {
7 | const url = userId ? `/api/v1/notification?userId=${userId}` : null
8 |
9 | const { data, error, isLoading, isValidating, mutate } = useSWR<
10 | NotificationAPI,
11 | Error
12 | >(url)
13 |
14 | return {
15 | notification: data?.data ?? null,
16 | error: error ?? data?.error ?? null,
17 | isLoading,
18 | isValidating,
19 | mutate,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/queries/client/statistics.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import useSWR from 'swr'
4 | import { setQueryString } from '@/lib/utils'
5 | import { type StatisticsAPI } from '@/types/api'
6 |
7 | export function useStatisticsAPI(
8 | userId: string | null,
9 | params?: {
10 | q?: string
11 | orderBy?: string
12 | order?: string
13 | perPage?: number
14 | page?: number
15 | limit?: number
16 | }
17 | ) {
18 | const query = setQueryString({ userId, ...params })
19 | const url = query ? `/api/v1/statistic/list?${query}` : null
20 |
21 | const { data, error, isLoading, isValidating, mutate } = useSWR<
22 | StatisticsAPI,
23 | Error
24 | >(url)
25 |
26 | return {
27 | statistics: data?.data ?? null,
28 | count: data?.count ?? null,
29 | error: error ?? data?.error ?? null,
30 | isLoading,
31 | isValidating,
32 | mutate,
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/queries/server/users.ts:
--------------------------------------------------------------------------------
1 | import { fetcher } from '@/lib/utils'
2 | import { getAuth } from '@/queries/server/auth'
3 | import { type UserAPI } from '@/types/api'
4 |
5 | export async function getUserAPI(
6 | id: string | null = null,
7 | params?: { username?: string }
8 | ) {
9 | const { session } = await getAuth()
10 |
11 | let url: string | null = null
12 |
13 | if (params?.username) {
14 | url = `/api/v1/user?username=${params?.username}`
15 | } else if (id) {
16 | url = `/api/v1/user?id=${id}`
17 | } else if (session?.user) {
18 | url = `/api/v1/user?id=${session?.user?.id}`
19 | }
20 |
21 | if (!url) return { user: null }
22 |
23 | const { data: user, error } = await fetcher(url)
24 |
25 | return error ? { user: null } : { user }
26 | }
27 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshot.png
--------------------------------------------------------------------------------
/screenshots/01.main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/01.main.png
--------------------------------------------------------------------------------
/screenshots/02.signin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/02.signin.png
--------------------------------------------------------------------------------
/screenshots/03.signup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/03.signup.png
--------------------------------------------------------------------------------
/screenshots/04.forgot-password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/04.forgot-password.png
--------------------------------------------------------------------------------
/screenshots/05.reset-password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/05.reset-password.png
--------------------------------------------------------------------------------
/screenshots/06.profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/06.profile.png
--------------------------------------------------------------------------------
/screenshots/07.favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/07.favorites.png
--------------------------------------------------------------------------------
/screenshots/08.posts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/08.posts.png
--------------------------------------------------------------------------------
/screenshots/09.post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/09.post.png
--------------------------------------------------------------------------------
/screenshots/10.search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/10.search.png
--------------------------------------------------------------------------------
/screenshots/20.dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/20.dashboard.png
--------------------------------------------------------------------------------
/screenshots/30.posts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/30.posts.png
--------------------------------------------------------------------------------
/screenshots/31.post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/31.post.png
--------------------------------------------------------------------------------
/screenshots/32.tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/32.tags.png
--------------------------------------------------------------------------------
/screenshots/33.tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/33.tag.png
--------------------------------------------------------------------------------
/screenshots/40.appearance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/40.appearance.png
--------------------------------------------------------------------------------
/screenshots/50.profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/50.profile.png
--------------------------------------------------------------------------------
/screenshots/60.account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/60.account.png
--------------------------------------------------------------------------------
/screenshots/61.notifications.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/61.notifications.png
--------------------------------------------------------------------------------
/screenshots/62.emails.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/62.emails.png
--------------------------------------------------------------------------------
/screenshots/63.security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/screenshots/63.security.png
--------------------------------------------------------------------------------
/store/root-reducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from '@reduxjs/toolkit'
2 | import appReducer from '@/store/reducers/app-reducer'
3 |
4 | // Nested Persists
5 | const rootReducer = combineReducers({
6 | app: appReducer,
7 | })
8 |
9 | export default rootReducer
10 |
--------------------------------------------------------------------------------
/supabase/.gitignore:
--------------------------------------------------------------------------------
1 | # Supabase
2 | .branches
3 | .temp
4 | .env
5 | config.toml
6 |
--------------------------------------------------------------------------------
/supabase/client.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserClient } from '@supabase/ssr'
2 | import { type Database } from '@/types/supabase'
3 |
4 | /**
5 | * Setting up Server-Side Auth for Next.js
6 | *
7 | * @link https://supabase.com/docs/guides/auth/server-side/nextjs
8 | */
9 | export function createClient() {
10 | return createBrowserClient(
11 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
12 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "module": "esnext",
8 | "incremental": true,
9 | "plugins": [
10 | {
11 | "name": "next"
12 | }
13 | ],
14 |
15 | /* Bundler mode */
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "preserve",
21 |
22 | /* Linting */
23 | "strict": true,
24 |
25 | /* Shadcn/ui */
26 | "baseUrl": ".",
27 | "paths": {
28 | "@/*": ["./*"]
29 | },
30 | },
31 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "types/**/*.d.ts"],
32 | "exclude": ["node_modules"]
33 | }
34 |
--------------------------------------------------------------------------------
/types/ckeditor5-react/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@ckeditor/ckeditor5-react' {
2 | import { ClassicEditor } from 'ckeditor5'
3 | import { EditorConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig'
4 | import Event from '@ckeditor/ckeditor5-utils/src/eventinfo'
5 | import * as React from 'react'
6 | const CKEditor: React.FunctionComponent<{
7 | editor: typeof ClassicEditor
8 | config?: EditorConfig
9 | onReady?: (editor: ClassicEditor) => void
10 | onChange?: (event: Event, editor: ClassicEditor) => void
11 | onBlur?: (event: Event, editor: ClassicEditor) => void
12 | onFocus?: (event: Event, editor: ClassicEditor) => void
13 | onError?: (event: Event, editor: ClassicEditor) => void
14 | }>
15 | export { CKEditor }
16 | }
17 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/31713fc805180f3abbc460fc455638f2285db254/types/index.d.ts
--------------------------------------------------------------------------------
/types/token.ts:
--------------------------------------------------------------------------------
1 | export interface VerifyTokenPayload {
2 | user_id: string
3 | email: string
4 | }
5 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "crons": [
3 | {
4 | "path": "/api/cron/daily-reset-posts",
5 | "schedule": "0 0 * * *"
6 | }
7 | ]
8 | }
--------------------------------------------------------------------------------