Files
echospace/src/components/app/ProjectSettings/ProjectSettings.tsx
2024-12-25 19:03:01 +01:00

357 lines
13 KiB
TypeScript

'use client';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import type { Project, User, UserProject } from '@prisma/client';
import { UniversalForm } from '../UniversalForm/UniversalForm';
import {
customData,
editProject,
githubSettings,
githubTestIssue,
ratelimitChange,
} from '@/lib/forms/actions';
import { bodyGen, bodyGenNoIdent } from '@/lib/bodyGen';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import GithubRepoChooser from '../GithubRepoChooser/GithubRepoChooser';
import React from 'react';
import Link from 'next/link';
import InviteCodeViewer from '../InviteCodeViewer/InviteCodeViewer';
import ProjectTeamUsers from '../ProjectTeamUsers/ProjectTeamUsers';
import { useSession } from '@/lib/providers/SessionProvider';
export default function ProjectSettings(project: ProjectWithUsers) {
const { user } = useSession();
const [ghRepo, setGhRepo] = React.useState('');
const [ghInstallationId, setGhInstallationId] = React.useState('');
const [hasSubmitted, setHasSubmitted] = React.useState(false);
const apiUrl = `https://${window.location.hostname}/api/feedback/${project.id}`;
React.useEffect(() => {
setHasSubmitted(project.github !== '');
}, [project.github]);
return (
<div className="w-full max-w-6xl mx-auto p-4">
<Breadcrumb className="pb-5">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/dashboard">Dashboard</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href={`/project/${project.id}`}>{project.name}</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Settings</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="mb-6">
<h1 className="pb-2">Project Settings</h1>
<p className="text-muted-foreground">Manage your project configuration and preferences</p>
</div>
<Tabs defaultValue="project" className="w-full">
<TabsList className="mb-4">
<TabsTrigger value="project">Project</TabsTrigger>
{user?.id === project.UserProject.find((u) => u.isOwner)?.userId && (
<TabsTrigger value="github">Github</TabsTrigger>
)}
<TabsTrigger value="api">API</TabsTrigger>
</TabsList>
<TabsContent value="project">
<div className="grid gap-4">
<Card>
<CardHeader>
<CardTitle>Project Details</CardTitle>
<CardDescription>Basic information about your project</CardDescription>
</CardHeader>
<CardContent>
<UniversalForm
fields={[
{
name: 'name',
label: 'Project Name',
placeholder: 'My Awesome Project',
value: project.name,
},
{
name: 'description',
label: 'Description',
placeholder: 'A brief description of your project',
value: project.description,
},
{
name: 'id',
label: 'ID',
type: 'hidden',
value: project.id,
},
]}
schemaName={'projectSettings'}
action={editProject}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Custom Data</CardTitle>
<CardDescription>Custom fields to store additional data</CardDescription>
</CardHeader>
<CardContent>
<UniversalForm
fields={[
{
name: 'data',
label: 'Custom data',
value: project.customData.join(','),
description: 'Comma separated list of custom data',
},
{
name: 'id',
label: 'ID',
type: 'hidden',
value: project.id,
},
]}
schemaName={'customData'}
action={customData}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Your team</CardTitle>
<CardDescription>Invite people to join the project!</CardDescription>
</CardHeader>
<CardContent>
<InviteCodeViewer code={project.inviteCode} />
<ProjectTeamUsers UserProject={project.UserProject} />
</CardContent>
</Card>
{/* <Card>
<CardHeader>
<CardTitle>Project Deletion</CardTitle>
<CardDescription>Permanently delete your project</CardDescription>
</CardHeader>
<CardContent>
<p className="text-muted-foreground text-sm">
This action is irreversible. All feedback and settings will be lost.
</p>
<UniversalForm
fields={[
{
name: 'id',
label: 'ID',
type: 'hidden',
value: project.id,
},
]}
schemaName={'deleteProject'}
action={editProject}
submitText="Delete Project"
submitClassname="!mt-0"
/>
</CardContent>
</Card> */}
</div>
</TabsContent>
{user?.id === project.UserProject.find((u) => u.isOwner)?.userId && (
<TabsContent value="github" className="grid gap-4">
<Card>
<CardHeader>
<CardTitle>Github Integration</CardTitle>
<CardDescription>Connect your project to Github</CardDescription>
</CardHeader>
<CardContent>
<GithubRepoChooser
onSelect={(repo, id) => {
setGhRepo(`https://github.com/${repo}`);
setGhInstallationId(id);
}}
selected={project.github ? project.github.replace('https://github.com/', '') : ''}
/>
<p className="text-muted-foreground text-xs mt-2">
Not the results you were expecting? You may have not allowed your user in the{' '}
<Link
href="https://github.com/apps/echospacedev/installations/new"
target="_blank"
className="text-primary"
>
installation settings
</Link>
.
</p>
<UniversalForm
fields={[
{
name: 'github',
label: 'GitHub Repository',
type: 'hidden',
value: ghRepo,
},
{
name: 'installationId',
label: 'Installation ID',
type: 'hidden',
value: ghInstallationId,
},
{
name: 'id',
label: 'ID',
type: 'hidden',
value: project.id,
},
]}
key={ghRepo}
schemaName={'githubSettings'}
action={githubSettings}
onActionComplete={() => setHasSubmitted(true)}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Issue submission testing</CardTitle>
<CardDescription>Make sure your setup works!</CardDescription>
</CardHeader>
<CardContent>
{hasSubmitted ? (
<UniversalForm
fields={[
{
name: 'id',
label: 'ID',
type: 'hidden',
value: project.id,
},
]}
schemaName={'githubTestIssue'}
action={githubTestIssue}
submitText="Create test issue"
submitClassname="!mt-0"
/>
) : (
<p className="text-muted-foreground text-sm">
You need to connect your project to a GitHub repository before you can test
issue submission.
</p>
)}
</CardContent>
</Card>
</TabsContent>
)}
<TabsContent value="api" className="space-y-5">
<Card>
<CardHeader>
<CardTitle>Making a Request</CardTitle>
<CardDescription>Instructions on how to make an API request</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<h3 className="text-sm font-medium">Endpoint</h3>
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
{apiUrl}
</code>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium">Method</h3>
<p className="text-sm">POST</p>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium">Body</h3>
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm block whitespace-pre">
{bodyGen(project.customData)}
</code>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium">Example Request (cURL)</h3>
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm block whitespace-pre overflow-x-auto">
{stripIndents`curl -X POST \\
-d '${bodyGenNoIdent(project.customData)}' \\
${apiUrl}`}
</code>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Rate limiting</CardTitle>
<CardDescription>Manage your API rate limits.</CardDescription>
</CardHeader>
<CardContent>
<UniversalForm
fields={[
{
name: 'requests',
label: 'Requests',
placeholder: 'The amount of requests you want to limit',
value: project.rateLimitReq.toString(),
type: 'number',
},
{
name: 'duration',
label: 'Duration',
placeholder: 'In seconds',
value: project.rateLimitTime.toString(),
type: 'number',
},
{
name: 'id',
label: 'ID',
type: 'hidden',
value: project.id,
},
]}
schemaName={'ratelimitChange'}
action={ratelimitChange}
/>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
function stripIndents(strings: TemplateStringsArray, ...values: any[]) {
const fullString = strings.reduce((accumulator, str, i) => {
const value = values[i] ? values[i] : '';
return accumulator + str + value;
}, '');
const lines = fullString.split('\n');
// Find minimum indentation level (excluding empty lines)
const minIndent = lines
.filter((line) => line.trim().length > 0)
.reduce((min, line) => {
const indent = line.match(/^\s*/)![0].length;
return indent < min ? indent : min;
}, Infinity);
// Apply minimum indent + 2 spaces to each line
return lines
.map((line) => line.trim())
.map((line) => (line ? ' '.repeat(minIndent + 2) + line : line))
.join('\n')
.trim();
}
interface ProjectWithUsers extends Project {
UserProject: (UserProject & {
user: User;
})[];
}