diff --git a/.eslintrc.js b/.eslintrc.js index 0290d4b8..642ed9cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,9 +2,14 @@ var tsConfigs = ['./tsconfig.json']; var tsConfigEmail = ['./tsconfig-emails.json']; var srcRuleOverrides = { - 'prettier/prettier': 1, - '@typescript-eslint/no-unused-vars': 1, - '@typescript-eslint/no-non-null-assertion': 'error', + 'prettier/prettier': 0, + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/no-unused-expressions': 0, + '@typescript-eslint/no-non-null-assertion': 0, + '@next/next/no-img-element': 0, + '@typescript-eslint/no-empty-object-type': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/ban-ts-comment': 0, }; module.exports = { diff --git a/next-env.d.ts b/next-env.d.ts index 725dd6f2..fd36f949 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -3,4 +3,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/src/app/(external-pages)/blog/(list)/page.tsx b/src/app/(external-pages)/blog/(list)/page.tsx deleted file mode 100644 index c7de6294..00000000 --- a/src/app/(external-pages)/blog/(list)/page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { T } from '@/components/ui/Typography'; -import { - anonGetAllBlogTags, - anonGetPublishedBlogPosts, -} from '@/data/anon/internalBlog'; -import { Suspense } from 'react'; -import { PublicBlogList } from '../PublicBlogList'; -import { TagsNav } from '../TagsNav'; - -export const metadata = { - title: 'Blog List | Nextbase', - description: 'Collection of the latest blog posts from the team at Nextbase', -}; - -async function Tags() { - const tags = await anonGetAllBlogTags(); - return ; -} - -async function BlogList() { - const blogPosts = await anonGetPublishedBlogPosts(); - return ; -} - -export default async function BlogListPage() { - return ( -
-
-
- Blog - All blog posts - - Here is a collection of the latest blog posts from the team at - Nextbase. - -
- Loading tags...}> - - -
- Loading posts...}> - - -
- ); -} diff --git a/src/app/(external-pages)/blog/AuthorCard.tsx b/src/app/(external-pages)/blog/AuthorCard.tsx deleted file mode 100644 index 7faea06d..00000000 --- a/src/app/(external-pages)/blog/AuthorCard.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { - Card, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import Link from 'next/link'; - -type Props = { - author: { - avatar_url: string; - bio: string; - created_at: string; - display_name: string; - facebook_handle: string | null; - instagram_handle: string | null; - linkedin_handle: string | null; - twitter_handle: string | null; - updated_at: string; - user_id: string; - website_url: string | null; - }; -}; - -const AuthorCard = ({ author }: Props) => { - return ( - - - -
- - - - {generateInitials(author.display_name)} - - -
- {author.display_name} - - {author.bio.length > 120 - ? author.bio.slice(0, 120) + '...' - : author.bio.length} - -
-
-
-
- - ); -}; - -export default AuthorCard; - -function generateInitials(name: string) { - // Split the name into individual words - const words = name.split(' '); - - // Initialize an empty string to store initials - let initials = ''; - - // Iterate through each word - for (let i = 0; i < words.length; i++) { - // Get the first character of each word and convert it to uppercase - initials += words[i][0].toUpperCase(); - } - - // Return the generated initials - return initials; -} diff --git a/src/app/(external-pages)/blog/MobileNavigation.tsx b/src/app/(external-pages)/blog/MobileNavigation.tsx deleted file mode 100644 index f1c79d8f..00000000 --- a/src/app/(external-pages)/blog/MobileNavigation.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client'; -import { Dialog } from '@headlessui/react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { ComponentProps, useState } from 'react'; - -function MenuIcon(props: ComponentProps<'svg'>) { - return ( - - ); -} - -function CloseIcon(props: ComponentProps<'svg'>) { - return ( - - ); -} - -export function MobileNavigation() { - const router = useRouter(); - const [isOpen, setIsOpen] = useState(false); - - // useEffect(() => { - // if (!isOpen) return - - // function onRouteChange() { - // setIsOpen(false) - // } - - // router.events.on('routeChangeComplete', onRouteChange) - // router.events.on('routeChangeError', onRouteChange) - - // return () => { - // router.events.off('routeChangeComplete', onRouteChange) - // router.events.off('routeChangeError', onRouteChange) - // } - // }, [router, isOpen]) - - return ( - <> - - - -
- - - Nextbase Logo - -
-
-
- - ); -} diff --git a/src/app/(external-pages)/blog/Navbar.tsx b/src/app/(external-pages)/blog/Navbar.tsx deleted file mode 100644 index 6e35efa3..00000000 --- a/src/app/(external-pages)/blog/Navbar.tsx +++ /dev/null @@ -1,87 +0,0 @@ -'use client'; -import { cn } from '@/utils/cn'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { ComponentPropsWithRef, useEffect, useState } from 'react'; -import { MobileNavigation } from './MobileNavigation'; - -function NavLink({ href, ...props }: ComponentPropsWithRef) { - const pathname = usePathname(); - const isActive = pathname === href; - return ( - - ); -} - -export function Navbar() { - const [isScrolled, setIsScrolled] = useState(false); - - useEffect(() => { - function onScroll() { - setIsScrolled(window.scrollY > 0); - } - onScroll(); - window.addEventListener('scroll', onScroll, { passive: true }); - return () => { - window.removeEventListener('scroll', onScroll); - }; - }, []); - - return ( -
-
- -
- - Nextbase Logo - -
-
- -
-
- - Nextbase Logo - - - Blog - - - Docs - - - Changelog - - - Roadmap - -
-
-
-
-
- ); -} diff --git a/src/app/(external-pages)/blog/PublicBlogList.tsx b/src/app/(external-pages)/blog/PublicBlogList.tsx deleted file mode 100644 index 036fb3d4..00000000 --- a/src/app/(external-pages)/blog/PublicBlogList.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { T } from '@/components/ui/Typography'; -import { Table } from '@/types'; -import moment from 'moment'; - -export function PublicBlogList({ - blogPosts, -}: { - blogPosts: Array>; -}) { - return ( - <> - {blogPosts.length ? ( -
-
- {blogPosts.map((post) => ( -
-
- {post.title} -
-
-
- -
-
-

- - - {post.title} - -

-

- {post.summary} -

-
-
-
- ))} -
-
- ) : ( - No blog posts yet. - )} - - ); -} diff --git a/src/app/(external-pages)/blog/TagsNav.tsx b/src/app/(external-pages)/blog/TagsNav.tsx deleted file mode 100644 index 2923d494..00000000 --- a/src/app/(external-pages)/blog/TagsNav.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Button } from '@/components/ui/button'; -import { Table } from '@/types'; -import Link from 'next/link'; - -export function TagsNav({ - tags, -}: { - tags: Table<'internal_blog_post_tags'>[]; -}) { - return ( -
- - - - - {tags.map((tag) => ( - - - - ))} -
- ); -} diff --git a/src/app/(external-pages)/blog/[slug]/BlogContent.tsx b/src/app/(external-pages)/blog/[slug]/BlogContent.tsx deleted file mode 100644 index 200e44d0..00000000 --- a/src/app/(external-pages)/blog/[slug]/BlogContent.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { getTipTapExtention } from '@/components/tip-tap-Editor/extensions'; -import type { Table } from '@/types'; -import { generateHTML } from '@tiptap/core'; - -export function BlogContent({ - jsonContent, -}: { - jsonContent: Table<'internal_blog_posts'>['json_content']; -}) { - const validContent = - typeof jsonContent === 'string' - ? JSON.parse(jsonContent) - : typeof jsonContent === 'object' && jsonContent !== null - ? jsonContent - : {}; - return ( -
- ); -} diff --git a/src/app/(external-pages)/blog/[slug]/BlogContentWrapper.tsx b/src/app/(external-pages)/blog/[slug]/BlogContentWrapper.tsx deleted file mode 100644 index e183c041..00000000 --- a/src/app/(external-pages)/blog/[slug]/BlogContentWrapper.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; - -import { Skeleton } from '@/components/ui/skeleton'; -import { Table } from '@/types'; -import dynamic from 'next/dynamic'; -import { Suspense } from 'react'; - -const BlogContent = dynamic( - () => import('./BlogContent').then((m) => m.BlogContent), - { ssr: false }, -); - -export function BlogContentWrapper({ - jsonContent, -}: { - jsonContent: Table<'internal_blog_posts'>['json_content']; -}) { - return ( - }> - - - ); -} diff --git a/src/app/(external-pages)/blog/[slug]/page.tsx b/src/app/(external-pages)/blog/[slug]/page.tsx deleted file mode 100644 index c22cfb7f..00000000 --- a/src/app/(external-pages)/blog/[slug]/page.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { - anonGetPublishedBlogPostBySlug, - anonGetPublishedBlogPosts, -} from "@/data/anon/internalBlog"; - -import { T } from "@/components/ui/Typography"; -import type { Metadata } from "next"; -import { notFound } from "next/navigation"; -import { z } from "zod"; -import AuthorCard from "../AuthorCard"; -import { BlogContentWrapper } from "./BlogContentWrapper"; - -const paramsSchema = z.object({ - slug: z.string(), -}); - -// Return a list of `params` to populate the [slug] dynamic segment -export async function generateStaticParams() { - const posts = await anonGetPublishedBlogPosts(); - - return posts.map((post) => ({ - slug: post.slug, - })); -} - -export async function generateMetadata({ - params, -}: { - params: unknown; -}): Promise { - // read route params - const { slug } = paramsSchema.parse(params); - const post = await anonGetPublishedBlogPostBySlug(slug); - - console.log(post) - - return { - title: `${post.title} | Blog | Nextbase Boilerplate`, - description: post.summary, - openGraph: { - title: `${post.title} | Blog | Nextbase Boilerplate`, - description: post.summary, - type: "website", - images: post.cover_image ? [post.cover_image] : undefined, - }, - twitter: { - images: post.cover_image ? [post.cover_image] : undefined, - title: `${post.title} | Blog | Nextbase Boilerplate`, - card: "summary_large_image", - site: "@usenextbase", - description: post.summary, - }, - }; -} -export default async function BlogPostPage({ params }: { params: unknown }) { - try { - const { slug } = paramsSchema.parse(params); - const post = await anonGetPublishedBlogPostBySlug(slug); - - return ( -
- {post.cover_image ? ( - {post.title} - ) : null} -
-

{post.title}

- -
- {post?.internal_blog_author_posts[0]?.internal_blog_author_profiles ? ( - <> - Author - - - ) : null} -
- ); - } catch (error) { - return notFound(); - } -} diff --git a/src/app/(external-pages)/blog/authors/[authorId]/page.tsx b/src/app/(external-pages)/blog/authors/[authorId]/page.tsx deleted file mode 100644 index 15ef2317..00000000 --- a/src/app/(external-pages)/blog/authors/[authorId]/page.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { T } from '@/components/ui/Typography'; -import { - anonGetBlogPostsByAuthorId, - anonGetOneAuthor, -} from '@/data/anon/internalBlog'; -import moment from 'moment'; -import { notFound } from 'next/navigation'; -import { Suspense } from 'react'; -import { z } from 'zod'; - -const paramsSchema = z.object({ - authorId: z.string(), -}); - -export default async function BlogPostPage({ params }: { params: unknown }) { - const { authorId } = paramsSchema.parse(params); - - const author = await anonGetOneAuthor(authorId); - const blogs = await anonGetBlogPostsByAuthorId(authorId); - try { - return ( -
-
-
- Blogs - All {author[0].display_name}'s posts - - Here is a collection of the latest blog posts - -
-
-
- Loading authors...}> -
-
- {blogs.map(({ internal_blog_posts }) => ( - - ))} -
-
-
-
-
- ); - } catch (error) { - return notFound(); - } -} diff --git a/src/app/(external-pages)/blog/authors/page.tsx b/src/app/(external-pages)/blog/authors/page.tsx deleted file mode 100644 index 357357e6..00000000 --- a/src/app/(external-pages)/blog/authors/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { T } from '@/components/ui/Typography'; -import { anonGetAllAuthors } from '@/data/anon/internalBlog'; -import { notFound } from 'next/navigation'; -import { Suspense } from 'react'; -import AuthorCard from '../AuthorCard'; - -export default async function BlogPostPage({ params }: { params: unknown }) { - const authors = await anonGetAllAuthors(); - try { - return ( -
-
-
- Contributions - Authors - - Our blog is made possible because of contributions from these - awesome folks! - -
-
-
- Loading authors...}> - {authors.map((author) => ( - - ))} - -
-
- ); - } catch (error) { - return notFound(); - } -} diff --git a/src/app/(external-pages)/blog/layout.css b/src/app/(external-pages)/blog/layout.css deleted file mode 100644 index cc1c72cc..00000000 --- a/src/app/(external-pages)/blog/layout.css +++ /dev/null @@ -1,3 +0,0 @@ -html { - scroll-behavior: smooth; -} diff --git a/src/app/(external-pages)/blog/layout.tsx b/src/app/(external-pages)/blog/layout.tsx deleted file mode 100644 index af27dff1..00000000 --- a/src/app/(external-pages)/blog/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import './layout.css'; -import { Navbar } from './Navbar'; - -export default function Layout({ children }: { children: React.ReactNode }) { - return ( -
- {/* */} -
-
{children}
-
-
- ); -} diff --git a/src/app/(external-pages)/blog/tag/[tagSlug]/page.tsx b/src/app/(external-pages)/blog/tag/[tagSlug]/page.tsx deleted file mode 100644 index c816a4be..00000000 --- a/src/app/(external-pages)/blog/tag/[tagSlug]/page.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { T } from '@/components/ui/Typography'; -import { Metadata } from 'next'; -import { z } from 'zod'; -import { PublicBlogList } from '../../PublicBlogList'; -import { TagsNav } from '../../TagsNav'; -import { - anonGetAllBlogTags, - anonGetPublishedBlogPosts, - anonGetPublishedBlogPostsByTagSlug, - anonGetTagBySlug, -} from '@/data/anon/internalBlog'; -import { Suspense } from 'react'; - -const BlogListByTagPageParamsSchema = z.object({ - tagSlug: z.string(), -}); - -export async function generateMetadata({ - params, -}: { - params: unknown; -}): Promise { - // read route params - const { tagSlug } = BlogListByTagPageParamsSchema.parse(params); - const tag = await anonGetTagBySlug(tagSlug); - - return { - title: `${tag.name} | Blog | Nextbase Ultimate`, - description: tag.description, - }; -} - -async function Tags() { - const tags = await anonGetAllBlogTags(); - return ; -} - -async function BlogList({ tagSlug }: { tagSlug: string }) { - const blogPosts = await anonGetPublishedBlogPostsByTagSlug(tagSlug); - return ; -} - -export default async function BlogListByTagPage({ - params, -}: { - params: unknown; -}) { - const { tagSlug } = BlogListByTagPageParamsSchema.parse(params); - - const tag = await anonGetTagBySlug(tagSlug); - - return ( -
-
-
- Blog - {tag.name} - {tag.description} -
- Loading tags...}> - - -
- Loading posts...}> - - -
- ); -} diff --git a/src/data/feedback.ts b/src/data/feedback.ts index d2667813..213ce51a 100644 --- a/src/data/feedback.ts +++ b/src/data/feedback.ts @@ -8,10 +8,17 @@ import { serverGetLoggedInUser } from '@/utils/server/serverGetLoggedInUser'; import { serverGetUserType } from '@/utils/server/serverGetUserType'; import { userRoles } from '@/utils/userTypes'; import { revalidatePath } from 'next/cache'; -import { createFeedbackAddedToRoadmapUpdatedNotification, createFeedbackPriorityChangedNotification, createFeedbackReceivedCommentNotification, createFeedbackStatusChangedNotification, createFeedbackTypeUpdatedNotification, createFeedbackVisibilityUpdatedNotification, createUpdateFeedbackOpenForCommentsNotification } from './user/notifications'; +import { + createFeedbackAddedToRoadmapUpdatedNotification, + createFeedbackPriorityChangedNotification, + createFeedbackReceivedCommentNotification, + createFeedbackStatusChangedNotification, + createFeedbackTypeUpdatedNotification, + createFeedbackVisibilityUpdatedNotification, + createUpdateFeedbackOpenForCommentsNotification, +} from './user/notifications'; import { getUserFullName } from './user/user'; - export async function addCommentToInternalFeedbackThread({ feedbackId, content, @@ -42,7 +49,13 @@ export async function addCommentToInternalFeedbackThread({ * App admins can comment on all the feedbacks * normal user can comment on their own feedback and the one's with open for public */ - if (!(feedbackThread?.open_for_public_discussion || feedbackThread?.user_id == user.id || userRoleType == userRoles.ADMIN)) { + if ( + !( + feedbackThread?.open_for_public_discussion || + feedbackThread?.user_id == user.id || + userRoleType == userRoles.ADMIN + ) + ) { return { status: 'error', message: 'This feedback thread is not open for public discussion', @@ -61,7 +74,7 @@ export async function addCommentToInternalFeedbackThread({ }; } - const userFullName = await getUserFullName(user.id) + const userFullName = await getUserFullName(user.id); await createFeedbackReceivedCommentNotification({ feedbackId, @@ -69,7 +82,7 @@ export async function addCommentToInternalFeedbackThread({ comment: content, commenterId: user.id, commenterName: userFullName ?? 'User', - }) + }); revalidatePath('/feedback', 'layout'); revalidatePath(`/feedback/${feedbackId}`, 'layout'); @@ -77,7 +90,6 @@ export async function addCommentToInternalFeedbackThread({ status: 'success', data, }; - } catch (error) { return { status: 'error', @@ -166,7 +178,7 @@ export async function adminUpdateFeedbackStatus({ .from('internal_feedback_threads') .select('user_id, status') .eq('id', feedbackId) - .single() + .single(); if (feedbackThreadError) { return { @@ -177,7 +189,7 @@ export async function adminUpdateFeedbackStatus({ const { error } = await supabaseAdminClient .from('internal_feedback_threads') .update({ status }) - .eq('id', feedbackId) + .eq('id', feedbackId); if (error) { return { status: 'error', message: error.message }; @@ -215,7 +227,7 @@ export async function adminUpdateFeedbackType({ .from('internal_feedback_threads') .select('user_id, type') .eq('id', feedbackId) - .single() + .single(); if (feedbackThreadError) { return { @@ -266,7 +278,7 @@ export async function adminUpdateFeedbackPriority({ .from('internal_feedback_threads') .select('user_id, priority') .eq('id', feedbackId) - .single() + .single(); if (feedbackThreadError) { return { @@ -331,8 +343,8 @@ export async function adminToggleFeedbackFromRoadmap({ await createFeedbackAddedToRoadmapUpdatedNotification({ feedbackId, isInRoadmap, - updaterId: user.id - }) + updaterId: user.id, + }); revalidatePath('/feedback', 'page'); revalidatePath(`/feedback/${feedbackId}`, 'page'); @@ -373,8 +385,8 @@ export async function adminToggleFeedbackOpenForComments({ await createUpdateFeedbackOpenForCommentsNotification({ feedbackId, isOpenForComments, - updaterId: user.id - }) + updaterId: user.id, + }); revalidatePath('/feedback', 'page'); revalidatePath(`/feedback/${feedbackId}`, 'page'); @@ -408,8 +420,8 @@ export async function adminToggleFeedbackVisibility({ await createFeedbackVisibilityUpdatedNotification({ feedbackId, isPubliclyVisible, - updaterId: user.id - }) + updaterId: user.id, + }); revalidatePath('/feedback', 'page'); revalidatePath(`/feedback/${feedbackId}`, 'page'); @@ -417,7 +429,13 @@ export async function adminToggleFeedbackVisibility({ return { status: 'success' }; } -export async function getFeedbackStakeholdersExceptMentionedUser({ feedbackId, excludedUserId }: { feedbackId: string, excludedUserId?: string }): Promise { +export async function getFeedbackStakeholdersExceptMentionedUser({ + feedbackId, + excludedUserId, +}: { + feedbackId: string; + excludedUserId?: string; +}): Promise { // return all the user ids that are concerned with the feedback conversation including owner // except the one mentioned, the mentioned user could be owner of the feedback thread or the commentator or logged in user try { @@ -430,15 +448,15 @@ export async function getFeedbackStakeholdersExceptMentionedUser({ feedbackId, e const feedbackCommentatorsQuery = supabaseClient .from('internal_feedback_comments') .select('user_id') - .eq('thread_id', feedbackId) + .eq('thread_id', feedbackId); - const [feedbackOwnerResult, feedbackCommentatorsResult] = await Promise.all([ - feedbackOwnerQuery, - feedbackCommentatorsQuery - ]); + const [feedbackOwnerResult, feedbackCommentatorsResult] = await Promise.all( + [feedbackOwnerQuery, feedbackCommentatorsQuery], + ); if (feedbackOwnerResult.error) throw feedbackOwnerResult.error; - if (feedbackCommentatorsResult.error) throw feedbackCommentatorsResult.error; + if (feedbackCommentatorsResult.error) + throw feedbackCommentatorsResult.error; const stakeholders = new Set(); @@ -446,7 +464,7 @@ export async function getFeedbackStakeholdersExceptMentionedUser({ feedbackId, e stakeholders.add(feedbackOwnerResult.data[0].user_id); } - feedbackCommentatorsResult.data.forEach(comment => { + feedbackCommentatorsResult.data.forEach((comment) => { stakeholders.add(comment.user_id); }); @@ -456,6 +474,7 @@ export async function getFeedbackStakeholdersExceptMentionedUser({ feedbackId, e return Array.from(stakeholders); } catch (error) { + console.log('error in getFeedbackStakeholdersExceptMentionedUser: ', error); throw error; } }