├── .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 |