Skip to content

Commit

Permalink
Merge pull request #18 from hotosm/feat/create-project-form
Browse files Browse the repository at this point in the history
Create project form
  • Loading branch information
varun2948 authored Jul 2, 2024
2 parents bb8a028 + da5c7c6 commit 8def1dc
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@tanstack/react-table": "^8.9.3",
"@terraformer/wkt": "^2.2.0",
"@turf/area": "^7.0.0",
"@turf/bbox": "^7.0.0",
"@turf/centroid": "^7.0.0",
"@turf/flatten": "^7.0.0",
"@turf/length": "^7.0.0",
Expand Down
11 changes: 11 additions & 0 deletions src/frontend/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
import { getProjectsList } from "@Services/createproject";

export const useGetProjectsListQuery = (id?:number, queryOptions?: Partial<UseQueryOptions>) => {
return useQuery({
queryKey: ['projects-list'],
queryFn: () => getProjectsList(id),
select: (res: any) => res.data,
...queryOptions,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function CreateProjectHeader() {
const navigate = useNavigate();
return (
<FlexRow className="naxatw-items-center">
<Icon name="west" onClick={() => navigate('/')} />
<Icon name="west" onClick={() => navigate('/projects')} />
<h5 className="naxatw-ml-4 naxatw-font-bold">Project /</h5>
<span className="naxatw-text-body-lg">&nbsp;Add Project</span>
</FlexRow>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-console */
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { useTypedSelector, useTypedDispatch } from '@Store/hooks';
Expand All @@ -17,9 +15,11 @@ import {
ContributionsForm,
GenerateTaskForm,
} from '@Components/CreateProject/FormContents';
import { postCreateProject } from '@Services/createproject';
import { postCreateProject, postTaskBoundary } from '@Services/createproject';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'react-toastify';
import { convertGeojsonToFile } from '@Utils/convertLayerUtils';
import prepareFormData from '@Utils/prepareFormData';

/**
* This function looks up the provided map of components to find and return
Expand Down Expand Up @@ -59,6 +59,9 @@ export default function CreateprojectLayout() {
const dispatch = useTypedDispatch();
const navigate = useNavigate();
const activeStep = useTypedSelector(state => state.createproject.activeStep);
const splitGeojson = useTypedSelector(
state => state.createproject.splitGeojson,
);

const initialState = {
name: '',
Expand All @@ -68,11 +71,26 @@ export default function CreateprojectLayout() {
outline_geojson: {},
};

const { mutate } = useMutation<any, any, any, unknown>({
const { mutate: uploadTaskBoundary } = useMutation<any, any, any, unknown>({
mutationFn: postTaskBoundary,
onSuccess: () => {
toast.success('Project Boundary Uploaded');
navigate('/projects');
},
onError: err => {
toast.error(err.message);
},
});

const { mutate: createProject } = useMutation<any, any, any, unknown>({
mutationFn: postCreateProject,
onSuccess: (res: any) => {
toast.success('Project Created Successfully');
navigate('/');
dispatch(setCreateProjectState({ projectId: res.data.id }));
if (!splitGeojson) return;
const geojson = convertGeojsonToFile(splitGeojson);
const formData = prepareFormData({ task_geojson: geojson });
uploadTaskBoundary({ id: res.data.id, data: formData });
},
onError: err => {
toast.error(err.message);
Expand All @@ -81,10 +99,11 @@ export default function CreateprojectLayout() {

const onSubmit = (data: any) => {
if (activeStep < 5) return;
mutate(data);
createProject(data);
reset();
};

const { register, setValue, handleSubmit } = useForm({
const { register, setValue, handleSubmit, reset } = useForm({
defaultValues: initialState,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import MapContainer from '@Components/common/MapLibreComponents/MapContainer';
import MeasureTool from '@Components/common/MapLibreComponents/MeasureTool';
import { setCreateProjectState } from '@Store/actions/createproject';
import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer';
import { Map } from 'maplibre-gl';
import { LngLatBoundsLike, Map } from 'maplibre-gl';
import { GeojsonType } from '@Components/common/MapLibreComponents/types';
import getBbox from '@turf/bbox';
import { useEffect } from 'react';
import { FeatureCollection } from 'geojson';

export default function MapSection() {
const dispatch = useTypedDispatch();
Expand All @@ -26,6 +29,12 @@ export default function MapSection() {
state => state.createproject.measureType,
);

useEffect(() => {
if (!uploadedGeojson) return;
const bbox = getBbox(uploadedGeojson as FeatureCollection);
map?.fitBounds(bbox as LngLatBoundsLike, { padding: 25 });
}, [map, uploadedGeojson]);

return (
<MapContainer
map={map}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import { setCreateProjectState } from '@Store/actions/createproject';
import { FlexColumn } from '@Components/common/Layouts';
Expand All @@ -12,6 +13,9 @@ export default function DefineAOI({ formProps }: { formProps: any }) {
const dispatch = useTypedDispatch();
const { setValue } = formProps;

const uploadedGeojson = useTypedSelector(
state => state.createproject.uploadedGeojson,
);
const uploadAreaOption = useTypedSelector(
state => state.createproject.uploadAreaOption,
);
Expand All @@ -26,7 +30,6 @@ export default function DefineAOI({ formProps }: { formProps: any }) {
dispatch(
setCreateProjectState({ uploadedGeojson: convertedGeojson }),
);
setValue('outline_geojson', convertedGeojson);
}
});
} catch (err: any) {
Expand All @@ -35,6 +38,11 @@ export default function DefineAOI({ formProps }: { formProps: any }) {
}
};

useEffect(() => {
if (!uploadedGeojson) return;
setValue('outline_geojson', uploadedGeojson);
}, [uploadedGeojson]);

return (
<FlexColumn>
<div className="naxatw-bg-white">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useEffect } from 'react';
import { useTypedSelector } from '@Store/hooks';
import { LngLatBoundsLike, Map } from 'maplibre-gl';
import { useMapLibreGLMap } from '@Components/common/MapLibreComponents';
import BaseLayerSwitcher from '@Components/common/MapLibreComponents/BaseLayerSwitcher';
import MapContainer from '@Components/common/MapLibreComponents/MapContainer';
import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer';
import { Map } from 'maplibre-gl';
import { GeojsonType } from '@Components/common/MapLibreComponents/types';
import getBbox from '@turf/bbox';
import { FeatureCollection } from 'geojson';

export default function MapSection() {
const { map, isMapLoaded } = useMapLibreGLMap({
Expand All @@ -20,6 +23,12 @@ export default function MapSection() {
state => state.createproject.splitGeojson,
);

useEffect(() => {
if (!splitGeojson) return;
const bbox = getBbox(splitGeojson as FeatureCollection);
map?.fitBounds(bbox as LngLatBoundsLike, { padding: 30 });
}, [splitGeojson, map]);

return (
<MapContainer
map={map}
Expand Down
10 changes: 8 additions & 2 deletions src/frontend/src/components/GoogleAuth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { Flex } from '@Components/common/Layouts';
import { toast } from 'react-toastify';

const { BASE_URL } = process.env;

Expand All @@ -18,12 +19,17 @@ function GoogleAuth() {
const loginRedirect = async () => {
if (authcode) {
const callbackUrl = `${BASE_URL}/users/callback/?code=${authcode}&state=${state}`;
const userDetailsUrl = `${BASE_URL}/users/me/`;

const completeLogin = async () => {
await fetch(callbackUrl, { credentials: 'include' });
const response = await fetch(callbackUrl, { credentials: 'include' });
const token = await response.json();
localStorage.setItem('token', token);
};

await completeLogin();

toast.success('Logged In Successfully');
navigate('/user-profile');
}
setIsReadyToRedirect(true);
};
Expand Down
10 changes: 10 additions & 0 deletions src/frontend/src/components/IndividualProject/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useParams } from 'react-router-dom';

export default function IndividualProject() {
const { id } = useParams();
return (
<section>
<h4>This is {id} project section</h4>
</section>
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { useEffect } from 'react';
import { LngLatBoundsLike, Map } from 'maplibre-gl';
import { useMapLibreGLMap } from '@Components/common/MapLibreComponents';
import BaseLayerSwitcher from '@Components/common/MapLibreComponents/BaseLayerSwitcher';
import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer';
import MapContainer from '@Components/common/MapLibreComponents/MapContainer';
import { GeojsonType } from '@Components/common/MapLibreComponents/types';
import getBbox from '@turf/bbox';
import { FeatureCollection } from 'geojson';

export default function MapSection({ containerId }: { containerId: string }) {
export default function MapSection({
containerId,
geojson,
}: {
containerId: string;
geojson: GeojsonType;
}) {
const { map, isMapLoaded } = useMapLibreGLMap({
containerId,
mapOptions: {
Expand All @@ -12,6 +24,13 @@ export default function MapSection({ containerId }: { containerId: string }) {
},
disableRotation: true,
});

useEffect(() => {
if (!geojson) return;
const bbox = getBbox(geojson as FeatureCollection);
map?.fitBounds(bbox as LngLatBoundsLike, { padding: 30 });
}, [geojson, map]);

return (
<MapContainer
containerId={containerId}
Expand All @@ -22,6 +41,15 @@ export default function MapSection({ containerId }: { containerId: string }) {
height: '150px',
}}
>
{geojson && (
<VectorLayer
map={map as Map}
isMapLoaded={isMapLoaded}
id={containerId}
geojson={geojson}
visibleOnMap={!!geojson}
/>
)}
<BaseLayerSwitcher />
</MapContainer>
);
Expand Down
43 changes: 31 additions & 12 deletions src/frontend/src/components/Projects/ProjectCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
import { useNavigate } from 'react-router-dom';
import MapSection from './MapSection';
import { GeojsonType } from '@Components/common/MapLibreComponents/types';

interface IProjectCardProps {
containerId: string;
id: number;
title: string;
description: string;
geojson: GeojsonType;
}

export default function ProjectCard({
containerId,
id,
title,
description,
geojson,
}: IProjectCardProps) {
const navigate = useNavigate();

const onProjectCardClick = () => {
navigate(`/projects/${id}`);
};

export default function ProjectCard({ containerId }: { containerId: string }) {
return (
<div className="!naxatw-col-span-1 naxatw-rounded-md naxatw-border naxatw-border-grey-400 naxatw-p-[0.625rem]">
<MapSection containerId={containerId} />
<p className="naxatw-mt-2 naxatw-text-body-sm">ID: #12468</p>
<p className="naxatw-text-body-btn naxatw-text-grey-800">
Lorem ipsum dolor sit amet consectur
</p>
<p className="naxatw-text-body-sm">
Cameroon RoLorem ipsum dolor sit amet consec.Lorem ipsum dolor sit amet
consectetur.Lorem ipsum dolor sit amet consectetur.ad Assessment for
Sustainable Development in Rural Communities in Africa
</p>
<div
onClick={onProjectCardClick}
className="!naxatw-col-span-1 naxatw-max-h-[19.25rem] naxatw-cursor-pointer naxatw-rounded-md naxatw-border naxatw-border-grey-400 naxatw-p-[0.625rem] naxatw-transition-all naxatw-duration-300 naxatw-ease-in-out hover:-naxatw-translate-y-1 hover:naxatw-scale-100 hover:naxatw-shadow-xl"
>
<MapSection containerId={containerId} geojson={geojson} />
<p className="naxatw-mt-2 naxatw-text-body-sm">ID: #{id}</p>
<p className="naxatw-text-body-btn naxatw-text-grey-800">{title}</p>
<p className="naxatw-line-clamp-4 naxatw-text-body-sm">{description}</p>
</div>
);
}
11 changes: 11 additions & 0 deletions src/frontend/src/components/Projects/ProjectCardSkeleton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Skeleton from '@Components/RadixComponents/Skeleton';

export default function ProjectCardSkeleton() {
return (
<Skeleton className="!naxatw-col-span-1 naxatw-max-h-[19.25rem] naxatw-w-[13.5rem] naxatw-cursor-pointer naxatw-rounded-md naxatw-border naxatw-border-grey-400 naxatw-p-[0.625rem] hover:naxatw-shadow-lg">
<Skeleton className="naxatw-h-[10rem] naxatw-bg-grey-50" />
<Skeleton className="naxatw-mt-2 naxatw-h-[20px] naxatw-w-1/2 naxatw-bg-grey-50" />
<Skeleton className="naxatw-mt-2 naxatw-h-[30px] naxatw-bg-grey-50" />
</Skeleton>
);
}
2 changes: 1 addition & 1 deletion src/frontend/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export const navLinks = [
{
id: 1,
link: '/',
link: '/projects',
linkName: 'Projects',
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function Login() {
localStorage.setItem('token', res.data.access_token);
localStorage.setItem('refresh', res.data.refresh_token);
toast.success('Logged In Successfully');
navigate('/');
navigate('/projects');
},
onError: err => {
toast.error(err.response.data.detail);
Expand Down
9 changes: 8 additions & 1 deletion src/frontend/src/routes/appRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import CreateProject from '@Components/CreateProject';
import GoogleAuth from '@Components/GoogleAuth';
import userRoutes from '@UserModule/routes';
import { IRoute } from './types';
import IndividualProject from '@Components/IndividualProject';

const appRoutes: IRoute[] = [
...userRoutes,
{
path: '/',
path: '/projects',
name: 'Projects ',
component: Projects,
authenticated: true,
Expand All @@ -32,6 +33,12 @@ const appRoutes: IRoute[] = [
component: CreateProject,
authenticated: true,
},
{
path: '/projects/:id',
name: 'Individual Project',
component: IndividualProject,
authenticated: true,
},
{
path: '/user-profile',
name: 'User Profile',
Expand Down
Loading

0 comments on commit 8def1dc

Please sign in to comment.