├── .cursorrules ├── .editorconfig ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── README.md ├── components.json ├── docker-compose.yml ├── eslint.config.mjs ├── next-env.d.ts ├── next-sitemap.config.cjs ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── KDCO.png ├── aaronsaunderspayload.txt ├── default-space.png ├── discordant-chat-controls.txt ├── discordant-modals.txt ├── discordant-sockets.txt ├── discordant.txt ├── ecommerce3.txt ├── favicon.ico ├── favicon.svg ├── icons │ ├── arrow-left-on-rectangle.svg │ ├── cog-8-tooth.svg │ ├── github-logo.svg │ └── user-circle.svg ├── images │ ├── 1.png │ ├── Technology-Watch.jpg │ ├── author.png │ ├── avatar │ │ ├── avatar_1.jpg │ │ ├── avatar_10.jpg │ │ ├── avatar_11.jpg │ │ ├── avatar_12.jpg │ │ ├── avatar_13.jpg │ │ ├── avatar_14.jpg │ │ ├── avatar_15.jpg │ │ ├── avatar_16.jpg │ │ ├── avatar_17.jpg │ │ ├── avatar_18.jpg │ │ ├── avatar_19.jpg │ │ ├── avatar_2.jpg │ │ ├── avatar_20.jpg │ │ ├── avatar_21.jpg │ │ ├── avatar_22.jpg │ │ ├── avatar_23.jpg │ │ ├── avatar_24.jpg │ │ ├── avatar_3.jpg │ │ ├── avatar_4.jpg │ │ ├── avatar_5.jpg │ │ ├── avatar_6.jpg │ │ ├── avatar_7.jpg │ │ ├── avatar_8.jpg │ │ └── avatar_9.jpg │ ├── favicon.ico │ ├── login-page-illustration.svg │ ├── logo-pink-white.png │ ├── logo-pink.png │ ├── placeholder.jpg │ ├── profile.jpg │ ├── seed │ │ ├── admin.jpg │ │ ├── author_1.svg │ │ ├── blog-1.jpg │ │ ├── blog-2.jpg │ │ ├── blog-3.jpg │ │ ├── blog-4.jpg │ │ ├── blog-5.jpg │ │ ├── blog-6.jpg │ │ ├── blog-7.jpg │ │ ├── blog-8.jpg │ │ ├── blogAuthor-1.jpg │ │ ├── blogAuthor-2.jpg │ │ ├── blogAuthor-3.jpg │ │ ├── blogAuthor-4.jpg │ │ ├── blogAuthor-5.jpg │ │ ├── blogAuthor-6.jpg │ │ ├── blogAuthor-7.jpg │ │ ├── blogAuthor-8.jpeg │ │ ├── contentql-logo.png │ │ ├── demo-user.webp │ │ ├── logo-pink-white.png │ │ ├── tag-Entrepreneurship.webp │ │ ├── tag-ai.png │ │ └── tag-projectmanagement.webp │ └── signup-page-illustration.svg ├── spaces-media │ └── Screenshot 2024-12-30 084153 (Small).png └── website-template-OG.webp ├── redirects.js ├── src ├── Footer │ ├── Component.tsx │ ├── RowLabel.tsx │ ├── config.ts │ └── hooks │ │ └── revalidateFooter.ts ├── Header │ ├── Component.client.tsx │ ├── Component.tsx │ ├── Nav │ │ └── index.tsx │ ├── RowLabel.tsx │ ├── config.ts │ └── hooks │ │ └── revalidateHeader.ts ├── access │ ├── anyone.ts │ ├── authenticated.ts │ └── authenticatedOrPublished.ts ├── app │ ├── (frontend) │ │ ├── 500 │ │ │ └── page.tsx │ │ ├── (auth) │ │ │ ├── create-account │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── login │ │ │ │ ├── LoginForm │ │ │ │ │ └── index.tsx │ │ │ │ ├── actions.ts │ │ │ │ └── page.tsx │ │ │ ├── logout │ │ │ │ ├── components │ │ │ │ │ └── logout-form.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ ├── recover-password │ │ │ │ ├── RecoverPasswordForm │ │ │ │ │ └── index.tsx │ │ │ │ ├── components │ │ │ │ │ └── client-wrapper.tsx │ │ │ │ └── page.tsx │ │ │ └── reset-password │ │ │ │ ├── ResetPasswordForm │ │ │ │ └── index.tsx │ │ │ │ └── page.tsx │ │ ├── (sitemaps) │ │ │ ├── pages-sitemap.xml │ │ │ │ └── route.ts │ │ │ └── posts-sitemap.xml │ │ │ │ └── route.ts │ │ ├── (spaces) │ │ │ ├── account │ │ │ │ ├── AccountForm │ │ │ │ │ └── index.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── spaces │ │ │ │ ├── [spaceId] │ │ │ │ ├── channels │ │ │ │ │ └── [channelId] │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── conversations │ │ │ │ │ └── [memberId] │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── members │ │ │ │ │ └── page.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── settings │ │ │ │ │ ├── components │ │ │ │ │ └── space-settings-content.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── actions.ts │ │ │ │ ├── not-found.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── types.ts │ │ ├── (website) │ │ │ ├── [slug] │ │ │ │ ├── page.client.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── next │ │ │ │ ├── exit-preview │ │ │ │ │ ├── GET.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── preview │ │ │ │ │ └── route.ts │ │ │ │ └── seed │ │ │ │ │ └── route.ts │ │ │ ├── not-found.tsx │ │ │ ├── page.tsx │ │ │ ├── posts │ │ │ │ ├── [slug] │ │ │ │ │ ├── page.client.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── page.client.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── page │ │ │ │ │ └── [pageNumber] │ │ │ │ │ ├── page.client.tsx │ │ │ │ │ └── page.tsx │ │ │ └── search │ │ │ │ ├── page.client.tsx │ │ │ │ └── page.tsx │ │ ├── 500.tsx │ │ ├── api │ │ │ ├── auth │ │ │ │ └── session │ │ │ │ │ └── route.ts │ │ │ ├── direct-messages │ │ │ │ ├── route.ts │ │ │ │ └── sse │ │ │ │ │ └── route.ts │ │ │ ├── livekit │ │ │ │ └── route.ts │ │ │ ├── messages │ │ │ │ ├── actions.ts │ │ │ │ ├── route.ts │ │ │ │ ├── sse │ │ │ │ │ ├── emitter.ts │ │ │ │ │ ├── health │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ └── update │ │ │ │ │ └── route.ts │ │ │ └── users │ │ │ │ ├── create │ │ │ │ └── route.ts │ │ │ │ ├── login │ │ │ │ └── route.ts │ │ │ │ └── me │ │ │ │ └── route.ts │ │ ├── error.tsx │ │ ├── global-error.tsx │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── not-found.tsx │ └── (payload) │ │ ├── admin │ │ ├── [[...segments]] │ │ │ ├── not-found.tsx │ │ │ └── page.tsx │ │ ├── importMap.js │ │ └── layout.tsx │ │ ├── api │ │ ├── [...slug] │ │ │ └── route.ts │ │ ├── graphql-playground │ │ │ └── route.ts │ │ └── graphql │ │ │ └── route.ts │ │ ├── custom.scss │ │ └── layout.tsx ├── blocks │ ├── ArchiveBlock │ │ ├── Component.tsx │ │ └── config.ts │ ├── Banner │ │ ├── Component.tsx │ │ └── config.ts │ ├── CallToAction │ │ ├── Component.tsx │ │ └── config.ts │ ├── Code │ │ ├── Component.client.tsx │ │ ├── Component.tsx │ │ ├── CopyButton.tsx │ │ └── config.ts │ ├── Content │ │ ├── Component.tsx │ │ └── config.ts │ ├── Form │ │ ├── Checkbox │ │ │ └── index.tsx │ │ ├── Component.tsx │ │ ├── Country │ │ │ ├── index.tsx │ │ │ └── options.ts │ │ ├── Email │ │ │ └── index.tsx │ │ ├── Error │ │ │ └── index.tsx │ │ ├── Message │ │ │ └── index.tsx │ │ ├── Number │ │ │ └── index.tsx │ │ ├── Select │ │ │ └── index.tsx │ │ ├── State │ │ │ ├── index.tsx │ │ │ └── options.ts │ │ ├── Text │ │ │ └── index.tsx │ │ ├── Textarea │ │ │ └── index.tsx │ │ ├── Width │ │ │ └── index.tsx │ │ ├── buildInitialFormState.tsx │ │ ├── config.ts │ │ └── fields.tsx │ ├── MediaBlock │ │ ├── Component.tsx │ │ └── config.ts │ ├── RelatedPosts │ │ └── Component.tsx │ └── RenderBlocks.tsx ├── collections │ ├── Categories.ts │ ├── Media.ts │ ├── Pages │ │ ├── hooks │ │ │ └── revalidatePage.ts │ │ └── index.ts │ ├── Posts │ │ ├── hooks │ │ │ ├── populateAuthors.ts │ │ │ └── revalidatePost.ts │ │ └── index.ts │ └── Users │ │ ├── hooks │ │ ├── addToHomeSpace.ts │ │ └── syncProfile.ts │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts ├── components │ ├── AdminBar │ │ ├── index.scss │ │ └── index.tsx │ ├── BeforeDashboard │ │ ├── SeedButton │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ ├── index.scss │ │ └── index.tsx │ ├── BeforeLogin │ │ └── index.tsx │ ├── Card │ │ └── index.tsx │ ├── CollectionArchive │ │ └── index.tsx │ ├── Link │ │ └── index.tsx │ ├── LivePreviewListener │ │ └── index.tsx │ ├── Logo │ │ └── Logo.tsx │ ├── Media │ │ ├── ImageMedia │ │ │ └── index.tsx │ │ ├── VideoMedia │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── types.ts │ ├── Message │ │ └── index.tsx │ ├── PageRange │ │ └── index.tsx │ ├── Pagination │ │ └── index.tsx │ ├── PayloadRedirects │ │ └── index.tsx │ ├── RenderParams │ │ ├── Component.tsx │ │ └── index.tsx │ ├── RichText │ │ └── index.tsx │ ├── admin │ │ └── CreateAccount │ │ │ ├── CreateAccountForm.tsx │ │ │ └── index.tsx │ ├── mode-toggle.tsx │ ├── providers │ │ └── socket-provider.tsx │ └── ui │ │ ├── 3d-pin.tsx │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── checkbox.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── fieldSet.tsx │ │ ├── form.tsx │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── tooltip.tsx │ │ └── use-toast.ts ├── cssVariables.js ├── endpoints │ └── seed │ │ ├── contact-form.ts │ │ ├── contact-page.ts │ │ ├── home-static.ts │ │ ├── home.ts │ │ ├── image-1.ts │ │ ├── image-2.ts │ │ ├── image-3.ts │ │ ├── image-hero-1.ts │ │ ├── image-hero1.webp │ │ ├── image-post1.webp │ │ ├── image-post2.webp │ │ ├── image-post3.webp │ │ ├── index.ts │ │ ├── post-1.ts │ │ ├── post-2.ts │ │ └── post-3.ts ├── environment.d.ts ├── error-handling.journal.md ├── fields │ ├── defaultLexical.ts │ ├── link.ts │ ├── linkGroup.ts │ └── slug │ │ ├── SlugComponent.tsx │ │ ├── formatSlug.ts │ │ ├── index.scss │ │ └── index.ts ├── heros │ ├── HighImpact │ │ └── index.tsx │ ├── LowImpact │ │ └── index.tsx │ ├── MediumImpact │ │ └── index.tsx │ ├── PostHero │ │ └── index.tsx │ ├── RenderHero.tsx │ └── config.ts ├── hooks │ ├── formatSlug.ts │ ├── populatePublishedAt.ts │ └── revalidateRedirects.ts ├── lib │ └── utils.ts ├── middleware.ts ├── payload-types.ts ├── payload.config.ts ├── plugins │ └── index.ts ├── providers │ ├── Auth │ │ └── index.tsx │ ├── HeaderTheme │ │ └── index.tsx │ ├── Theme │ │ ├── InitTheme │ │ │ └── index.tsx │ │ ├── ThemeSelector │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ ├── index.tsx │ │ ├── shared.ts │ │ └── types.ts │ └── index.tsx ├── search │ ├── Component.tsx │ ├── beforeSync.ts │ └── fieldOverrides.ts ├── spaces │ ├── README.md │ ├── access │ │ ├── isAdmin.ts │ │ ├── isAdminInHomeSpace.ts │ │ ├── isAdminorCreator.ts │ │ └── isCreator.ts │ ├── actions │ │ ├── channels.ts │ │ ├── create-space.ts │ │ ├── getCurrentUser.ts │ │ ├── members.ts │ │ ├── messages.ts │ │ ├── profiles.ts │ │ ├── redirect.ts │ │ └── spaces.ts │ ├── collections │ │ ├── Channels.ts │ │ ├── Conversations.ts │ │ ├── DirectMessages.ts │ │ ├── Members.ts │ │ ├── Messages.ts │ │ ├── Profiles.ts │ │ ├── Spaces.ts │ │ ├── SpacesMedia.ts │ │ ├── index.ts │ │ └── types.ts │ ├── components │ │ ├── action-tooltip.tsx │ │ ├── admin │ │ │ ├── MembersField.config.ts │ │ │ └── MembersField.tsx │ │ ├── ai │ │ │ ├── audio-input.tsx │ │ │ └── index.ts │ │ ├── chat │ │ │ ├── chat-header.tsx │ │ │ ├── chat-input.tsx │ │ │ ├── chat-item.tsx │ │ │ ├── chat-messages.tsx │ │ │ ├── chat-video-button.tsx │ │ │ ├── chat-welcome.tsx │ │ │ ├── index.ts │ │ │ └── load-more-button.tsx │ │ ├── editor.tsx │ │ ├── emoji-picker.tsx │ │ ├── error-boundary.tsx │ │ ├── error-display.tsx │ │ ├── file-upload.tsx │ │ ├── index.ts │ │ ├── loading-redirect.tsx │ │ ├── media-room.tsx │ │ ├── members │ │ │ ├── add-member-button.tsx │ │ │ └── members-list.tsx │ │ ├── mobile-toggle.tsx │ │ ├── mode-toggle-wrapper.tsx │ │ ├── mode-toggle.tsx │ │ ├── navigation │ │ │ ├── index.ts │ │ │ ├── navigation-action.tsx │ │ │ ├── navigation-item.tsx │ │ │ ├── navigation-menu.tsx │ │ │ └── navigation-sidebar.tsx │ │ ├── onboarding-form.tsx │ │ ├── preview.tsx │ │ ├── profile-settings.tsx │ │ ├── quill-viewer.tsx │ │ ├── socket-indicator.tsx │ │ ├── space │ │ │ ├── index.ts │ │ │ ├── join-space-button.tsx │ │ │ ├── space-channel.tsx │ │ │ ├── space-header.tsx │ │ │ ├── space-member.tsx │ │ │ ├── space-search.tsx │ │ │ ├── space-section.tsx │ │ │ └── space-sidebar.tsx │ │ ├── ui │ │ │ ├── action-tooltip.tsx │ │ │ ├── emoji-picker.tsx │ │ │ ├── error-boundary.tsx │ │ │ └── index.ts │ │ ├── upload-button.tsx │ │ ├── upload-dropzone.tsx │ │ ├── user-avatar.tsx │ │ ├── user-button.tsx │ │ └── visually-hidden.tsx │ ├── constants │ │ ├── channels.ts │ │ ├── collections.ts │ │ ├── index.ts │ │ ├── media.ts │ │ └── roles.ts │ ├── globals │ │ ├── CurrentUser │ │ │ └── config.ts │ │ └── Settings │ │ │ ├── Component.tsx │ │ │ └── config.ts │ ├── hooks │ │ ├── index.ts │ │ ├── use-analytics.ts │ │ ├── use-auth.ts │ │ ├── use-chat-query.ts │ │ ├── use-chat-scroll.ts │ │ ├── use-current-profile.ts │ │ ├── use-debounce.ts │ │ ├── use-infinite-query.ts │ │ ├── use-infinite-scroll.ts │ │ ├── use-media-query.ts │ │ ├── use-messages.ts │ │ ├── use-modal-store.ts │ │ ├── use-origin.ts │ │ ├── use-presence.ts │ │ ├── use-spaces.ts │ │ ├── use-toast.ts │ │ ├── use-upload.ts │ │ └── use-user.ts │ ├── index.ts │ ├── modals │ │ ├── create-channel-modal.tsx │ │ ├── create-space-modal.tsx │ │ ├── delete-channel-modal.tsx │ │ ├── delete-message-modal.tsx │ │ ├── delete-space-modal.tsx │ │ ├── edit-channel-modal.tsx │ │ ├── edit-space-modal.tsx │ │ ├── index.ts │ │ ├── initial-modal.tsx │ │ ├── invite-modal.tsx │ │ ├── leave-space-modal.tsx │ │ ├── members-modal.tsx │ │ └── message-file-modal.tsx │ ├── providers │ │ ├── current-user-provider.tsx │ │ ├── modal-provider.tsx │ │ ├── query-provider.tsx │ │ ├── socket-provider.tsx │ │ └── theme-provider.tsx │ ├── services │ │ ├── channelService.ts │ │ ├── channels.ts │ │ ├── index.ts │ │ ├── invite.ts │ │ ├── memberService.ts │ │ ├── members.ts │ │ ├── messageService.ts │ │ ├── messages.ts │ │ ├── socket │ │ │ └── socket-provider.tsx │ │ ├── spaceService.client.ts │ │ ├── spaceService.ts │ │ └── spaces.ts │ ├── types.ts │ ├── types │ │ ├── index.ts │ │ ├── messages.ts │ │ ├── richText.ts │ │ ├── server.ts │ │ ├── space.ts │ │ └── user.ts │ └── utilities │ │ ├── actions │ │ ├── index.ts │ │ ├── spaces.ts │ │ └── upload.ts │ │ ├── analytics │ │ ├── events.ts │ │ └── index.ts │ │ ├── analyzeImage.ts │ │ ├── auth │ │ ├── auth.ts │ │ ├── checkSession.ts │ │ └── checkSessionClient.ts │ │ ├── constants │ │ ├── channels.ts │ │ ├── index.ts │ │ ├── media.ts │ │ └── roles.ts │ │ ├── debug.ts │ │ ├── getSecret.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── logging.ts │ │ ├── messages │ │ └── createSystemMessage.ts │ │ ├── payload │ │ ├── getCurrentUser.server.ts │ │ ├── getCurrentUser.ts │ │ ├── getCurrentUserWithProfile.ts │ │ ├── getPayloadClient.ts │ │ ├── getUserWithProfile.ts │ │ ├── importMedia.ts │ │ └── index.ts │ │ ├── profile │ │ └── current-profile-pages.ts │ │ ├── sharp.server.ts │ │ ├── socket │ │ ├── messageQueue.ts │ │ └── presence.ts │ │ ├── toast.ts │ │ ├── toast │ │ └── index.ts │ │ ├── transforms │ │ └── space.ts │ │ └── url.ts └── utilities │ ├── auth.ts │ ├── canUseDOM.ts │ ├── cn.ts │ ├── deepMerge.ts │ ├── email-templates.ts │ ├── formatAuthors.ts │ ├── formatDateTime.ts │ ├── generateMeta.ts │ ├── generatePreviewPath.ts │ ├── getAuthToken.ts │ ├── getAuthTokenServer.ts │ ├── getAuthenticatedPayload.ts │ ├── getDocument.ts │ ├── getGlobals.ts │ ├── getMeUser.ts │ ├── getRedirects.ts │ ├── getURL.ts │ ├── mergeOpenGraph.ts │ ├── toKebabCase.ts │ ├── ui.ts │ ├── useClickableCard.ts │ └── useDebounce.ts ├── tailwind.config.mjs └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:import/recommended", 6 | "plugin:import/typescript" 7 | ], 8 | "plugins": ["@typescript-eslint"], 9 | "rules": { 10 | "@typescript-eslint/no-explicit-any": "warn", 11 | "@typescript-eslint/no-unused-vars": "warn", 12 | "@typescript-eslint/explicit-function-return-type": "off", 13 | "import/no-unresolved": "error", 14 | "import/order": [ 15 | "error", 16 | { 17 | "groups": ["builtin", "external", "internal", "parent", "sibling", "index"], 18 | "newlines-between": "always", 19 | "alphabetize": { 20 | "order": "asc", 21 | "caseInsensitive": true 22 | } 23 | } 24 | ] 25 | }, 26 | "settings": { 27 | "import/resolver": { 28 | "typescript": true, 29 | "node": true 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist / media 3 | node_modules 4 | .DS_Store 5 | .env 6 | .next 7 | .vercel 8 | 9 | # Payload default media upload directory 10 | public/media/ 11 | 12 | public/robots.txt 13 | public/sitemap*.xml 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | enable-pre-post-scripts=true 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/payload-types.ts 2 | .tmp 3 | **/.git 4 | **/.hg 5 | **/.pnp.* 6 | **/.svn 7 | **/.yarn/** 8 | **/build 9 | **/dist/** 10 | **/node_modules 11 | **/temp 12 | **/docs/** 13 | tsconfig.json 14 | 15 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Next.js: debug full stack", 9 | "type": "node", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/node_modules/next/dist/bin/next", 12 | "runtimeArgs": ["--inspect"], 13 | "skipFiles": ["/**"], 14 | "serverReadyAction": { 15 | "action": "debugWithChrome", 16 | "killOnServerStop": true, 17 | "pattern": "- Local:.+(https?://.+)", 18 | "uriFormat": "%s", 19 | "webRoot": "${workspaceFolder}" 20 | }, 21 | "cwd": "${workspaceFolder}" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm.packageManager": "pnpm", 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[typescript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.formatOnSave": true, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit" 9 | } 10 | }, 11 | "[typescriptreact]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "editor.formatOnSave": true, 14 | "editor.codeActionsOnSave": { 15 | "source.fixAll.eslint": "explicit" 16 | } 17 | }, 18 | "[javascript]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode", 20 | "editor.formatOnSave": true, 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.eslint": "explicit" 23 | } 24 | }, 25 | "[json]": { 26 | "editor.defaultFormatter": "esbenp.prettier-vscode", 27 | "editor.formatOnSave": true 28 | }, 29 | "[jsonc]": { 30 | "editor.defaultFormatter": "esbenp.prettier-vscode", 31 | "editor.formatOnSave": true 32 | }, 33 | "editor.formatOnSaveMode": "file", 34 | "typescript.tsdk": "node_modules/typescript/lib", 35 | "[javascript][typescript][typescriptreact]": { 36 | "editor.codeActionsOnSave": { 37 | "source.fixAll.eslint": "explicit" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.1.0] - 2024-03-21 4 | 5 | ### Added 6 | - Server/client boundary optimizations for improved performance 7 | - Comprehensive error handling system in space layouts 8 | - Enhanced session management with secure cookie policies 9 | - Improved invite modal with better UX and validation 10 | - Real-time SSE connection management with auto-reconnect 11 | 12 | ### Fixed 13 | - Proper HTML structure in error and layout components 14 | - Authentication redirect loops in middleware 15 | - Server-side import path resolution issues 16 | - Type safety improvements across components 17 | - SSE connection cleanup on unmount 18 | 19 | ### Changed 20 | - Reorganized layout structure for better maintainability 21 | - Optimized React provider hierarchy 22 | - Implemented proper error boundaries with logging 23 | - Enhanced gradient background handling 24 | - Improved state management with URL parameters 25 | 26 | ### Security 27 | - Added rate limiting for authentication routes 28 | - Implemented proper CORS policies 29 | - Enhanced session cookie security 30 | - Added request validation middleware 31 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/(frontend)/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/utilities" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | payload: 5 | image: node:18-alpine 6 | ports: 7 | - '3000:3000' 8 | volumes: 9 | - .:/home/node/app 10 | - node_modules:/home/node/app/node_modules 11 | working_dir: /home/node/app/ 12 | command: sh -c "yarn install && yarn dev" 13 | depends_on: 14 | - mongo 15 | env_file: 16 | - .env 17 | 18 | mongo: 19 | image: mongo:latest 20 | ports: 21 | - '27017:27017' 22 | command: 23 | - --storageEngine=wiredTiger 24 | volumes: 25 | - data:/data/db 26 | logging: 27 | driver: none 28 | 29 | volumes: 30 | data: 31 | node_modules: 32 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from 'path' 2 | import { fileURLToPath } from 'url' 3 | import { FlatCompat } from '@eslint/eslintrc' 4 | 5 | const __filename = fileURLToPath(import.meta.url) 6 | const __dirname = dirname(__filename) 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }) 11 | 12 | const eslintConfig = [ 13 | ...compat.extends('next/core-web-vitals', 'next/typescript'), 14 | { 15 | rules: { 16 | '@typescript-eslint/ban-ts-comment': 'warn', 17 | '@typescript-eslint/no-empty-object-type': 'warn', 18 | '@typescript-eslint/no-explicit-any': 'warn', 19 | '@typescript-eslint/no-unused-vars': [ 20 | 'warn', 21 | { 22 | vars: 'all', 23 | args: 'after-used', 24 | ignoreRestSiblings: false, 25 | argsIgnorePattern: '^_', 26 | varsIgnorePattern: '^_', 27 | destructuredArrayIgnorePattern: '^_', 28 | caughtErrorsIgnorePattern: '^(_|ignore)', 29 | }, 30 | ], 31 | }, 32 | }, 33 | ] 34 | 35 | export default eslintConfig 36 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /next-sitemap.config.cjs: -------------------------------------------------------------------------------- 1 | const SITE_URL = 2 | process.env.NEXT_PUBLIC_SERVER_URL || 3 | process.env.VERCEL_PROJECT_PRODUCTION_URL || 4 | 'https://example.com' 5 | 6 | /** @type {import('next-sitemap').IConfig} */ 7 | module.exports = { 8 | siteUrl: SITE_URL, 9 | generateRobotsTxt: true, 10 | exclude: ['/posts-sitemap.xml', '/pages-sitemap.xml', '/*', '/posts/*'], 11 | robotsTxtOptions: { 12 | policies: [ 13 | { 14 | userAgent: '*', 15 | disallow: '/admin/*', 16 | }, 17 | ], 18 | additionalSitemaps: [`${SITE_URL}/pages-sitemap.xml`, `${SITE_URL}/posts-sitemap.xml`], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {}, 4 | tailwindcss: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/KDCO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/KDCO.png -------------------------------------------------------------------------------- /public/default-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/default-space.png -------------------------------------------------------------------------------- /public/discordant-sockets.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/discordant-sockets.txt -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/arrow-left-on-rectangle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/user-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/1.png -------------------------------------------------------------------------------- /public/images/Technology-Watch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/Technology-Watch.jpg -------------------------------------------------------------------------------- /public/images/author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/author.png -------------------------------------------------------------------------------- /public/images/avatar/avatar_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_1.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_10.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_11.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_12.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_13.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_14.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_15.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_16.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_17.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_18.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_19.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_2.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_20.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_21.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_22.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_23.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_24.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_3.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_4.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_5.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_6.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_7.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_8.jpg -------------------------------------------------------------------------------- /public/images/avatar/avatar_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/avatar/avatar_9.jpg -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/logo-pink-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/logo-pink-white.png -------------------------------------------------------------------------------- /public/images/logo-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/logo-pink.png -------------------------------------------------------------------------------- /public/images/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/placeholder.jpg -------------------------------------------------------------------------------- /public/images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/profile.jpg -------------------------------------------------------------------------------- /public/images/seed/admin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/admin.jpg -------------------------------------------------------------------------------- /public/images/seed/author_1.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/author_1.svg -------------------------------------------------------------------------------- /public/images/seed/blog-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-1.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-2.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-3.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-4.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-5.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-6.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-7.jpg -------------------------------------------------------------------------------- /public/images/seed/blog-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blog-8.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-1.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-2.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-3.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-4.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-5.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-6.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-7.jpg -------------------------------------------------------------------------------- /public/images/seed/blogAuthor-8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/blogAuthor-8.jpeg -------------------------------------------------------------------------------- /public/images/seed/contentql-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/contentql-logo.png -------------------------------------------------------------------------------- /public/images/seed/demo-user.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/demo-user.webp -------------------------------------------------------------------------------- /public/images/seed/logo-pink-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/logo-pink-white.png -------------------------------------------------------------------------------- /public/images/seed/tag-Entrepreneurship.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/tag-Entrepreneurship.webp -------------------------------------------------------------------------------- /public/images/seed/tag-ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/tag-ai.png -------------------------------------------------------------------------------- /public/images/seed/tag-projectmanagement.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/images/seed/tag-projectmanagement.webp -------------------------------------------------------------------------------- /public/spaces-media/Screenshot 2024-12-30 084153 (Small).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/spaces-media/Screenshot 2024-12-30 084153 (Small).png -------------------------------------------------------------------------------- /public/website-template-OG.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/public/website-template-OG.webp -------------------------------------------------------------------------------- /redirects.js: -------------------------------------------------------------------------------- 1 | const redirects = async () => { 2 | const internetExplorerRedirect = { 3 | destination: '/ie-incompatible.html', 4 | has: [ 5 | { 6 | type: 'header', 7 | key: 'user-agent', 8 | value: '(.*Trident.*)', // all ie browsers 9 | }, 10 | ], 11 | permanent: false, 12 | source: '/:path((?!ie-incompatible.html$).*)', // all pages except the incompatibility page 13 | } 14 | 15 | const redirects = [internetExplorerRedirect] 16 | 17 | return redirects 18 | } 19 | 20 | export default redirects 21 | -------------------------------------------------------------------------------- /src/Footer/Component.tsx: -------------------------------------------------------------------------------- 1 | import { getCachedGlobal } from '@/utilities/getGlobals' 2 | import Link from 'next/link' 3 | import React from 'react' 4 | 5 | import type { Footer } from '@/payload-types' 6 | 7 | import { ThemeSelector } from '@/providers/Theme/ThemeSelector' 8 | import { CMSLink } from '@/components/Link' 9 | import { Logo } from '@/components/Logo/Logo' 10 | 11 | export async function Footer() { 12 | const footerData: Footer = await getCachedGlobal('footer', 1)() 13 | 14 | const navItems = footerData?.navItems || [] 15 | 16 | return ( 17 |
18 |
19 | 20 | 21 | 22 | 23 |
24 | 25 | 30 |
31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/Footer/RowLabel.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Header } from '@/payload-types' 3 | import { RowLabelProps, useRowLabel } from '@payloadcms/ui' 4 | 5 | export const RowLabel: React.FC = (props) => { 6 | const data = useRowLabel[number]>() 7 | 8 | const label = data?.data?.link?.label 9 | ? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ''}: ${data?.data?.link?.label}` 10 | : 'Row' 11 | 12 | return
{label}
13 | } 14 | -------------------------------------------------------------------------------- /src/Footer/config.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalConfig } from 'payload' 2 | 3 | import { link } from '@/fields/link' 4 | import { revalidateFooter } from './hooks/revalidateFooter' 5 | 6 | export const Footer: GlobalConfig = { 7 | slug: 'footer', 8 | access: { 9 | read: () => true, 10 | }, 11 | fields: [ 12 | { 13 | name: 'navItems', 14 | type: 'array', 15 | fields: [ 16 | link({ 17 | appearances: false, 18 | }), 19 | ], 20 | maxRows: 6, 21 | admin: { 22 | initCollapsed: true, 23 | components: { 24 | RowLabel: '@/Footer/RowLabel#RowLabel', 25 | }, 26 | }, 27 | }, 28 | ], 29 | hooks: { 30 | afterChange: [revalidateFooter], 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /src/Footer/hooks/revalidateFooter.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalAfterChangeHook } from 'payload' 2 | 3 | import { revalidateTag } from 'next/cache' 4 | 5 | export const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => { 6 | if (!context.disableRevalidate) { 7 | payload.logger.info(`Revalidating footer`) 8 | 9 | revalidateTag('global_footer') 10 | } 11 | 12 | return doc 13 | } 14 | -------------------------------------------------------------------------------- /src/Header/Component.tsx: -------------------------------------------------------------------------------- 1 | import { HeaderClient } from './Component.client' 2 | import { getCachedGlobal } from '@/utilities/getGlobals' 3 | import React from 'react' 4 | 5 | import type { Header } from '@/payload-types' 6 | 7 | export async function Header() { 8 | const headerData: Header = await getCachedGlobal('header', 1)() 9 | 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /src/Header/Nav/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import React from 'react' 4 | 5 | import type { Header as HeaderType } from '@/payload-types' 6 | 7 | import { CMSLink } from '@/components/Link' 8 | import Link from 'next/link' 9 | import { SearchIcon } from 'lucide-react' 10 | 11 | export const HeaderNav: React.FC<{ data: HeaderType }> = ({ data }) => { 12 | const navItems = data?.navItems || [] 13 | 14 | return ( 15 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/Header/RowLabel.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Header } from '@/payload-types' 3 | import { RowLabelProps, useRowLabel } from '@payloadcms/ui' 4 | 5 | export const RowLabel: React.FC = (props) => { 6 | const data = useRowLabel[number]>() 7 | 8 | const label = data?.data?.link?.label 9 | ? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ''}: ${data?.data?.link?.label}` 10 | : 'Row' 11 | 12 | return
{label}
13 | } 14 | -------------------------------------------------------------------------------- /src/Header/config.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalConfig } from 'payload' 2 | 3 | import { link } from '@/fields/link' 4 | import { revalidateHeader } from './hooks/revalidateHeader' 5 | 6 | export const Header: GlobalConfig = { 7 | slug: 'header', 8 | access: { 9 | read: () => true, 10 | }, 11 | fields: [ 12 | { 13 | name: 'navItems', 14 | type: 'array', 15 | fields: [ 16 | link({ 17 | appearances: false, 18 | }), 19 | ], 20 | maxRows: 6, 21 | admin: { 22 | initCollapsed: true, 23 | components: { 24 | RowLabel: '@/Header/RowLabel#RowLabel', 25 | }, 26 | }, 27 | }, 28 | ], 29 | hooks: { 30 | afterChange: [revalidateHeader], 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /src/Header/hooks/revalidateHeader.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalAfterChangeHook } from 'payload' 2 | 3 | import { revalidateTag } from 'next/cache' 4 | 5 | export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => { 6 | if (!context.disableRevalidate) { 7 | payload.logger.info(`Revalidating header`) 8 | 9 | revalidateTag('global_header') 10 | } 11 | 12 | return doc 13 | } 14 | -------------------------------------------------------------------------------- /src/access/anyone.ts: -------------------------------------------------------------------------------- 1 | import type { Access } from 'payload' 2 | 3 | export const anyone: Access = () => true 4 | -------------------------------------------------------------------------------- /src/access/authenticated.ts: -------------------------------------------------------------------------------- 1 | import type { AccessArgs } from 'payload' 2 | 3 | import type { User } from '@/payload-types' 4 | 5 | type isAuthenticated = (args: AccessArgs) => boolean 6 | 7 | export const authenticated: isAuthenticated = ({ req: { user } }) => { 8 | return Boolean(user) 9 | } 10 | -------------------------------------------------------------------------------- /src/access/authenticatedOrPublished.ts: -------------------------------------------------------------------------------- 1 | import { Access, Where } from 'payload' 2 | 3 | export const authenticatedOrPublished: Access = ({ req: { user } }): Where | boolean => { 4 | if (user) return true 5 | 6 | return { 7 | or: [ 8 | { 9 | _status: { 10 | equals: 'published', 11 | }, 12 | } as Where, 13 | { 14 | publishedAt: { 15 | exists: true, 16 | not: { 17 | equals: null, 18 | }, 19 | }, 20 | } as Where, 21 | ], 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/(frontend)/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export default function AuthLayout({ children }: { children: ReactNode }) { 4 | return ( 5 |
6 | {children} 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/app/(frontend)/(auth)/login/actions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { getMeUser } from '@/utilities/getMeUser' 4 | 5 | export async function getLoginPageData() { 6 | const { user } = await getMeUser() 7 | return { user } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/(frontend)/(auth)/logout/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | export const metadata: Metadata = { 4 | title: 'Logout', 5 | description: 'Logging out of your account', 6 | } 7 | 8 | export default function LogoutLayout({ children }: { children: React.ReactNode }) { 9 | return children 10 | } 11 | -------------------------------------------------------------------------------- /src/app/(frontend)/(auth)/recover-password/components/client-wrapper.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/src/app/(frontend)/(auth)/recover-password/components/client-wrapper.tsx -------------------------------------------------------------------------------- /src/app/(frontend)/(auth)/reset-password/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | import { mergeOpenGraph } from '@/utilities/mergeOpenGraph' 4 | import React, { Suspense } from 'react' 5 | 6 | import { ResetPasswordForm } from './ResetPasswordForm' 7 | 8 | export const dynamic = 'force-dynamic' 9 | 10 | export default function ResetPassword() { 11 | return ( 12 |
13 |

Reset Password

14 |

Please enter a new password below.

15 | }> 16 | 17 | 18 |
19 | ) 20 | } 21 | 22 | export const metadata: Metadata = { 23 | description: 'Enter a new password.', 24 | openGraph: mergeOpenGraph({ 25 | title: 'Reset Password', 26 | url: '/reset-password', 27 | }), 28 | title: 'Reset Password', 29 | } 30 | -------------------------------------------------------------------------------- /src/app/(frontend)/(spaces)/spaces/[spaceId]/settings/components/space-settings-content.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Space } from '@/payload-types' 4 | import { useModal } from '@/spaces/hooks/use-modal-store' 5 | import { transformPayloadSpace } from '@/spaces/utilities/transforms/space' 6 | import { useEffect } from 'react' 7 | 8 | interface SpaceSettingsContentProps { 9 | space: Space 10 | } 11 | 12 | export function SpaceSettingsContent({ space }: SpaceSettingsContentProps) { 13 | const { onOpen } = useModal() 14 | 15 | useEffect(() => { 16 | onOpen('editSpace', { space }) 17 | }, [space, onOpen]) 18 | 19 | return ( 20 |
21 |
22 |

Loading space settings...

23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/app/(frontend)/(spaces)/spaces/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import React from 'react' 3 | 4 | import { Button } from '@/components/ui/button' 5 | 6 | export default function NotFound() { 7 | return ( 8 |
9 |
10 |

404

11 |

This page could not be found.

12 |
13 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(frontend)/(spaces)/spaces/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export interface LayoutProps { 4 | children: ReactNode 5 | params: Promise<{ spaceId: string }> | { spaceId: string } 6 | } 7 | 8 | export interface PageProps { 9 | params: 10 | | Promise<{ 11 | spaceId: string 12 | memberId?: string 13 | }> 14 | | { 15 | spaceId: string 16 | memberId?: string 17 | } 18 | searchParams?: Promise<{ video?: boolean }> | { video?: boolean } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/[slug]/page.client.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useHeaderTheme } from '@/providers/HeaderTheme' 3 | import React, { useEffect } from 'react' 4 | 5 | const PageClient: React.FC = () => { 6 | /* Force the header to be dark mode while we have an image behind it */ 7 | const { setHeaderTheme } = useHeaderTheme() 8 | 9 | useEffect(() => { 10 | setHeaderTheme('light') 11 | }, [setHeaderTheme]) 12 | return 13 | } 14 | 15 | export default PageClient 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/next/exit-preview/GET.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | 3 | export async function GET(): Promise { 4 | const draft = await draftMode() 5 | draft.disable() 6 | return new Response('Draft mode is disabled') 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/next/exit-preview/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | 3 | export async function GET(): Promise { 4 | const draft = await draftMode() 5 | draft.disable() 6 | return new Response('Draft mode is disabled') 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/next/seed/route.ts: -------------------------------------------------------------------------------- 1 | import { createLocalReq, getPayload } from 'payload' 2 | import { seed } from '@/endpoints/seed' 3 | import config from '@payload-config' 4 | import { headers } from 'next/headers' 5 | 6 | export const maxDuration = 60 // This function can run for a maximum of 60 seconds 7 | 8 | export async function POST( 9 | req: Request & { 10 | cookies: { 11 | get: (name: string) => { 12 | value: string 13 | } 14 | } 15 | }, 16 | ): Promise { 17 | const payload = await getPayload({ config }) 18 | const requestHeaders = await headers() 19 | 20 | // Authenticate by passing request headers 21 | const { user } = await payload.auth({ headers: requestHeaders }) 22 | 23 | if (!user) { 24 | return new Response('Action forbidden.', { status: 403 }) 25 | } 26 | 27 | try { 28 | // Create a Payload request object to pass to the Local API for transactions 29 | // At this point you should pass in a user, locale, and any other context you need for the Local API 30 | const payloadReq = await createLocalReq({ user }, payload) 31 | 32 | await seed({ payload, req: payloadReq }) 33 | 34 | return Response.json({ success: true }) 35 | } catch { 36 | return new Response('Error seeding data.', { status: 500 }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import React from 'react' 3 | 4 | import { Button } from '@/components/ui/button' 5 | 6 | export default function NotFound() { 7 | return ( 8 |
9 |
10 |

404

11 |

This page could not be found.

12 |
13 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/page.tsx: -------------------------------------------------------------------------------- 1 | import PageTemplate, { generateMetadata } from './[slug]/page' 2 | 3 | export default PageTemplate 4 | 5 | export { generateMetadata } 6 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/posts/[slug]/page.client.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useHeaderTheme } from '@/providers/HeaderTheme' 3 | import React, { useEffect } from 'react' 4 | 5 | const PageClient: React.FC = () => { 6 | /* Force the header to be dark mode while we have an image behind it */ 7 | const { setHeaderTheme } = useHeaderTheme() 8 | 9 | useEffect(() => { 10 | setHeaderTheme('dark') 11 | }, [setHeaderTheme]) 12 | return 13 | } 14 | 15 | export default PageClient 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/posts/page.client.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useHeaderTheme } from '@/providers/HeaderTheme' 3 | import React, { useEffect } from 'react' 4 | 5 | const PageClient: React.FC = () => { 6 | /* Force the header to be dark mode while we have an image behind it */ 7 | const { setHeaderTheme } = useHeaderTheme() 8 | 9 | useEffect(() => { 10 | setHeaderTheme('light') 11 | }, [setHeaderTheme]) 12 | return 13 | } 14 | 15 | export default PageClient 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/posts/page/[pageNumber]/page.client.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useHeaderTheme } from '@/providers/HeaderTheme' 3 | import React, { useEffect } from 'react' 4 | 5 | const PageClient: React.FC = () => { 6 | /* Force the header to be dark mode while we have an image behind it */ 7 | const { setHeaderTheme } = useHeaderTheme() 8 | 9 | useEffect(() => { 10 | setHeaderTheme('light') 11 | }, [setHeaderTheme]) 12 | return 13 | } 14 | 15 | export default PageClient 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/(website)/search/page.client.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useHeaderTheme } from '@/providers/HeaderTheme' 3 | import React, { useEffect } from 'react' 4 | 5 | const PageClient: React.FC = () => { 6 | /* Force the header to be dark mode while we have an image behind it */ 7 | const { setHeaderTheme } = useHeaderTheme() 8 | 9 | useEffect(() => { 10 | setHeaderTheme('light') 11 | }, [setHeaderTheme]) 12 | return 13 | } 14 | 15 | export default PageClient 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/500.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Link from 'next/link' 4 | 5 | export default function Error500() { 6 | return ( 7 |
8 |

500 - Server Error

9 |

Something went wrong on our end.

10 | 11 | Return Home 12 | 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/500/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button } from '@/components/ui/button' 4 | 5 | export default function Error500() { 6 | return ( 7 |
8 |

Server Error

9 |

Something went wrong on our servers.

10 | 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/(frontend)/api/auth/session/route.ts: -------------------------------------------------------------------------------- 1 | import { getPayloadClient } from '@/spaces/utilities/payload/getPayloadClient' 2 | import { cookies } from 'next/headers' 3 | import { NextResponse } from 'next/server' 4 | 5 | export async function GET() { 6 | try { 7 | const payload = await getPayloadClient() 8 | const cookieStore = await cookies() 9 | const token = cookieStore.get('payload-token') 10 | 11 | if (!token?.value) { 12 | return new NextResponse('Unauthorized', { status: 401 }) 13 | } 14 | 15 | const { user } = await payload.auth({ 16 | headers: new Headers({ 17 | cookie: `payload-token=${token.value}`, 18 | }), 19 | }) 20 | 21 | if (!user) { 22 | return new NextResponse('Invalid token', { status: 401 }) 23 | } 24 | 25 | return NextResponse.json(user) 26 | } catch (error) { 27 | console.log('[AUTH_SESSION]', error) 28 | return new NextResponse('Internal error', { status: 500 }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/(frontend)/api/messages/sse/health/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | import { headers } from 'next/headers' 3 | 4 | export async function GET() { 5 | try { 6 | const headersList = new Headers(await headers()) 7 | headersList.set('Content-Type', 'text/event-stream') 8 | headersList.set('Connection', 'keep-alive') 9 | headersList.set('Cache-Control', 'no-cache, no-transform') 10 | headersList.set('X-Accel-Buffering', 'no') 11 | 12 | // Return a single health check message 13 | return new NextResponse( 14 | `data: ${JSON.stringify({ type: 'health', status: 'connected' })}\n\n`, 15 | { 16 | headers: headersList, 17 | }, 18 | ) 19 | } catch (error) { 20 | console.error('[MESSAGES_SSE_HEALTH] Error:', error) 21 | return new NextResponse('Internal Server Error', { status: 500 }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/(frontend)/api/messages/update/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | import { updateMessage } from '@/spaces/actions/messages' 3 | 4 | export async function POST(request: Request) { 5 | const data = await request.json() 6 | try { 7 | const { messageId, content, spaceId, channelId } = data 8 | // Call your server action method from messages.ts 9 | const result = await updateMessage({ 10 | messageId, 11 | content, 12 | spaceId, 13 | channelId, 14 | }) 15 | return NextResponse.json(result) 16 | } catch (error) { 17 | console.error('[UPDATE_MESSAGE_ERROR]', error) 18 | return NextResponse.json({ success: false, error: 'Failed to update message' }, { status: 500 }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/(frontend)/api/users/me/route.ts: -------------------------------------------------------------------------------- 1 | import { getPayloadClient } from '@/spaces/utilities/payload/getPayloadClient' 2 | import { cookies } from 'next/headers' 3 | import { NextResponse } from 'next/server' 4 | 5 | export async function GET() { 6 | try { 7 | const cookieStore = await cookies() 8 | const token = cookieStore.get('payload-token')?.value 9 | 10 | if (!token) { 11 | return NextResponse.json({ user: null }, { status: 401 }) 12 | } 13 | 14 | try { 15 | const payload = await getPayloadClient() 16 | const { user } = await payload.auth({ 17 | headers: new Headers({ 18 | Authorization: `JWT ${token}`, 19 | }), 20 | }) 21 | 22 | if (!user) { 23 | return NextResponse.json({ user: null }, { status: 401 }) 24 | } 25 | 26 | return NextResponse.json({ user }) 27 | } catch (authError) { 28 | console.error('[USERS_ME] Auth error:', authError) 29 | // Don't expose internal errors, return 401 for auth failures 30 | return NextResponse.json({ user: null }, { status: 401 }) 31 | } 32 | } catch (error) { 33 | console.error('[USERS_ME]', error) 34 | // Return 401 instead of 500 for auth-related errors 35 | return NextResponse.json({ user: null }, { status: 401 }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/(frontend)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button } from '@/components/ui/button' 4 | 5 | export default function Error({ 6 | error, 7 | reset, 8 | }: { 9 | error: Error & { digest?: string } 10 | reset: () => void 11 | }) { 12 | return ( 13 |
14 |

Something went wrong!

15 |

{error.message}

16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(frontend)/global-error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button } from '@/components/ui/button' 4 | 5 | export default function GlobalError({ 6 | error, 7 | reset, 8 | }: { 9 | error: Error & { digest?: string } 10 | reset: () => void 11 | }) { 12 | return ( 13 |
14 |

Something went wrong!

15 |

{error.message}

16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(frontend)/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import '@/app/(frontend)/globals.css' 3 | import { GeistMono } from 'geist/font/mono' 4 | import { GeistSans } from 'geist/font/sans' 5 | import { cn } from '@/utilities/ui' 6 | import { InitTheme } from '@/providers/Theme/InitTheme' 7 | import { Providers } from '@/providers' 8 | import { Analytics } from '@vercel/analytics/react' 9 | import { headers } from 'next/headers' 10 | 11 | export const dynamic = 'force-dynamic' 12 | export const dynamicParams = true 13 | 14 | export default function RootLayout({ children }: { children: React.ReactNode }) { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/app/(frontend)/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | export default function NotFound() { 4 | return ( 5 |
6 |

404 - Page Not Found

7 |

The page you are looking for does not exist.

8 | 12 | Return Home 13 | 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(payload)/admin/[[...segments]]/not-found.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import type { Metadata } from 'next' 4 | 5 | import config from '@payload-config' 6 | import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views' 7 | import { importMap } from '../importMap' 8 | 9 | type Args = { 10 | params: Promise<{ 11 | segments: string[] 12 | }> 13 | searchParams: Promise<{ 14 | [key: string]: string | string[] 15 | }> 16 | } 17 | 18 | export const generateMetadata = ({ params, searchParams }: Args): Promise => 19 | generatePageMetadata({ config, params, searchParams }) 20 | 21 | const NotFound = ({ params, searchParams }: Args) => 22 | NotFoundPage({ config, params, searchParams, importMap }) 23 | 24 | export default NotFound 25 | -------------------------------------------------------------------------------- /src/app/(payload)/admin/[[...segments]]/page.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import type { Metadata } from 'next' 4 | 5 | import config from '@payload-config' 6 | import { RootPage, generatePageMetadata } from '@payloadcms/next/views' 7 | import { importMap } from '../importMap' 8 | 9 | type Args = { 10 | params: Promise<{ 11 | segments: string[] 12 | }> 13 | searchParams: Promise<{ 14 | [key: string]: string | string[] 15 | }> 16 | } 17 | 18 | export const generateMetadata = ({ params, searchParams }: Args): Promise => 19 | generatePageMetadata({ config, params, searchParams }) 20 | 21 | const Page = ({ params, searchParams }: Args) => 22 | RootPage({ config, params, searchParams, importMap }) 23 | 24 | export default Page 25 | -------------------------------------------------------------------------------- /src/app/(payload)/admin/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function AdminLayout({ children }: { children: React.ReactNode }) { 2 | return children 3 | } 4 | -------------------------------------------------------------------------------- /src/app/(payload)/api/[...slug]/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import '@payloadcms/next/css' 5 | import { 6 | REST_DELETE, 7 | REST_GET, 8 | REST_OPTIONS, 9 | REST_PATCH, 10 | REST_POST, 11 | REST_PUT, 12 | } from '@payloadcms/next/routes' 13 | 14 | export const GET = REST_GET(config) 15 | export const POST = REST_POST(config) 16 | export const DELETE = REST_DELETE(config) 17 | export const PATCH = REST_PATCH(config) 18 | 19 | export const PUT = REST_PUT(config) 20 | export const OPTIONS = REST_OPTIONS(config) 21 | -------------------------------------------------------------------------------- /src/app/(payload)/api/graphql-playground/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import '@payloadcms/next/css' 5 | import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' 6 | 7 | export const GET = GRAPHQL_PLAYGROUND_GET(config) 8 | -------------------------------------------------------------------------------- /src/app/(payload)/api/graphql/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes' 5 | 6 | export const POST = GRAPHQL_POST(config) 7 | 8 | export const OPTIONS = REST_OPTIONS(config) 9 | -------------------------------------------------------------------------------- /src/app/(payload)/custom.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kendevco/spaces/17eb97bf51bf074f20b5aa63e7d9bf86698debef/src/app/(payload)/custom.scss -------------------------------------------------------------------------------- /src/app/(payload)/layout.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import '@payloadcms/next/css' 5 | import type { ServerFunctionClient } from 'payload' 6 | import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts' 7 | import React from 'react' 8 | 9 | import { importMap } from './admin/importMap.js' 10 | import './custom.scss' 11 | 12 | type Args = { 13 | children: React.ReactNode 14 | } 15 | 16 | const serverFunction: ServerFunctionClient = async function (args) { 17 | 'use server' 18 | return handleServerFunctions({ 19 | ...args, 20 | config, 21 | importMap, 22 | }) 23 | } 24 | 25 | const Layout = ({ children }: Args) => ( 26 | 27 | {children} 28 | 29 | ) 30 | 31 | export default Layout 32 | -------------------------------------------------------------------------------- /src/blocks/Banner/Component.tsx: -------------------------------------------------------------------------------- 1 | import type { BannerBlock as BannerBlockProps } from '@/payload-types' 2 | 3 | import { cn } from '@/utilities/ui' 4 | import React from 'react' 5 | import RichText from '@/components/RichText' 6 | 7 | type Props = { 8 | className?: string 9 | } & BannerBlockProps 10 | 11 | export const BannerBlock: React.FC = ({ className, content, style }) => { 12 | return ( 13 |
14 |
22 | 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/blocks/Banner/config.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from 'payload' 2 | 3 | import { 4 | FixedToolbarFeature, 5 | InlineToolbarFeature, 6 | lexicalEditor, 7 | } from '@payloadcms/richtext-lexical' 8 | 9 | export const Banner: Block = { 10 | slug: 'banner', 11 | fields: [ 12 | { 13 | name: 'style', 14 | type: 'select', 15 | defaultValue: 'info', 16 | options: [ 17 | { label: 'Info', value: 'info' }, 18 | { label: 'Warning', value: 'warning' }, 19 | { label: 'Error', value: 'error' }, 20 | { label: 'Success', value: 'success' }, 21 | ], 22 | required: true, 23 | }, 24 | { 25 | name: 'content', 26 | type: 'richText', 27 | editor: lexicalEditor({ 28 | features: ({ rootFeatures }) => { 29 | return [...rootFeatures, FixedToolbarFeature(), InlineToolbarFeature()] 30 | }, 31 | }), 32 | label: false, 33 | required: true, 34 | }, 35 | ], 36 | interfaceName: 'BannerBlock', 37 | } 38 | -------------------------------------------------------------------------------- /src/blocks/CallToAction/Component.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import type { CallToActionBlock as CTABlockProps } from '@/payload-types' 4 | 5 | import RichText from '@/components/RichText' 6 | import { CMSLink } from '@/components/Link' 7 | 8 | export const CallToActionBlock: React.FC = ({ links, richText }) => { 9 | return ( 10 |
11 |
12 |
13 | {richText && } 14 |
15 |
16 | {(links || []).map(({ link }, i) => { 17 | return 18 | })} 19 |
20 |
21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/blocks/CallToAction/config.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from 'payload' 2 | 3 | import { 4 | FixedToolbarFeature, 5 | HeadingFeature, 6 | InlineToolbarFeature, 7 | lexicalEditor, 8 | } from '@payloadcms/richtext-lexical' 9 | 10 | import { linkGroup } from '../../fields/linkGroup' 11 | 12 | export const CallToAction: Block = { 13 | slug: 'cta', 14 | interfaceName: 'CallToActionBlock', 15 | fields: [ 16 | { 17 | name: 'richText', 18 | type: 'richText', 19 | editor: lexicalEditor({ 20 | features: ({ rootFeatures }) => { 21 | return [ 22 | ...rootFeatures, 23 | HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }), 24 | FixedToolbarFeature(), 25 | InlineToolbarFeature(), 26 | ] 27 | }, 28 | }), 29 | label: false, 30 | }, 31 | linkGroup({ 32 | appearances: ['default', 'outline'], 33 | overrides: { 34 | maxRows: 2, 35 | }, 36 | }), 37 | ], 38 | labels: { 39 | plural: 'Calls to Action', 40 | singular: 'Call to Action', 41 | }, 42 | } 43 | -------------------------------------------------------------------------------- /src/blocks/Code/Component.client.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Highlight, themes } from 'prism-react-renderer' 3 | import React from 'react' 4 | import { CopyButton } from './CopyButton' 5 | 6 | type Props = { 7 | code: string 8 | language?: string 9 | } 10 | 11 | export const Code: React.FC = ({ code, language = '' }) => { 12 | if (!code) return null 13 | 14 | return ( 15 | 16 | {({ getLineProps, getTokenProps, tokens }) => ( 17 |
18 |           {tokens.map((line, i) => (
19 |             
20 | {i + 1} 21 | 22 | {line.map((token, key) => ( 23 | 24 | ))} 25 | 26 |
27 | ))} 28 | 29 |
30 | )} 31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/blocks/Code/Component.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Code } from './Component.client' 4 | 5 | export type CodeBlockProps = { 6 | code: string 7 | language?: string 8 | blockType: 'code' 9 | } 10 | 11 | type Props = CodeBlockProps & { 12 | className?: string 13 | } 14 | 15 | export const CodeBlock: React.FC = ({ className, code, language }) => { 16 | return ( 17 |
18 | 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/blocks/Code/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Button } from '@/components/ui/button' 3 | import { CopyIcon } from '@payloadcms/ui/icons/Copy' 4 | import { useState } from 'react' 5 | 6 | export function CopyButton({ code }: { code: string }) { 7 | const [text, setText] = useState('Copy') 8 | 9 | function updateCopyStatus() { 10 | if (text === 'Copy') { 11 | setText(() => 'Copied!') 12 | setTimeout(() => { 13 | setText(() => 'Copy') 14 | }, 1000) 15 | } 16 | } 17 | 18 | return ( 19 |
20 | 31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/blocks/Code/config.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from 'payload' 2 | 3 | export const Code: Block = { 4 | slug: 'code', 5 | interfaceName: 'CodeBlock', 6 | fields: [ 7 | { 8 | name: 'language', 9 | type: 'select', 10 | defaultValue: 'typescript', 11 | options: [ 12 | { 13 | label: 'Typescript', 14 | value: 'typescript', 15 | }, 16 | { 17 | label: 'Javascript', 18 | value: 'javascript', 19 | }, 20 | { 21 | label: 'CSS', 22 | value: 'css', 23 | }, 24 | ], 25 | }, 26 | { 27 | name: 'code', 28 | type: 'code', 29 | label: false, 30 | required: true, 31 | }, 32 | ], 33 | } 34 | -------------------------------------------------------------------------------- /src/blocks/Content/Component.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utilities/ui' 2 | import React from 'react' 3 | import RichText from '@/components/RichText' 4 | 5 | import type { ContentBlock as ContentBlockProps } from '@/payload-types' 6 | 7 | import { CMSLink } from '../../components/Link' 8 | 9 | export const ContentBlock: React.FC = (props) => { 10 | const { columns } = props 11 | 12 | const colsSpanClasses = { 13 | full: '12', 14 | half: '6', 15 | oneThird: '4', 16 | twoThirds: '8', 17 | } 18 | 19 | return ( 20 |
21 |
22 | {columns && 23 | columns.length > 0 && 24 | columns.map((col, index) => { 25 | const { enableLink, link, richText, size } = col 26 | 27 | return ( 28 |
34 | {richText && } 35 | 36 | {enableLink && } 37 |
38 | ) 39 | })} 40 |
41 |
42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/blocks/Form/Email/index.tsx: -------------------------------------------------------------------------------- 1 | import type { EmailField } from '@payloadcms/plugin-form-builder/types' 2 | import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form' 3 | 4 | import { Input } from '@/components/ui/input' 5 | import { Label } from '@/components/ui/label' 6 | import React from 'react' 7 | 8 | import { Error } from '../Error' 9 | import { Width } from '../Width' 10 | 11 | export const Email: React.FC< 12 | EmailField & { 13 | errors: Partial< 14 | FieldErrorsImpl<{ 15 | [x: string]: any 16 | }> 17 | > 18 | register: UseFormRegister 19 | } 20 | > = ({ name, defaultValue, errors, label, register, required: requiredFromProps, width }) => { 21 | return ( 22 | 23 | 24 | 30 | 31 | {requiredFromProps && errors[name] && } 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/blocks/Form/Error/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export const Error: React.FC = () => { 4 | return
This field is required
5 | } 6 | -------------------------------------------------------------------------------- /src/blocks/Form/Message/index.tsx: -------------------------------------------------------------------------------- 1 | import RichText from '@/components/RichText' 2 | import React from 'react' 3 | 4 | import { Width } from '../Width' 5 | import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical' 6 | 7 | export const Message: React.FC<{ message: SerializedEditorState }> = ({ message }) => { 8 | return ( 9 | 10 | {message && } 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/blocks/Form/Number/index.tsx: -------------------------------------------------------------------------------- 1 | import type { TextField } from '@payloadcms/plugin-form-builder/types' 2 | import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form' 3 | 4 | import { Input } from '@/components/ui/input' 5 | import { Label } from '@/components/ui/label' 6 | import React from 'react' 7 | 8 | import { Error } from '../Error' 9 | import { Width } from '../Width' 10 | export const Number: React.FC< 11 | TextField & { 12 | errors: Partial< 13 | FieldErrorsImpl<{ 14 | [x: string]: any 15 | }> 16 | > 17 | register: UseFormRegister 18 | } 19 | > = ({ name, defaultValue, errors, label, register, required: requiredFromProps, width }) => { 20 | return ( 21 | 22 | 23 | 29 | {requiredFromProps && errors[name] && } 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/blocks/Form/Text/index.tsx: -------------------------------------------------------------------------------- 1 | import type { TextField } from '@payloadcms/plugin-form-builder/types' 2 | import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form' 3 | 4 | import { Input } from '@/components/ui/input' 5 | import { Label } from '@/components/ui/label' 6 | import React from 'react' 7 | 8 | import { Error } from '../Error' 9 | import { Width } from '../Width' 10 | 11 | export const Text: React.FC< 12 | TextField & { 13 | errors: Partial< 14 | FieldErrorsImpl<{ 15 | [x: string]: any 16 | }> 17 | > 18 | register: UseFormRegister 19 | } 20 | > = ({ name, defaultValue, errors, label, register, required: requiredFromProps, width }) => { 21 | return ( 22 | 23 | 24 | 30 | {requiredFromProps && errors[name] && } 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/blocks/Form/Textarea/index.tsx: -------------------------------------------------------------------------------- 1 | import type { TextField } from '@payloadcms/plugin-form-builder/types' 2 | import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form' 3 | 4 | import { Label } from '@/components/ui/label' 5 | import { Textarea as TextAreaComponent } from '@/components/ui/textarea' 6 | import React from 'react' 7 | 8 | import { Error } from '../Error' 9 | import { Width } from '../Width' 10 | 11 | export const Textarea: React.FC< 12 | TextField & { 13 | errors: Partial< 14 | FieldErrorsImpl<{ 15 | [x: string]: any 16 | }> 17 | > 18 | register: UseFormRegister 19 | rows?: number 20 | } 21 | > = ({ 22 | name, 23 | defaultValue, 24 | errors, 25 | label, 26 | register, 27 | required: requiredFromProps, 28 | rows = 3, 29 | width, 30 | }) => { 31 | return ( 32 | 33 | 34 | 35 | 41 | 42 | {requiredFromProps && errors[name] && } 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/blocks/Form/Width/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export const Width: React.FC<{ 4 | children: React.ReactNode 5 | className?: string 6 | width?: number | string 7 | }> = ({ children, className, width }) => { 8 | return ( 9 |
10 | {children} 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/blocks/Form/buildInitialFormState.tsx: -------------------------------------------------------------------------------- 1 | import type { FormFieldBlock } from '@payloadcms/plugin-form-builder/types' 2 | 3 | export const buildInitialFormState = ( 4 | fields: FormFieldBlock[] | undefined, 5 | ): Record | undefined => { 6 | if (!fields) return undefined 7 | return fields.reduce>((initialSchema, field) => { 8 | if (field.blockType === 'checkbox') { 9 | return { 10 | ...initialSchema, 11 | [field.name]: field.defaultValue, 12 | } 13 | } 14 | if (field.blockType === 'country') { 15 | return { 16 | ...initialSchema, 17 | [field.name]: '', 18 | } 19 | } 20 | if (field.blockType === 'email') { 21 | return { 22 | ...initialSchema, 23 | [field.name]: '', 24 | } 25 | } 26 | if (field.blockType === 'text') { 27 | return { 28 | ...initialSchema, 29 | [field.name]: '', 30 | } 31 | } 32 | if (field.blockType === 'select') { 33 | return { 34 | ...initialSchema, 35 | [field.name]: '', 36 | } 37 | } 38 | if (field.blockType === 'state') { 39 | return { 40 | ...initialSchema, 41 | [field.name]: '', 42 | } 43 | } 44 | return initialSchema 45 | }, {}) 46 | } 47 | -------------------------------------------------------------------------------- /src/blocks/Form/config.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from 'payload' 2 | 3 | import { 4 | FixedToolbarFeature, 5 | HeadingFeature, 6 | InlineToolbarFeature, 7 | lexicalEditor, 8 | } from '@payloadcms/richtext-lexical' 9 | 10 | export const FormBlock: Block = { 11 | slug: 'formBlock', 12 | interfaceName: 'FormBlock', 13 | fields: [ 14 | { 15 | name: 'form', 16 | type: 'relationship', 17 | relationTo: 'forms', 18 | required: true, 19 | }, 20 | { 21 | name: 'enableIntro', 22 | type: 'checkbox', 23 | label: 'Enable Intro Content', 24 | }, 25 | { 26 | name: 'introContent', 27 | type: 'richText', 28 | admin: { 29 | condition: (_, { enableIntro }) => Boolean(enableIntro), 30 | }, 31 | editor: lexicalEditor({ 32 | features: ({ rootFeatures }) => { 33 | return [ 34 | ...rootFeatures, 35 | HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }), 36 | FixedToolbarFeature(), 37 | InlineToolbarFeature(), 38 | ] 39 | }, 40 | }), 41 | label: 'Intro Content', 42 | }, 43 | ], 44 | graphQL: { 45 | singularName: 'FormBlock', 46 | }, 47 | labels: { 48 | plural: 'Form Blocks', 49 | singular: 'Form Block', 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /src/blocks/Form/fields.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox } from './Checkbox' 2 | import { Country } from './Country' 3 | import { Email } from './Email' 4 | import { Message } from './Message' 5 | import { Number } from './Number' 6 | import { Select } from './Select' 7 | import { State } from './State' 8 | import { Text } from './Text' 9 | import { Textarea } from './Textarea' 10 | 11 | export const fields = { 12 | checkbox: Checkbox, 13 | country: Country, 14 | email: Email, 15 | message: Message, 16 | number: Number, 17 | select: Select, 18 | state: State, 19 | text: Text, 20 | textarea: Textarea, 21 | } 22 | -------------------------------------------------------------------------------- /src/blocks/MediaBlock/config.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from 'payload' 2 | 3 | export const MediaBlock: Block = { 4 | slug: 'mediaBlock', 5 | interfaceName: 'MediaBlock', 6 | fields: [ 7 | { 8 | name: 'media', 9 | type: 'upload', 10 | relationTo: 'media', 11 | required: true, 12 | }, 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /src/blocks/RelatedPosts/Component.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React from 'react' 3 | import RichText from '@/components/RichText' 4 | 5 | import type { Post } from '@/payload-types' 6 | 7 | import { Card } from '../../components/Card' 8 | 9 | export type RelatedPostsProps = { 10 | className?: string 11 | docs?: Post[] 12 | introContent?: any 13 | } 14 | 15 | export const RelatedPosts: React.FC = (props) => { 16 | const { className, docs, introContent } = props 17 | 18 | return ( 19 |
20 | {introContent && } 21 | 22 |
23 | {docs?.map((doc, index) => { 24 | if (typeof doc === 'string') return null 25 | 26 | return 27 | })} 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/collections/Categories.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionConfig } from 'payload' 2 | 3 | import { anyone } from '../access/anyone' 4 | import { authenticated } from '../access/authenticated' 5 | import { slugField } from '@/fields/slug' 6 | 7 | export const Categories: CollectionConfig = { 8 | slug: 'categories', 9 | access: { 10 | create: authenticated, 11 | delete: authenticated, 12 | read: anyone, 13 | update: authenticated, 14 | }, 15 | admin: { 16 | useAsTitle: 'title', 17 | }, 18 | fields: [ 19 | { 20 | name: 'title', 21 | type: 'text', 22 | required: true, 23 | }, 24 | ...slugField(), 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /src/collections/Posts/hooks/populateAuthors.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionAfterReadHook } from 'payload' 2 | import { User } from '@/payload-types' 3 | 4 | // The `user` collection has access control locked so that users are not publicly accessible 5 | // This means that we need to populate the authors manually here to protect user privacy 6 | // GraphQL will not return mutated user data that differs from the underlying schema 7 | // So we use an alternative `populatedAuthors` field to populate the user data, hidden from the admin UI 8 | export const populateAuthors: CollectionAfterReadHook = async ({ doc, req, req: { payload } }) => { 9 | if (doc?.authors) { 10 | const authorDocs: User[] = [] 11 | 12 | for (const author of doc.authors) { 13 | const authorDoc = await payload.findByID({ 14 | id: typeof author === 'object' ? author?.id : author, 15 | collection: 'users', 16 | depth: 0, 17 | req, 18 | }) 19 | 20 | if (authorDoc) { 21 | authorDocs.push(authorDoc) 22 | } 23 | } 24 | 25 | doc.populatedAuthors = authorDocs.map((authorDoc) => ({ 26 | id: authorDoc.id, 27 | name: authorDoc.name, 28 | })) 29 | } 30 | 31 | return doc 32 | } 33 | -------------------------------------------------------------------------------- /src/collections/Users/schema.ts: -------------------------------------------------------------------------------- 1 | import { CollectionConfig } from 'payload' 2 | import { syncProfileOnLogin } from './hooks/syncProfile' 3 | 4 | const Users: CollectionConfig = { 5 | slug: 'users', 6 | auth: true, 7 | admin: { 8 | useAsTitle: 'name', 9 | }, 10 | fields: [ 11 | { 12 | name: 'name', 13 | type: 'text', 14 | required: true, 15 | }, 16 | { 17 | name: 'profile', 18 | type: 'relationship', 19 | relationTo: 'profiles', 20 | hasMany: false, 21 | }, 22 | ], 23 | } 24 | 25 | export default Users 26 | -------------------------------------------------------------------------------- /src/collections/Users/types.ts: -------------------------------------------------------------------------------- 1 | import { User as PayloadUser } from '@/payload-types' 2 | import { MemberRole } from '@/spaces/types' 3 | 4 | export interface User extends PayloadUser { 5 | role: MemberRole 6 | authorized: boolean 7 | } 8 | 9 | // Use this type in your Users collection 10 | export type { User as UserType } 11 | -------------------------------------------------------------------------------- /src/components/AdminBar/index.scss: -------------------------------------------------------------------------------- 1 | @import '~@payloadcms/ui/scss'; 2 | 3 | .admin-bar { 4 | @include small-break { 5 | display: none; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/BeforeDashboard/SeedButton/index.scss: -------------------------------------------------------------------------------- 1 | .seedButton { 2 | appearance: none; 3 | background: none; 4 | border: none; 5 | padding: 0; 6 | text-decoration: underline; 7 | 8 | &:hover { 9 | cursor: pointer; 10 | opacity: 0.85; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/BeforeDashboard/index.scss: -------------------------------------------------------------------------------- 1 | @import '~@payloadcms/ui/scss'; 2 | 3 | .dashboard .before-dashboard { 4 | margin-bottom: base(1.5); 5 | 6 | &__banner { 7 | & h4 { 8 | margin: 0; 9 | } 10 | } 11 | 12 | &__instructions { 13 | list-style: decimal; 14 | margin-bottom: base(0.5); 15 | 16 | & li { 17 | width: 100%; 18 | } 19 | } 20 | 21 | & a:hover { 22 | opacity: 0.85; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/BeforeLogin/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BeforeLogin: React.FC = () => { 4 | return ( 5 |
6 |

7 | Welcome to your dashboard! 8 | {' This is where site admins will log in to manage your website.'} 9 |

10 |
11 | ) 12 | } 13 | 14 | export default BeforeLogin 15 | -------------------------------------------------------------------------------- /src/components/CollectionArchive/index.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utilities/ui' 2 | import React from 'react' 3 | 4 | import type { Post } from '@/payload-types' 5 | 6 | import { Card, CardPostData } from '@/components/Card' 7 | 8 | export type Props = { 9 | posts: CardPostData[] 10 | } 11 | 12 | export const CollectionArchive: React.FC = (props) => { 13 | const { posts } = props 14 | 15 | return ( 16 |
17 |
18 |
19 | {posts?.map((result, index) => { 20 | if (typeof result === 'object' && result !== null) { 21 | return ( 22 |
23 | 24 |
25 | ) 26 | } 27 | 28 | return null 29 | })} 30 |
31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/LivePreviewListener/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { getClientSideURL } from '@/utilities/getURL' 3 | import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react' 4 | import { useRouter } from 'next/navigation' 5 | import React from 'react' 6 | 7 | export const LivePreviewListener: React.FC = () => { 8 | const router = useRouter() 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React from 'react' 3 | 4 | interface Props { 5 | className?: string 6 | loading?: 'lazy' | 'eager' 7 | priority?: 'auto' | 'high' | 'low' 8 | } 9 | 10 | export const Logo = (props: Props) => { 11 | const { loading: loadingFromProps, priority: priorityFromProps, className } = props 12 | 13 | const loading = loadingFromProps || 'lazy' 14 | const priority = priorityFromProps || 'low' 15 | 16 | return ( 17 | /* eslint-disable @next/next/no-img-element */ 18 | KDCO Logo 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Media/VideoMedia/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { cn } from '@/utilities/ui' 4 | import React, { useEffect, useRef } from 'react' 5 | 6 | import type { Props as MediaProps } from '../types' 7 | 8 | import { getClientSideURL } from '@/utilities/getURL' 9 | 10 | export const VideoMedia: React.FC = (props) => { 11 | const { onClick, resource, videoClassName } = props 12 | 13 | const videoRef = useRef(null) 14 | // const [showFallback] = useState() 15 | 16 | useEffect(() => { 17 | const { current: video } = videoRef 18 | if (video) { 19 | video.addEventListener('suspend', () => { 20 | // setShowFallback(true); 21 | // console.warn('Video was suspended, rendering fallback image.') 22 | }) 23 | } 24 | }, []) 25 | 26 | if (resource && typeof resource === 'object') { 27 | const { filename } = resource 28 | 29 | return ( 30 | 42 | ) 43 | } 44 | 45 | return null 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Media/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | 3 | import type { Props } from './types' 4 | 5 | import { ImageMedia } from './ImageMedia' 6 | import { VideoMedia } from './VideoMedia' 7 | 8 | export const Media: React.FC = (props) => { 9 | const { className, htmlElement = 'div', resource } = props 10 | 11 | const isVideo = typeof resource === 'object' && resource?.mimeType?.includes('video') 12 | const Tag = (htmlElement as any) || Fragment 13 | 14 | return ( 15 | 22 | {isVideo ? : } 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Media/types.ts: -------------------------------------------------------------------------------- 1 | import type { StaticImageData } from 'next/image' 2 | import type { ElementType, Ref } from 'react' 3 | 4 | import type { Media as MediaType } from '@/payload-types' 5 | 6 | export interface Props { 7 | alt?: string 8 | className?: string 9 | fill?: boolean // for NextImage only 10 | htmlElement?: ElementType | null 11 | imgClassName?: string 12 | onClick?: () => void 13 | onLoad?: () => void 14 | loading?: 'lazy' | 'eager' // for NextImage only 15 | priority?: boolean // for NextImage only 16 | ref?: Ref 17 | resource?: MediaType | string | number // for Payload media 18 | size?: string // for NextImage only 19 | src?: StaticImageData // for static media 20 | videoClassName?: string 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Message/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React from 'react' 3 | 4 | /* [ 5 | classes.message, 6 | className, 7 | error && classes.error, 8 | success && classes.success, 9 | warning && classes.warning, 10 | !error && !success && !warning && classes.default, 11 | ] 12 | .filter(Boolean) 13 | .join(' '), */ 14 | 15 | export const Message: React.FC<{ 16 | className?: string 17 | error?: React.ReactNode 18 | message?: React.ReactNode 19 | success?: React.ReactNode 20 | warning?: React.ReactNode 21 | }> = ({ className, error, message, success, warning }) => { 22 | const messageToRender = message || error || success || warning 23 | 24 | if (messageToRender) { 25 | return ( 26 |
37 | {messageToRender} 38 |
39 | ) 40 | } 41 | return null 42 | } 43 | -------------------------------------------------------------------------------- /src/components/RenderParams/Component.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useSearchParams } from 'next/navigation' 4 | import React, { useEffect } from 'react' 5 | 6 | import { Message } from '../Message' 7 | 8 | export type Props = { 9 | className?: string 10 | message?: string 11 | onParams?: (paramValues: ((null | string | undefined) | string[])[]) => void 12 | params?: string[] 13 | } 14 | 15 | export const RenderParamsComponent: React.FC = ({ 16 | className, 17 | onParams, 18 | params = ['error', 'warning', 'success', 'message'], 19 | }) => { 20 | const searchParams = useSearchParams() 21 | const paramValues = params.map((param) => searchParams?.get(param)) 22 | 23 | useEffect(() => { 24 | if (paramValues.length && onParams) { 25 | onParams(paramValues) 26 | } 27 | }, [paramValues, onParams]) 28 | 29 | if (paramValues.length) { 30 | return ( 31 |
32 | {paramValues.map((paramValue, index) => { 33 | if (!paramValue) return null 34 | 35 | return ( 36 | 43 | ) 44 | })} 45 |
46 | ) 47 | } 48 | 49 | return null 50 | } 51 | -------------------------------------------------------------------------------- /src/components/RenderParams/index.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | 3 | import type { Props } from './Component' 4 | 5 | import { RenderParamsComponent } from './Component' 6 | 7 | // Using `useSearchParams` from `next/navigation` causes the entire route to de-optimize into client-side rendering 8 | // To fix this, we wrap the component in a `Suspense` component 9 | // See https://nextjs.org/docs/messages/deopted-into-client-rendering for more info 10 | 11 | export const RenderParams: React.FC = (props) => { 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/admin/CreateAccount/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import { CreateAccountForm } from './CreateAccountForm' 4 | 5 | const CreateAccountViewComponent: React.FC = () => { 6 | return ( 7 |
8 |
9 |
10 |

11 | Create your account 12 |

13 |

14 | Or{' '} 15 | 16 | sign in to your account 17 | 18 |

19 |
20 | 21 |
22 |
23 | ) 24 | } 25 | 26 | export default CreateAccountViewComponent 27 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from '@/utilities/cn' 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps { } 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { cn } from '@/utilities/ui' 4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox' 5 | import { Check } from 'lucide-react' 6 | import * as React from 'react' 7 | 8 | const Checkbox: React.FC< 9 | { 10 | ref?: React.Ref 11 | } & React.ComponentProps 12 | > = ({ className, ref, ...props }) => ( 13 | 21 | 22 | 23 | 24 | 25 | ) 26 | 27 | export { Checkbox } 28 | -------------------------------------------------------------------------------- /src/components/ui/fieldSet.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps } from 'react' 2 | 3 | import { cn } from '@/utilities/cn' 4 | 5 | export function Fieldset({ className, ...props }: ComponentProps<'div'>) { 6 | return
7 | } 8 | -------------------------------------------------------------------------------- /src/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './accordion' 2 | export { Checkbox } from './checkbox' 3 | export { Button } from './button' 4 | export { 5 | Form, 6 | FormField, 7 | FormItem, 8 | FormLabel, 9 | FormControl, 10 | FormDescription, 11 | FormMessage, 12 | } from './form' 13 | export { Input } from './input' 14 | export { Pagination, PaginationContent, PaginationItem, PaginationLink } from './pagination' 15 | export { Popover, PopoverTrigger, PopoverContent } from './popover' 16 | export { Progress } from './progress' 17 | export { ScrollArea } from './scroll-area' 18 | export { Separator } from './separator' 19 | export { 20 | Select, 21 | SelectContent, 22 | SelectGroup, 23 | SelectItem, 24 | SelectLabel, 25 | SelectSeparator, 26 | SelectTrigger, 27 | SelectValue, 28 | } from './select' 29 | export { Sheet, SheetContent, SheetTrigger } from './sheet' 30 | export { Skeleton } from './skeleton' 31 | export { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs' 32 | export { Textarea } from './textarea' 33 | export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip' 34 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utilities/ui' 2 | import * as React from 'react' 3 | 4 | const Input: React.FC< 5 | { 6 | ref?: React.Ref 7 | } & React.InputHTMLAttributes 8 | > = ({ type, className, ref, ...props }) => { 9 | return ( 10 | 19 | ) 20 | } 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { cn } from '@/utilities/ui' 4 | import * as LabelPrimitive from '@radix-ui/react-label' 5 | import { type VariantProps, cva } from 'class-variance-authority' 6 | import * as React from 'react' 7 | 8 | const labelVariants = cva( 9 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 10 | ) 11 | 12 | const Label: React.FC< 13 | { ref?: React.Ref } & React.ComponentProps & 14 | VariantProps 15 | > = ({ className, ref, ...props }) => ( 16 | 17 | ) 18 | 19 | export { Label } 20 | -------------------------------------------------------------------------------- /src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from '@/utilities/cn' 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from '@/utilities/cn' 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utilities/cn' 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utilities/ui' 2 | import * as React from 'react' 3 | 4 | const Textarea: React.FC< 5 | { 6 | ref?: React.Ref 7 | } & React.TextareaHTMLAttributes 8 | > = ({ className, ref, ...props }) => { 9 | return ( 10 |