diff --git a/apps/platform/public/svg/shared/Error.svg b/apps/platform/public/svg/shared/Error.svg new file mode 100644 index 00000000..6b5ae5ea --- /dev/null +++ b/apps/platform/public/svg/shared/Error.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/platform/public/svg/shared/Vector.svg b/apps/platform/public/svg/shared/Vector.svg new file mode 100644 index 00000000..c7aad118 --- /dev/null +++ b/apps/platform/public/svg/shared/Vector.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/platform/public/svg/shared/index.ts b/apps/platform/public/svg/shared/index.ts index 07133a8c..0d09dde2 100644 --- a/apps/platform/public/svg/shared/index.ts +++ b/apps/platform/public/svg/shared/index.ts @@ -7,6 +7,9 @@ import SettingsSVG from './settings.svg' import ThreeDotOptionSVG from './3dotOption.svg' import AddSVG from './add.svg' import LoadingSVG from './loading.svg' +import MessageSVG from './message.svg' +import VectorSVG from './vector.svg' +import ErrorSVG from './Error.svg' export { DropdownSVG, @@ -17,5 +20,8 @@ export { SettingsSVG, ThreeDotOptionSVG, AddSVG, - LoadingSVG + LoadingSVG, + MessageSVG, + VectorSVG, + ErrorSVG } diff --git a/apps/platform/public/svg/shared/message.svg b/apps/platform/public/svg/shared/message.svg new file mode 100644 index 00000000..f7757498 --- /dev/null +++ b/apps/platform/public/svg/shared/message.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/platform/src/app/(main)/project/[project]/@variable/page.tsx b/apps/platform/src/app/(main)/project/[project]/@variable/page.tsx index a4362de4..4557192f 100644 --- a/apps/platform/src/app/(main)/project/[project]/@variable/page.tsx +++ b/apps/platform/src/app/(main)/project/[project]/@variable/page.tsx @@ -1,7 +1,174 @@ -import React from 'react' +'use client' -function VariablePage(): React.JSX.Element { - return
VariablePage
+import { useEffect, useState } from 'react' +import { Button } from '@/components/ui/button' +import { VariableController } from '@keyshade/api-client' +import { + ClientResponse, + GetAllVariablesOfProjectResponse, + Project, +} from '@keyshade/schema' +import { FolderSVG } from '@public/svg/dashboard' +import { MessageSVG } from '@public/svg/shared' +import { ChevronDown, ChevronUp, MessageSquare } from 'lucide-react' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/ui/table' + +interface VariablePageProps { + currentProject: Project | undefined + // availableEnvironments: Environment[] +} + + +function VariablePage({ + currentProject + // availableEnvironments +}: VariablePageProps): React.JSX.Element { + + const [allVariables, setAllVariables] = useState([]) + // Holds the currently open section ID + const [openSections, setOpenSections] = useState>(new Set()) + + const toggleSection = (id: string) => { + setOpenSections((prev) => { + const newSet = new Set(prev) + if (newSet.has(id)) { + newSet.delete(id) + } else { + newSet.add(id) + } + return newSet + }) + } + + useEffect(() => { + const variableController = new VariableController( + process.env.NEXT_PUBLIC_BACKEND_URL + ) + + const getVariables = async () => { + const { + success, + error, + data + }: ClientResponse = + await variableController.getAllVariablesOfProject( + { projectSlug: currentProject?.slug }, + {} + ) + + if (success && data) { + setAllVariables(data.items) + } else { + // eslint-disable-next-line no-console -- we need to log the error + console.error(error) + } + } + + getVariables() + }, [currentProject]) + + return ( +
+ {/* Showing this when there are no variables present */} + {allVariables.length === 0 ? ( +
+ + +
+

+ Declare your first variable +

+

+ Declare and store a variable against different environments +

+
+ + +
+ ) : ( +
+ {allVariables.map((variable) => ( + toggleSection(variable.variable.id)} + className="w-full" + > + +
+ + {variable.variable.name} + + +
+
+
+
+ {(() => { + const days = Math.ceil(Math.abs(new Date().getTime() - new Date(variable.variable.createdAt).getTime()) / (1000 * 60 * 60 * 24)); + return `${days} ${days === 1 ? 'day' : 'days'} ago by`; + })()} +
+
+
+ {variable.variable.lastUpdatedBy.name.split(' ')[0]} +
+ + + + {variable.variable.lastUpdatedBy.name.charAt(0).toUpperCase() + variable.variable.lastUpdatedBy.name.slice(1, 2).toLowerCase()} + + +
+
+ +
+
+ + {variable.values ? ( + + + + Environment + Value + + + + {variable.values.map((env) => ( + + + {env.environment.name} + + + {env.value} + + + ))} + +
+ ) : ( +

+ No content available for this section. +

+ )} +
+
+ ))} +
+ )} +
+ ) } export default VariablePage diff --git a/apps/platform/src/app/(main)/project/[project]/layout.tsx b/apps/platform/src/app/(main)/project/[project]/layout.tsx index 8a747ac4..23f5b560 100644 --- a/apps/platform/src/app/(main)/project/[project]/layout.tsx +++ b/apps/platform/src/app/(main)/project/[project]/layout.tsx @@ -1,7 +1,8 @@ 'use client' + import { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' -import { AddSVG } from '@public/svg/shared' +import { AddSVG, ErrorSVG, VectorSVG } from '@public/svg/shared' import { Button } from '@/components/ui/button' import { Dialog, @@ -11,10 +12,30 @@ import { DialogTitle, DialogTrigger } from '@/components/ui/dialog' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -import type { Project } from '@/types' -import { Projects } from '@/lib/api-functions/projects' +import { + ProjectController, + EnvironmentController, + VariableController +} from '@keyshade/api-client' +import { + ClientResponse, + CreateVariableRequest, + Environment, + GetAllEnvironmentsOfProjectResponse, + Project +} from '@keyshade/schema' +import VariablePage from './@variable/page' +import { toast } from "sonner" +import { Toaster } from '@/components/ui/sonner' interface DetailedProjectPageProps { params: { project: string } @@ -31,84 +52,339 @@ 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 searchParams = useSearchParams() const tab = searchParams.get('tab') ?? 'rollup-details' useEffect(() => { - Projects.getProjectbyID(params.project) - .then((project) => { - setCurrentProject(project) - }) - .catch((error) => { + const getCurrentProject = async () => { + const projectController = new ProjectController( + process.env.NEXT_PUBLIC_BACKEND_URL + ) + + const { success, error, data } = await projectController.getProject( + { projectSlug: params.project }, + {} + ) + + if (success && data) { + setCurrentProject(data as Project) + } else { // eslint-disable-next-line no-console -- we need to log the error console.error(error) - }) + } + } + + getCurrentProject() }, [params.project]) + + //VARIABLES PART + const [isOpen, setIsOpen] = useState(false) + const [newVariableData, setNewVariableData] = useState({ + variableName: '', + note: '', + environmentName: '', + environmentValue: '' + }) + const [availableEnvironments, setAvailableEnvironments] = useState< + Environment[] + >([]) + + + const addVariable = async (e: any) => { + + e.preventDefault() + + const variableController = new VariableController( + process.env.NEXT_PUBLIC_BACKEND_URL + ) + + const request: CreateVariableRequest = { + name: newVariableData.variableName, + projectSlug: currentProject?.slug as string, + entries: newVariableData.environmentValue + ? [ + { + value: newVariableData.environmentValue, + environmentSlug: newVariableData.environmentName + } + ] + : undefined, + note: newVariableData.note + } + + const { success, error, data } = await variableController.createVariable( + request, + {} + ) + + if( success ){ + toast( +
+
+ +
+

Variable created successfully

+

You created new variable

+
+
+
, { + style: { height: '4.5rem', width: '23.438rem', borderRadius: '0.375rem', backgroundColor: '#022C22', color: '#6EE7B7', overflow: 'hidden' } + }) + } + if( error ){ + toast( +
+
+ +
+

Variable name already exists

+

Variable name is already there, kindly use different one

+
+
+
, { + style: { height: '4.5rem', width: '23.438rem', borderRadius: '0.375rem', backgroundColor: '#450A0A', color: '#E92D1F', overflow: 'hidden' } + }) + } + + setNewVariableData({ + variableName: '', + note: '', + environmentName: '', + environmentValue: '' + }) + + setIsOpen(false) + } + + useEffect(() => { + const getAllEnvironments = async () => { + const environmentController = new EnvironmentController( + process.env.NEXT_PUBLIC_BACKEND_URL + ) + + const { + success, + error, + data + }: ClientResponse = + await 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]) + + useEffect(() => { + console.log('Value inside the env state: ', availableEnvironments) + }, [availableEnvironments]) + return (
-
+
{currentProject?.name}
- - - + {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" + /> +
+
+
+ +
+
+
+
+ )} + + {/* {tab === 'variable' && } */} + { tab === 'variable' && ( + + + - + - Add a new secret - - Add a new secret to the project. This secret will be encrypted - and stored securely. - + +
+

+ Add a new variable +

+

+ Add a new variable to the project +

+
+
-
-
-
- + +
+
+
+ { - setKey(e.target.value) - }} - placeholder="Enter the name of the secret" + id="variable-name" + placeholder="Enter the key of the variable" + value={newVariableData.variableName} + onChange={(e) => + setNewVariableData({ + ...newVariableData, + variableName: e.target.value + }) + } + className="h-[2.75rem] w-[20rem] border-0 bg-[#2a2a2a] text-gray-300 placeholder:text-gray-500" />
-
- + +
+ { - setValue(e.target.value) - }} - placeholder="Enter the value of the secret" + id="variable-name" + placeholder="Enter the note of the secret" + value={newVariableData.note} + onChange={(e) => + setNewVariableData({ + ...newVariableData, + note: e.target.value + }) + } + className="h-[2.75rem] w-[20rem] border-0 bg-[#2a2a2a] text-gray-300 placeholder:text-gray-500" />
-
-
- -
+ +
+
+ + +
+ +
+ + + setNewVariableData({ + ...newVariableData, + environmentValue: e.target.value + }) + } + className="h-[2.75rem] w-[13.5rem] border-0 bg-[#2a2a2a] text-gray-300 placeholder:text-gray-500" + /> +
+
+ +
+ +
+
-
+
+ )}
-
+ +
{tab === 'secret' && secret} - {tab === 'variable' && variable} + {tab === 'variable' && } + {/* {tab === 'variable' && variable} */}
+
) } export default DetailedProjectPage + 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/collapsible.tsx b/apps/platform/src/components/ui/collapsible.tsx new file mode 100644 index 00000000..28dd1567 --- /dev/null +++ b/apps/platform/src/components/ui/collapsible.tsx @@ -0,0 +1,12 @@ +"use client" + +import * as React from "react" +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } \ No newline at end of file