diff --git a/.eslintrc.js b/.eslintrc.js index 3b524c527..8834a81fd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,6 +31,14 @@ module.exports = { ], "react/display-name": ["off", { ignoreTranspilerName: true }], "sort-imports": "error", + "max-len": ["warn", { + code: 100, + ignoreComments: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + tabWidth: 4, + }], + }, settings: { react: { diff --git a/packages/common/src/colors.ts b/packages/common/src/colors.ts index 97e2e1986..b89ff68bb 100644 --- a/packages/common/src/colors.ts +++ b/packages/common/src/colors.ts @@ -8,7 +8,7 @@ const colors: Record = { greyDark: "#3c3c3c", greyMedium: "#707070", greyLight: "#A2A2A2", - lavendar: "#9F6CBA", + lavender: "#9F6CBA", orangeDark: "#CE5A30", orangePrimary: "#F05A28", orangeSecondary: "#DB5427", diff --git a/packages/native/src/App.styles.ts b/packages/native/src/App.styles.ts index 78737fc76..8abbd0c50 100644 --- a/packages/native/src/App.styles.ts +++ b/packages/native/src/App.styles.ts @@ -8,7 +8,7 @@ export const colors = { greyDark: "#3c3c3c", greyMedium: "#707070", greyLight: "#A2A2A2", - lavendar: "#9F6CBA", + lavender: "#9F6CBA", orangeDark: "#CE5A30", orangePrimary: "#F05A28", orangeSecondary: "#DB5427", diff --git a/packages/native/src/components/Categories.tsx b/packages/native/src/components/Categories.tsx index 469e7ab1d..c278969a6 100644 --- a/packages/native/src/components/Categories.tsx +++ b/packages/native/src/components/Categories.tsx @@ -101,7 +101,7 @@ const categories: Record = { ], }, JobTraining: { - color: "lavendar", + color: "lavender", mainCategory: { text: "Job Training", query: "CATEGORY-jobTraining", diff --git a/packages/native/src/components/HomeButtons.tsx b/packages/native/src/components/HomeButtons.tsx index 0a6552706..6897f5cb3 100644 --- a/packages/native/src/components/HomeButtons.tsx +++ b/packages/native/src/components/HomeButtons.tsx @@ -70,7 +70,7 @@ const routerLinkButtons: THomeButtonRouterLink[] = [ text: "Job Training", icon: BusinessCenterIcon, linkState: "/job-training", - color: colors.lavendar, + color: colors.lavender, }, { text: "Social Services", diff --git a/packages/server/src/bin/setupCategories.ts b/packages/server/src/bin/setupCategories.ts index 6c92bd1c0..2cec88c8b 100644 --- a/packages/server/src/bin/setupCategories.ts +++ b/packages/server/src/bin/setupCategories.ts @@ -49,7 +49,7 @@ const categories: { ], }, { - color: "lavendar", + color: "lavender", name: "Job Training", stub: "job_training", subcategories: [ diff --git a/packages/server/src/models/Category.ts b/packages/server/src/models/Category.ts index c182d2d4d..7cd5823b0 100644 --- a/packages/server/src/models/Category.ts +++ b/packages/server/src/models/Category.ts @@ -21,28 +21,25 @@ export interface TCategoryDocument extends Document { export async function categoryDocumentToCategory( d: TCategoryDocument ): Promise { - let c = d; - if (d.toObject) { - c = d.toObject(); - } else { + if (!d || !d.toObject) { // console.warn( // `\`categoryToDocumentCategory\` received category which does not appear to be a Mongoose Document [${Object.keys( // d // )}]:\n${JSON.stringify(d, null, 2)}` // ); - if (d.hasOwnProperty("_bsontype")) { - // console.warn("This appears to be an ObjectId"); - // console.trace(); - return null; - } + return null; + } else if (d.hasOwnProperty("_bsontype")) { + // console.warn("This appears to be an ObjectId"); + // console.trace(); + return null; } const result = { - ...c, - _id: c._id.toHexString(), + ...d, + _id: d._id.toHexString(), subcategories: ( await Promise.all( - ((c.subcategories || []) as TSubcategoryDocument[]).map( + ((d.subcategories || []) as TSubcategoryDocument[]).map( subcategoryDocumentToSubcategory ) ) @@ -110,6 +107,72 @@ CategorySchema.statics.getByStub = async function( return result; }; +CategorySchema.statics.getByStubLocation = async function( + stub: string, + options: { + includeDeleted?: boolean; + latitude: number; + longitude: number; + } +): Promise { + const { includeDeleted = false, latitude, longitude } = options; + + const match: { + stub: string; + deleted?: { $ne: true }; + } = { stub }; + + if (!includeDeleted) { + match.deleted = { $ne: true }; + } + + const result = await this.aggregate([ + { $match: match }, + { $limit: 1 }, + { + $lookup: { + from: "subcategories", + as: "subcategories", + let: { subcategories: "$subcategories" }, + pipeline: [ + { + $lookup: { + localField: "subcategories", + foreignField: "_id", + + from: "subcategories", + as: "resources", + + // let: { resources: "$resources" }, + // pipeline: [{ + // $geoNear: { + // near: { + // type: "Point", + // coordinates: [longitude, latitude], + // }, + // spherical: true, + // distanceField: "dist", + // }, + // }], + }, + }, + ], + }, + }, + { + $unwind: { + path: `$subcategories`, + preserveNullAndEmptyArrays: false, + }, + }, + ]); + + if (result.length) { + return result[0]; + } + return null; +}; + /** * Creates or finds an existing subcategory by its name and adds * it as a child of this category @@ -137,5 +200,16 @@ export default Category as typeof Category & { color?: string ) => Promise; getCategoryList: () => Promise; - getByStub: (stub: string) => Promise; + getByStub: ( + stub: string, + includeDeletedResources?: boolean + ) => Promise; + getByStubLocation: ( + stub: string, + options: { + includeDeletedResources?: boolean; + latitude: number; + longitude: number; + } + ) => Promise; }; diff --git a/packages/server/src/models/Resource.ts b/packages/server/src/models/Resource.ts index 20e7e1d70..b98cd20a1 100644 --- a/packages/server/src/models/Resource.ts +++ b/packages/server/src/models/Resource.ts @@ -36,7 +36,7 @@ export interface TResourceDocument extends Document { lastModifiedAt: Date; lastModifiedBy: ObjectId | undefined; // always populate legacyId: string | null | undefined; - location: { type: string; coordinates: number[] }; + location: { type: "Point"; coordinates: number[] }; name: string; phone: string; schedule: TResourceScheduleData; diff --git a/packages/server/src/routes/api/category/[stub].ts b/packages/server/src/routes/api/category/[stub].ts index 72665299b..cf27f1df7 100644 --- a/packages/server/src/routes/api/category/[stub].ts +++ b/packages/server/src/routes/api/category/[stub].ts @@ -3,13 +3,33 @@ import Category, { categoryDocumentToCategory } from "../../../models/Category"; export async function get(req, res, _next) { const { stub } = req.params; - const categoryDocument = await Category.getByStub(stub); - - if (categoryDocument) { - res.status(200).json({ - category: await categoryDocumentToCategory(categoryDocument), - }); - } else { - res.status(404).json({ message: `Category ${stub} not found` }); + let categoryDocument; + try { + if (req.query.latitude && req.query.longitude) { + categoryDocument = await Category.getByStubLocation(stub, { + latitude: parseInt(req.query.latitude, 10), + longitude: parseInt(req.query.longitude, 10), + }); + } else { + categoryDocument = await Category.getByStub(stub); + } + + if (categoryDocument) { + console.log( + `\n categoryDocument:\n\n\n\t${JSON.stringify(categoryDocument)}\n\n` + ); + + res.status(200).json({ + category: await categoryDocumentToCategory(categoryDocument), + categoryDocument, + }); + } else { + res.status(404).json({ message: `Category ${stub} not found` }); + } + } catch (e) { + console.error(e); + return res + .status(500) + .json({ message: `Error retrieving category: ${e.message}` }); } } diff --git a/packages/web/src/components/Categories.tsx b/packages/web/src/components/Categories.tsx index 07f07ffc3..0e1ee0e57 100644 --- a/packages/web/src/components/Categories.tsx +++ b/packages/web/src/components/Categories.tsx @@ -119,7 +119,7 @@ export const categories: Record = { ], }, JobTraining: { - color: "lavendar", + color: "lavender", placeholder: BusinessCenterIcon, mainCategory: { translationKey: "jobTraining", diff --git a/packages/web/src/components/HomeButtons.tsx b/packages/web/src/components/HomeButtons.tsx index 8070ef72c..a776d593a 100644 --- a/packages/web/src/components/HomeButtons.tsx +++ b/packages/web/src/components/HomeButtons.tsx @@ -101,7 +101,7 @@ export const routerLinkButtons: THomeButtonRouterLink[] = [ linkProps: { to: "/job_training", }, - color: colors.lavendar, + color: colors.lavender, }, { text: "Social Services", diff --git a/packages/web/src/components/useResourcesByCategory.tsx b/packages/web/src/components/useResourcesByCategory.tsx index 7562844fd..2d8276df4 100644 --- a/packages/web/src/components/useResourcesByCategory.tsx +++ b/packages/web/src/components/useResourcesByCategory.tsx @@ -1,14 +1,25 @@ import { TResource } from "@upswyng/types"; import { TResourcesByCategoryPayload } from "../webTypes"; import apiClient from "../utils/apiClient"; +import { getUserCoordinates } from "../utils/location"; import { useQuery } from "react-query"; const getResourcesByCategory = async ( _queryKey: string, params: { category: string } ): Promise => { + console.log(`getResourcesByCategory`); + + const userPosition = await getUserCoordinates(); + const { data } = await apiClient.get( - `/category/${params.category}` + `/category/${params.category}`, + { + params: { + latitude: userPosition.latitude, + longitude: userPosition.longitude, + }, + } ); if (!data.category) { diff --git a/packages/web/src/utils/location.ts b/packages/web/src/utils/location.ts new file mode 100644 index 000000000..b5f9408fb --- /dev/null +++ b/packages/web/src/utils/location.ts @@ -0,0 +1,10 @@ +export const getUserCoordinates = async () => { + const pos = (await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject); + })) as Position; + + return { + longitude: pos.coords.longitude, + latitude: pos.coords.latitude, + }; +};