├── types ├── index.d.ts ├── token.ts └── ckeditor5-react │ └── index.d.ts ├── .nvmrc ├── .prettierignore ├── hooks ├── url │ ├── index.ts │ └── use-query-string.ts ├── headers │ ├── index.ts │ └── url.ts ├── i18next │ ├── index.ts │ └── get-translation.ts └── use-auth.ts ├── supabase ├── .gitignore └── client.ts ├── components ├── banners │ └── index.ts ├── latest-posts │ └── index.tsx ├── paging │ └── index.tsx ├── ui │ ├── aspect-ratio.tsx │ ├── skeleton.tsx │ ├── collapsible.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── separator.tsx │ ├── progress.tsx │ ├── input.tsx │ ├── toaster.tsx │ └── sonner.tsx ├── copyright.tsx ├── hentry │ ├── entry-summary.tsx │ ├── entry-updated.tsx │ ├── entry-published.tsx │ ├── entry-author.tsx │ ├── entry-title.tsx │ └── index.ts ├── site-brand.tsx ├── tailwind-indicator.tsx ├── signin-with.tsx ├── description.tsx ├── mobile-navigation.tsx ├── title.tsx ├── navigation.tsx └── footer.tsx ├── app ├── favicon.ico ├── dashboard │ ├── dashboard │ │ └── index.tsx │ ├── users │ │ ├── page.tsx │ │ ├── profile │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── settings │ │ ├── page.tsx │ │ ├── sessions │ │ │ ├── sessions-form.tsx │ │ │ └── page.tsx │ │ ├── security │ │ │ └── manage-2fa-form.tsx │ │ ├── notifications │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── emails │ │ │ └── page.tsx │ ├── posts │ │ ├── edit │ │ │ ├── components │ │ │ │ ├── fields │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── field-meta.tsx │ │ │ │ │ ├── field-user-id.tsx │ │ │ │ │ └── field-title.tsx │ │ │ │ ├── back-link.tsx │ │ │ │ └── metaboxes │ │ │ │ │ └── index.ts │ │ │ └── page.tsx │ │ ├── components │ │ │ ├── bulk-actions │ │ │ │ └── index.ts │ │ │ └── quick-links │ │ │ │ ├── index.ts │ │ │ │ ├── quick-view.tsx │ │ │ │ └── quick-edit.tsx │ │ └── layout.tsx │ ├── tags │ │ ├── components │ │ │ ├── quick-links │ │ │ │ ├── index.ts │ │ │ │ ├── quick-edit.tsx │ │ │ │ └── quick-view.tsx │ │ │ └── bulk-actions │ │ │ │ └── index.ts │ │ ├── edit │ │ │ ├── components │ │ │ │ ├── metaboxes │ │ │ │ │ ├── index.ts │ │ │ │ │ └── metabox-description.tsx │ │ │ │ ├── fields │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── field-meta.tsx │ │ │ │ │ ├── field-user-id.tsx │ │ │ │ │ └── field-post-tags.tsx │ │ │ │ └── back-link.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── admin │ │ ├── page.tsx │ │ └── layout.tsx │ ├── layout.tsx │ ├── appearance │ │ └── layout.tsx │ └── components │ │ ├── demo-site-warning-notification.tsx │ │ └── app-bar.tsx ├── auth │ ├── page.tsx │ ├── blocked │ │ └── layout.tsx │ ├── auth-code-error │ │ └── page.tsx │ └── signup │ │ └── policy.tsx ├── [username] │ └── layout.tsx ├── api │ ├── revalidate │ │ └── route.ts │ ├── v1 │ │ ├── notify │ │ │ └── route.ts │ │ └── email │ │ │ └── list │ │ │ └── route.ts │ └── ip │ │ └── route.ts ├── sitemap.ts ├── not-found.tsx ├── robots.ts └── posts │ └── sitemap.ts ├── screenshot.png ├── screenshots ├── 01.main.png ├── 09.post.png ├── 31.post.png ├── 32.tags.png ├── 33.tag.png ├── 02.signin.png ├── 03.signup.png ├── 06.profile.png ├── 08.posts.png ├── 10.search.png ├── 30.posts.png ├── 50.profile.png ├── 60.account.png ├── 62.emails.png ├── 07.favorites.png ├── 20.dashboard.png ├── 63.security.png ├── 40.appearance.png ├── 05.reset-password.png ├── 61.notifications.png └── 04.forgot-password.png ├── .browserslistrc ├── postcss.config.js ├── public ├── assets │ ├── icons │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png │ └── images │ │ └── main │ │ ├── photo-1549388604-817d15aa0110.jpg │ │ ├── photo-1563298723-dcfebaa392e3.jpg │ │ ├── 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-1574180045827-681f8a1a9622.jpg │ │ ├── photo-1588436706487-9d55d73a39e3.jpg │ │ └── photo-1597262975002-c5c3b14bbd62.jpg ├── locales │ ├── ko │ │ ├── zod-custom.json │ │ └── components.json │ └── en │ │ ├── zod-custom.json │ │ └── components.json ├── data │ └── country-flag-icons │ │ ├── BO.svg │ │ ├── JP.svg │ │ ├── ID.svg │ │ ├── MQ.svg │ │ ├── BD.svg │ │ ├── IC.svg │ │ ├── MC.svg │ │ ├── PW.svg │ │ ├── UA.svg │ │ ├── GN.svg │ │ ├── GE-OS.svg │ │ ├── AM.svg │ │ ├── AT.svg │ │ ├── BE.svg │ │ ├── BG.svg │ │ ├── DE.svg │ │ ├── EE.svg │ │ ├── GA.svg │ │ ├── HU.svg │ │ ├── LT.svg │ │ ├── LU.svg │ │ ├── NL.svg │ │ ├── CI.svg │ │ ├── CO.svg │ │ ├── FR.svg │ │ ├── GP.svg │ │ ├── IE.svg │ │ ├── PM.svg │ │ ├── RE.svg │ │ ├── MF.svg │ │ ├── DK.svg │ │ ├── CZ.svg │ │ ├── NG.svg │ │ ├── YE.svg │ │ ├── SL.svg │ │ ├── FI.svg │ │ ├── BJ.svg │ │ ├── LV.svg │ │ ├── AE.svg │ │ ├── IT.svg │ │ ├── ML.svg │ │ ├── RO.svg │ │ ├── TD.svg │ │ ├── GM.svg │ │ ├── MW.svg │ │ ├── CR.svg │ │ ├── PL.svg │ │ ├── KW.svg │ │ ├── GF.svg │ │ ├── BW.svg │ │ ├── SE.svg │ │ ├── AQ.svg │ │ ├── CG.svg │ │ ├── PE.svg │ │ ├── AR.svg │ │ ├── BH.svg │ │ ├── BS.svg │ │ ├── NE.svg │ │ ├── SO.svg │ │ ├── LA.svg │ │ ├── MG.svg │ │ ├── MU.svg │ │ ├── PS.svg │ │ ├── SD.svg │ │ ├── GL.svg │ │ ├── GY.svg │ │ ├── BF.svg │ │ ├── LC.svg │ │ ├── CH.svg │ │ ├── TH.svg │ │ ├── TT.svg │ │ ├── VN.svg │ │ ├── NF.svg │ │ ├── CL.svg │ │ ├── MT.svg │ │ ├── RU.svg │ │ ├── MD.svg │ │ ├── QA.svg │ │ ├── GW.svg │ │ ├── GH.svg │ │ ├── CM.svg │ │ ├── DJ.svg │ │ ├── BT.svg │ │ ├── TO.svg │ │ ├── TZ.svg │ │ ├── MM.svg │ │ ├── CD.svg │ │ ├── SC.svg │ │ ├── LI.svg │ │ ├── AX.svg │ │ ├── TL.svg │ │ ├── FO.svg │ │ ├── BV.svg │ │ ├── JO.svg │ │ ├── CW.svg │ │ ├── GG.svg │ │ ├── TR.svg │ │ ├── AW.svg │ │ ├── IS.svg │ │ ├── SN.svg │ │ ├── WF.svg │ │ ├── PT.svg │ │ ├── CA.svg │ │ ├── KH.svg │ │ ├── NR.svg │ │ ├── MV.svg │ │ ├── SR.svg │ │ ├── BQ.svg │ │ ├── GR.svg │ │ ├── MH.svg │ │ ├── MZ.svg │ │ ├── MA.svg │ │ ├── KP.svg │ │ ├── SS.svg │ │ ├── BB.svg │ │ ├── JE.svg │ │ ├── JM.svg │ │ ├── OM.svg │ │ ├── PR.svg │ │ ├── TG.svg │ │ ├── VC.svg │ │ ├── MP.svg │ │ ├── RW.svg │ │ ├── AF.svg │ │ ├── BR.svg │ │ ├── CU.svg │ │ ├── HT.svg │ │ ├── GE.svg │ │ ├── LY.svg │ │ ├── DZ.svg │ │ ├── EG.svg │ │ ├── LR.svg │ │ ├── NO.svg │ │ ├── SJ.svg │ │ ├── ZM.svg │ │ ├── AG.svg │ │ ├── IN.svg │ │ ├── SY.svg │ │ ├── AS.svg │ │ ├── KN.svg │ │ ├── MN.svg │ │ ├── MR.svg │ │ ├── EH.svg │ │ ├── LS.svg │ │ ├── IL.svg │ │ ├── BZ.svg │ │ ├── PA.svg │ │ ├── SZ.svg │ │ ├── AZ.svg │ │ ├── ZA.svg │ │ ├── GT.svg │ │ ├── ST.svg │ │ ├── IQ.svg │ │ ├── TN.svg │ │ ├── MX.svg │ │ ├── FM.svg │ │ ├── NA.svg │ │ ├── CF.svg │ │ ├── TW.svg │ │ ├── SI.svg │ │ ├── PF.svg │ │ ├── PY.svg │ │ ├── DO.svg │ │ ├── ER.svg │ │ ├── SV.svg │ │ ├── CN.svg │ │ ├── EC.svg │ │ ├── ES.svg │ │ ├── NI.svg │ │ ├── TF.svg │ │ └── MY.svg └── vercel.svg ├── vercel.json ├── lib ├── utils │ ├── tailwind.ts │ ├── error.ts │ ├── fetcher.ts │ ├── cookie.ts │ └── index.ts ├── emblor.ts ├── country-flag-icons.tsx ├── lucide-icon.tsx ├── redux │ ├── persist-provider.tsx │ ├── redux-provider.tsx │ ├── hooks.ts │ ├── store-provider.tsx │ └── storage.ts ├── dayjs.ts ├── nodemailer.ts └── jsonwebtoken.ts ├── store └── root-reducer.ts ├── context ├── swr-provider.tsx ├── theme-provider.tsx └── i18n-provider.tsx ├── components.json ├── queries ├── client │ ├── emails.ts │ ├── notifications.ts │ ├── favorites.ts │ └── statistics.ts └── server │ └── users.ts ├── .prettierrc.json ├── config ├── site.ts └── middleware.ts ├── tsconfig.json ├── .env.example └── docs └── CONFIGURATION.md /types/index.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.18.2 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out/ 2 | dist/ 3 | node_modules/ 4 | components/ui/ 5 | -------------------------------------------------------------------------------- /hooks/url/index.ts: -------------------------------------------------------------------------------- 1 | export { useQueryString } from './use-query-string' 2 | -------------------------------------------------------------------------------- /hooks/headers/index.ts: -------------------------------------------------------------------------------- 1 | export { getUrl, getOrigin, getPathname } from './url' 2 | -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | .env 5 | config.toml 6 | -------------------------------------------------------------------------------- /components/banners/index.ts: -------------------------------------------------------------------------------- 1 | export { UpgradeProBanner } from './upgrade-pro-banner' 2 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshot.png -------------------------------------------------------------------------------- /components/latest-posts/index.tsx: -------------------------------------------------------------------------------- 1 | export { LatestPosts, type LatestPostsProps } from './latest-posts' 2 | -------------------------------------------------------------------------------- /types/token.ts: -------------------------------------------------------------------------------- 1 | export interface VerifyTokenPayload { 2 | user_id: string 3 | email: string 4 | } 5 | -------------------------------------------------------------------------------- /screenshots/01.main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/01.main.png -------------------------------------------------------------------------------- /screenshots/09.post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/09.post.png -------------------------------------------------------------------------------- /screenshots/31.post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/31.post.png -------------------------------------------------------------------------------- /screenshots/32.tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/32.tags.png -------------------------------------------------------------------------------- /screenshots/33.tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/33.tag.png -------------------------------------------------------------------------------- /screenshots/02.signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/02.signin.png -------------------------------------------------------------------------------- /screenshots/03.signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/03.signup.png -------------------------------------------------------------------------------- /screenshots/06.profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/06.profile.png -------------------------------------------------------------------------------- /screenshots/08.posts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/08.posts.png -------------------------------------------------------------------------------- /screenshots/10.search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/10.search.png -------------------------------------------------------------------------------- /screenshots/30.posts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/30.posts.png -------------------------------------------------------------------------------- /screenshots/50.profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/50.profile.png -------------------------------------------------------------------------------- /screenshots/60.account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/60.account.png -------------------------------------------------------------------------------- /screenshots/62.emails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/62.emails.png -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | defaults and fully supports es6-module 4 | maintained node versions 5 | -------------------------------------------------------------------------------- /screenshots/07.favorites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/07.favorites.png -------------------------------------------------------------------------------- /screenshots/20.dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/20.dashboard.png -------------------------------------------------------------------------------- /screenshots/63.security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/63.security.png -------------------------------------------------------------------------------- /app/dashboard/dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | export { LatestPosts } from './latest-posts' 2 | export { PostRanks } from './post-ranks' 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /screenshots/40.appearance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/40.appearance.png -------------------------------------------------------------------------------- /screenshots/05.reset-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/05.reset-password.png -------------------------------------------------------------------------------- /screenshots/61.notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/61.notifications.png -------------------------------------------------------------------------------- /hooks/i18next/index.ts: -------------------------------------------------------------------------------- 1 | export { useTrans } from './use-trans' 2 | export { getTranslation, type Translation } from './get-translation' 3 | -------------------------------------------------------------------------------- /public/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/assets/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/icons/icon-256x256.png -------------------------------------------------------------------------------- /public/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /screenshots/04.forgot-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/screenshots/04.forgot-password.png -------------------------------------------------------------------------------- /app/auth/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation' 2 | 3 | export default function AuthPage() { 4 | redirect('/auth/signin') 5 | } 6 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "crons": [ 3 | { 4 | "path": "/api/cron/daily-reset-posts", 5 | "schedule": "0 0 * * *" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /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/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/sessions/sessions-form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | const SessionsForm = () => { 4 | return <>... 5 | } 6 | 7 | export { SessionsForm } 8 | -------------------------------------------------------------------------------- /public/assets/images/main/photo-1549388604-817d15aa0110.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1549388604-817d15aa0110.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1563298723-dcfebaa392e3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1563298723-dcfebaa392e3.jpg -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /public/assets/images/main/photo-1481277542470-605612bd2d61.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1481277542470-605612bd2d61.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1516455207990-7a41ce80f7ee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1516455207990-7a41ce80f7ee.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1517487881594-2787fef5ebf7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1517487881594-2787fef5ebf7.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1519710164239-da123dc03ef4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1519710164239-da123dc03ef4.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1523413651479-597eb2da0ad6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1523413651479-597eb2da0ad6.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1525097487452-6278ff080c31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1525097487452-6278ff080c31.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1530731141654-5993c3016c77.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1530731141654-5993c3016c77.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1574180045827-681f8a1a9622.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1574180045827-681f8a1a9622.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1588436706487-9d55d73a39e3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1588436706487-9d55d73a39e3.jpg -------------------------------------------------------------------------------- /public/assets/images/main/photo-1597262975002-c5c3b14bbd62.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3labkr/nextjs14-supabase-blog/HEAD/public/assets/images/main/photo-1597262975002-c5c3b14bbd62.jpg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/tags/components/bulk-actions/index.ts: -------------------------------------------------------------------------------- 1 | export { BulkActions, type BulkActionsProps } from './bulk-actions' 2 | export { BulkActionsProvider, useBulkActions } from './bulk-actions-provider' 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/locales/ko/zod-custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "invalid_confirm_password": "비밀번호가 일치하지 않습니다.", 3 | "invalid_confirmation_phrase": "확인 텍스트가 일치하지 않습니다.", 4 | "invalid_username": "사용자 이름에는 영어, 숫자, 밑줄, 하이픈만 사용할 수 있습니다." 5 | } -------------------------------------------------------------------------------- /public/data/country-flag-icons/BO.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/JP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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/data/country-flag-icons/ID.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MQ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/UA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GE-OS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/DE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/EE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/HU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/FR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/RE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/DK.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/YE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/data/country-flag-icons/SL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/FI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BJ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LV.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/ML.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/RO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/KW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/data/country-flag-icons/GF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AQ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GY.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/VN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/RU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/QA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/DJ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/FO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BV.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/JO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/WF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/KH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MV.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BQ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/KP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/JE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/JM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/OM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/VC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/RW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/HT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LY.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/DZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/EG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SJ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/ZM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SY.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/KN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/EH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/LS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/BZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/PA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/SZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/AZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/ZA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/GT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/ST.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/IQ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/MX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/FM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/NA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/CF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 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 | -------------------------------------------------------------------------------- /public/data/country-flag-icons/TW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 |