diff --git a/apps/platform/src/app/(main)/page.tsx b/apps/platform/src/app/(main)/page.tsx index 9a9cb2ac..af66c101 100644 --- a/apps/platform/src/app/(main)/page.tsx +++ b/apps/platform/src/app/(main)/page.tsx @@ -38,6 +38,7 @@ import { DialogTrigger } from '@/components/ui/dialog' import ControllerInstance from '@/lib/controller-instance' +import { Textarea } from '@/components/ui/textarea' export default function Index(): JSX.Element { const [isSheetOpen, setIsSheetOpen] = useState(false) @@ -144,10 +145,12 @@ export default function Index(): JSX.Element { - + {isProjectEmpty ? null : ( + + )}
@@ -190,8 +193,8 @@ export default function Index(): JSX.Element { > Description - { setNewProjectData((prev) => ({ @@ -217,7 +220,10 @@ export default function Index(): JSX.Element { onChange={(e) => { setNewProjectData((prev) => ({ ...prev, - envName: e.target.value + environments: (prev.environments || []).map( + (env, index) => + index === 0 ? { ...env, name: e.target.value } : env + ) })) }} placeholder="Your project default environment name" @@ -232,13 +238,18 @@ export default function Index(): JSX.Element { > Env. Description - { setNewProjectData((prev) => ({ ...prev, - envDescription: e.target.value + environments: (prev.environments || []).map( + (env, index) => + index === 0 + ? { ...env, description: e.target.value } + : env + ) })) }} placeholder="Detailed description about your environment" @@ -265,7 +276,7 @@ export default function Index(): JSX.Element { })) }} > - + @@ -334,7 +345,9 @@ export default function Index(): JSX.Element {
Create a file and start setting up your environment and secret keys
- +
)} diff --git a/apps/platform/src/app/(main)/project/[project]/layout.tsx b/apps/platform/src/app/(main)/project/[project]/layout.tsx index 9edfb0bb..b9470c5c 100644 --- a/apps/platform/src/app/(main)/project/[project]/layout.tsx +++ b/apps/platform/src/app/(main)/project/[project]/layout.tsx @@ -1,8 +1,16 @@ 'use client' +import type { MouseEvent } from 'react' import { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' import { AddSVG } from '@public/svg/shared' -import type { Project } from '@keyshade/schema' +import type { + ClientResponse, + CreateVariableRequest, + Environment, + GetAllEnvironmentsOfProjectResponse, + Project +} from '@keyshade/schema' +import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Dialog, @@ -15,6 +23,13 @@ import { import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import ControllerInstance from '@/lib/controller-instance' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' interface DetailedProjectPageProps { params: { project: string } @@ -31,12 +46,78 @@ function DetailedProjectPage({ const [key, setKey] = useState('') // eslint-disable-next-line @typescript-eslint/no-unused-vars -- will be used later const [value, setValue] = useState('') - const [currentProject, setCurrentProject] = useState() + const [isOpen, setIsOpen] = useState(false) + const [newVariableData, setNewVariableData] = useState({ + variableName: '', + note: '', + environmentName: '', + environmentValue: '' + }) + const [availableEnvironments, setAvailableEnvironments] = useState< + Environment[] + >([]) const searchParams = useSearchParams() const tab = searchParams.get('tab') ?? 'rollup-details' + const addVariable = async (e: MouseEvent) => { + e.preventDefault() + + if (!currentProject) { + throw new Error("Current project doesn't exist") + } + + const request: CreateVariableRequest = { + name: newVariableData.variableName, + projectSlug: currentProject.slug, + entries: newVariableData.environmentValue + ? [ + { + value: newVariableData.environmentValue, + environmentSlug: newVariableData.environmentName + } + ] + : undefined, + note: newVariableData.note + } + + const { success, error } = + await ControllerInstance.getInstance().variableController.createVariable( + request, + {} + ) + + if (success) { + toast.success('Variable added successfully', { + // eslint-disable-next-line react/no-unstable-nested-components -- we need to nest the description + description: () => ( +

+ The variable has been added to the project +

+ ) + }) + } + + if (error) { + if (error.statusCode === 409) { + toast.error('Variable name already exists', { + // eslint-disable-next-line react/no-unstable-nested-components -- we need to nest the description + description: () => ( +

+ Variable name is already there, kindly use different one. +

+ ) + }) + } else { + // eslint-disable-next-line no-console -- we need to log the error that are not in the if condition + console.error(error) + } + } + + setIsOpen(false) + } + useEffect(() => { async function getProjectBySlug() { const { success, error, data } = @@ -56,60 +137,218 @@ function DetailedProjectPage({ getProjectBySlug() }, [params.project]) + useEffect(() => { + const getAllEnvironments = async () => { + if (!currentProject) { + return + } + + const { + success, + error, + data + }: ClientResponse = + await ControllerInstance.getInstance().environmentController.getAllEnvironmentsOfProject( + { projectSlug: currentProject.slug }, + {} + ) + + if (success && data) { + setAvailableEnvironments(data.items) + } else { + // eslint-disable-next-line no-console -- we need to log the error + console.error(error) + } + } + + getAllEnvironments() + }, [currentProject]) + return (
{currentProject?.name}
- - - - - - - Add a new secret - - Add a new secret to the project. This secret will be encrypted - and stored securely. - - -
-
-
- - { - setKey(e.target.value) - }} - placeholder="Enter the name of the secret" - /> + {tab === 'secret' && ( + + + + + + + Add a new secret + + Add a new secret to the project. This secret will be encrypted + and stored securely. + + +
+
+
+ + { + setKey(e.target.value) + }} + placeholder="Enter the name of the secret" + /> +
+
+ + { + setValue(e.target.value) + }} + placeholder="Enter the value of the secret" + /> +
-
- - { - setValue(e.target.value) - }} - placeholder="Enter the value of the secret" - /> +
+
-
- + +
+ )} + {tab === 'variable' && ( + + + + + + + + Add a new variable + + + Add a new variable to the project + + + +
+
+
+ + + setNewVariableData({ + ...newVariableData, + variableName: e.target.value + }) + } + placeholder="Enter the key of the variable" + value={newVariableData.variableName} + /> +
+ +
+ + + setNewVariableData({ + ...newVariableData, + note: e.target.value + }) + } + placeholder="Enter the note of the secret" + value={newVariableData.note} + /> +
+ +
+
+ + +
+ +
+ + + setNewVariableData({ + ...newVariableData, + environmentValue: e.target.value + }) + } + placeholder="Environment Value" + value={newVariableData.environmentValue} + /> +
+
+ +
+ +
+
-
- -
+ +
+ )}
{tab === 'secret' && secret} diff --git a/apps/platform/src/components/dashboard/projectCard/index.tsx b/apps/platform/src/components/dashboard/projectCard/index.tsx index 4cd0ad52..1818c51f 100644 --- a/apps/platform/src/components/dashboard/projectCard/index.tsx +++ b/apps/platform/src/components/dashboard/projectCard/index.tsx @@ -25,6 +25,7 @@ function ProjectCard({ }: ProjectCardProps): JSX.Element { const { id, + slug, name, description, environmentCount, @@ -69,7 +70,7 @@ function ProjectCard({
diff --git a/apps/platform/src/components/ui/textarea.tsx b/apps/platform/src/components/ui/textarea.tsx new file mode 100644 index 00000000..2a10e86f --- /dev/null +++ b/apps/platform/src/components/ui/textarea.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Textarea = React.forwardRef< + HTMLTextAreaElement, + React.ComponentProps<'textarea'> +>(({ className, ...props }, ref) => { + return ( +