diff --git a/app/(dashboard)/repo-settings/[repositoryId]/description/page.tsx b/app/(dashboard)/repo-settings/[repositoryId]/description/page.tsx index fe5711e..a46a504 100644 --- a/app/(dashboard)/repo-settings/[repositoryId]/description/page.tsx +++ b/app/(dashboard)/repo-settings/[repositoryId]/description/page.tsx @@ -1,12 +1,25 @@ +import UpdateProjectDescription from "@/components/ui/updateProjectDescription"; +import { fetchRepoDetails, updateRepositoryDescritpion } from "@/lib/repository/service"; + export const metadata = { title: "Project description", description: "Change how your project is presented to players.", }; -export default async function DescriptionPage({ params }) { +export default async function DescriptionPage({ params, useState }) { + + const repoDetail = await fetchRepoDetails(params.repositoryId); + + console.log({ repoDetail }) + + async function updateRepoDescritpion(description: string) { + "use server" + return updateRepositoryDescritpion(params.repositoryId, description); + } + return (
-

Make work with the current description on repository detail page.

+
); } diff --git a/app/[githubLogin]/page.tsx b/app/[githubLogin]/page.tsx index ae42cc7..f2b26ef 100644 --- a/app/[githubLogin]/page.tsx +++ b/app/[githubLogin]/page.tsx @@ -100,15 +100,17 @@ export default async function ProfilePage({ params }) { const user = await getUserByLogin(githubLogin); if (user) { + const userEnrollments = await getEnrolledRepositories(user?.id); + + const repos = userEnrollments.length > 0 ? [...userEnrollments].map((repo) => repo.name) : null; // get repo names from userEnrollments const [githubUserData, mergedIssues, openPRs] = await Promise.all([ getGithubUserByLogin(githubLogin).then( (data) => data || { name: "Rick", avatar_url: Rick, bio: "is never gonna give you up 🕺" } ), - getMergedPullRequestsByGithubLogin("formbricks/formbricks", githubLogin), + getMergedPullRequestsByGithubLogin(repos, githubLogin), getOpenPullRequestsByGithubLogin("formbricks/formbricks", githubLogin), ]); - const userEnrollments = await getEnrolledRepositories(user?.id); const arrayCurrentLevelOfUserInEnrolledRepos = await Promise.all( userEnrollments.map(async (repo) => { diff --git a/components/ui/tiptap-editor.tsx b/components/ui/tiptap-editor.tsx new file mode 100644 index 0000000..c5e4e95 --- /dev/null +++ b/components/ui/tiptap-editor.tsx @@ -0,0 +1,261 @@ +"use client"; + +import { Color } from "@tiptap/extension-color"; +import ListItem from "@tiptap/extension-list-item"; +import TextStyle from "@tiptap/extension-text-style"; +import { EditorProvider, useCurrentEditor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import React from "react"; + +import "../../styles/editor.css"; + +const MenuBar = () => { + const { editor } = useCurrentEditor(); + + if (!editor) { + return null; + } + + editor.setOptions({ + editorProps: { + attributes: { + class: + "border-none prose prose-sm sm:prose lg:prose-lg xl:prose-2xl mx-auto focus:outline-none", + }, + }, + }); + + return ( + <> +
+ + + + + + + + + + + + + +
+ + ); +}; + +const extensions = [ + Color.configure({ types: [TextStyle.name, ListItem.name] }), + TextStyle.configure({ types: [ListItem.name] }), + StarterKit.configure({ + bulletList: { + keepMarks: true, + keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help + }, + orderedList: { + keepMarks: true, + keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help + }, + }), +]; + +export default ({ content, fn }) => { + return } extensions={extensions} onUpdate={({editor}) => fn(editor.getHTML()) } content={content}>; +}; diff --git a/components/ui/updateProjectDescription.tsx b/components/ui/updateProjectDescription.tsx new file mode 100644 index 0000000..0d58a29 --- /dev/null +++ b/components/ui/updateProjectDescription.tsx @@ -0,0 +1,26 @@ +"use client"; + +import Tiptap from "@/components/ui/tiptap-editor"; +import { use, useEffect, useState } from "react"; + +export default function UpdateProjectDescription({ content, action }) { + const [description, setDescription] = useState(content); + + useEffect(() => { + const fetchDescription = async () => { + const repo = await action(description); + setDescription(repo.projectDescription || description); + }; + // add debounce here to avoid too many requests + const debounced = setTimeout(() => { + fetchDescription(); + }, 2000); + return () => clearTimeout(debounced); + }, [description]); + + return ( +
+ +
+ ); +} diff --git a/lib/github/service.ts b/lib/github/service.ts index 731bff1..85a4999 100644 --- a/lib/github/service.ts +++ b/lib/github/service.ts @@ -5,38 +5,67 @@ import { unstable_cache } from "next/cache"; import { GITHUB_APP_ACCESS_TOKEN, GITHUB_CACHE_REVALIDATION_INTERVAL, OSS_GG_LABEL } from "../constants"; -export const getMergedPullRequestsByGithubLogin = (repo: string, githubLogin: string) => - unstable_cache( - async () => { - const url = `https://api.github.com/search/issues?q=repo:${repo}+is:pull-request+is:merged+author:${githubLogin}&per_page=10&sort=created&order=desc`; - - const headers = { - Authorization: `Bearer ${GITHUB_APP_ACCESS_TOKEN}`, - Accept: "application/vnd.github.v3+json", - }; - - const response = await fetch(url, { headers }); - const data = await response.json(); - - const validatedData = ZGithubApiResponseSchema.parse(data); - - // Map the GitHub API response to issue format - const mergedPRs = validatedData.items.map((pr) => ({ - logoUrl: "https://avatars.githubusercontent.com/u/105877416?s=200&v=4", - href: pr.html_url, - title: pr.title, - author: pr.user.login, - key: pr.id.toString(), - isIssue: false, - })); +export const getMergedPullRequestsByGithubLogin = async (repos: Array | null, githubLogin: string) => { + if (!repos || repos.length === 0) { + return Promise.resolve([]); + } + + const mergedPRs: Array<{ + logoUrl: string; + href: string; + title: string; + author: string; + key: string; + isIssue: boolean; + }> = []; + + for (const repoName of repos) { + await unstable_cache( + async () => { + const url = `https://api.github.com/search/issues?q=repo:${repoName}+is:pull-request+is:merged+author:${githubLogin}&per_page=10&sort=created&order=desc`; + + const headers = { + Authorization: `Bearer ${GITHUB_APP_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + }; - return mergedPRs; - }, - [`getMergedPullRequests-${repo}-${githubLogin}`], - { - revalidate: GITHUB_CACHE_REVALIDATION_INTERVAL, - } - )(); + const response = await fetch(url, { headers }); + const data = await response.json(); + + const validatedData = ZGithubApiResponseSchema.parse(data); + + mergedPRs.push( + ...validatedData.items.map((pr) => ({ + logoUrl: "https://avatars.githubusercontent.com/u/105877416?s=200&v=4", + href: pr.html_url, + title: pr.title, + author: pr.user.login, + key: pr.id.toString(), + isIssue: false, + })) + ); + + // Map the GitHub API response to issue format + // const mergedPRs = validatedData.items.map((pr) => ({ + // logoUrl: "https://avatars.githubusercontent.com/u/105877416?s=200&v=4", + // href: pr.html_url, + // title: pr.title, + // author: pr.user.login, + // key: pr.id.toString(), + // isIssue: false, + // })); + + return mergedPRs; + }, + [`getMergedPullRequests-${repos}-${githubLogin}`], + { + revalidate: GITHUB_CACHE_REVALIDATION_INTERVAL, + } + )(); + } + + return mergedPRs; +}; export const getOpenPullRequestsByGithubLogin = (repo: string, githubLogin: string) => unstable_cache( diff --git a/lib/repository/service.ts b/lib/repository/service.ts index 1e7a6b6..614ed3b 100644 --- a/lib/repository/service.ts +++ b/lib/repository/service.ts @@ -99,6 +99,24 @@ export const updateRepository = async (id: string, configuredValue: boolean) => return updatedRepository; }; +/** + * Updates a repository's description field + * @returns The updated repository. + */ + +export const updateRepositoryDescritpion = async (id: string, description: string) => { + const updatedRepository = await db.repository.update({ + where: { id }, + data: { projectDescription: description }, + }); + + if (!updatedRepository) { + throw new Error("Repository not found."); + } + + return updatedRepository; +}; + /** * Fetches a repository * @returns The fetched repository. diff --git a/package.json b/package.json index 8ec9414..5fb9ac0 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,6 @@ "start": "next start" }, "dependencies": { - "@aws-sdk/client-s3": "^3.577.0", - "@aws-sdk/s3-presigned-post": "^3.577.0", - "@aws-sdk/s3-request-presigner": "^3.577.0", - "@hookform/resolvers": "^3.4.0", "@aws-sdk/client-s3": "^3.540.0", "@aws-sdk/s3-presigned-post": "^3.540.0", "@aws-sdk/s3-request-presigner": "^3.540.0", @@ -45,9 +41,20 @@ "@radix-ui/react-toast": "^1.1.5", "@smithy/middleware-serde": "3.0.0", "@t3-oss/env-nextjs": "^0.8.0", + "@tiptap/extension-blockquote": "^2.4.0", + "@tiptap/extension-bold": "^2.4.0", + "@tiptap/extension-bullet-list": "^2.4.0", + "@tiptap/extension-color": "^2.4.0", "@tiptap/extension-heading": "^2.4.0", "@tiptap/extension-highlight": "^2.4.0", + "@tiptap/extension-link": "^2.4.0", + "@tiptap/extension-list-item": "^2.4.0", + "@tiptap/extension-ordered-list": "^2.4.0", + "@tiptap/extension-paragraph": "^2.4.0", + "@tiptap/extension-placeholder": "^2.4.0", + "@tiptap/extension-text-style": "^2.4.0", "@tiptap/extension-typography": "^2.4.0", + "@tiptap/extension-underline": "^2.4.0", "@tiptap/react": "^2.4.0", "@tiptap/starter-kit": "^2.4.0", "@trigger.dev/nextjs": "^2.3.18", @@ -79,6 +86,7 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "tremendous": "3.0.1", + "trix": "^2.1.1", "uuid": "^9.0.1", "zod": "^3.23.8" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9427a9..4ae1547 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,15 +80,48 @@ dependencies: '@t3-oss/env-nextjs': specifier: ^0.8.0 version: 0.8.0(typescript@5.4.5)(zod@3.23.8) + '@tiptap/extension-blockquote': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-bold': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-bullet-list': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-color': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0)(@tiptap/extension-text-style@2.4.0) '@tiptap/extension-heading': specifier: ^2.4.0 version: 2.4.0(@tiptap/core@2.4.0) '@tiptap/extension-highlight': specifier: ^2.4.0 version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-link': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0)(@tiptap/pm@2.4.0) + '@tiptap/extension-list-item': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-ordered-list': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-paragraph': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-placeholder': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0)(@tiptap/pm@2.4.0) + '@tiptap/extension-text-style': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) '@tiptap/extension-typography': specifier: ^2.4.0 version: 2.4.0(@tiptap/core@2.4.0) + '@tiptap/extension-underline': + specifier: ^2.4.0 + version: 2.4.0(@tiptap/core@2.4.0) '@tiptap/react': specifier: ^2.4.0 version: 2.4.0(@tiptap/core@2.4.0)(@tiptap/pm@2.4.0)(react-dom@18.3.1)(react@18.3.1) @@ -182,6 +215,9 @@ dependencies: tremendous: specifier: 3.0.1 version: 3.0.1 + trix: + specifier: ^2.1.1 + version: 2.1.1 uuid: specifier: ^9.0.1 version: 9.0.1 @@ -3408,6 +3444,16 @@ packages: '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) dev: false + /@tiptap/extension-color@2.4.0(@tiptap/core@2.4.0)(@tiptap/extension-text-style@2.4.0): + resolution: {integrity: sha512-aVuqGtzTIZO93niADdu+Hx8g03X0pS7wjrJcCcYkkDEbC/siC03zlxKZIYBW1Jiabe99Z7/s2KdtLoK6DW2A2g==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/extension-text-style': ^2.0.0 + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/extension-text-style': 2.4.0(@tiptap/core@2.4.0) + dev: false + /@tiptap/extension-document@2.4.0(@tiptap/core@2.4.0): resolution: {integrity: sha512-3jRodQJZDGbXlRPERaloS+IERg/VwzpC1IO6YSJR9jVIsBO6xC29P3cKTQlg1XO7p6ZH/0ksK73VC5BzzTwoHg==} peerDependencies: @@ -3499,6 +3545,17 @@ packages: '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) dev: false + /@tiptap/extension-link@2.4.0(@tiptap/core@2.4.0)(@tiptap/pm@2.4.0): + resolution: {integrity: sha512-r3PjT0bjSKAorHAEBPA0icSMOlqALbxVlWU9vAc+Q3ndzt7ht0CTPNewzFF9kjzARABVt1cblXP/2+c0qGzcsg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + linkifyjs: 4.1.3 + dev: false + /@tiptap/extension-list-item@2.4.0(@tiptap/core@2.4.0): resolution: {integrity: sha512-reUVUx+2cI2NIAqMZhlJ9uK/+zvRzm1GTmlU2Wvzwc7AwLN4yemj6mBDsmBLEXAKPvitfLh6EkeHaruOGymQtg==} peerDependencies: @@ -3523,6 +3580,16 @@ packages: '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) dev: false + /@tiptap/extension-placeholder@2.4.0(@tiptap/core@2.4.0)(@tiptap/pm@2.4.0): + resolution: {integrity: sha512-SmWOjgWpmhFt0BPOnL65abCUH0wS5yksUJgtANn5bQoHF4HFSsyl7ETRmgf0ykxdjc7tzOg31FfpWVH4wzKSYg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + dev: false + /@tiptap/extension-strike@2.4.0(@tiptap/core@2.4.0): resolution: {integrity: sha512-pE1uN/fQPOMS3i+zxPYMmPmI3keubnR6ivwM+KdXWOMnBiHl9N4cNpJgq1n2eUUGKLurC2qrQHpnVyGAwBS6Vg==} peerDependencies: @@ -3531,6 +3598,14 @@ packages: '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) dev: false + /@tiptap/extension-text-style@2.4.0(@tiptap/core@2.4.0): + resolution: {integrity: sha512-H0uPWeZ4sXz3o836TDWnpd38qClqzEM2d6QJ9TK+cQ1vE5Gp8wQ5W4fwUV1KAHzpJKE/15+BXBjLyVYQdmXDaQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + dev: false + /@tiptap/extension-text@2.4.0(@tiptap/core@2.4.0): resolution: {integrity: sha512-LV0bvE+VowE8IgLca7pM8ll7quNH+AgEHRbSrsI3SHKDCYB9gTHMjWaAkgkUVaO1u0IfCrjnCLym/PqFKa+vvg==} peerDependencies: @@ -3547,6 +3622,14 @@ packages: '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) dev: false + /@tiptap/extension-underline@2.4.0(@tiptap/core@2.4.0): + resolution: {integrity: sha512-guWojb7JxUwLz4OKzwNExJwOkhZjgw/ttkXCMBT0PVe55k998MMYe1nvN0m2SeTW9IxurEPtScH4kYJ0XuSm8Q==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + dev: false + /@tiptap/pm@2.4.0: resolution: {integrity: sha512-B1HMEqGS4MzIVXnpgRZDLm30mxDWj51LkBT/if1XD+hj5gm8B9Q0c84bhvODX6KIs+c6z+zsY9VkVu8w9Yfgxg==} dependencies: @@ -6122,6 +6205,10 @@ packages: uc.micro: 2.1.0 dev: false + /linkifyjs@4.1.3: + resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} + dev: false + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -8354,6 +8441,10 @@ packages: engines: {node: '>=8'} dev: true + /trix@2.1.1: + resolution: {integrity: sha512-IljOMGOlRUPg1i5Pk/+x/Ia65ZY7Gw5JxxKCh/4caxG5ZaKuFJCKdn1+TF0efUYfdg+bqWenB/mAYCHjZu0zpQ==} + dev: false + /trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} dev: false diff --git a/styles/editor.css b/styles/editor.css index 5695fc6..501fa0a 100644 --- a/styles/editor.css +++ b/styles/editor.css @@ -1,26 +1,16 @@ .tiptap > * + * { margin-top: 0.75em; } - -.tiptap ul, -.tiptap ol { +.tiptap ul, .tiptap ol { padding: 0 1rem; } - -.tiptap h1, -.tiptap h2, -.tiptap h3, -.tiptap h4, -.tiptap h5, -.tiptap h6 { +.tiptap h1, .tiptap h2, .tiptap h3, .tiptap h4, .tiptap h5, .tiptap h6 { line-height: 1.1; } - .tiptap code { background-color: rgba(97, 97, 97, 0.1); color: #616161; } - .tiptap pre { background: #0D0D0D; color: #FFF; @@ -28,24 +18,22 @@ padding: 0.75rem 1rem; border-radius: 0.5rem; } - .tiptap pre code { color: inherit; padding: 0; background: none; font-size: 0.8rem; } - .tiptap img { max-width: 100%; height: auto; } - -.tiptap hr { - margin: 1rem 0; -} - .tiptap blockquote { padding-left: 1rem; border-left: 2px solid rgba(13, 13, 13, 0.1); -} \ No newline at end of file +} +.tiptap hr { + border: none; + border-top: 2px solid rgba(13, 13, 13, 0.1); + margin: 2rem 0; +}