Skip to content

Commit ade46b5

Browse files
Merge pull request #235 from CivicDataLab/232-restructure-user-dashboard
Restructure user dashboard
2 parents efac188 + 244eb16 commit ade46b5

File tree

20 files changed

+742
-403
lines changed

20 files changed

+742
-403
lines changed

app/[locale]/dashboard/[entityType]/[entitySlug]/consumers/page.tsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

app/[locale]/dashboard/[entityType]/[entitySlug]/layout.tsx

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import BreadCrumbs from '@/components/BreadCrumbs';
1111
import { DashboardNav } from '../../components/dashboard-nav';
1212
import { MobileDashboardNav } from '../../components/mobile-dashboard-nav';
1313
import styles from '../../components/styles.module.scss';
14-
import { getDataSpaceDetailsQryDoc, getOrgDetailsQryDoc } from './schema';
14+
import { getOrgDetailsQryDoc } from './schema';
1515

1616
interface DashboardLayoutProps {
1717
children?: React.ReactNode;
@@ -24,9 +24,7 @@ export default function OrgDashboardLayout({ children }: DashboardLayoutProps) {
2424
const EntityDetailsQryRes: { data: any; isLoading: boolean; error: any } =
2525
useQuery([`entity_details_${params.entityType}`], () =>
2626
GraphQL(
27-
params.entityType === 'organization'
28-
? getOrgDetailsQryDoc
29-
: getDataSpaceDetailsQryDoc,
27+
params.entityType === 'organization' && getOrgDetailsQryDoc,
3028
{},
3129
{ slug: params.entitySlug }
3230
)
@@ -45,43 +43,52 @@ export default function OrgDashboardLayout({ children }: DashboardLayoutProps) {
4543
href: `/dashboard/${params.entityType}/${params.entitySlug}/dataset`,
4644
icon: 'datasetEdit',
4745
},
48-
{
49-
title: 'Manage Consumers',
50-
href: `/dashboard/${params.entityType}/${params.entitySlug}/consumers`,
51-
icon: 'userList',
52-
},
5346
{
5447
title: 'UseCases',
5548
href: `/dashboard/${params.entityType}/${params.entitySlug}/usecases`,
5649
icon: 'userList',
57-
}
50+
},
51+
{
52+
title: 'Profile',
53+
href: `/dashboard/${params.entityType}/${params.entitySlug}/profile`,
54+
icon: 'setting',
55+
},
5856
];
5957

6058
return (
6159
<>
6260
<BreadCrumbs
63-
data={[
64-
{ href: '/', label: 'Home' },
65-
{
66-
href: '/dashboard/user/datasets',
67-
label: 'User Dashboard',
68-
},
69-
{
70-
href: `/dashboard/${params.entityType}`,
71-
label:
72-
params.entityType === 'organization'
73-
? 'My Organizations'
74-
: 'My DataSpaces',
75-
},
76-
{
77-
href: '',
78-
label:
79-
(params.entityType === 'organization'
80-
? EntityDetailsQryRes.data?.organizations[0]
81-
: EntityDetailsQryRes.data?.dataspaces[0]
82-
)?.name || params.entitySlug,
83-
},
84-
]}
61+
data={
62+
params.entityType === 'organization'
63+
? [
64+
{ href: '/', label: 'Home' },
65+
{
66+
href: '/dashboard',
67+
label: 'User Dashboard',
68+
},
69+
{
70+
href: `/dashboard/${params.entityType}`,
71+
label: 'My Organizations',
72+
},
73+
{
74+
href: '',
75+
label:
76+
EntityDetailsQryRes.data?.organizations[0]?.name ||
77+
params.entitySlug,
78+
},
79+
]
80+
: [
81+
{ href: '/', label: 'Home' },
82+
{
83+
href: '/dashboard',
84+
label: 'User Dashboard',
85+
},
86+
{
87+
href: `/dashboard/`,
88+
label: 'My Dashboard',
89+
},
90+
]
91+
}
8592
/>
8693
<div
8794
className={cn(
@@ -94,7 +101,7 @@ export default function OrgDashboardLayout({ children }: DashboardLayoutProps) {
94101
entityDetails={
95102
params.entityType === 'organization'
96103
? EntityDetailsQryRes.data?.organizations[0]
97-
: EntityDetailsQryRes.data?.dataspaces[0]
104+
: params.entitySlug.toLocaleLowerCase()
98105
}
99106
/>
100107

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import React, { useEffect } from 'react';
2+
import { useParams } from 'next/navigation';
3+
import { graphql } from '@/gql';
4+
import {
5+
ApiOrganizationOrganizationTypesEnum,
6+
OrganizationInputPartial,
7+
} from '@/gql/generated/graphql';
8+
import { useMutation, useQuery } from '@tanstack/react-query';
9+
import { Button, DropZone, Select, Text, TextField, toast } from 'opub-ui';
10+
11+
import { GraphQL } from '@/lib/api';
12+
import { organizationTypes } from '../../page';
13+
14+
const OrgDetails: any = graphql(`
15+
query orgDetails($slug: String) {
16+
organizations(slug: $slug) {
17+
id
18+
name
19+
logo {
20+
name
21+
path
22+
}
23+
homepage
24+
organizationTypes
25+
contactEmail
26+
description
27+
slug
28+
}
29+
}
30+
`);
31+
32+
const organizationUpdateMutation: any = graphql(`
33+
mutation updateOrganization($input: OrganizationInputPartial!) {
34+
updateOrganization(input: $input) {
35+
__typename
36+
... on TypeOrganization {
37+
id
38+
name
39+
logo {
40+
name
41+
path
42+
url
43+
}
44+
homepage
45+
organizationTypes
46+
contactEmail
47+
description
48+
slug
49+
}
50+
}
51+
}
52+
`);
53+
54+
const OrgProfile = () => {
55+
const params = useParams<{ entitySlug: string }>();
56+
57+
const orgDetails: any = useQuery([`org_details_${params.entitySlug}`], () =>
58+
GraphQL(
59+
OrgDetails,
60+
{},
61+
{
62+
slug: params.entitySlug,
63+
}
64+
)
65+
);
66+
67+
useEffect(() => {
68+
if (orgDetails.data) {
69+
setFormData({
70+
name: orgDetails.data?.organizations[0].name,
71+
contactEmail: orgDetails.data?.organizations[0].contactEmail,
72+
organizationTypes: orgDetails.data?.organizations[0].organizationTypes,
73+
homepage: orgDetails.data?.organizations[0].homepage,
74+
description: orgDetails.data?.organizations[0].description,
75+
logo: orgDetails.data?.organizations[0].logo,
76+
id: orgDetails.data?.organizations[0].id,
77+
});
78+
}
79+
}, [orgDetails.data]);
80+
81+
const initialFormData = {
82+
name: '',
83+
contactEmail: '',
84+
organizationTypes: ApiOrganizationOrganizationTypesEnum.StateGovernment, // or whichever is most appropriate
85+
homepage: '',
86+
description: '',
87+
logo: null as File | null,
88+
id: '',
89+
};
90+
91+
const [formData, setFormData] = React.useState(initialFormData);
92+
93+
const { mutate, isLoading: editMutationLoading } = useMutation(
94+
(input: { input: OrganizationInputPartial }) =>
95+
GraphQL(organizationUpdateMutation, {}, input),
96+
{
97+
onSuccess: (res: any) => {
98+
toast('Organization updated successfully');
99+
setFormData({
100+
name: res?.updateOrganization?.name,
101+
contactEmail: res?.updateOrganization?.contactEmail,
102+
organizationTypes: res?.updateOrganization?.organizationTypes,
103+
homepage: res?.updateOrganization?.homepage,
104+
description: res?.updateOrganization?.description,
105+
logo: res?.updateOrganization?.logo,
106+
id: res?.updateOrganization?.id,
107+
});
108+
},
109+
onError: (error: any) => {
110+
toast(`Error: ${error.message}`);
111+
},
112+
}
113+
);
114+
const handleSave = () => {
115+
116+
// Create mutation input with only changed fields
117+
const inputData: OrganizationInputPartial = {
118+
name: formData.name,
119+
contactEmail: formData.contactEmail,
120+
organizationTypes: formData.organizationTypes,
121+
homepage: formData.homepage,
122+
description: formData.description,
123+
id: formData.id,
124+
};
125+
126+
// Only add logo if it has changed
127+
if (formData.logo instanceof File) {
128+
inputData.logo = formData.logo;
129+
}
130+
131+
mutate({ input: inputData });
132+
};
133+
return (
134+
<div>
135+
<div>
136+
<Text variant="headingXl">My Organization Profile</Text>
137+
</div>
138+
<div className=" mt-6 flex flex-col gap-6">
139+
<div className="flex flex-wrap gap-6 lg:flex-nowrap">
140+
<div className="w-full">
141+
<TextField
142+
label="Name"
143+
name="name"
144+
value={formData.name}
145+
onChange={(e) => setFormData({ ...formData, name: e })}
146+
/>
147+
</div>
148+
149+
<div className="w-full">
150+
<TextField
151+
label="Email"
152+
name="email"
153+
value={formData.contactEmail}
154+
onChange={(e) => setFormData({ ...formData, contactEmail: e })}
155+
/>
156+
</div>
157+
</div>
158+
<div className="flex flex-wrap gap-6 lg:flex-nowrap">
159+
<div className="w-full">
160+
<Select
161+
label="Organization Type"
162+
name="organizationType"
163+
value={
164+
formData.organizationTypes ? formData.organizationTypes : ''
165+
}
166+
onChange={(e) =>
167+
setFormData({
168+
...formData,
169+
organizationTypes: e as ApiOrganizationOrganizationTypesEnum,
170+
})
171+
}
172+
options={organizationTypes}
173+
/>
174+
</div>
175+
<div className="w-full">
176+
<TextField
177+
label="Homepage"
178+
name="homepage"
179+
value={formData.homepage}
180+
onChange={(e) => setFormData({ ...formData, homepage: e })}
181+
/>
182+
</div>
183+
</div>
184+
<div className="flex flex-wrap gap-6 lg:flex-nowrap">
185+
<div className="w-full">
186+
<TextField
187+
label="Description"
188+
name="description"
189+
multiline={6}
190+
value={formData.description}
191+
onChange={(e) => setFormData({ ...formData, description: e })}
192+
/>
193+
</div>
194+
<div className="w-full">
195+
<DropZone
196+
label={'Upload Organization Logo'}
197+
onDrop={(e) => setFormData({ ...formData, logo: e[0] })}
198+
name={'Logo'}
199+
>
200+
<DropZone.FileUpload
201+
actionTitle={
202+
formData.logo
203+
? formData.logo.name.split('/').pop()
204+
: 'Name of the logo'
205+
}
206+
/>
207+
</DropZone>
208+
</div>
209+
</div>
210+
<Button className="m-auto w-1/6" onClick={handleSave}>
211+
Save
212+
</Button>
213+
</div>
214+
</div>
215+
);
216+
};
217+
218+
export default OrgProfile;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { usePathname } from 'next/navigation';
5+
6+
import OrgProfile from './orgProfile';
7+
import UserProfile from './userProfile';
8+
9+
const Profile = () => {
10+
const path = usePathname();
11+
12+
return (
13+
<div className="mt-8 flex h-full flex-col">
14+
{path.includes('self') ? <UserProfile /> : <OrgProfile />}
15+
</div>
16+
);
17+
};
18+
19+
export default Profile;

0 commit comments

Comments
 (0)