feat: implemented organization profile

This commit is contained in:
Ruben 2025-03-28 15:48:25 +01:00
parent 3e1cf17b2d
commit 98ba95dc01
7 changed files with 241 additions and 148 deletions

View File

@ -16,134 +16,143 @@ export default function Profile() {
const segments = pathname.split("/"); const segments = pathname.split("/");
const uid = segments[segments.length - 1]; const uid = segments[segments.length - 1];
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const { data: companyInfos, isLoading } = useQuery({ const { data: companyInfos, isLoading } = useQuery({
enabled: status === 'authenticated', enabled: status === 'authenticated',
queryKey: ["stats", session?.user.access_token], queryKey: ["companyStats", session?.user.access_token],
queryFn: async () => { queryFn: async () => {
try { try {
const response = await axios.get( const response = await axios.get(
`https://private-docs-api.intside.co/companies/${uid}`, { `https://private-docs-api.intside.co/companies/${uid}`, {
headers: { headers: {
'Authorization': `Bearer ${session?.user.access_token}` 'Authorization': `Bearer ${session?.user.access_token}`
},
params: {
details: true
} }
} }
) );
if (response.data) { if (response.data) {
return response.data as CompanyById return response.data as CompanyById;
} }
} catch (error: any) { } catch (error: any) {
console.error(error) console.error(error);
} }
} }
}) });
const adminId = companyInfos?.owner
/*
const adminId = companyInfos?.owner;
console.log('will run the admin request');
const { data: adminInfos } = useQuery({ const { data: adminInfos } = useQuery({
enabled: status === 'authenticated', enabled: !!adminId && status === 'authenticated', // Only run when adminId is available
queryKey: ["stats", session?.user.access_token], queryKey: ["admin", adminId], // Ensure adminId is used in the query key
queryFn: async () => { queryFn: async () => {
console.log('running the admin request');
//console.log('url :', `https://private-docs-api.intside.co/users/${adminId}`);
try { try {
const response = await axios.get( const response = await axios.get(
`https://private-docs-api.intside.co/users/${companyInfos}`, { `https://private-docs-api.intside.co/users/${adminId}`, { // Use adminId instead of companyInfos
headers: { headers: {
'Authorization': `Bearer ${session?.user.access_token}` 'Authorization': `Bearer ${session?.user.access_token}`
} }
} }
) );
if (response.data) { if (response.data) {
return response.data.data as Owner return response.data as Owner;
} }
} catch (error: any) { } catch (error) {
console.error(error) console.error(error);
} }
} }
})
console.log(`https://private-docs-api.intside.co/users/${companyInfos}`); });
*/
return ( return (
<> <>
{/* {companyInfos[0]?.id} */} {/* {companyInfos[0]?.id} */}
<div className="p-container "> <div className="p-container ">
<div className="r-flex-column"> <div className="r-flex-column">
<div className="r-flex-between items-center bg-bluegray p-[24px] m-0 "> <div className="r-flex-between items-center bg-bluegray p-[24px] m-0 ">
<div className=""></div> <div className=""></div>
<h2 className="admin-name text-[20px]" >{companyInfos?.name || "Pentatonic"}</h2> <h2 className="admin-name text-[20px]" >{companyInfos?.name || "Pentatonic"}</h2>
<button type="button" className="update-profile-name cta"> <Link href={`http://localhost:3000/admin/organizations/${uid}/update`} type="Link" className="update-profile-name cta">
Modifier Modifier
</button> </Link>
</div> </div>
<div className="r-flex-column p-[32px] r-gap-24"> <div className="r-flex-column p-[32px] r-gap-24 items-center lg:items-start ">
<h3 className="font-semibold">Détails de l'admin</h3> <h3 className="font-semibold">Détails de l'admin</h3>
<div className="r-flex gap-[60px]"> <div className="r-flex flex-wrap r-r-gap-12 gap-[12px]">
<div className="r-flex-between items-center rr-gap-12 w-max"> <div className="r-flex-between items-center w-max">
<div className="icon-rounded"> <div className="icon-rounded">
<Image src={icons.mailIcon} alt="E-mail" /> <Image src={icons.mailIcon} alt="E-mail" />
</div> </div>
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] admin-card r-gap-2">
<h4 className="email-label font-semibold">Adresse e-mail</h4> <h4 className="email-label font-semibold">Adresse e-mail</h4>
<p className="email">{adminInfos?.email || "email"}</p> <p className="email">{companyInfos?.owner?.email || "email"}</p>
</div> </div>
</div> </div>
<div className="r-flex-between items-center rr-gap-12 w-max"> <div className="r-flex-between items-center w-max">
<div className="icon-rounded"> <div className="icon-rounded">
<Image src={icons.personalCard} alt="E-mail" /> <Image src={icons.personalCard} alt="E-mail" />
</div> </div>
<div className="r-flex gap-[60px]"> <div className="r-flex gap-[12px] admin-card">
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] r-gap-2">
<h4 className="surname-label font-semibold">Prénom</h4> <h4 className="surname-label font-semibold">Prénom</h4>
<p className="surname">{adminInfos?.first_name || "nom"}</p> <p className="surname">{companyInfos?.owner?.first_name || "nom"}</p>
</div> </div>
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] r-gap-2">
<h4 className="name-label font-semibold">Nom</h4> <h4 className="name-label font-semibold">Nom</h4>
<p className="name">{adminInfos?.last_name || "nom"}</p> <p className="name">{companyInfos?.owner?.last_name || "nom"}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<hr /> <hr />
<div className="r-flex-column p-[32px] r-gap-24"> <div className="r-flex p-[32px] r-gap-24">
<div className="r-flex-between gap-[84px]"> <div className="admin-infos r-flex flex-wrap justifyc-center lg:justifyc-start ">
<div className="r-flex-between items-center rr-gap-12 w-max"> <div className="r-flex-between items-center w-max">
<div className="icon-rounded"> <div className="icon-rounded">
<Image src={icons.userGroupBlue} alt="Documents" /> <Image src={icons.userGroupBlue} alt="Documents" />
</div> </div>
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] admin-card r-gap-2">
<h4 className="users-label font-semibold">Documents</h4> <h4 className="users-label font-semibold">Documents</h4>
<p className="r-secondary users">123</p> <p className="r-secondary users">{companyInfos?.total_documents || "0"}</p>
</div> </div>
</div> </div>
<div className="r-flex-between items-center rr-gap-12 w-max"> <div className="r-flex-between items-center w-max">
<div className="icon-rounded"> <div className="icon-rounded">
<Image src={icons.userGroupBlue} alt="Documents" /> <Image src={icons.userGroupBlue} alt="Documents" />
</div> </div>
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] admin-card r-gap-2">
<h4 className="users-label font-semibold">Utilisateurs</h4> <h4 className="users-label font-semibold">Utilisateurs</h4>
<p className="r-secondary users">24</p> <p className="r-secondary users">{companyInfos?.total_users || "0"}</p>
</div> </div>
</div> </div>
<div className="r-flex-between items-center rr-gap-12 w-max"> <div className="r-flex-between items-center w-max">
<div className="icon-rounded"> <div className="icon-rounded">
<Image src={icons.maximizeIcon} alt="Fichiers" /> <Image src={icons.maximizeIcon} alt="Fichiers" />
</div> </div>
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] admin-card r-gap-2">
<h4 className="files-size-label font-semibold">Taille des fichiers</h4> <h4 className="files-size-label font-semibold">Taille des fichiers</h4>
<p className="r-secondary files-size">3.41GB</p> <p className="r-secondary files-size">{companyInfos?.total_documents_sizes + " "}GB</p>
</div> </div>
</div> </div>
<div className="r-flex-between items-center rr-gap-12 w-max"> <div className="r-flex-between items-center w-max">
<div className="icon-rounded"> <div className="icon-rounded">
<Image src={icons.timerIcon} alt="Horlorge" /> <Image src={icons.timerIcon} alt="Horlorge" />
</div> </div>
<div className="r-flex-column p-[14px] r-gap-2"> <div className="r-flex-column p-[14px] admin-card r-gap-2">
<h4 className="last-connexion-label font-semibold">Dernière utilisation</h4> <h4 className="last-connexion-label font-semibold">Dernière utilisation</h4>
<p className="r-secondary last-connexion">26 Jan 2024 - 14h15</p> <p className="r-secondary last-connexion">{companyInfos?.last_use || "-"}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,57 +3,72 @@ import { icons } from "#/assets/icons"
import Image from "next/image" import Image from "next/image"
import axios from "axios"; import axios from "axios";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useQuery } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Company } from "#/types"; import { Company, CompanyById } from "#/types";
import FloatingLabelInput from "#/components/floatingLabelInput"; import FloatingLabelInput from "#/components/floatingLabelInput";
import { usePathname } from "next/navigation";
import Link from "next/link";
import Form from "#/components/form/form";
import { adminSchema } from "#/schema/loginSchema";
export default function Update() { export default function Update() {
const pathname = usePathname();
const segments = pathname.split("/");
const uid = segments[segments.length - 2];
const queryClient = useQueryClient()
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const { data: company, isLoading } = useQuery({ const { data: companyInfos, refetch, isLoading } = useQuery({
enabled: status === 'authenticated', enabled: status === 'authenticated',
queryKey: ["stats", session?.user.access_token], queryKey: ["companyStats", session?.user.access_token],
queryFn: async () => { queryFn: async () => {
try { try {
const response = await axios.get( const response = await axios.get(
'https://private-docs-api.intside.co/companies', { `https://private-docs-api.intside.co/users/${uid}`, {
headers: { headers: {
'Authorization': `Bearer ${session?.user.access_token}` 'Authorization': `Bearer ${session?.user.access_token}`
},
params: {
details: true
} }
} }
) );
if (response.data) { if (response.data) {
return response.data.data as Company return response.data as CompanyById;
} }
} catch (error: any) { } catch (error: any) {
console.error(error) console.error(error);
} }
} }
}) });
const { data: adminOrganization } = useQuery({ const { mutate, isPending } = useMutation({
enabled: status === 'authenticated', mutationFn: async (id: string) => {
queryKey: ["stats", session?.user.access_token],
queryFn: async () => {
try { try {
const response = await axios.get( const response = await axios.delete(
'https://private-docs-api.intside.co/users/me', { `https://private-docs-api.intside.co/companies/${id}/`, {
headers: { headers: {
'Authorization': `Bearer ${session?.user.access_token}` 'Authorization': `Bearer ${session?.user.access_token}`
} }
} }
) )
if (response.data) { if (response.status === 200 || response.status === 201) {
return response.data as Company console.log('Suppresion réussie !')
} }
} catch (error: any) { } catch (error) {
console.error(error) console.error(error)
} }
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["companies"] })
refetch()
} }
}) })
@ -62,28 +77,61 @@ export default function Update() {
<> <>
{/* { company.map() } */} {/* { company.map() } */}
{/* <Form
title="Connexion"
className="grid grid-cols-2"
fields={[
{
label: "Nom de lorganisation",
name: "company",
type: "text",
placeholder: "Nom de l'organisation",
defaultValue: companyInfos?.name
},
{
label: "Nom de ladmin",
name: "last_name",
type: "text",
placeholder: "Nom de l'admin",
defaultValue: companyInfos?.owner.last_name
},
{
label: "Prénom de ladmin",
name: "first_name",
type: "text",
placeholder: "Prénom de l'admin",
defaultValue: companyInfos?.owner.first_name
}
]}
submit={mutate}
schema={adminSchema}
child={<button type="submit" className="btn-auth">Connexion</button>}
/> */}
<div className="p-container "> <div className="p-container ">
<div className="r-flex-column"> <div className="r-flex-column">
<div className="r-flex-between items-center bg-bluegray p-[24px] m-0 "> <div className="r-flex-between items-center bg-bluegray p-[24px] m-0 ">
<div className=""></div> <div className=""></div>
<h2 className="admin-name text-[20px]" >Pentatonic</h2> <h2 className="admin-name text-[20px]" >{companyInfos?.name || "Pentatonic"}</h2>
<button type="button" className="update-profile-name cta cancel"> <Link href={`http://localhost:3000/admin/organizations/${uid}/`} type="Link" className="cta cancel">
Annuler Annuler
</button> </Link>
</div> </div>
<div className="r-flex-column p-[32px] r-gap-24"> <div className="r-flex-column p-[32px] r-gap-24">
<div className="labels-container "> <div className="labels-container ">
<div className="label-container"> <div className="label-container">
<FloatingLabelInput label="Nom de lorganisation" name="org-name" type="text" placeholder="Intside" defaultValue="" /> <FloatingLabelInput label="Nom de lorganisation" name="org-name" type="text" placeholder="Intside" defaultValue={companyInfos?.name} />
</div> </div>
<div className="label-container"> <div className="label-container">
<FloatingLabelInput label="Adresse e-mail de ladmin" name="email" type="text" placeholder="contact@intside.com" defaultValue={company?.owner.email} /> <FloatingLabelInput label="Adresse e-mail de ladmin" name="email" type="text" placeholder="contact@intside.com" defaultValue={companyInfos?.owner.email} />
</div> </div>
<div className="label-container"> <div className="label-container">
<FloatingLabelInput label="Nom de ladmin" name="admin-name" type="text" placeholder="Company" defaultValue={company?.owner.last_name} /> <FloatingLabelInput label="Nom de ladmin" name="admin-name" type="text" placeholder="Company" defaultValue={companyInfos?.owner.last_name} />
</div> </div>
<div className="label-container"> <div className="label-container">
<FloatingLabelInput label="Prénom de ladmin" name="admin-surname" type="text" placeholder="Intside" defaultValue={company?.owner.last_name} /> <FloatingLabelInput label="Prénom de ladmin" name="admin-surname" type="text" placeholder="Intside" defaultValue={companyInfos?.owner.last_name} />
</div> </div>
</div> </div>
<div className="ctas flex gap-12 "> <div className="ctas flex gap-12 ">

View File

@ -1,51 +1,51 @@
/* Sidebar */ /* Sidebar */
.sidebar{ .sidebar {
border-right: 1px solid var(--cinder); border-right: 1px solid var(--cinder);
position: fixed; position: fixed;
} }
.nav-item .nav-home{ .nav-item .nav-home {
margin-bottom: -10px; margin-bottom: -10px;
} }
.nav-item{ .nav-item {
width: 100%; width: 100%;
height: max-content; height: max-content;
} }
.nav-item.active{ .nav-item.active {
border-right: 2px solid var(--primary); border-right: 2px solid var(--primary);
} }
.nav-item svg{ .nav-item svg {
color: var(--primary)!important; color: var(--primary) !important;
background-color: var(--primary)!important; background-color: var(--primary) !important;
fill: var(--primary)!important; fill: var(--primary) !important;
stroke: var(--primary)!important stroke: var(--primary) !important
} }
.nav-home{ .nav-home {
margin-top: -11px; margin-top: -11px;
margin-bottom: -11px; margin-bottom: -11px;
} }
/* Border */ /* Border */
.icon-border{ .icon-border {
border: 1px solid var(--cinder); border: 1px solid var(--cinder);
padding: 8px; padding: 8px;
border-radius: 12px; border-radius: 12px;
cursor: pointer; cursor: pointer;
} }
.dropdown-menu{ .dropdown-menu {
width: max-content; width: max-content;
background-color: var(--background); background-color: var(--background);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 0 24px #0000001A; box-shadow: 0 0 24px #0000001A;
} }
.dropdown-item a{ .dropdown-item a {
width: max-content; width: max-content;
padding: 10px 20px; padding: 10px 20px;
display: flex; display: flex;
@ -62,7 +62,7 @@
overflow: hidden; overflow: hidden;
} }
.icon-rounded{ .icon-rounded {
width: max-content; width: max-content;
height: max-content; height: max-content;
padding: 8px; padding: 8px;
@ -72,15 +72,37 @@
background-color: var(--bluegray); background-color: var(--bluegray);
border-radius: 100px; border-radius: 100px;
} }
.labels-container{
.labels-container {
min-width: 100%; min-width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 24px; gap: 24px;
} }
.labels-container .label-container{
.labels-container .label-container {
width: 40%; width: 40%;
} }
.labels-container .floating-label div.relative{
.labels-container .floating-label div.relative {
width: 100%; width: 100%;
}
.admin-infos {
gap: 12px;
}
.admin-card {
width: 284px;
}
@media (max-width: 1024px) {
.admin-infos {
/* justify-content: space-between; */
/* gap: 30px; */
}
.admin-card {
/* max-width: 100%; */
}
} }

View File

@ -119,6 +119,9 @@
.r-gap-70{ .r-gap-70{
gap: 70px; gap: 70px;
} }
.r-r-gap-12{
row-gap: 12px;
}
/* Small (SM) */ /* Small (SM) */
@media (min-width: 640px) { @media (min-width: 640px) {
/* Styles for small devices and up */ /* Styles for small devices and up */

View File

@ -5,71 +5,71 @@ import { FormProps } from "#/types"
import { FormEvent, useState } from "react" import { FormEvent, useState } from "react"
export default function Form({ export default function Form({
fields, fields,
submit, submit,
className, className,
child, child,
title, title,
schema schema
} : FormProps) { }: FormProps) {
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
const handleSubmit = (e: FormEvent<HTMLFormElement>) => { const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.currentTarget) const formData = new FormData(e.currentTarget)
const data = Object.fromEntries(formData) const data = Object.fromEntries(formData)
console.log("FORM DATA = ", data) console.log("FORM DATA = ", data)
const result = schema.safeParse(data); const result = schema.safeParse(data);
console.log("ZOD = ", result.error?.format()) console.log("ZOD = ", result.error?.format())
if(!result.success) { if (!result.success) {
const formatedErrors = result.error.format() as Record<string, { _errors?: string[] }>; const formatedErrors = result.error.format() as Record<string, { _errors?: string[] }>;
const newErrors: Record<string, string> = {}; const newErrors: Record<string, string> = {};
Object.keys(formatedErrors).forEach((field) => { Object.keys(formatedErrors).forEach((field) => {
if (field !== "_errors" && formatedErrors[field]._errors?.length) { if (field !== "_errors" && formatedErrors[field]._errors?.length) {
newErrors[field] = formatedErrors[field]._errors[0]; newErrors[field] = formatedErrors[field]._errors[0];
}
});
setErrors(newErrors)
} else {
setErrors({})
submit(result.data)
} }
});
setErrors(newErrors)
} else {
setErrors({})
submit(result.data)
} }
}
return ( return (
<form className={className} onSubmit={handleSubmit}> <form className={className} onSubmit={handleSubmit}>
<div className="flex justify-center text-black"> <div className="flex justify-center text-black">
<p className="text-3xl font-bold">{title}</p> <p className="text-3xl font-bold">{title}</p>
</div>
<div className="flex flex-col gap-8 mt-2">
{
fields.map((item, index) => (
<div key={index}>
<FloatingLabelInput
label={item.label}
name={item.name}
type={item.type}
button={item.button}
defaultValue={item.defaultValue}
options={item.options}
placeholder={item.placeholder}
showPasswordToggle={item.showPasswordToggle}
/>
<span className="text-red-500 text-xs mt-1">{errors[item.name]}</span>
</div> </div>
))
}
{child}
</div>
</form>
<div className="flex flex-col gap-8 mt-2"> )
{
fields.map((item, index) => (
<div key={index}>
<FloatingLabelInput
label={item.label}
name={item.name}
type={item.type}
button={item.button}
defaultValue={item.defaultValue}
options={item.options}
placeholder={item.placeholder}
showPasswordToggle={item.showPasswordToggle}
/>
<span className="text-red-500 text-xs mt-1">{errors[item.name]}</span>
</div>
))
}
{child}
</div>
</form>
)
} }

View File

@ -3,4 +3,12 @@ import { z } from "zod";
export const loginSchema = z.object({ export const loginSchema = z.object({
email: z.string().email("Email invalide"), email: z.string().email("Email invalide"),
password: z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères") password: z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères")
});
export const adminSchema = z.object({
id: z.string().optional(),
last_name: z.string(),
first_name: z.string(),
email: z.string().min(1, "L'email est requis").email("Email invalide"),
organization: z.string().optional(),
}); });

View File

@ -51,11 +51,14 @@ export interface Owner {
last_name: string last_name: string
} }
export interface CompanyById { export interface CompanyById {
id: string id: string
name: string name: string
is_premium: boolean is_premium: boolean
status: string status: string
owner: string owner: Owner
description: string total_users: number
} total_documents: number
total_documents_sizes: number
last_use: any
}