From 39c28a3a87a05311610a44c13a23da73c6e53c2a Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 15 Feb 2024 15:42:38 -0500
Subject: [PATCH 10/61] update i18n generation
---
apps/app/lib/generators/translationKeys.ts | 6 +++---
apps/app/public/locales/en/attribute.json | 5 +++++
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/apps/app/lib/generators/translationKeys.ts b/apps/app/lib/generators/translationKeys.ts
index 0ee892a469..3659724328 100644
--- a/apps/app/lib/generators/translationKeys.ts
+++ b/apps/app/lib/generators/translationKeys.ts
@@ -50,9 +50,9 @@ export const generateTranslationKeys = async (task: PassedTask) => {
if (typeof value !== 'string') throw new Error('Invalid nested plural item')
outputData[`${item.key}_${key}`] = value
}
- } else {
- outputData[item.key] = item.text
- }
+ } //else {
+ if (item.ns === 'attribute') outputData[item.key] = item.text
+ //}
}
const filename = `${localePath}/${namespace.name}.json`
diff --git a/apps/app/public/locales/en/attribute.json b/apps/app/public/locales/en/attribute.json
index fb08e90c86..2befe345df 100644
--- a/apps/app/public/locales/en/attribute.json
+++ b/apps/app/public/locales/en/attribute.json
@@ -9,6 +9,7 @@
"private-practice": "Private Practice",
"religiously-affiliated": "Is religiously affiliated",
"time-walk-in": "Has Walk-In Hours",
+ "wheelchair-accessible": "Accessible",
"wheelchair-accessible_false": "Not Accessible",
"wheelchair-accessible_true": "Accessible"
},
@@ -76,6 +77,7 @@
},
"eligibility": {
"CATEGORYNAME": "Eligibility Requirements",
+ "elig-age": "Age eligibility",
"elig-age_max": "Under {{max}}",
"elig-age_min": "{{min}} and older",
"elig-age_range": "{{min}} - {{max}}",
@@ -138,6 +140,9 @@
"CATEGORYNAME": "System",
"incompatible-info": "Incompatible Information"
},
+ "tpop": {
+ "other": "Target Population - Other"
+ },
"userlawpractice": {
"CATEGORYNAME": "Law Practice Options",
"corp-law-firm": "Corporate law firm",
From 3bac47756699919a0c3e5e3b1b42b20cdc1c55d2 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 15 Feb 2024 15:45:42 -0500
Subject: [PATCH 11/61] update schema & migrations
---
.../index.ts | 24 +-
.../data.json | 1396 +++++++++++++++++
.../2024-02-15_attribute-attachments/index.ts | 64 +
packages/db/prisma/data-migrations/index.ts | 1 +
.../migration.sql | 11 +
.../migration.sql | 33 +
packages/db/prisma/schema.prisma | 9 +
packages/ui/mockData/fieldOpt.ts | 30 +-
.../json/fieldOpt.attributesByCategory.json | 2 +-
9 files changed, 1553 insertions(+), 17 deletions(-)
create mode 100644 packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/data.json
create mode 100644 packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/index.ts
create mode 100644 packages/db/prisma/migrations/20240215164734_attribute_attachment/migration.sql
create mode 100644 packages/db/prisma/migrations/20240215172645_update_attrib_by_cat_view/migration.sql
diff --git a/packages/db/prisma/data-migrations/2024-02-14_attribute-supplement-schemas/index.ts b/packages/db/prisma/data-migrations/2024-02-14_attribute-supplement-schemas/index.ts
index e06e0a6d6d..ca89dd481c 100644
--- a/packages/db/prisma/data-migrations/2024-02-14_attribute-supplement-schemas/index.ts
+++ b/packages/db/prisma/data-migrations/2024-02-14_attribute-supplement-schemas/index.ts
@@ -73,7 +73,7 @@ export const job20240214_attribute_supplement_schemas = {
},
{
data: {
- definition: { key: 'min', label: 'Min', name: 'min', type: FieldType.number },
+ definition: [{ key: 'min', label: 'Min', name: 'min', type: FieldType.number }],
// tag: 'numMin',
},
where: {
@@ -82,7 +82,7 @@ export const job20240214_attribute_supplement_schemas = {
},
{
data: {
- definition: { key: 'max', label: 'Max', name: 'max', type: FieldType.number },
+ definition: [{ key: 'max', label: 'Max', name: 'max', type: FieldType.number }],
// tag: 'numMax',
},
where: {
@@ -91,7 +91,7 @@ export const job20240214_attribute_supplement_schemas = {
},
{
data: {
- definition: { key: 'num', label: 'Number', name: 'num', type: FieldType.number },
+ definition: [{ key: 'num', label: 'Number', name: 'num', type: FieldType.number }],
// tag: 'number',
},
where: {
@@ -100,12 +100,14 @@ export const job20240214_attribute_supplement_schemas = {
},
{
data: {
- definition: {
- key: 'incompatible',
- label: 'Incompatible',
- name: 'incompatible',
- type: FieldType.text,
- },
+ definition: [
+ {
+ key: 'incompatible',
+ label: 'Incompatible',
+ name: 'incompatible',
+ type: FieldType.text,
+ },
+ ],
// tag: 'incompatibleData',
},
where: {
@@ -114,7 +116,7 @@ export const job20240214_attribute_supplement_schemas = {
},
{
data: {
- definition: { key: 'other', label: 'Other', name: 'other', type: FieldType.text },
+ definition: [{ key: 'other', label: 'Other', name: 'other', type: FieldType.text }],
// tag: 'otherDescribe',
},
where: {
@@ -135,7 +137,7 @@ export const job20240214_attribute_supplement_schemas = {
*
* This writes a record to the DB to register that this migration has run successfully.
*/
- // await jobPostRunner(jobDef)
+ await jobPostRunner(jobDef)
},
def: jobDef,
} satisfies MigrationJob
diff --git a/packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/data.json b/packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/data.json
new file mode 100644
index 0000000000..74a3a2fdf2
--- /dev/null
+++ b/packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/data.json
@@ -0,0 +1,1396 @@
+[
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "at-capacity"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "geo-near-public-transit"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "geo-public-transit-description"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "has-confidentiality-policy"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "offers-remote-services"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "private-practice"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "religiously-affiliated"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "time-walk-in"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "wheelchair-accessible"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "info"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "warn"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "adults"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "africa-immigrant"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "african-american"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "api"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "asexual"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "asia-immigrant"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "asylee"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "asylum-seeker"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "bipoc"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "bisexual"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "black"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "citizens"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "conversion-therapy-survivors"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "daca-recipient-seeker"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "detained-immigrant"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "disabled"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "gay"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "gender-nonconforming"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "hiv-aids"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "homeless"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "human-trafficking-survivor"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "intersex"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "language-speakers"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "latin-america-immigrant"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "latinx"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "lesbian"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "lgbtq-youth"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "lgbtq-youth-caregivers"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "middle-east-immigrant"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "muslim"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "native-american-two-spirit"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "nonbinary"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "queer"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "refugee"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "residents-green-card-holders"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "seniors"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "sex-workers"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "teens"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "transfeminine"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "transgender"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "transmasculine"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "trans-youth"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "trans-youth-caregivers"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "unaccompanied-minors"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "undocumented"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "cost-fees"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "cost-free"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "elders"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "general-lgbtq"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "elig-age"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "other-describe"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "req-medical-insurance"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "req-photo-id"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "req-proof-of-age"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "req-proof-of-income"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "req-proof-of-residence"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "req-referral"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "time-appointment-required"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "all-languages-by-interpreter"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "american-sign-language"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE",
+ "ORGANIZATION",
+ "LOCATION"
+ ]
+ },
+ "where": {
+ "tag": "lang-offered"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION"
+ ]
+ },
+ "where": {
+ "tag": "bipoc-led"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION"
+ ]
+ },
+ "where": {
+ "tag": "black-led"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION"
+ ]
+ },
+ "where": {
+ "tag": "immigrant-led"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION"
+ ]
+ },
+ "where": {
+ "tag": "trans-led"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION"
+ ]
+ },
+ "where": {
+ "tag": "women-led"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accessemail"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accessfile"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accesslink"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accesslocation"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accessphone"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accesspublictransit"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accesssms"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accesstext"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "accesswhatsapp"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "asylum-seekers"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "bipoc-comm"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "caregivers-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "disabled-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "elder-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "gender-nc"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "hiv-comm"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "immigrant-comm"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "incarcerated-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "lgbtq-youth-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "resettled-refugees"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "spanish-speakers"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "trans-comm"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "trans-fem"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "trans-masc"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "trans-youth-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "women-focus"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "LOCATION",
+ "ORGANIZATION",
+ "SERVICE",
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "incompatible-info"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "SERVICE"
+ ]
+ },
+ "where": {
+ "tag": "tpop-other"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "corp-law-firm"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "law-other"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "law-school-clinic"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "legal-nonprofit"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.case-mananger"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.community-org"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.friend-family"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.govt-agency"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.grassroots-direct"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.healthcare"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.lawyer"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.other"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.paralegal"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.social-worker"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.student-club"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.teacher"
+ }
+ },
+ {
+ "data": {
+ "canAttachTo": [
+ "USER"
+ ]
+ },
+ "where": {
+ "tag": "userserviceprovider.therapist-counselor"
+ }
+ }
+]
\ No newline at end of file
diff --git a/packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/index.ts b/packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/index.ts
new file mode 100644
index 0000000000..b39733c37e
--- /dev/null
+++ b/packages/db/prisma/data-migrations/2024-02-15_attribute-attachments/index.ts
@@ -0,0 +1,64 @@
+import { z } from 'zod'
+
+import { prisma, Prisma } from '~db/client'
+import { formatMessage } from '~db/prisma/common'
+import { type MigrationJob } from '~db/prisma/dataMigrationRunner'
+import { createLogger, type JobDef, jobPostRunner } from '~db/prisma/jobPreRun'
+
+import data from './data.json'
+
+const Schema = z
+ .object({
+ where: z.object({ tag: z.string() }),
+ data: z.object({
+ canAttachTo: z
+ .enum(['SERVICE', 'ORGANIZATION', 'LOCATION', 'USER'])
+ .array()
+ .transform((x) => ({ set: x })),
+ }),
+ })
+ .array()
+
+/** Define the job metadata here. */
+const jobDef: JobDef = {
+ jobId: '2024-02-15_attribute-attachments',
+ title: 'attribute attachments',
+ createdBy: 'Joe Karow',
+ /** Optional: Longer description for the job */
+ description: undefined,
+}
+/**
+ * Job export - this variable MUST be UNIQUE
+ */
+export const job20240215_attribute_attachments = {
+ title: `[${jobDef.jobId}] ${jobDef.title}`,
+ task: async (_ctx, task) => {
+ /** Create logging instance */
+ createLogger(task, jobDef.jobId)
+ const log = (...args: Parameters
) => (task.output = formatMessage(...args))
+ /**
+ * Start defining your data migration from here.
+ *
+ * To log output, use `task.output = 'Message to log'`
+ *
+ * This will be written to `stdout` and to a log file in `/prisma/migration-logs/`
+ */
+ const parsed = Schema.parse(data)
+
+ const updates = await prisma.$transaction(
+ parsed.map((args) => {
+ return prisma.attribute.update(args)
+ })
+ )
+
+ log(`Updated ${updates.length} records.`)
+
+ /**
+ * DO NOT REMOVE BELOW
+ *
+ * This writes a record to the DB to register that this migration has run successfully.
+ */
+ await jobPostRunner(jobDef)
+ },
+ def: jobDef,
+} satisfies MigrationJob
diff --git a/packages/db/prisma/data-migrations/index.ts b/packages/db/prisma/data-migrations/index.ts
index f16237b5e7..13ac73e580 100644
--- a/packages/db/prisma/data-migrations/index.ts
+++ b/packages/db/prisma/data-migrations/index.ts
@@ -4,4 +4,5 @@ export * from './2024-01-31_target-population-attrib'
export * from './2024-02-01_add-missing-attributes/index'
export * from './2024-02-02_deactivate-incompatible-attribs'
export * from './2024-02-14_attribute-supplement-schemas/index'
+export * from './2024-02-15_attribute-attachments/index'
// codegen:end
diff --git a/packages/db/prisma/migrations/20240215164734_attribute_attachment/migration.sql b/packages/db/prisma/migrations/20240215164734_attribute_attachment/migration.sql
new file mode 100644
index 0000000000..3b72a68a83
--- /dev/null
+++ b/packages/db/prisma/migrations/20240215164734_attribute_attachment/migration.sql
@@ -0,0 +1,11 @@
+-- CreateEnum
+CREATE TYPE "AttributeAttachment" AS ENUM(
+ 'ORGANIZATION',
+ 'LOCATION',
+ 'SERVICE',
+ 'USER'
+);
+
+-- AlterTable
+ALTER TABLE "Attribute"
+ ADD COLUMN "canAttachTo" "AttributeAttachment"[];
diff --git a/packages/db/prisma/migrations/20240215172645_update_attrib_by_cat_view/migration.sql b/packages/db/prisma/migrations/20240215172645_update_attrib_by_cat_view/migration.sql
new file mode 100644
index 0000000000..704492a538
--- /dev/null
+++ b/packages/db/prisma/migrations/20240215172645_update_attrib_by_cat_view/migration.sql
@@ -0,0 +1,33 @@
+CREATE OR REPLACE VIEW public.attributes_by_category AS
+SELECT
+ ac.id AS "categoryId",
+ ac.tag AS "categoryName",
+ ac.name AS "categoryDisplay",
+ a.id AS "attributeId",
+ a.tag AS "attributeName",
+ a."tsKey" AS "attributeKey",
+ a."tsNs" AS "attributeNs",
+ a.icon,
+ a."iconBg",
+ ac."renderVariant" AS "badgeRender",
+ a."requireText",
+ a."requireLanguage",
+ a."requireGeo",
+ a."requireBoolean",
+ a."requireData",
+ asds.definition AS "dataSchema",
+ tkey."interpolationValues",
+ asds.tag AS "dataSchemaName",
+ a."canAttachTo"
+FROM
+ "AttributeCategory" ac
+ JOIN "AttributeToCategory" atc ON atc."categoryId" = ac.id
+ JOIN "Attribute" a ON a.id = atc."attributeId"
+ LEFT JOIN "AttributeSupplementDataSchema" asds ON asds.id = a."requiredSchemaId"
+ LEFT JOIN "TranslationKey" tkey ON tkey.key = a."tsKey"
+WHERE
+ a.active = TRUE
+ AND ac.active = TRUE
+ORDER BY
+ ac.tag,
+ a.tag;
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index 9b9f04b707..ae0370dd93 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -997,6 +997,7 @@ model Attribute {
requireData Boolean @default(false)
requireDataSchema AttributeSupplementDataSchema? @relation(fields: [requiredSchemaId], references: [id])
requiredSchemaId String?
+ canAttachTo AttributeAttachment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -1007,6 +1008,13 @@ model Attribute {
@@unique([tsKey, tsNs])
}
+enum AttributeAttachment {
+ ORGANIZATION
+ LOCATION
+ SERVICE
+ USER
+}
+
enum FilterType {
INCLUDE
EXCLUDE
@@ -2299,6 +2307,7 @@ view AttributesByCategory {
requireData Boolean
dataSchemaName String?
dataSchema Json?
+ canAttachTo AttributeAttachment[]
@@unique([categoryId, attributeId])
@@map("attributes_by_category")
diff --git a/packages/ui/mockData/fieldOpt.ts b/packages/ui/mockData/fieldOpt.ts
index ae4c279f48..c558b7b864 100644
--- a/packages/ui/mockData/fieldOpt.ts
+++ b/packages/ui/mockData/fieldOpt.ts
@@ -1,6 +1,7 @@
import { z } from 'zod'
import { type ApiOutput } from '@weareinreach/api'
+import { type $Enums } from '@weareinreach/db'
import { getTRPCMock, type MockAPIHandler, type MockHandlerObject } from '~ui/lib/getTrpcMock'
const queryAttributeCategories: MockAPIHandler<'fieldOpt', 'attributeCategories'> = async (query) => {
@@ -12,12 +13,31 @@ const queryAttributeCategories: MockAPIHandler<'fieldOpt', 'attributeCategories'
}
const queryAttributesByCategory: MockAPIHandler<'fieldOpt', 'attributesByCategory'> = async (query) => {
- const attributesByCategory = (await import('./json/fieldOpt.attributesByCategory.json')).default
- if (typeof query === 'string' || Array.isArray(query)) {
- return attributesByCategory.filter(({ categoryName }) =>
- Array.isArray(query) ? query.includes(categoryName) : query === categoryName
- ) as ApiOutput['fieldOpt']['attributesByCategory']
+ const attributesByCategory = (await import('./json/fieldOpt.attributesByCategory.json'))
+ .default as ApiOutput['fieldOpt']['attributesByCategory']
+
+ if (query?.categoryName || query?.canAttachTo?.length) {
+ const canAttachSet = new Set(query.canAttachTo)
+ const catNameSet = new Set(Array.isArray(query.categoryName) ? query.categoryName : [query.categoryName])
+ return attributesByCategory.filter(({ canAttachTo, categoryName }) => {
+ let match = false
+
+ if (query.canAttachTo?.length) {
+ for (const item of canAttachTo) {
+ if (canAttachSet.has(item as $Enums.AttributeAttachment)) {
+ match = true
+ break
+ }
+ }
+ }
+ if (query.categoryName) {
+ match = catNameSet.has(categoryName)
+ }
+
+ return match
+ })
}
+
return attributesByCategory as ApiOutput['fieldOpt']['attributesByCategory']
}
diff --git a/packages/ui/mockData/json/fieldOpt.attributesByCategory.json b/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
index e15c5fabd6..31b758d22e 100644
--- a/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
+++ b/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
@@ -1 +1 @@
-[{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3YJ2AWADHVKG79BQ0","attributeName":"at-capacity","attributeKey":"additional.at-capacity","attributeNs":"attribute","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4D5ZHFMAE7852GB4P","attributeName":"geo-near-public-transit","attributeKey":"additional.geo-near-public-transit","attributeNs":"attribute","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV48VQJBMFA05QCBBV9","attributeName":"geo-public-transit-description","attributeKey":"additional.geo-public-transit-description","attributeNs":"attribute","badgeRender":"ATTRIBUTE","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","attributeName":"has-confidentiality-policy","attributeKey":"additional.has-confidentiality-policy","attributeNs":"attribute","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5Q7XN2ZNTYFR1AD3M","attributeName":"offers-remote-services","attributeKey":"additional.offers-remote-services","attributeNs":"attribute","icon":"carbon:globe","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","attributeName":"time-walk-in","attributeKey":"additional.time-walk-in","attributeNs":"attribute","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5FYXQNGTPAQB7G2TF","attributeName":"wheelchair-accessible","attributeKey":"additional.wheelchair-accessible","attributeNs":"attribute","interpolationValues":{"true":"Accessible","false":"Not Accessible"},"icon":"carbon:accessibility","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":true,"requireData":false},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAMR6RDV6M69H4KN3T","attributeName":"info","attributeKey":"alerts.info","attributeNs":"attribute","icon":"carbon:information-filled","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAKP7C6JKJ342ZM35M","attributeName":"warn","attributeKey":"alerts.warn","attributeNs":"attribute","icon":"carbon:warning-filled","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGWKWB53HWAAHQ9AAZ","attributeName":"cost-fees","attributeKey":"cost.cost-fees","attributeNs":"attribute","icon":"carbon:piggy-bank","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","dataSchema":{"anyOf":[{"type":"object","required":["min"],"properties":{"min":{"type":"number"}}},{"type":"object","required":["max"],"properties":{"max":{"type":"number"}}},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}}}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","attributeName":"cost-free","attributeKey":"cost.cost-free","attributeNs":"attribute","icon":"carbon:piggy-bank","badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","icon":"️️✊🏿","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P951P0V3CR807P8KRH82S1","attributeName":"elders","attributeKey":"crisis-support-community.elders","attributeNs":"attribute","icon":"🌳","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P8T277D0C8HFQA6N09FJWD","attributeName":"general-lgbtq","attributeKey":"crisis-support-community.general-lgbtq","attributeNs":"attribute","icon":"🏳️🌈","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","icon":"🌱","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","icon":"🏳️⚧️","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","attributeName":"elig-age","attributeKey":"eligibility.elig-age","attributeNs":"attribute","interpolationValues":{"max":"Under{{max}}","min":"{{min}} and older","range":"{{min}} -{{max}}"},"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","dataSchema":{"anyOf":[{"type":"object","required":["min"],"properties":{"min":{"type":"number"}}},{"type":"object","required":["max"],"properties":{"max":{"type":"number"}}},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}}}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJDKVF1HV7559CNZCY","attributeName":"other-describe","attributeKey":"eligibility.other-describe","attributeNs":"attribute","badgeRender":"LIST","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH9DPBZ968VXGE50E7","attributeName":"req-medical-insurance","attributeKey":"eligibility.req-medical-insurance","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHZ599M48CMSPGDCSC","attributeName":"req-photo-id","attributeKey":"eligibility.req-photo-id","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH0GQK0GAJR5D952V3","attributeName":"req-proof-of-age","attributeKey":"eligibility.req-proof-of-age","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHEVX4PMNN077ASQMG","attributeName":"req-proof-of-income","attributeKey":"eligibility.req-proof-of-income","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHGMVCAY1G5BWF1PFB","attributeName":"req-proof-of-residence","attributeKey":"eligibility.req-proof-of-residence","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJH8MADHYTHBV54CER","attributeName":"req-referral","attributeKey":"eligibility.req-referral","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","attributeName":"time-appointment-required","attributeKey":"eligibility.time-appointment-required","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJGDDWTR5D0C8BY357","attributeName":"all-languages-by-interpreter","attributeKey":"lang.all-languages-by-interpreter","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJF09GXY5N5CKMSANJ","attributeName":"american-sign-language","attributeKey":"lang.american-sign-language","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","attributeName":"lang-offered","attributeKey":"lang.lang-offered","attributeNs":"attribute","badgeRender":"LIST","requireText":false,"requireLanguage":true,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRH531R2HAV8DMDZSC","attributeName":"corp-law-firm","attributeKey":"userlawpractice.corp-law-firm","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVSE2074QZJ4SKEW74J","attributeName":"law-other","attributeKey":"userlawpractice.law-other","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"otherDescribe","dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["other"],"properties":{"other":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRS8XEJ3TJBBEQJ707","attributeName":"law-school-clinic","attributeKey":"userlawpractice.law-school-clinic","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRFPRQCQHNJA6BM3XP","attributeName":"legal-nonprofit","attributeKey":"userlawpractice.legal-nonprofit","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNPKMHYK12DDRVC1VJ","attributeName":"bipoc-led","attributeKey":"orgleader.bipoc-led","attributeNs":"attribute","icon":"🤎","iconBg":"#F1DD7F","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3JX2J7REFFT5NAMS","attributeName":"black-led","attributeKey":"orgleader.black-led","attributeNs":"attribute","icon":"️️✊🏿","iconBg":"#C77E54","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNHMF72WHVKRF6W4TA","attributeName":"immigrant-led","attributeKey":"orgleader.immigrant-led","attributeNs":"attribute","icon":"️️🌎","iconBg":"#79ADD7","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3RYX9JMXDZSQZM70","attributeName":"trans-led","attributeKey":"orgleader.trans-led","attributeNs":"attribute","icon":"️🏳️⚧️","iconBg":"#705890","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKFM4TDY4QRK4AR2ZW","attributeName":"accessemail","attributeKey":"serviceaccess.accessemail","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKMRHFD8SMDAZM3SSM","attributeName":"accessfile","attributeKey":"serviceaccess.accessfile","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","attributeName":"accesslink","attributeKey":"serviceaccess.accesslink","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","attributeName":"accesslocation","attributeKey":"serviceaccess.accesslocation","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","attributeName":"accessphone","attributeKey":"serviceaccess.accessphone","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMSX7T1WDNZ5QEHKWT","attributeName":"accesspublictransit","attributeKey":"serviceaccess.accesspublictransit","attributeNs":"attribute","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMMF19AX2KPBTMV6P3","attributeName":"accesstext","attributeKey":"serviceaccess.accesstext","attributeNs":"attribute","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPCVX8F3B7M30ZJEHW","attributeName":"asylum-seekers","attributeKey":"srvfocus.asylum-seekers","attributeNs":"attribute","icon":"️️🌎","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","icon":"️️✊🏿","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ7SYGD3KM8WP9X50B","attributeName":"gender-nc","attributeKey":"srvfocus.gender-nc","attributeNs":"attribute","icon":"🏳️⚧️","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVRMQFJ9AMA633SQQGV","attributeName":"hiv-comm","attributeKey":"srvfocus.hiv-comm","attributeNs":"attribute","icon":"💛","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPTK9555WHJHDBDA2J","attributeName":"immigrant-comm","attributeKey":"srvfocus.immigrant-comm","attributeNs":"attribute","icon":"️️🌎","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","icon":"🌱","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPJERY0GS9D7F56A23","attributeName":"resettled-refugees","attributeKey":"srvfocus.resettled-refugees","attributeNs":"attribute","icon":"️️🌎","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ8AGBKBBZJWTHNP2F","attributeName":"spanish-speakers","attributeKey":"srvfocus.spanish-speakers","attributeNs":"attribute","icon":"🗣️","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","icon":"🏳️⚧️","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQX4M8DY1FSAYSJSSK","attributeName":"trans-fem","attributeKey":"srvfocus.trans-fem","attributeNs":"attribute","icon":"🏳️⚧️","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQEFWW42MBAD64BWXZ","attributeName":"trans-masc","attributeKey":"srvfocus.trans-masc","attributeNs":"attribute","icon":"🏳️⚧️","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQVEGH6W3A2ANH1QZE","attributeName":"trans-youth-focus","attributeKey":"srvfocus.trans-youth-focus","attributeNs":"attribute","icon":"🌱","badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TK83N5E52PPP828SD88KP8","attributeName":"userserviceprovider.case-mananger","attributeKey":"userserviceprovider.case-mananger","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTTZ83PZR61M37R8R7","attributeName":"userserviceprovider.community-org","attributeKey":"userserviceprovider.community-org","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVSPXWJJPFG9DKXESEK","attributeName":"userserviceprovider.healthcare","attributeKey":"userserviceprovider.healthcare","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM092CFVG6H0MR148AVAP7","attributeName":"userserviceprovider.lawyer","attributeKey":"userserviceprovider.lawyer","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AJHVK8TSR8JNFANFNZ7","attributeName":"userserviceprovider.other","attributeKey":"userserviceprovider.other","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09EG0G84NXH40G5TESB5","attributeName":"userserviceprovider.paralegal","attributeKey":"userserviceprovider.paralegal","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09RAK024ZDZQ6FSY0TXB","attributeName":"userserviceprovider.social-worker","attributeKey":"userserviceprovider.social-worker","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTN6MSCMBW740Y7HN1","attributeName":"userserviceprovider.student-club","attributeKey":"userserviceprovider.student-club","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0A19DD6S97DNH76ZVP40","attributeName":"userserviceprovider.teacher","attributeKey":"userserviceprovider.teacher","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AA4CZXJJHMXHE1PHMVV","attributeName":"userserviceprovider.therapist-counselor","attributeKey":"userserviceprovider.therapist-counselor","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false},{"categoryId":"attc_01GW2HHFVKM2PSHFWVFM0TWX1P","categoryName":"system","categoryDisplay":"System","attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","attributeName":"incompatible-info","attributeKey":"sys.incompatible-info","attributeNs":"attribute","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"incompatibleData","dataSchema":{"type":"array","items":{"type":"object","additionalProperties":{}},"$schema":"http://json-schema.org/draft-07/schema#"}}]
\ No newline at end of file
+[{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3YJ2AWADHVKG79BQ0","attributeName":"at-capacity","attributeKey":"additional.at-capacity","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4D5ZHFMAE7852GB4P","attributeName":"geo-near-public-transit","attributeKey":"additional.geo-near-public-transit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV48VQJBMFA05QCBBV9","attributeName":"geo-public-transit-description","attributeKey":"additional.geo-public-transit-description","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","attributeName":"has-confidentiality-policy","attributeKey":"additional.has-confidentiality-policy","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5Q7XN2ZNTYFR1AD3M","attributeName":"offers-remote-services","attributeKey":"additional.offers-remote-services","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:globe","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","attributeName":"time-walk-in","attributeKey":"additional.time-walk-in","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5FYXQNGTPAQB7G2TF","attributeName":"wheelchair-accessible","attributeKey":"additional.wheelchair-accessible","attributeNs":"attribute","interpolationValues":{"true":"Accessible","false":"Not Accessible"},"icon":"carbon:accessibility","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":true,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAMR6RDV6M69H4KN3T","attributeName":"info","attributeKey":"alerts.info","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:information-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAKP7C6JKJ342ZM35M","attributeName":"warn","attributeKey":"alerts.warn","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:warning-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGWKWB53HWAAHQ9AAZ","attributeName":"cost-fees","attributeKey":"cost.cost-fees","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"dataSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}]},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","attributeName":"cost-free","attributeKey":"cost.cost-free","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P951P0V3CR807P8KRH82S1","attributeName":"elders","attributeKey":"crisis-support-community.elders","attributeNs":"attribute","interpolationValues":null,"icon":"🌳","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P8T277D0C8HFQA6N09FJWD","attributeName":"general-lgbtq","attributeKey":"crisis-support-community.general-lgbtq","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️🌈","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","attributeName":"elig-age","attributeKey":"eligibility.elig-age","attributeNs":"attribute","interpolationValues":{"max":"Under{{max}}","min":"{{min}} and older","range":"{{min}} -{{max}}"},"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"dataSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}]},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJDKVF1HV7559CNZCY","attributeName":"other-describe","attributeKey":"eligibility.other-describe","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH9DPBZ968VXGE50E7","attributeName":"req-medical-insurance","attributeKey":"eligibility.req-medical-insurance","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHZ599M48CMSPGDCSC","attributeName":"req-photo-id","attributeKey":"eligibility.req-photo-id","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH0GQK0GAJR5D952V3","attributeName":"req-proof-of-age","attributeKey":"eligibility.req-proof-of-age","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHEVX4PMNN077ASQMG","attributeName":"req-proof-of-income","attributeKey":"eligibility.req-proof-of-income","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHGMVCAY1G5BWF1PFB","attributeName":"req-proof-of-residence","attributeKey":"eligibility.req-proof-of-residence","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJH8MADHYTHBV54CER","attributeName":"req-referral","attributeKey":"eligibility.req-referral","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","attributeName":"time-appointment-required","attributeKey":"eligibility.time-appointment-required","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJGDDWTR5D0C8BY357","attributeName":"all-languages-by-interpreter","attributeKey":"lang.all-languages-by-interpreter","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJF09GXY5N5CKMSANJ","attributeName":"american-sign-language","attributeKey":"lang.american-sign-language","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","attributeName":"lang-offered","attributeKey":"lang.lang-offered","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":true,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRH531R2HAV8DMDZSC","attributeName":"corp-law-firm","attributeKey":"userlawpractice.corp-law-firm","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVSE2074QZJ4SKEW74J","attributeName":"law-other","attributeKey":"userlawpractice.law-other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"otherDescribe","canAttachTo":["USER"],"dataSchema":[{"key":"other","label":"Other","name":"other","type":"text"}]},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRS8XEJ3TJBBEQJ707","attributeName":"law-school-clinic","attributeKey":"userlawpractice.law-school-clinic","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRFPRQCQHNJA6BM3XP","attributeName":"legal-nonprofit","attributeKey":"userlawpractice.legal-nonprofit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNPKMHYK12DDRVC1VJ","attributeName":"bipoc-led","attributeKey":"orgleader.bipoc-led","attributeNs":"attribute","interpolationValues":null,"icon":"🤎","iconBg":"#F1DD7F","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3JX2J7REFFT5NAMS","attributeName":"black-led","attributeKey":"orgleader.black-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":"#C77E54","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNHMF72WHVKRF6W4TA","attributeName":"immigrant-led","attributeKey":"orgleader.immigrant-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":"#79ADD7","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3RYX9JMXDZSQZM70","attributeName":"trans-led","attributeKey":"orgleader.trans-led","attributeNs":"attribute","interpolationValues":null,"icon":"️🏳️⚧️","iconBg":"#705890","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKFM4TDY4QRK4AR2ZW","attributeName":"accessemail","attributeKey":"serviceaccess.accessemail","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKMRHFD8SMDAZM3SSM","attributeName":"accessfile","attributeKey":"serviceaccess.accessfile","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","attributeName":"accesslink","attributeKey":"serviceaccess.accesslink","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","attributeName":"accesslocation","attributeKey":"serviceaccess.accesslocation","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","attributeName":"accessphone","attributeKey":"serviceaccess.accessphone","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMSX7T1WDNZ5QEHKWT","attributeName":"accesspublictransit","attributeKey":"serviceaccess.accesspublictransit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMMF19AX2KPBTMV6P3","attributeName":"accesstext","attributeKey":"serviceaccess.accesstext","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPCVX8F3B7M30ZJEHW","attributeName":"asylum-seekers","attributeKey":"srvfocus.asylum-seekers","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ7SYGD3KM8WP9X50B","attributeName":"gender-nc","attributeKey":"srvfocus.gender-nc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVRMQFJ9AMA633SQQGV","attributeName":"hiv-comm","attributeKey":"srvfocus.hiv-comm","attributeNs":"attribute","interpolationValues":null,"icon":"💛","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPTK9555WHJHDBDA2J","attributeName":"immigrant-comm","attributeKey":"srvfocus.immigrant-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPJERY0GS9D7F56A23","attributeName":"resettled-refugees","attributeKey":"srvfocus.resettled-refugees","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ8AGBKBBZJWTHNP2F","attributeName":"spanish-speakers","attributeKey":"srvfocus.spanish-speakers","attributeNs":"attribute","interpolationValues":null,"icon":"🗣️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQX4M8DY1FSAYSJSSK","attributeName":"trans-fem","attributeKey":"srvfocus.trans-fem","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQEFWW42MBAD64BWXZ","attributeName":"trans-masc","attributeKey":"srvfocus.trans-masc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQVEGH6W3A2ANH1QZE","attributeName":"trans-youth-focus","attributeKey":"srvfocus.trans-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TK83N5E52PPP828SD88KP8","attributeName":"userserviceprovider.case-mananger","attributeKey":"userserviceprovider.case-mananger","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTTZ83PZR61M37R8R7","attributeName":"userserviceprovider.community-org","attributeKey":"userserviceprovider.community-org","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVSPXWJJPFG9DKXESEK","attributeName":"userserviceprovider.healthcare","attributeKey":"userserviceprovider.healthcare","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM092CFVG6H0MR148AVAP7","attributeName":"userserviceprovider.lawyer","attributeKey":"userserviceprovider.lawyer","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AJHVK8TSR8JNFANFNZ7","attributeName":"userserviceprovider.other","attributeKey":"userserviceprovider.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09EG0G84NXH40G5TESB5","attributeName":"userserviceprovider.paralegal","attributeKey":"userserviceprovider.paralegal","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09RAK024ZDZQ6FSY0TXB","attributeName":"userserviceprovider.social-worker","attributeKey":"userserviceprovider.social-worker","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTN6MSCMBW740Y7HN1","attributeName":"userserviceprovider.student-club","attributeKey":"userserviceprovider.student-club","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0A19DD6S97DNH76ZVP40","attributeName":"userserviceprovider.teacher","attributeKey":"userserviceprovider.teacher","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AA4CZXJJHMXHE1PHMVV","attributeName":"userserviceprovider.therapist-counselor","attributeKey":"userserviceprovider.therapist-counselor","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKM2PSHFWVFM0TWX1P","categoryName":"system","categoryDisplay":"System","attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","attributeName":"incompatible-info","attributeKey":"sys.incompatible-info","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"incompatibleData","canAttachTo":["LOCATION","ORGANIZATION","SERVICE","USER"],"dataSchema":[{"key":"incompatible","label":"Incompatible","name":"incompatible","type":"text"}]},{"categoryId":"attc_01HNG5BPYJADWX4YFVNENS3TRD","categoryName":"target-population","categoryDisplay":"Target Population","attributeId":"attr_01HNG5GDC5MXW30F32FWJNJ98C","attributeName":"tpop-other","attributeKey":"tpop.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null}]
From ad8d544bb602abc8564f1508bbaf0442600db916 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 15 Feb 2024 15:46:52 -0500
Subject: [PATCH 12/61] update api
---
.../query.attributesByCategory.handler.ts | 37 ++++++-------
.../query.attributesByCategory.schema.ts | 53 +++++++++++++++++--
2 files changed, 69 insertions(+), 21 deletions(-)
diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
index 435bff34ab..e5a863d006 100644
--- a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
+++ b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
@@ -1,29 +1,30 @@
-import flush from 'just-flush'
-import { type SetOptional } from 'type-fest'
-
import { prisma } from '@weareinreach/db'
-import { type AttributesByCategory } from '@weareinreach/db/client'
+import { type FieldAttributes } from '@weareinreach/db/zod_util/attributeSupplement'
import { type TRPCHandlerParams } from '~api/types/handler'
-import { type TAttributesByCategorySchema } from './query.attributesByCategory.schema'
+import { fieldAttributesSchema, type TAttributesByCategorySchema } from './query.attributesByCategory.schema'
export const attributesByCategory = async ({ input }: TRPCHandlerParams) => {
- const where = Array.isArray(input)
- ? { categoryName: { in: input } }
- : typeof input === 'string'
- ? { categoryName: input }
- : undefined
+ console.log(input)
const result = await prisma.attributesByCategory.findMany({
- where,
+ where: {
+ categoryName: Array.isArray(input?.categoryName) ? { in: input.categoryName } : input?.categoryName,
+ canAttachTo: input?.canAttachTo?.length ? { hasSome: input.canAttachTo } : undefined,
+ },
orderBy: [{ categoryName: 'asc' }, { attributeName: 'asc' }],
})
- const flushedResults = result.map((item) =>
- flush(item)
- ) as FlushedAttributesByCategory[]
+ const flushedResults = result.map((item) => {
+ const { dataSchema, ...rest } = item
+
+ const parsedDataSchema = fieldAttributesSchema.safeParse(dataSchema)
+
+ return {
+ ...rest,
+ dataSchema: parsedDataSchema.success
+ ? (parsedDataSchema.data as FieldAttributes[] | FieldAttributes[][])
+ : null,
+ }
+ })
return flushedResults
}
-type FlushedAttributesByCategory = SetOptional<
- AttributesByCategory,
- 'interpolationValues' | 'icon' | 'iconBg' | 'badgeRender' | 'dataSchema' | 'dataSchemaName'
->
diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts b/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts
index 8fc56977e4..9c7f4052b4 100644
--- a/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts
+++ b/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts
@@ -1,8 +1,55 @@
import { z } from 'zod'
+import { FieldType } from '@weareinreach/db/zod_util/attributeSupplement'
+
export const ZAttributesByCategorySchema = z
- .string()
- .or(z.string().array())
+ .object({
+ categoryName: z.string().or(z.string().array()).optional().describe('categoryName'),
+ canAttachTo: z.enum(['LOCATION', 'ORGANIZATION', 'SERVICE', 'USER']).array().optional(),
+ })
.optional()
- .describe('categoryName')
export type TAttributesByCategorySchema = z.infer
+
+const fieldTypeSchema = z.nativeEnum(FieldType)
+const baseFieldAttributesSchema = z.object({
+ key: z.string(),
+ label: z.string(),
+ name: z.string(),
+ type: fieldTypeSchema,
+ required: z.boolean().optional(),
+})
+
+const textFieldAttributesSchema = baseFieldAttributesSchema.extend({
+ type: z.literal(FieldType.text),
+})
+
+const selectFieldAttributesSchema = baseFieldAttributesSchema.extend({
+ type: z.literal(FieldType.select),
+ options: z.array(
+ z.object({
+ value: z.string(),
+ label: z.string(),
+ })
+ ),
+})
+
+const numberFieldAttributesSchema = baseFieldAttributesSchema.extend({
+ type: z.literal(FieldType.number),
+})
+
+const currencyFieldAttributesSchema = baseFieldAttributesSchema.extend({
+ type: z.literal(FieldType.currency),
+})
+
+const fieldAttributesObject = z
+ .union([
+ textFieldAttributesSchema,
+ selectFieldAttributesSchema,
+ numberFieldAttributesSchema,
+ currencyFieldAttributesSchema,
+ ])
+ .brand('FieldAttributes')
+ .array()
+export const fieldAttributesSchema = fieldAttributesObject.or(fieldAttributesObject.array())
+
+export type TFieldAttributesSchema = z.infer
From baf803a57e2874329a526c78a497d7b19222b299 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 15 Feb 2024 15:49:47 -0500
Subject: [PATCH 13/61] Attribute modal
---
packages/db/zod_util/attributeSupplement.ts | 10 +-
.../modals/dataPortal/Attributes/fields.tsx | 12 +-
.../dataPortal/Attributes/index.stories.tsx | 12 +-
.../ui/modals/dataPortal/Attributes/index.tsx | 282 +++++-------------
.../ui/modals/dataPortal/Attributes/types.ts | 35 +++
5 files changed, 133 insertions(+), 218 deletions(-)
diff --git a/packages/db/zod_util/attributeSupplement.ts b/packages/db/zod_util/attributeSupplement.ts
index 5e0cd88f7f..b0d120251a 100644
--- a/packages/db/zod_util/attributeSupplement.ts
+++ b/packages/db/zod_util/attributeSupplement.ts
@@ -139,7 +139,7 @@ export enum FieldType {
number = 'number',
currency = 'currency',
}
-interface BaseFieldAttributes {
+export interface BaseFieldAttributes {
key: string
label: string
name: string
@@ -147,17 +147,17 @@ interface BaseFieldAttributes {
required?: boolean
}
-interface TextFieldAttributes extends BaseFieldAttributes {
+export interface TextFieldAttributes extends BaseFieldAttributes {
type: FieldType.text
}
-interface SelectFieldAttributes extends BaseFieldAttributes {
+export interface SelectFieldAttributes extends BaseFieldAttributes {
type: FieldType.select
options: { value: string; label: string }[]
}
-interface NumberFieldAttributes extends BaseFieldAttributes {
+export interface NumberFieldAttributes extends BaseFieldAttributes {
type: FieldType.number
}
-interface CurrencyFieldAttributes extends BaseFieldAttributes {
+export interface CurrencyFieldAttributes extends BaseFieldAttributes {
type: FieldType.currency
}
export type FieldAttributes =
diff --git a/packages/ui/modals/dataPortal/Attributes/fields.tsx b/packages/ui/modals/dataPortal/Attributes/fields.tsx
index b16d3d4ef7..55ab79ec99 100644
--- a/packages/ui/modals/dataPortal/Attributes/fields.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/fields.tsx
@@ -1,9 +1,8 @@
import { Group, Select as MantineSelect, Stack, Text } from '@mantine/core'
import { useTranslation } from 'next-i18next'
import { type ComponentPropsWithoutRef, forwardRef, useState } from 'react'
-import { type FieldPath, useFormContext } from 'react-hook-form'
+import { useFormContext } from 'react-hook-form'
import { NumberInput, Radio, Select, TextInput } from 'react-hook-form-mantine'
-import { type TupleToUnion } from 'type-fest'
import { type ApiOutput } from '@weareinreach/api'
import { type FieldAttributes, FieldType } from '@weareinreach/db/zod_util/attributeSupplement'
@@ -26,7 +25,6 @@ const SuppBoolean = () => {
const SuppText = () => {
const { control } = useFormContext()
- const { t } = useTranslation('common')
return (
@@ -118,10 +116,10 @@ const SuppGeo = ({ countryOnly }: SuppGeoProps) => {
const [secondarySearch, onSecondarySearch] = useState(null)
const [tertiarySearch, onTertiarySearch] = useState(null)
- const [finalValue, setFinalValue] = useState(null)
- const [fieldName, setFieldName] = useState | undefined>(
- countryOnly ? 'countryId' : undefined
- )
+ // const [finalValue, setFinalValue] = useState(null)
+ // const [fieldName, setFieldName] = useState | undefined>(
+ // countryOnly ? 'countryId' : undefined
+ // )
const { data: countryList, ...countries } = api.fieldOpt.countries.useQuery(undefined, {
enabled: countryOnly ?? false,
diff --git a/packages/ui/modals/dataPortal/Attributes/index.stories.tsx b/packages/ui/modals/dataPortal/Attributes/index.stories.tsx
index bbadd4633c..1d6b966cc2 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.stories.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.stories.tsx
@@ -1,10 +1,11 @@
-import { type Meta } from '@storybook/react'
+import { type Meta, type StoryObj } from '@storybook/react'
import { Button } from '~ui/components/core/Button'
import { allFieldOptHandlers } from '~ui/mockData/fieldOpt'
import { AttributeModal } from './index'
+type StoryDef = StoryObj
export default {
title: 'Data Portal/Modals/Attributes',
component: AttributeModal,
@@ -18,7 +19,14 @@ export default {
component: Button,
children: 'Open Modal',
variant: 'inlineInvertedUtil1',
+ restrictCategories: undefined,
+ attachesTo: undefined,
},
} satisfies Meta
-export const Modal = {}
+export const AllCategories = {} satisfies StoryDef
+export const AttachesToService = {
+ args: {
+ attachesTo: ['SERVICE'],
+ },
+} satisfies StoryDef
diff --git a/packages/ui/modals/dataPortal/Attributes/index.tsx b/packages/ui/modals/dataPortal/Attributes/index.tsx
index 05257008ca..90146af75a 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.tsx
@@ -8,7 +8,6 @@ import {
Group,
Select as MantineSelect,
Modal,
- Select,
Skeleton,
Stack,
Text,
@@ -16,31 +15,29 @@ import {
import { useDisclosure } from '@mantine/hooks'
import Ajv from 'ajv'
import { useTranslation } from 'next-i18next'
-import { type ComponentPropsWithoutRef, forwardRef, useEffect, useRef, useState } from 'react'
-import { FormProvider, useFieldArray, useForm } from 'react-hook-form'
+import { forwardRef, useMemo, useRef, useState } from 'react'
+import { FormProvider, useForm } from 'react-hook-form'
-import { Badge } from '~ui/components/core/Badge'
+import { type ApiOutput } from '@weareinreach/api'
import { Button } from '~ui/components/core/Button'
-import { Icon, isValidIcon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
import { ModalTitle } from '~ui/modals/ModalTitle'
import { Supplement } from './fields'
import { formSchema, type FormSchema } from './schema'
import { SelectionItem } from './SelectionItem'
-import { type SupplementEventHandler } from './types'
-const supplementFields = {
+const supplementDefaults = {
boolean: false,
geo: false,
language: false,
text: false,
data: false,
} as const
-type SupplementFieldsNeeded = { [K in keyof typeof supplementFields]: boolean }
+type SupplementFieldsNeeded = { [K in keyof typeof supplementDefaults]: boolean }
const AttributeModalBody = forwardRef(
- ({ restrictCategories, ...props }, ref) => {
+ ({ restrictCategories, attachesTo, ...props }, ref) => {
const { t } = useTranslation(['attribute', 'common'])
const [opened, handler] = useDisclosure(false)
@@ -50,74 +47,70 @@ const AttributeModalBody = forwardRef(
const selectAttrRef = useRef(null)
// #region tRPC
const utils = api.useUtils()
- const { data: attributeCategories, ...attributeCategoriesApi } =
- api.fieldOpt.attributeCategories.useQuery(restrictCategories, {
- refetchOnWindowFocus: false,
- select: (data) => data.map(({ name, tag }) => ({ value: tag, label: name })),
- })
const [attrCat, setAttrCat] = useState()
-
const { data: attributesByCategory, ...attributesByCategoryApi } =
- api.fieldOpt.attributesByCategory.useQuery(attrCat ?? '', {
- enabled: Boolean(attrCat),
+ api.fieldOpt.attributesByCategory.useQuery(undefined, {
refetchOnWindowFocus: false,
- select: (data) =>
- data.map(
- ({
- attributeId,
- attributeKey,
- interpolationValues,
- icon,
- iconBg,
- badgeRender,
- requireBoolean,
- requireGeo,
- requireData,
- requireLanguage,
- requireText,
- dataSchemaName,
- dataSchema,
- }) => ({
- value: attributeId,
- label: t(attributeKey),
- tKey: attributeKey,
- interpolationValues,
- icon: icon ?? undefined,
- iconBg: iconBg ?? undefined,
- variant: badgeRender ?? undefined,
- requireBoolean,
- requireGeo,
- requireData,
- requireLanguage,
- requireText,
- dataSchemaName,
- dataSchema,
- })
- ),
+ select: (data) => {
+ return data.map(({ attributeId, attributeKey, ...rest }) => ({
+ value: attributeId,
+ label: t(attributeKey),
+ tKey: attributeKey,
+ ...rest,
+ }))
+ },
})
+ const attributeCategories = useMemo(() => {
+ return [
+ ...new Set(
+ attributesByCategory
+ ?.filter(({ canAttachTo }) => {
+ if (!attachesTo?.length) {
+ return true
+ }
+ let match = false
+ for (const item of canAttachTo) {
+ if (attachesTo.includes(item)) {
+ match = true
+ break
+ }
+ }
+ return match
+ })
+ .map(({ categoryName, categoryDisplay }) => {
+ return JSON.stringify({
+ value: categoryName,
+ label: categoryDisplay,
+ })
+ })
+ ),
+ ].map((v) => {
+ return JSON.parse(v) as { value: string; label: string }
+ })
+ }, [attributesByCategory, attachesTo])
const [selectedAttr, setSelectedAttr] = useState[number] | null>(
null
)
- const [supplements, setSupplements] = useState(supplementFields)
+ const [supplements, setSupplements] = useState(supplementDefaults)
const saveAttributes = api.organization.attachAttribute.useMutation()
// #endregion
- console.log({ attrCat, selectedAttr, supplements })
+
// #region Handlers
const selectHandler = (e: string | null) => {
- console.log(e)
- if (e === null) return setSelectedAttr(null)
+ console.log('selectHandler', e)
+ if (e === null) {
+ setSupplements(supplementDefaults)
+ setSelectedAttr(null)
+ return
+ }
const item = attributesByCategory?.find(({ value }) => value === e)
if (item) {
setSelectedAttr(item)
const { requireBoolean, requireGeo, requireData, requireLanguage, requireText } = item
/** Check if supplemental info required */
if (requireBoolean || requireGeo || requireData || requireLanguage || requireText) {
- // const { boolean, countryId, govDistId, languageId, text, data } = form.values.supplement ?? {}
/** Handle if supplemental info is provided */
-
- // if (!form.values.supplement) {
- console.log('init supp handler', item)
const suppRequired: SupplementFieldsNeeded = {
boolean: requireBoolean ?? false,
geo: requireGeo ?? false,
@@ -128,74 +121,11 @@ const AttributeModalBody = forwardRef(
setSupplements(suppRequired)
return
- // }
}
form.setValue('attributeId', item.value)
- // const { label, value, icon, iconBg, variant, tKey } = item
- // form.setFieldValue('selected', [
- // ...form.values.selected,
- // { label, value, icon, iconBg, variant, tKey },
- // ])
- // form.setFieldValue(
- // 'attributes',
- // form.values.attributes?.filter(({ value }) => value !== e)
- // )
selectAttrRef.current && (selectAttrRef.current.value = '')
}
}
- const handleSupplement = (e: SupplementEventHandler) => {
- const item = attributesByCategory?.find(({ value }) => value === e.attributeId)
- if (!item) return
- const { requireBoolean, requireGeo, requireData, requireLanguage, requireText } = item
- const { boolean, countryId, govDistId, languageId, text, data } = e ?? {}
- console.log('🚀 ~ file: index.tsx:219 ~ handleSupplement ~ e:', e)
-
- if (
- (requireBoolean && boolean !== undefined) ||
- (requireGeo && (countryId || govDistId)) ||
- (requireLanguage && languageId) ||
- (requireText && text) ||
- (requireData && data)
- ) {
- console.log('handler after supp')
- const { value, label, icon, iconBg, variant, tKey } = item
- // clear state
- setSupplements(supplementFields)
-
- // form.setValues({
- // selected: [
- // ...form.values.selected,
- // {
- // value,
- // label,
- // icon,
- // iconBg,
- // variant,
- // countryId,
- // govDistId,
- // languageId,
- // text,
- // boolean,
- // data,
- // tKey,
- // },
- // ],
- // attributes: form.values.attributes?.filter(({ value }) => value !== e.attributeId),
- // supplement: undefined,
- // })
-
- // clear out supplement store
- return
- }
- }
-
- const removeHandler = (e: string) => {
- // form.setFieldValue(
- // 'selected',
- // form.values.selected?.filter(({ value }) => value !== e)
- // )
- utils.fieldOpt.attributesByCategory.invalidate()
- }
const submitHandler = () => {
//TODO: [IN-871] Create submit handler - convert tRPC organization.attachAttribute to be able to handle multiple items & accept org, serv, loc
@@ -205,69 +135,13 @@ const AttributeModalBody = forwardRef(
// #region Title & Selected items display
const modalTitle =
- // const selectedItems = form.values.selected?.map(({ label, icon, variant, value, iconBg, tKey, data }) => {
- // switch (variant) {
- // case 'ATTRIBUTE': {
- // return (
- //
- //
- // removeHandler(value)} />
- //
- // )
- // }
- // case 'COMMUNITY': {
- // return (
- //
- //
- // removeHandler(value)} />
- //
- // )
- // }
- // case 'LEADER': {
- // return (
- //
- //
- // removeHandler(value)} />
- //
- // )
- // }
- // case 'LIST': {
- // return (
- //
- // {t(tKey, { ns: 'attribute', context: 'range', ...data })}
- // removeHandler(value)} />
- //
- // )
- // }
- // }
- // })
- // #endregion
-
- /** Validate supplement data against defined JSON schema */
- // useEffect(() => {
- // const { data, schema } = form.values.supplement ?? {}
- // if (data && schema) {
- // const ajv = new Ajv()
- // const validate = ajv.compile(schema as object)
- // const validData = validate(data)
- // if (!validData && validate.errors) {
- // form.setFieldError(
- // 'supplement.data',
- // validate.errors.map(({ message }) => message)
- // )
- // }
- // }
- // // eslint-disable-next-line react-hooks/exhaustive-deps
- // }, [form.values.supplement])
-
const needsSupplement = Object.values(supplements).includes(true)
return (
handler.close()}>
- {/* {selectedItems} */}
-
+
(
clearable
/>
- {true && (
- //
- ({ label, value }))
- }
- value={selectedAttr?.value ?? null}
- label='Select Attribute'
- disabled={!attrCat || !attributesByCategory?.length}
- withinPortal
- itemComponent={SelectionItem}
- searchable={(attributesByCategory?.length ?? 0) > 10}
- ref={selectAttrRef}
- clearable
- // {...form.getInputProps('selected')}
- onChange={selectHandler}
- inputContainer={(children) => (
-
- {children}
-
- )}
- />
- //
- )}
+ categoryName === attrCat)
+ .map(({ label, value }) => ({ label, value }))
+ }
+ value={selectedAttr?.value ?? null}
+ label='Select Attribute'
+ disabled={!attrCat || !attributesByCategory?.length}
+ withinPortal
+ itemComponent={SelectionItem}
+ searchable={(attributesByCategory?.length ?? 0) > 10}
+ ref={selectAttrRef}
+ clearable
+ onChange={selectHandler}
+ inputContainer={(children) => (
+
+ {children}
+
+ )}
+ />
{supplements.boolean && }
{supplements.text && }
- {supplements.data && selectedAttr?.dataSchemaName && (
-
+ {supplements.data && selectedAttr?.dataSchema && (
+
)}
{supplements.language && }
{supplements.geo && }
@@ -329,4 +202,5 @@ export const AttributeModal = createPolymorphicComponent<'button', AttributeModa
export interface AttributeModalProps extends ButtonProps {
restrictCategories?: string[]
+ attachesTo?: ApiOutput['fieldOpt']['attributesByCategory'][number]['canAttachTo']
}
diff --git a/packages/ui/modals/dataPortal/Attributes/types.ts b/packages/ui/modals/dataPortal/Attributes/types.ts
index 04f1b0b93b..71afb5e562 100644
--- a/packages/ui/modals/dataPortal/Attributes/types.ts
+++ b/packages/ui/modals/dataPortal/Attributes/types.ts
@@ -7,3 +7,38 @@ export interface SupplementEventHandler {
boolean?: boolean
data?: object
}
+
+/** Dynamic Fields for Supplement Data Schemas */
+
+export enum FieldType {
+ text = 'text',
+ select = 'select',
+ number = 'number',
+ currency = 'currency',
+}
+interface BaseFieldAttributes {
+ key: string
+ label: string
+ name: string
+ type: FieldType
+ required?: boolean
+}
+
+interface TextFieldAttributes extends BaseFieldAttributes {
+ type: FieldType.text
+}
+interface SelectFieldAttributes extends BaseFieldAttributes {
+ type: FieldType.select
+ options: { value: string; label: string }[]
+}
+interface NumberFieldAttributes extends BaseFieldAttributes {
+ type: FieldType.number
+}
+interface CurrencyFieldAttributes extends BaseFieldAttributes {
+ type: FieldType.currency
+}
+export type FieldAttributes =
+ | TextFieldAttributes
+ | SelectFieldAttributes
+ | NumberFieldAttributes
+ | CurrencyFieldAttributes
From cec13663bd05b01ec87192ac595051e6deb2dd0a Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 15 Feb 2024 16:05:52 -0500
Subject: [PATCH 14/61] update handler cache
---
packages/api/router/fieldOpt/index.ts | 147 +++++++-----------
.../query.attributeCategories.handler.ts | 1 +
.../query.attributesByCategory.handler.ts | 1 +
.../fieldOpt/query.countries.handler.ts | 1 +
.../query.countryGovDistMap.handler.ts | 1 +
.../fieldOpt/query.getSubDistricts.handler.ts | 1 +
.../router/fieldOpt/query.govDists.handler.ts | 1 +
.../query.govDistsByCountry.handler.ts | 1 +
.../query.govDistsByCountryNoSub.handler.ts | 1 +
.../fieldOpt/query.languages.handler.ts | 1 +
.../fieldOpt/query.orgBadges.handler.ts | 3 +-
.../fieldOpt/query.phoneTypes.handler.ts | 1 +
.../fieldOpt/query.userTitle.handler.ts | 1 +
13 files changed, 69 insertions(+), 92 deletions(-)
diff --git a/packages/api/router/fieldOpt/index.ts b/packages/api/router/fieldOpt/index.ts
index 880b3cbdf9..9a16c851ab 100644
--- a/packages/api/router/fieldOpt/index.ts
+++ b/packages/api/router/fieldOpt/index.ts
@@ -1,23 +1,10 @@
-import { defineRouter, publicProcedure } from '~api/lib/trpc'
+import { defineRouter, importHandler, publicProcedure } from '~api/lib/trpc'
import * as schema from './schemas'
-const HandlerCache: Partial = {}
+const NAMESPACE = 'fieldOpt'
-type FieldOptHandlerCache = {
- govDistsByCountry: typeof import('./query.govDistsByCountry.handler').govDistsByCountry
- govDistsByCountryNoSub: typeof import('./query.govDistsByCountryNoSub.handler').govDistsByCountryNoSub
- phoneTypes: typeof import('./query.phoneTypes.handler').phoneTypes
- attributesByCategory: typeof import('./query.attributesByCategory.handler').attributesByCategory
- attributeCategories: typeof import('./query.attributeCategories.handler').attributeCategories
- languages: typeof import('./query.languages.handler').languages
- countries: typeof import('./query.countries.handler').countries
- userTitle: typeof import('./query.userTitle.handler').userTitle
- countryGovDistMap: typeof import('./query.countryGovDistMap.handler').countryGovDistMap
- getSubDistricts: typeof import('./query.getSubDistricts.handler').getSubDistricts
- govDists: typeof import('./query.govDists.handler').govDists
- orgBadges: typeof import('./query.orgBadges.handler').orgBadges
-}
+const namespaced = (s: string) => `${NAMESPACE}.${s}`
export const fieldOptRouter = defineRouter({
/** All government districts by country (active for org listings). Gives up to 2 levels of sub-districts */
govDistsByCountry: publicProcedure
@@ -26,97 +13,75 @@ export const fieldOptRouter = defineRouter({
'All government districts by country (active for org listings). Gives 2 levels of sub-districts',
})
.input(schema.ZGovDistsByCountrySchema)
- .query(async ({ ctx, input }) => {
- if (!HandlerCache.govDistsByCountry)
- HandlerCache.govDistsByCountry = await import('./query.govDistsByCountry.handler').then(
- (mod) => mod.govDistsByCountry
- )
- if (!HandlerCache.govDistsByCountry) throw new Error('Failed to load handler')
- return HandlerCache.govDistsByCountry({ ctx, input })
+ .query(async (opts) => {
+ const handler = await importHandler(
+ namespaced('govDistsByCountry'),
+ () => import('./query.govDistsByCountry.handler')
+ )
+ return handler(opts)
}),
govDistsByCountryNoSub: publicProcedure
.meta({
description: 'All government districts by country (active for org listings).',
})
.input(schema.ZGovDistsByCountryNoSubSchema)
- .query(async ({ ctx, input }) => {
- if (!HandlerCache.govDistsByCountryNoSub)
- HandlerCache.govDistsByCountryNoSub = await import('./query.govDistsByCountryNoSub.handler').then(
- (mod) => mod.govDistsByCountryNoSub
- )
- if (!HandlerCache.govDistsByCountryNoSub) throw new Error('Failed to load handler')
- return HandlerCache.govDistsByCountryNoSub({ ctx, input })
+ .query(async (opts) => {
+ const handler = await importHandler(
+ namespaced('govDistsByCountryNoSub'),
+ () => import('./query.govDistsByCountryNoSub.handler')
+ )
+ return handler(opts)
}),
phoneTypes: publicProcedure.query(async () => {
- if (!HandlerCache.phoneTypes)
- HandlerCache.phoneTypes = await import('./query.phoneTypes.handler').then((mod) => mod.phoneTypes)
- if (!HandlerCache.phoneTypes) throw new Error('Failed to load handler')
- return HandlerCache.phoneTypes()
+ const handler = await importHandler(namespaced('phoneTypes'), () => import('./query.phoneTypes.handler'))
+ return handler()
}),
- attributesByCategory: publicProcedure
- .input(schema.ZAttributesByCategorySchema)
- .query(async ({ ctx, input }) => {
- if (!HandlerCache.attributesByCategory)
- HandlerCache.attributesByCategory = await import('./query.attributesByCategory.handler').then(
- (mod) => mod.attributesByCategory
- )
- if (!HandlerCache.attributesByCategory) throw new Error('Failed to load handler')
- return HandlerCache.attributesByCategory({ ctx, input })
- }),
- attributeCategories: publicProcedure
- .input(schema.ZAttributeCategoriesSchema)
- .query(async ({ ctx, input }) => {
- if (!HandlerCache.attributeCategories)
- HandlerCache.attributeCategories = await import('./query.attributeCategories.handler').then(
- (mod) => mod.attributeCategories
- )
- if (!HandlerCache.attributeCategories) throw new Error('Failed to load handler')
- return HandlerCache.attributeCategories({ ctx, input })
- }),
- languages: publicProcedure.input(schema.ZLanguagesSchema).query(async ({ ctx, input }) => {
- if (!HandlerCache.languages)
- HandlerCache.languages = await import('./query.languages.handler').then((mod) => mod.languages)
- if (!HandlerCache.languages) throw new Error('Failed to load handler')
- return HandlerCache.languages({ ctx, input })
+ attributesByCategory: publicProcedure.input(schema.ZAttributesByCategorySchema).query(async (opts) => {
+ const handler = await importHandler(
+ namespaced('attributesByCategory'),
+ () => import('./query.attributesByCategory.handler')
+ )
+ return handler(opts)
}),
- countries: publicProcedure.input(schema.ZCountriesSchema).query(async ({ ctx, input }) => {
- if (!HandlerCache.countries)
- HandlerCache.countries = await import('./query.countries.handler').then((mod) => mod.countries)
- if (!HandlerCache.countries) throw new Error('Failed to load handler')
- return HandlerCache.countries({ ctx, input })
+ attributeCategories: publicProcedure.input(schema.ZAttributeCategoriesSchema).query(async (opts) => {
+ const handler = await importHandler(
+ namespaced('attributeCategories'),
+ () => import('./query.attributeCategories.handler')
+ )
+ return handler(opts)
+ }),
+ languages: publicProcedure.input(schema.ZLanguagesSchema).query(async (opts) => {
+ const handler = await importHandler(namespaced('languages'), () => import('./query.languages.handler'))
+ return handler(opts)
+ }),
+ countries: publicProcedure.input(schema.ZCountriesSchema).query(async (opts) => {
+ const handler = await importHandler(namespaced('countries'), () => import('./query.countries.handler'))
+ return handler(opts)
}),
userTitle: publicProcedure.query(async () => {
- if (!HandlerCache.userTitle)
- HandlerCache.userTitle = await import('./query.userTitle.handler').then((mod) => mod.userTitle)
- if (!HandlerCache.userTitle) throw new Error('Failed to load handler')
- return HandlerCache.userTitle()
+ const handler = await importHandler(namespaced('userTitle'), () => import('./query.userTitle.handler'))
+ return handler()
}),
countryGovDistMap: publicProcedure.query(async () => {
- if (!HandlerCache.countryGovDistMap)
- HandlerCache.countryGovDistMap = await import('./query.countryGovDistMap.handler').then(
- (mod) => mod.countryGovDistMap
- )
- if (!HandlerCache.countryGovDistMap) throw new Error('Failed to load handler')
- return HandlerCache.countryGovDistMap()
+ const handler = await importHandler(
+ namespaced('countryGovDistMap'),
+ () => import('./query.countryGovDistMap.handler')
+ )
+ return handler()
}),
- getSubDistricts: publicProcedure.input(schema.ZGetSubDistrictsSchema).query(async ({ ctx, input }) => {
- if (!HandlerCache.getSubDistricts)
- HandlerCache.getSubDistricts = await import('./query.getSubDistricts.handler').then(
- (mod) => mod.getSubDistricts
- )
- if (!HandlerCache.getSubDistricts) throw new Error('Failed to load handler')
- return HandlerCache.getSubDistricts({ ctx, input })
+ getSubDistricts: publicProcedure.input(schema.ZGetSubDistrictsSchema).query(async (opts) => {
+ const handler = await importHandler(
+ namespaced('getSubDistricts'),
+ () => import('./query.getSubDistricts.handler')
+ )
+ return handler(opts)
}),
- govDists: publicProcedure.input(schema.ZGovDistsSchema).query(async ({ ctx, input }) => {
- if (!HandlerCache.govDists)
- HandlerCache.govDists = await import('./query.govDists.handler').then((mod) => mod.govDists)
- if (!HandlerCache.govDists) throw new Error('Failed to load handler')
- return HandlerCache.govDists({ ctx, input })
+ govDists: publicProcedure.input(schema.ZGovDistsSchema).query(async (opts) => {
+ const handler = await importHandler(namespaced('govDists'), () => import('./query.govDists.handler'))
+ return handler(opts)
}),
- orgBadges: publicProcedure.input(schema.ZOrgBadgesSchema).query(async ({ ctx, input }) => {
- if (!HandlerCache.orgBadges)
- HandlerCache.orgBadges = await import('./query.orgBadges.handler').then((mod) => mod.orgBadges)
- if (!HandlerCache.orgBadges) throw new Error('Failed to load handler')
- return HandlerCache.orgBadges({ ctx, input })
+ orgBadges: publicProcedure.input(schema.ZOrgBadgesSchema).query(async (opts) => {
+ const handler = await importHandler(namespaced('orgBadges'), () => import('./query.orgBadges.handler'))
+ return handler(opts)
}),
})
diff --git a/packages/api/router/fieldOpt/query.attributeCategories.handler.ts b/packages/api/router/fieldOpt/query.attributeCategories.handler.ts
index a9c3d6e8eb..a368b90c21 100644
--- a/packages/api/router/fieldOpt/query.attributeCategories.handler.ts
+++ b/packages/api/router/fieldOpt/query.attributeCategories.handler.ts
@@ -17,3 +17,4 @@ export const attributeCategories = async ({ input }: TRPCHandlerParams)
type CountryResult = (typeof result)[number][]
return result as CountryResult
}
+export default countries
diff --git a/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts b/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts
index 064e9af05d..6293163f3e 100644
--- a/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts
+++ b/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts
@@ -46,3 +46,4 @@ interface CountryGovDistMapItem {
children: CountryGovDistMapItemBasic[]
parent?: CountryGovDistMapItemBasic & { parent?: CountryGovDistMapItemBasic }
}
+export default countryGovDistMap
diff --git a/packages/api/router/fieldOpt/query.getSubDistricts.handler.ts b/packages/api/router/fieldOpt/query.getSubDistricts.handler.ts
index 76ef4261f8..08d81fcecb 100644
--- a/packages/api/router/fieldOpt/query.getSubDistricts.handler.ts
+++ b/packages/api/router/fieldOpt/query.getSubDistricts.handler.ts
@@ -30,3 +30,4 @@ export const getSubDistricts = async ({ input }: TRPCHandlerParams) =>
handleError(error)
}
}
+export default govDists
diff --git a/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts b/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts
index 25d0581b92..c4e2eaa70d 100644
--- a/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts
+++ b/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts
@@ -58,3 +58,4 @@ export const govDistsByCountry = async ({ input }: TRPCHandlerParams)
})
return results
}
+export default languages
diff --git a/packages/api/router/fieldOpt/query.orgBadges.handler.ts b/packages/api/router/fieldOpt/query.orgBadges.handler.ts
index b7a74822cc..31c357bf47 100644
--- a/packages/api/router/fieldOpt/query.orgBadges.handler.ts
+++ b/packages/api/router/fieldOpt/query.orgBadges.handler.ts
@@ -4,7 +4,7 @@ import { type TRPCHandlerParams } from '~api/types/handler'
import { type TOrgBadgesSchema } from './query.orgBadges.schema'
-export const orgBadges = async ({ ctx, input }: TRPCHandlerParams) => {
+export const orgBadges = async ({ input }: TRPCHandlerParams) => {
try {
const badges = await prisma.attribute.findMany({
where: {
@@ -24,3 +24,4 @@ export const orgBadges = async ({ ctx, input }: TRPCHandlerParams {
})
return result
}
+export default phoneTypes
diff --git a/packages/api/router/fieldOpt/query.userTitle.handler.ts b/packages/api/router/fieldOpt/query.userTitle.handler.ts
index 0a72ea0878..a3749b7a8a 100644
--- a/packages/api/router/fieldOpt/query.userTitle.handler.ts
+++ b/packages/api/router/fieldOpt/query.userTitle.handler.ts
@@ -7,3 +7,4 @@ export const userTitle = async () => {
})
return results
}
+export default userTitle
From c1796416018e297f12addfbe33990f5ed861c760 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 15 Feb 2024 16:41:12 -0500
Subject: [PATCH 15/61] fix linting errors
---
packages/ui/.storybook/preview.tsx | 27 ++++++++++-----------
packages/ui/components/core/Badge/index.tsx | 25 ++++++++++---------
packages/ui/lib/trpcResponse.ts | 24 +++++++++---------
packages/ui/providers/SearchState.tsx | 17 ++++++-------
4 files changed, 45 insertions(+), 48 deletions(-)
diff --git a/packages/ui/.storybook/preview.tsx b/packages/ui/.storybook/preview.tsx
index 3aaf9176cf..2532169539 100644
--- a/packages/ui/.storybook/preview.tsx
+++ b/packages/ui/.storybook/preview.tsx
@@ -38,9 +38,9 @@ initializeMsw({
if (url.startsWith('/trpc' || '/api')) {
console.error(`Unhandled ${method} request to ${url}.
- This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.
- If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
- `)
+ This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.
+ If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
+ `)
}
},
})
@@ -134,14 +134,13 @@ type PseudoStates =
| 'link'
| 'target'
-type DesignParams = { name?: string } & (
- | {
- type: 'figma'
- url: `https://${string}`
- }
- | {
- type: 'figspec'
- url: `https://${string}`
- accessToken: string
- }
-)
+type DesignParams = ({ name?: string } & DesignFigma) | DesignFigspec
+type DesignFigma = {
+ type: 'figma'
+ url: `https://${string}`
+}
+type DesignFigspec = {
+ type: 'figspec'
+ url: `https://${string}`
+ accessToken: string
+}
diff --git a/packages/ui/components/core/Badge/index.tsx b/packages/ui/components/core/Badge/index.tsx
index a4eba11c81..24134c207a 100644
--- a/packages/ui/components/core/Badge/index.tsx
+++ b/packages/ui/components/core/Badge/index.tsx
@@ -520,19 +520,20 @@ interface BadgeStylesParams {
minify?: boolean
hideBg?: boolean
}
+interface BadgeOtherProps extends Omit {
+ /** Preset designs */
+ variant?:
+ | Exclude
+ | 'outline'
+ /**
+ * Item rendered on the left side of the badge. Should be either an emoji unicode string or an Icon
+ * component
+ */
+ leftSection?: ReactNode
+ hideTooltip?: boolean
+}
export type CustomBadgeProps =
- | (Omit & {
- /** Preset designs */
- variant?:
- | Exclude
- | 'outline'
- /**
- * Item rendered on the left side of the badge. Should be either an emoji unicode string or an Icon
- * component
- */
- leftSection?: ReactNode
- hideTooltip?: boolean
- })
+ | BadgeOtherProps
| LeaderBadgeProps
| VerifiedBadgeProps
| AttributeTagProps
diff --git a/packages/ui/lib/trpcResponse.ts b/packages/ui/lib/trpcResponse.ts
index b03fee1e72..4be780aaa4 100644
--- a/packages/ui/lib/trpcResponse.ts
+++ b/packages/ui/lib/trpcResponse.ts
@@ -13,20 +13,18 @@ export type RpcSuccessResponse = {
data: Data | SuperJSONResult
}
}
-
+type ErrorObject = {
+ message: string
+ code: number
+ data: {
+ code: string
+ httpStatus: number
+ stack: string
+ path: string //TQuery
+ }
+}
export type RpcErrorResponse = {
- error:
- | {
- message: string
- code: number
- data: {
- code: string
- httpStatus: number
- stack: string
- path: string //TQuery
- }
- }
- | SuperJSONResult
+ error: ErrorObject | SuperJSONResult
}
// According to JSON-RPC 2.0 and tRPC documentation.
diff --git a/packages/ui/providers/SearchState.tsx b/packages/ui/providers/SearchState.tsx
index 42e3dae1c3..fd25659ca6 100644
--- a/packages/ui/providers/SearchState.tsx
+++ b/packages/ui/providers/SearchState.tsx
@@ -89,16 +89,15 @@ export const SearchStateProvider = ({ children, initState }: SearchStateProvider
)
}
+type RouteParams = {
+ params: string[]
+ page: string
+ a?: string[]
+ s?: string[]
+ sort?: string[]
+}
-type GetRoute = () =>
- | {
- params: string[]
- page: string
- a?: string[]
- s?: string[]
- sort?: string[]
- }
- | undefined
+type GetRoute = () => RouteParams | undefined
export interface SearchStateContext {
searchState: State & { attributes: string[]; services: string[]; getRoute: GetRoute }
From 7fa5892d393e29d1664a0a02584d6cf115c2d6e9 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Fri, 16 Feb 2024 17:10:21 -0500
Subject: [PATCH 16/61] alter view
---
packages/api/package.json | 1 +
.../query.attributesByCategory.handler.ts | 20 ++++++++---
.../20240216161351_alter_view/migration.sql | 36 +++++++++++++++++++
packages/db/prisma/schema.prisma | 3 +-
.../json/fieldOpt.attributesByCategory.json | 2 +-
packages/ui/package.json | 1 +
pnpm-lock.yaml | 27 ++++++++++----
7 files changed, 77 insertions(+), 13 deletions(-)
create mode 100644 packages/db/prisma/migrations/20240216161351_alter_view/migration.sql
diff --git a/packages/api/package.json b/packages/api/package.json
index f91bb7e4cb..fbfb8c520b 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -31,6 +31,7 @@
"@weareinreach/db": "workspace:*",
"@weareinreach/env": "workspace:*",
"@weareinreach/util": "workspace:*",
+ "ajv": "8.12.0",
"alex": "11.0.1",
"crud-object-diff": "2.3.6",
"geo-tz": "8.0.1",
diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
index c39e0f75d7..771c577cff 100644
--- a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
+++ b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
@@ -1,9 +1,19 @@
+import Ajv, { type JSONSchemaType } from 'ajv'
+
import { prisma } from '@weareinreach/db'
import { type FieldAttributes } from '@weareinreach/db/zod_util/attributeSupplement'
import { type TRPCHandlerParams } from '~api/types/handler'
import { fieldAttributesSchema, type TAttributesByCategorySchema } from './query.attributesByCategory.schema'
+const ajv = new Ajv()
+const validateJsonSchema = (schema: unknown): schema is JSONSchemaType => {
+ if (schema && typeof schema === 'object' && ajv.validateSchema(schema)) {
+ return true
+ }
+ return false
+}
+
export const attributesByCategory = async ({ input }: TRPCHandlerParams) => {
console.log(input)
const result = await prisma.attributesByCategory.findMany({
@@ -15,15 +25,17 @@ export const attributesByCategory = async ({ input }: TRPCHandlerParams {
- const { dataSchema, ...rest } = item
+ const { formSchema, dataSchema, ...rest } = item
- const parsedDataSchema = fieldAttributesSchema.safeParse(dataSchema)
+ const parsedFormSchema = fieldAttributesSchema.safeParse(formSchema)
+ const parsedDataSchema = validateJsonSchema(dataSchema) ? dataSchema : null
return {
...rest,
- dataSchema: parsedDataSchema.success
- ? (parsedDataSchema.data as FieldAttributes[] | FieldAttributes[][])
+ formSchema: parsedFormSchema.success
+ ? (parsedFormSchema.data as FieldAttributes[] | FieldAttributes[][])
: null,
+ dataSchema: parsedDataSchema,
}
})
return flushedResults
diff --git a/packages/db/prisma/migrations/20240216161351_alter_view/migration.sql b/packages/db/prisma/migrations/20240216161351_alter_view/migration.sql
new file mode 100644
index 0000000000..10b021aa1f
--- /dev/null
+++ b/packages/db/prisma/migrations/20240216161351_alter_view/migration.sql
@@ -0,0 +1,36 @@
+ALTER VIEW public.attributes_by_category RENAME COLUMN "dataSchema" TO "formSchema";
+
+CREATE OR REPLACE VIEW public.attributes_by_category AS
+SELECT
+ ac.id AS "categoryId",
+ ac.tag AS "categoryName",
+ ac.name AS "categoryDisplay",
+ a.id AS "attributeId",
+ a.tag AS "attributeName",
+ a."tsKey" AS "attributeKey",
+ a."tsNs" AS "attributeNs",
+ a.icon,
+ a."iconBg",
+ ac."renderVariant" AS "badgeRender",
+ a."requireText",
+ a."requireLanguage",
+ a."requireGeo",
+ a."requireBoolean",
+ a."requireData",
+ asds.definition AS "formSchema",
+ tkey."interpolationValues",
+ asds.tag AS "dataSchemaName",
+ a."canAttachTo",
+ asds.SCHEMA AS "dataSchema"
+FROM
+ "AttributeCategory" ac
+ JOIN "AttributeToCategory" atc ON atc."categoryId" = ac.id
+ JOIN "Attribute" a ON a.id = atc."attributeId"
+ LEFT JOIN "AttributeSupplementDataSchema" asds ON asds.id = a."requiredSchemaId"
+ LEFT JOIN "TranslationKey" tkey ON tkey.key = a."tsKey"
+WHERE
+ a.active = TRUE
+ AND ac.active = TRUE
+ORDER BY
+ ac.tag,
+ a.tag;
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index ae0370dd93..82229b3ada 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -2306,8 +2306,9 @@ view AttributesByCategory {
requireBoolean Boolean
requireData Boolean
dataSchemaName String?
- dataSchema Json?
+ formSchema Json?
canAttachTo AttributeAttachment[]
+ dataSchema Json?
@@unique([categoryId, attributeId])
@@map("attributes_by_category")
diff --git a/packages/ui/mockData/json/fieldOpt.attributesByCategory.json b/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
index 31b758d22e..7f269e70be 100644
--- a/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
+++ b/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
@@ -1 +1 @@
-[{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3YJ2AWADHVKG79BQ0","attributeName":"at-capacity","attributeKey":"additional.at-capacity","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4D5ZHFMAE7852GB4P","attributeName":"geo-near-public-transit","attributeKey":"additional.geo-near-public-transit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV48VQJBMFA05QCBBV9","attributeName":"geo-public-transit-description","attributeKey":"additional.geo-public-transit-description","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","attributeName":"has-confidentiality-policy","attributeKey":"additional.has-confidentiality-policy","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5Q7XN2ZNTYFR1AD3M","attributeName":"offers-remote-services","attributeKey":"additional.offers-remote-services","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:globe","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","attributeName":"time-walk-in","attributeKey":"additional.time-walk-in","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5FYXQNGTPAQB7G2TF","attributeName":"wheelchair-accessible","attributeKey":"additional.wheelchair-accessible","attributeNs":"attribute","interpolationValues":{"true":"Accessible","false":"Not Accessible"},"icon":"carbon:accessibility","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":true,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAMR6RDV6M69H4KN3T","attributeName":"info","attributeKey":"alerts.info","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:information-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAKP7C6JKJ342ZM35M","attributeName":"warn","attributeKey":"alerts.warn","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:warning-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGWKWB53HWAAHQ9AAZ","attributeName":"cost-fees","attributeKey":"cost.cost-fees","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"dataSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}]},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","attributeName":"cost-free","attributeKey":"cost.cost-free","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P951P0V3CR807P8KRH82S1","attributeName":"elders","attributeKey":"crisis-support-community.elders","attributeNs":"attribute","interpolationValues":null,"icon":"🌳","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P8T277D0C8HFQA6N09FJWD","attributeName":"general-lgbtq","attributeKey":"crisis-support-community.general-lgbtq","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️🌈","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","attributeName":"elig-age","attributeKey":"eligibility.elig-age","attributeNs":"attribute","interpolationValues":{"max":"Under{{max}}","min":"{{min}} and older","range":"{{min}} -{{max}}"},"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"dataSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}]},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJDKVF1HV7559CNZCY","attributeName":"other-describe","attributeKey":"eligibility.other-describe","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH9DPBZ968VXGE50E7","attributeName":"req-medical-insurance","attributeKey":"eligibility.req-medical-insurance","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHZ599M48CMSPGDCSC","attributeName":"req-photo-id","attributeKey":"eligibility.req-photo-id","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH0GQK0GAJR5D952V3","attributeName":"req-proof-of-age","attributeKey":"eligibility.req-proof-of-age","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHEVX4PMNN077ASQMG","attributeName":"req-proof-of-income","attributeKey":"eligibility.req-proof-of-income","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHGMVCAY1G5BWF1PFB","attributeName":"req-proof-of-residence","attributeKey":"eligibility.req-proof-of-residence","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJH8MADHYTHBV54CER","attributeName":"req-referral","attributeKey":"eligibility.req-referral","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","attributeName":"time-appointment-required","attributeKey":"eligibility.time-appointment-required","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJGDDWTR5D0C8BY357","attributeName":"all-languages-by-interpreter","attributeKey":"lang.all-languages-by-interpreter","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJF09GXY5N5CKMSANJ","attributeName":"american-sign-language","attributeKey":"lang.american-sign-language","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","attributeName":"lang-offered","attributeKey":"lang.lang-offered","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":true,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRH531R2HAV8DMDZSC","attributeName":"corp-law-firm","attributeKey":"userlawpractice.corp-law-firm","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVSE2074QZJ4SKEW74J","attributeName":"law-other","attributeKey":"userlawpractice.law-other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"otherDescribe","canAttachTo":["USER"],"dataSchema":[{"key":"other","label":"Other","name":"other","type":"text"}]},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRS8XEJ3TJBBEQJ707","attributeName":"law-school-clinic","attributeKey":"userlawpractice.law-school-clinic","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRFPRQCQHNJA6BM3XP","attributeName":"legal-nonprofit","attributeKey":"userlawpractice.legal-nonprofit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNPKMHYK12DDRVC1VJ","attributeName":"bipoc-led","attributeKey":"orgleader.bipoc-led","attributeNs":"attribute","interpolationValues":null,"icon":"🤎","iconBg":"#F1DD7F","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3JX2J7REFFT5NAMS","attributeName":"black-led","attributeKey":"orgleader.black-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":"#C77E54","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNHMF72WHVKRF6W4TA","attributeName":"immigrant-led","attributeKey":"orgleader.immigrant-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":"#79ADD7","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3RYX9JMXDZSQZM70","attributeName":"trans-led","attributeKey":"orgleader.trans-led","attributeNs":"attribute","interpolationValues":null,"icon":"️🏳️⚧️","iconBg":"#705890","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKFM4TDY4QRK4AR2ZW","attributeName":"accessemail","attributeKey":"serviceaccess.accessemail","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKMRHFD8SMDAZM3SSM","attributeName":"accessfile","attributeKey":"serviceaccess.accessfile","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","attributeName":"accesslink","attributeKey":"serviceaccess.accesslink","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","attributeName":"accesslocation","attributeKey":"serviceaccess.accesslocation","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","attributeName":"accessphone","attributeKey":"serviceaccess.accessphone","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMSX7T1WDNZ5QEHKWT","attributeName":"accesspublictransit","attributeKey":"serviceaccess.accesspublictransit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMMF19AX2KPBTMV6P3","attributeName":"accesstext","attributeKey":"serviceaccess.accesstext","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPCVX8F3B7M30ZJEHW","attributeName":"asylum-seekers","attributeKey":"srvfocus.asylum-seekers","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ7SYGD3KM8WP9X50B","attributeName":"gender-nc","attributeKey":"srvfocus.gender-nc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVRMQFJ9AMA633SQQGV","attributeName":"hiv-comm","attributeKey":"srvfocus.hiv-comm","attributeNs":"attribute","interpolationValues":null,"icon":"💛","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPTK9555WHJHDBDA2J","attributeName":"immigrant-comm","attributeKey":"srvfocus.immigrant-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPJERY0GS9D7F56A23","attributeName":"resettled-refugees","attributeKey":"srvfocus.resettled-refugees","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ8AGBKBBZJWTHNP2F","attributeName":"spanish-speakers","attributeKey":"srvfocus.spanish-speakers","attributeNs":"attribute","interpolationValues":null,"icon":"🗣️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQX4M8DY1FSAYSJSSK","attributeName":"trans-fem","attributeKey":"srvfocus.trans-fem","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQEFWW42MBAD64BWXZ","attributeName":"trans-masc","attributeKey":"srvfocus.trans-masc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQVEGH6W3A2ANH1QZE","attributeName":"trans-youth-focus","attributeKey":"srvfocus.trans-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TK83N5E52PPP828SD88KP8","attributeName":"userserviceprovider.case-mananger","attributeKey":"userserviceprovider.case-mananger","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTTZ83PZR61M37R8R7","attributeName":"userserviceprovider.community-org","attributeKey":"userserviceprovider.community-org","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVSPXWJJPFG9DKXESEK","attributeName":"userserviceprovider.healthcare","attributeKey":"userserviceprovider.healthcare","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM092CFVG6H0MR148AVAP7","attributeName":"userserviceprovider.lawyer","attributeKey":"userserviceprovider.lawyer","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AJHVK8TSR8JNFANFNZ7","attributeName":"userserviceprovider.other","attributeKey":"userserviceprovider.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09EG0G84NXH40G5TESB5","attributeName":"userserviceprovider.paralegal","attributeKey":"userserviceprovider.paralegal","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09RAK024ZDZQ6FSY0TXB","attributeName":"userserviceprovider.social-worker","attributeKey":"userserviceprovider.social-worker","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTN6MSCMBW740Y7HN1","attributeName":"userserviceprovider.student-club","attributeKey":"userserviceprovider.student-club","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0A19DD6S97DNH76ZVP40","attributeName":"userserviceprovider.teacher","attributeKey":"userserviceprovider.teacher","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AA4CZXJJHMXHE1PHMVV","attributeName":"userserviceprovider.therapist-counselor","attributeKey":"userserviceprovider.therapist-counselor","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"dataSchema":null},{"categoryId":"attc_01GW2HHFVKM2PSHFWVFM0TWX1P","categoryName":"system","categoryDisplay":"System","attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","attributeName":"incompatible-info","attributeKey":"sys.incompatible-info","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"incompatibleData","canAttachTo":["LOCATION","ORGANIZATION","SERVICE","USER"],"dataSchema":[{"key":"incompatible","label":"Incompatible","name":"incompatible","type":"text"}]},{"categoryId":"attc_01HNG5BPYJADWX4YFVNENS3TRD","categoryName":"target-population","categoryDisplay":"Target Population","attributeId":"attr_01HNG5GDC5MXW30F32FWJNJ98C","attributeName":"tpop-other","attributeKey":"tpop.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"dataSchema":null}]
+[{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3YJ2AWADHVKG79BQ0","attributeName":"at-capacity","attributeKey":"additional.at-capacity","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4D5ZHFMAE7852GB4P","attributeName":"geo-near-public-transit","attributeKey":"additional.geo-near-public-transit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV48VQJBMFA05QCBBV9","attributeName":"geo-public-transit-description","attributeKey":"additional.geo-public-transit-description","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","attributeName":"has-confidentiality-policy","attributeKey":"additional.has-confidentiality-policy","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5Q7XN2ZNTYFR1AD3M","attributeName":"offers-remote-services","attributeKey":"additional.offers-remote-services","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:globe","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","attributeName":"time-walk-in","attributeKey":"additional.time-walk-in","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5FYXQNGTPAQB7G2TF","attributeName":"wheelchair-accessible","attributeKey":"additional.wheelchair-accessible","attributeNs":"attribute","interpolationValues":{"true":"Accessible","false":"Not Accessible"},"icon":"carbon:accessibility","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":true,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAMR6RDV6M69H4KN3T","attributeName":"info","attributeKey":"alerts.info","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:information-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAKP7C6JKJ342ZM35M","attributeName":"warn","attributeKey":"alerts.warn","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:warning-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGWKWB53HWAAHQ9AAZ","attributeName":"cost-fees","attributeKey":"cost.cost-fees","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"formSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}],"dataSchema":{"anyOf":[{"type":"object","required":["min"],"properties":{"min":{"type":"number"}}},{"type":"object","required":["max"],"properties":{"max":{"type":"number"}}},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}}}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","attributeName":"cost-free","attributeKey":"cost.cost-free","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P951P0V3CR807P8KRH82S1","attributeName":"elders","attributeKey":"crisis-support-community.elders","attributeNs":"attribute","interpolationValues":null,"icon":"🌳","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P8T277D0C8HFQA6N09FJWD","attributeName":"general-lgbtq","attributeKey":"crisis-support-community.general-lgbtq","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️🌈","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","attributeName":"elig-age","attributeKey":"eligibility.elig-age","attributeNs":"attribute","interpolationValues":{"max":"Under{{max}}","min":"{{min}} and older","range":"{{min}} -{{max}}"},"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"formSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}],"dataSchema":{"anyOf":[{"type":"object","required":["min"],"properties":{"min":{"type":"number"}}},{"type":"object","required":["max"],"properties":{"max":{"type":"number"}}},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}}}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJDKVF1HV7559CNZCY","attributeName":"other-describe","attributeKey":"eligibility.other-describe","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH9DPBZ968VXGE50E7","attributeName":"req-medical-insurance","attributeKey":"eligibility.req-medical-insurance","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHZ599M48CMSPGDCSC","attributeName":"req-photo-id","attributeKey":"eligibility.req-photo-id","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH0GQK0GAJR5D952V3","attributeName":"req-proof-of-age","attributeKey":"eligibility.req-proof-of-age","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHEVX4PMNN077ASQMG","attributeName":"req-proof-of-income","attributeKey":"eligibility.req-proof-of-income","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHGMVCAY1G5BWF1PFB","attributeName":"req-proof-of-residence","attributeKey":"eligibility.req-proof-of-residence","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJH8MADHYTHBV54CER","attributeName":"req-referral","attributeKey":"eligibility.req-referral","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","attributeName":"time-appointment-required","attributeKey":"eligibility.time-appointment-required","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJGDDWTR5D0C8BY357","attributeName":"all-languages-by-interpreter","attributeKey":"lang.all-languages-by-interpreter","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJF09GXY5N5CKMSANJ","attributeName":"american-sign-language","attributeKey":"lang.american-sign-language","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","attributeName":"lang-offered","attributeKey":"lang.lang-offered","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":true,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRH531R2HAV8DMDZSC","attributeName":"corp-law-firm","attributeKey":"userlawpractice.corp-law-firm","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVSE2074QZJ4SKEW74J","attributeName":"law-other","attributeKey":"userlawpractice.law-other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"otherDescribe","canAttachTo":["USER"],"formSchema":[{"key":"other","label":"Other","name":"other","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["other"],"properties":{"other":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRS8XEJ3TJBBEQJ707","attributeName":"law-school-clinic","attributeKey":"userlawpractice.law-school-clinic","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRFPRQCQHNJA6BM3XP","attributeName":"legal-nonprofit","attributeKey":"userlawpractice.legal-nonprofit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNPKMHYK12DDRVC1VJ","attributeName":"bipoc-led","attributeKey":"orgleader.bipoc-led","attributeNs":"attribute","interpolationValues":null,"icon":"🤎","iconBg":"#F1DD7F","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3JX2J7REFFT5NAMS","attributeName":"black-led","attributeKey":"orgleader.black-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":"#C77E54","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNHMF72WHVKRF6W4TA","attributeName":"immigrant-led","attributeKey":"orgleader.immigrant-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":"#79ADD7","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3RYX9JMXDZSQZM70","attributeName":"trans-led","attributeKey":"orgleader.trans-led","attributeNs":"attribute","interpolationValues":null,"icon":"️🏳️⚧️","iconBg":"#705890","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKFM4TDY4QRK4AR2ZW","attributeName":"accessemail","attributeKey":"serviceaccess.accessemail","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKMRHFD8SMDAZM3SSM","attributeName":"accessfile","attributeKey":"serviceaccess.accessfile","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","attributeName":"accesslink","attributeKey":"serviceaccess.accesslink","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","attributeName":"accesslocation","attributeKey":"serviceaccess.accesslocation","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","attributeName":"accessphone","attributeKey":"serviceaccess.accessphone","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMSX7T1WDNZ5QEHKWT","attributeName":"accesspublictransit","attributeKey":"serviceaccess.accesspublictransit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMMF19AX2KPBTMV6P3","attributeName":"accesstext","attributeKey":"serviceaccess.accesstext","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPCVX8F3B7M30ZJEHW","attributeName":"asylum-seekers","attributeKey":"srvfocus.asylum-seekers","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ7SYGD3KM8WP9X50B","attributeName":"gender-nc","attributeKey":"srvfocus.gender-nc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVRMQFJ9AMA633SQQGV","attributeName":"hiv-comm","attributeKey":"srvfocus.hiv-comm","attributeNs":"attribute","interpolationValues":null,"icon":"💛","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPTK9555WHJHDBDA2J","attributeName":"immigrant-comm","attributeKey":"srvfocus.immigrant-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPJERY0GS9D7F56A23","attributeName":"resettled-refugees","attributeKey":"srvfocus.resettled-refugees","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ8AGBKBBZJWTHNP2F","attributeName":"spanish-speakers","attributeKey":"srvfocus.spanish-speakers","attributeNs":"attribute","interpolationValues":null,"icon":"🗣️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQX4M8DY1FSAYSJSSK","attributeName":"trans-fem","attributeKey":"srvfocus.trans-fem","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQEFWW42MBAD64BWXZ","attributeName":"trans-masc","attributeKey":"srvfocus.trans-masc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQVEGH6W3A2ANH1QZE","attributeName":"trans-youth-focus","attributeKey":"srvfocus.trans-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TK83N5E52PPP828SD88KP8","attributeName":"userserviceprovider.case-mananger","attributeKey":"userserviceprovider.case-mananger","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTTZ83PZR61M37R8R7","attributeName":"userserviceprovider.community-org","attributeKey":"userserviceprovider.community-org","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVSPXWJJPFG9DKXESEK","attributeName":"userserviceprovider.healthcare","attributeKey":"userserviceprovider.healthcare","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM092CFVG6H0MR148AVAP7","attributeName":"userserviceprovider.lawyer","attributeKey":"userserviceprovider.lawyer","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AJHVK8TSR8JNFANFNZ7","attributeName":"userserviceprovider.other","attributeKey":"userserviceprovider.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09EG0G84NXH40G5TESB5","attributeName":"userserviceprovider.paralegal","attributeKey":"userserviceprovider.paralegal","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09RAK024ZDZQ6FSY0TXB","attributeName":"userserviceprovider.social-worker","attributeKey":"userserviceprovider.social-worker","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTN6MSCMBW740Y7HN1","attributeName":"userserviceprovider.student-club","attributeKey":"userserviceprovider.student-club","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0A19DD6S97DNH76ZVP40","attributeName":"userserviceprovider.teacher","attributeKey":"userserviceprovider.teacher","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AA4CZXJJHMXHE1PHMVV","attributeName":"userserviceprovider.therapist-counselor","attributeKey":"userserviceprovider.therapist-counselor","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKM2PSHFWVFM0TWX1P","categoryName":"system","categoryDisplay":"System","attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","attributeName":"incompatible-info","attributeKey":"sys.incompatible-info","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"incompatibleData","canAttachTo":["LOCATION","ORGANIZATION","SERVICE","USER"],"formSchema":[{"key":"incompatible","label":"Incompatible","name":"incompatible","type":"text"}],"dataSchema":{"type":"array","items":{"type":"object","additionalProperties":{}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01HNG5BPYJADWX4YFVNENS3TRD","categoryName":"target-population","categoryDisplay":"Target Population","attributeId":"attr_01HNG5GDC5MXW30F32FWJNJ98C","attributeName":"tpop-other","attributeKey":"tpop.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null}]
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 3c80910585..89eab18bd0 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -32,6 +32,7 @@
"@weareinreach/util": "workspace:*",
"ahooks": "3.7.10",
"ajv": "8.12.0",
+ "ajv-errors": "3.0.0",
"alex": "11.0.1",
"cookies-next": "4.1.1",
"crud-object-diff": "2.3.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7442e48fb2..915e36e235 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -685,6 +685,9 @@ importers:
'@weareinreach/util':
specifier: workspace:*
version: link:../util
+ ajv:
+ specifier: 8.12.0
+ version: 8.12.0
alex:
specifier: 11.0.1
version: 11.0.1
@@ -1248,6 +1251,9 @@ importers:
ajv:
specifier: 8.12.0
version: 8.12.0
+ ajv-errors:
+ specifier: 3.0.0
+ version: 3.0.0(ajv@8.12.0)
alex:
specifier: 11.0.1
version: 11.0.1
@@ -5169,7 +5175,7 @@ packages:
dependencies:
'@mantine/ssr': 6.0.21(@emotion/react@11.11.3)(@emotion/server@11.11.0)(react-dom@18.2.0)(react@18.2.0)
'@mantine/styles': 6.0.21(@emotion/react@11.11.3)(react-dom@18.2.0)(react@18.2.0)
- next: 14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0)
+ next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
@@ -5575,7 +5581,7 @@ packages:
next: ^13.0.0 || ^14.0.0 || 13
react: ^18.2.0 || 18
dependencies:
- next: 14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0)
+ next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
third-party-capital: 1.0.20
@@ -9755,7 +9761,7 @@ packages:
'@trpc/client': 10.45.1(@trpc/server@10.45.1)
'@trpc/react-query': 10.45.1(@tanstack/react-query@4.36.1)(@trpc/client@10.45.1)(@trpc/server@10.45.1)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server': 10.45.1
- next: 14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0)
+ next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -11121,6 +11127,14 @@ packages:
tslib: 2.6.2
dev: false
+ /ajv-errors@3.0.0(ajv@8.12.0):
+ resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==}
+ peerDependencies:
+ ajv: ^8.0.1
+ dependencies:
+ ajv: 8.12.0
+ dev: false
+
/ajv-formats@2.1.1(ajv@8.12.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
@@ -19734,7 +19748,7 @@ packages:
'@panva/hkdf': 1.1.1
cookie: 0.5.0
jose: 4.15.4
- next: 14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0)
+ next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
oauth: 0.9.15
openid-client: 5.6.4
preact: 10.19.3
@@ -19758,7 +19772,7 @@ packages:
hoist-non-react-statics: 3.3.2
i18next: 23.8.2
i18next-fs-backend: 2.3.1
- next: 14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0)
+ next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-i18next: 14.0.5(i18next@23.8.2)(react-dom@18.2.0)(react@18.2.0)
@@ -19811,7 +19825,6 @@ packages:
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
- dev: true
/next@14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==}
@@ -19870,7 +19883,7 @@ packages:
next: '*'
dependencies:
chokidar: 3.5.3
- next: 14.1.0(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0)
+ next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
dev: false
/nice-try@1.0.5:
From c2f3f5a38fd901cb6d41cdad6903b5bb0d251814 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Fri, 16 Feb 2024 17:11:52 -0500
Subject: [PATCH 17/61] supplement handling
---
.../ui/modals/dataPortal/Attributes/index.tsx | 20 +++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/packages/ui/modals/dataPortal/Attributes/index.tsx b/packages/ui/modals/dataPortal/Attributes/index.tsx
index 90146af75a..bae7257103 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.tsx
@@ -13,7 +13,7 @@ import {
Text,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
-import Ajv from 'ajv'
+import { type JSONSchemaType } from 'ajv'
import { useTranslation } from 'next-i18next'
import { forwardRef, useMemo, useRef, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
@@ -41,9 +41,6 @@ const AttributeModalBody = forwardRef(
const { t } = useTranslation(['attribute', 'common'])
const [opened, handler] = useDisclosure(false)
- const form = useForm({
- resolver: zodResolver(formSchema),
- })
const selectAttrRef = useRef(null)
// #region tRPC
const utils = api.useUtils()
@@ -93,15 +90,23 @@ const AttributeModalBody = forwardRef(
null
)
const [supplements, setSupplements] = useState(supplementDefaults)
+ const [supplementSchema, setSupplementSchema] = useState | null>(null)
const saveAttributes = api.organization.attachAttribute.useMutation()
// #endregion
// #region Handlers
+ const form = useForm({
+ resolver: zodResolver(formSchema),
+ })
+
const selectHandler = (e: string | null) => {
console.log('selectHandler', e)
if (e === null) {
setSupplements(supplementDefaults)
setSelectedAttr(null)
+ if (supplementSchema !== null) {
+ setSupplementSchema(null)
+ }
return
}
const item = attributesByCategory?.find(({ value }) => value === e)
@@ -119,6 +124,9 @@ const AttributeModalBody = forwardRef(
data: requireData ?? false,
}
setSupplements(suppRequired)
+ if (requireData && item.dataSchema) {
+ setSupplementSchema(item.dataSchema)
+ }
return
}
@@ -182,8 +190,8 @@ const AttributeModalBody = forwardRef(
{supplements.boolean && }
{supplements.text && }
- {supplements.data && selectedAttr?.dataSchema && (
-
+ {supplements.data && selectedAttr?.formSchema && (
+
)}
{supplements.language && }
{supplements.geo && }
From 8430e69868a24854f3286e2dc99367774ab78f99 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 20 Feb 2024 17:26:13 -0500
Subject: [PATCH 18/61] uncomment criteria
---
.../api/router/fieldOpt/query.attributesByCategory.handler.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
index 22dc9be67b..771c577cff 100644
--- a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
+++ b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts
@@ -19,7 +19,7 @@ export const attributesByCategory = async ({ input }: TRPCHandlerParams
Date: Mon, 18 Mar 2024 14:23:30 -0400
Subject: [PATCH 19/61] update mock data
---
packages/ui/mockData/json/location.forLocationCard.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/ui/mockData/json/location.forLocationCard.json b/packages/ui/mockData/json/location.forLocationCard.json
index de10234432..8d843aa5e1 100644
--- a/packages/ui/mockData/json/location.forLocationCard.json
+++ b/packages/ui/mockData/json/location.forLocationCard.json
@@ -1 +1 @@
-{"id":"oloc_01GVH3VEVBERFNA9PHHJYEBGA3","name":"Whitman-Walker 1525","street1":"1525 14th St. NW","street2":null,"city":"Washington","postCode":"20005","latitude":38.91,"longitude":-77.032,"country":"US","govDist":{"abbrev":"DC","tsKey":"us-district-of-columbia","tsNs":"gov-dist"},"phones":[],"attributes":[],"services":["medical.CATEGORYNAME","mental-health.CATEGORYNAME"]}
\ No newline at end of file
+{"id":"oloc_01GVH3VEVBERFNA9PHHJYEBGA3","name":"Whitman-Walker 1525","street1":"1525 14th St. NW","street2":null,"city":"Washington","postCode":"20005","latitude":38.91,"longitude":-77.032,"notVisitable":false,"country":"US","govDist":{"abbrev":"DC","tsKey":"us-district-of-columbia","tsNs":"gov-dist"},"phones":[],"attributes":[],"services":["medical.CATEGORYNAME","mental-health.CATEGORYNAME"]}
\ No newline at end of file
From acc8dcc9924be08bba1dfba94bd67d04611620c5 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Mon, 18 Mar 2024 16:01:10 -0400
Subject: [PATCH 20/61] update msw worker, clean up common schema files
---
packages/api/schemas/create/organization.ts | 98 ---------------------
packages/api/schemas/nestedOps.ts | 67 --------------
packages/ui/public/mockServiceWorker.js | 19 ++--
3 files changed, 8 insertions(+), 176 deletions(-)
delete mode 100644 packages/api/schemas/create/organization.ts
diff --git a/packages/api/schemas/create/organization.ts b/packages/api/schemas/create/organization.ts
deleted file mode 100644
index ed09a8744d..0000000000
--- a/packages/api/schemas/create/organization.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { z } from 'zod'
-
-import { generateFreeText, generateId, Prisma } from '@weareinreach/db'
-import { CreationBase, idString, InputJsonValue } from '~api/schemas/common'
-import { createManyWithAudit } from '~api/schemas/nestedOps'
-
-import { SuggestionSchema } from './browserSafe/suggestOrg'
-import { createFreeText } from './freeText'
-import { CreateNestedOrgEmailSchema } from './orgEmail'
-import { CreateNestedOrgLocationSchema } from './orgLocation'
-import { CreateNestedOrgPhoneSchema } from './orgPhone'
-import { CreateNestedOrgSocialMediaSchema } from './orgSocialMedia'
-import { CreateNestedOrgWebsiteSchema } from './orgWebsite'
-
-const CreateOrgBase = {
- name: z.string(),
- slug: z.string(),
- sourceId: idString,
-}
-const CreateOrgLinks = {
- description: z.string().optional(),
- locations: CreateNestedOrgLocationSchema.optional(),
- emails: CreateNestedOrgEmailSchema.optional(),
- phones: CreateNestedOrgPhoneSchema.optional(),
- websites: CreateNestedOrgWebsiteSchema.optional(),
- socialMedia: CreateNestedOrgSocialMediaSchema.optional(),
-}
-const CreateQuickOrg = z.object({ ...CreateOrgBase, ...CreateOrgLinks })
-
-export const CreateQuickOrgSchema = () => {
- const { dataParser: parser, inputSchema } = CreationBase(CreateQuickOrg)
-
- const dataParser = parser.transform(({ actorId, operation, data }) => {
- const { name, slug, sourceId } = data
- return Prisma.validator()({
- data: {
- name,
- slug,
- source: {
- connect: { id: sourceId },
- },
- description: createFreeText(data.slug, data.description),
- locations: createManyWithAudit(data.locations, actorId),
- emails: createManyWithAudit(data.emails, actorId),
- phones: createManyWithAudit(data.phones, actorId),
- socialMedia: createManyWithAudit(data.socialMedia, actorId),
- websites: createManyWithAudit(data.websites, actorId),
- },
- include: {
- description: Boolean(data.description),
- locations: Boolean(data.locations),
- emails: Boolean(data.emails),
- phones: Boolean(data.phones),
- websites: Boolean(data.websites),
- socialMedia: Boolean(data.socialMedia),
- },
- })
- })
- return { dataParser, inputSchema }
-}
-
-export const CreateOrgSuggestionSchema = () => {
- const { dataParser: parser, inputSchema } = CreationBase(SuggestionSchema)
- const dataParser = parser.transform(({ actorId, operation, data }) => {
- const { countryId, orgName, orgSlug, communityFocus, orgAddress, orgWebsite, serviceCategories } = data
- const organizationId = generateId('organization')
-
- return Prisma.validator()({
- data: {
- organization: {
- create: {
- id: organizationId,
- name: orgName,
- slug: orgSlug,
- source: { connect: { source: 'suggestion' } },
- },
- },
- data: {
- orgWebsite,
- orgAddress,
- countryId,
- communityFocus,
- serviceCategories,
- },
- },
- select: {
- id: true,
- },
- })
- })
- return { dataParser, inputSchema }
-}
-
-type CreateQuickOrgReturn = ReturnType
-export type CreateQuickOrgData = z.infer
-export type CreateQuickOrgInput = z.input
-type CreateOrgSuggestionReturn = ReturnType
-export type CreateOrgSuggestionInput = z.input
diff --git a/packages/api/schemas/nestedOps.ts b/packages/api/schemas/nestedOps.ts
index e9f5433269..a8dc10f051 100644
--- a/packages/api/schemas/nestedOps.ts
+++ b/packages/api/schemas/nestedOps.ts
@@ -1,7 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import compact from 'just-compact'
-import omit from 'just-omit'
-import pick from 'just-pick'
import invariant from 'tiny-invariant'
/**
@@ -33,16 +31,7 @@ export const createManyOptional = >(data: T | undefined) =>
skipDuplicates: true,
},
}
-/** Array to individual nested create records with individual Audit Logs */
-export const createManyWithAudit = >(data: T | undefined, actorId: string) =>
- !data
- ? undefined
- : ({
- create: compact(data).map((record) => ({
- ...record,
- })),
- } as const)
/** Individual create record */
export const createOne = >(data: T | undefined) =>
!data
@@ -50,17 +39,6 @@ export const createOne = >(data: T | undefined) =>
: ({
create: data,
} as const)
-/** Individual create record with audit log */
-
-export const createOneWithAudit = >(data: T | undefined, actorId: string) =>
- !data
- ? undefined
- : ({
- create: {
- ...data,
- },
- } as const)
-
export const connectOne = >(data: T | undefined) =>
!data
? undefined
@@ -129,48 +107,3 @@ export const connectOneRequired = >(data: T) => {
connect: data,
}
}
-type LinkManyOptions = {
- auditDataKeys?: Array
-}
-export const linkManyWithAudit = (
- data: T[] | undefined,
- actorId: string,
- opts?: LinkManyOptions
-) => {
- if (!data) return [undefined, []] as const
- const links = {
- createMany: {
- data,
- skipDuplicates: true,
- },
- }
- const logs = null
-
- return [links, logs] as const
-}
-
-export const createOneSeparateLog = (
- data: T | undefined,
- actorId: string,
- opts?: LinkManyOptions
-) => {
- if (!data) return [undefined, undefined] as const
- const links = {
- create: data,
- }
-
- const log = null
-
- return [links, log] as const
-}
-
-export const deleteOneSeparateLog = (data: T | undefined, actorId: string) => {
- if (!data) return [undefined, undefined] as const
- const links = {
- delete: data,
- }
-
- const log = null
-
- return [links, log] as const
-}
diff --git a/packages/ui/public/mockServiceWorker.js b/packages/ui/public/mockServiceWorker.js
index 01875955f9..5d16500534 100644
--- a/packages/ui/public/mockServiceWorker.js
+++ b/packages/ui/public/mockServiceWorker.js
@@ -2,13 +2,14 @@
/* tslint:disable */
/**
- * Mock Service Worker (2.1.0).
+ * Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
-const INTEGRITY_CHECKSUM = '223d191a56023cd36aa88c802961b911'
+const PACKAGE_VERSION = '2.2.7'
+const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
@@ -48,7 +49,10 @@ self.addEventListener('message', async function (event) {
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
- payload: INTEGRITY_CHECKSUM,
+ payload: {
+ packageVersion: PACKAGE_VERSION,
+ checksum: INTEGRITY_CHECKSUM,
+ },
})
break
}
@@ -202,13 +206,6 @@ async function getResponse(event, client, requestId) {
return passthrough()
}
- // Bypass requests with the explicit bypass header.
- // Such requests can be issued by "ctx.fetch()".
- const mswIntention = request.headers.get('x-msw-intention')
- if (['bypass', 'passthrough'].includes(mswIntention)) {
- return passthrough()
- }
-
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
@@ -240,7 +237,7 @@ async function getResponse(event, client, requestId) {
return respondWithMock(clientMessage.data)
}
- case 'MOCK_NOT_FOUND': {
+ case 'PASSTHROUGH': {
return passthrough()
}
}
From 0ab69aac2b2be8776772fc66335ace014cb03f33 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Mon, 18 Mar 2024 16:01:56 -0400
Subject: [PATCH 21/61] update handler
---
.../mutation.attachAttribute.handler.ts | 46 +++++++++---
.../mutation.attachAttribute.schema.ts | 72 ++++---------------
packages/ui/mockData/organization.ts | 7 ++
3 files changed, 56 insertions(+), 69 deletions(-)
diff --git a/packages/api/router/organization/mutation.attachAttribute.handler.ts b/packages/api/router/organization/mutation.attachAttribute.handler.ts
index 14b4836d79..9282eee957 100644
--- a/packages/api/router/organization/mutation.attachAttribute.handler.ts
+++ b/packages/api/router/organization/mutation.attachAttribute.handler.ts
@@ -1,4 +1,5 @@
-import { getAuditedClient } from '@weareinreach/db'
+import { generateNestedFreeText, getAuditedClient } from '@weareinreach/db'
+import { connectOneId, connectOneIdRequired } from '~api/schemas/nestedOps'
import { type TRPCHandlerParams } from '~api/types/handler'
import { type TAttachAttributeSchema } from './mutation.attachAttribute.schema'
@@ -8,17 +9,40 @@ export const attachAttribute = async ({
input,
}: TRPCHandlerParams) => {
const prisma = getAuditedClient(ctx.actorId)
- const { translationKey, freeText, attributeSupplement } = input
+ const { locationId, organizationId, serviceId } = input
- const result = await prisma.$transaction(async (tx) => {
- const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined
- const fText = freeText ? await tx.freeText.create(freeText) : undefined
- const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined
- return {
- translationKey: tKey,
- freeText: fText,
- attributeSupplement: aSupp,
- }
+ const { id: orgId } = organizationId
+ ? { id: organizationId }
+ : await prisma.organization.findFirstOrThrow({
+ where: {
+ OR: [{ locations: { some: { id: locationId } } }, { services: { some: { id: serviceId } } }],
+ },
+ select: {
+ id: true,
+ },
+ })
+
+ const freeText = input.text
+ ? generateNestedFreeText({ orgId, text: input.text, type: 'attSupp', itemId: input.id })
+ : undefined
+
+ const result = await prisma.attributeSupplement.create({
+ data: {
+ id: input.id,
+ attribute: connectOneIdRequired(input.attributeId),
+ organization: connectOneId(organizationId),
+ country: connectOneId(input.countryId),
+ govDist: connectOneId(input.govDistId),
+ language: connectOneId(input.languageId),
+ service: connectOneId(serviceId),
+ location: connectOneId(locationId),
+ boolean: input.boolean,
+ data: input.data,
+ text: freeText,
+ },
+ select: {
+ id: true,
+ },
})
return result
}
diff --git a/packages/api/router/organization/mutation.attachAttribute.schema.ts b/packages/api/router/organization/mutation.attachAttribute.schema.ts
index 6cfcf27a17..7d1ead360d 100644
--- a/packages/api/router/organization/mutation.attachAttribute.schema.ts
+++ b/packages/api/router/organization/mutation.attachAttribute.schema.ts
@@ -1,64 +1,20 @@
import { z } from 'zod'
-import { generateFreeText, generateId, InputJsonValue, Prisma } from '@weareinreach/db'
+import { JsonInputOrNull } from '@weareinreach/db'
import { prefixedId } from '~api/schemas/idPrefix'
-export const ZAttachAttributeSchema = z
- .object({
- organizationId: prefixedId('organization'),
- attributeId: prefixedId('attribute'),
- supplement: z
- .object({
- data: InputJsonValue.optional(),
- boolean: z.boolean().optional(),
- countryId: z.string().optional(),
- govDistId: z.string().optional(),
- languageId: z.string().optional(),
- text: z.string().optional(),
- })
- .optional(),
- })
- .transform((parsedData) => {
- const { organizationId, attributeId, supplement: supplementInput } = parsedData
+export const ZAttachAttributeSchema = z.object({
+ id: prefixedId('attributeSupplement'),
+ attributeId: prefixedId('attribute'),
+ organizationId: prefixedId('organization').optional(),
+ serviceId: prefixedId('orgService').optional(),
+ locationId: prefixedId('orgLocation').optional(),
+ countryId: z.string().optional(),
+ govDistId: z.string().optional(),
+ languageId: z.string().optional(),
+ text: z.string().optional(),
+ boolean: z.coerce.boolean().optional(),
+ data: JsonInputOrNull.optional(),
+})
- const supplementId = supplementInput ? generateId('attributeSupplement') : undefined
-
- const { freeText, translationKey } =
- supplementId && supplementInput?.text
- ? generateFreeText({
- orgId: organizationId,
- text: supplementInput.text,
- type: 'attSupp',
- itemId: supplementId,
- })
- : { freeText: undefined, translationKey: undefined }
-
- const { boolean, countryId, data, govDistId, languageId } = supplementInput ?? {}
-
- const supplementData = supplementInput
- ? {
- id: supplementId,
- countryId,
- boolean,
- data,
- govDistId,
- languageId,
- textId: freeText?.id,
- attributeId,
- organizationId,
- }
- : undefined
-
- return {
- freeText: freeText ? Prisma.validator()({ data: freeText }) : undefined,
- translationKey: translationKey
- ? Prisma.validator()({ data: translationKey })
- : undefined,
- attributeSupplement: supplementData
- ? Prisma.validator()({
- data: supplementData,
- })
- : undefined,
- }
- })
export type TAttachAttributeSchema = z.infer
diff --git a/packages/ui/mockData/organization.ts b/packages/ui/mockData/organization.ts
index 9e2ac025ad..df7703377e 100644
--- a/packages/ui/mockData/organization.ts
+++ b/packages/ui/mockData/organization.ts
@@ -146,4 +146,11 @@ export const organization = {
removed: input.deletedVals?.length ?? 0,
}),
}),
+ attachAttribute: getTRPCMock({
+ path: ['organization', 'attachAttribute'],
+ type: 'mutation',
+ response: () => ({
+ id: 'atts_NEW0ID',
+ }),
+ }),
} satisfies MockHandlerObject<'organization'> & { searchDistanceLongTitle: HttpHandler }
From d2d0cf32ed6e928d12497424444c04d4797fc8d4 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Mon, 18 Mar 2024 16:43:06 -0400
Subject: [PATCH 22/61] add submit handler, update schema
---
.../modals/dataPortal/Attributes/fields.tsx | 2 +-
.../dataPortal/Attributes/index.stories.tsx | 4 ++-
.../ui/modals/dataPortal/Attributes/index.tsx | 25 +++++++++++++------
.../ui/modals/dataPortal/Attributes/schema.ts | 4 ++-
4 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/packages/ui/modals/dataPortal/Attributes/fields.tsx b/packages/ui/modals/dataPortal/Attributes/fields.tsx
index 55ab79ec99..0701a79ecf 100644
--- a/packages/ui/modals/dataPortal/Attributes/fields.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/fields.tsx
@@ -27,7 +27,7 @@ const SuppText = () => {
const { control } = useFormContext()
return (
-
+
{/* */}
)
diff --git a/packages/ui/modals/dataPortal/Attributes/index.stories.tsx b/packages/ui/modals/dataPortal/Attributes/index.stories.tsx
index 1d6b966cc2..85fdeb3de0 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.stories.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.stories.tsx
@@ -2,6 +2,7 @@ import { type Meta, type StoryObj } from '@storybook/react'
import { Button } from '~ui/components/core/Button'
import { allFieldOptHandlers } from '~ui/mockData/fieldOpt'
+import { organization } from '~ui/mockData/organization'
import { AttributeModal } from './index'
@@ -12,7 +13,7 @@ export default {
parameters: {
layout: 'fullscreen',
layoutWrapper: 'centeredHalf',
- msw: [...allFieldOptHandlers],
+ msw: [...allFieldOptHandlers, organization.attachAttribute],
rqDevtools: true,
},
args: {
@@ -28,5 +29,6 @@ export const AllCategories = {} satisfies StoryDef
export const AttachesToService = {
args: {
attachesTo: ['SERVICE'],
+ parentRecord: { serviceId: 'osvc_123456' },
},
} satisfies StoryDef
diff --git a/packages/ui/modals/dataPortal/Attributes/index.tsx b/packages/ui/modals/dataPortal/Attributes/index.tsx
index bae7257103..b642dfc2bd 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.tsx
@@ -16,9 +16,10 @@ import { useDisclosure } from '@mantine/hooks'
import { type JSONSchemaType } from 'ajv'
import { useTranslation } from 'next-i18next'
import { forwardRef, useMemo, useRef, useState } from 'react'
-import { FormProvider, useForm } from 'react-hook-form'
+import { FormProvider, useForm, useFormState } from 'react-hook-form'
import { type ApiOutput } from '@weareinreach/api'
+import { generateId } from '@weareinreach/db/lib/idGen'
import { Button } from '~ui/components/core/Button'
import { trpc as api } from '~ui/lib/trpcClient'
import { ModalTitle } from '~ui/modals/ModalTitle'
@@ -37,7 +38,7 @@ const supplementDefaults = {
type SupplementFieldsNeeded = { [K in keyof typeof supplementDefaults]: boolean }
const AttributeModalBody = forwardRef(
- ({ restrictCategories, attachesTo, ...props }, ref) => {
+ ({ restrictCategories, attachesTo, parentRecord, ...props }, ref) => {
const { t } = useTranslation(['attribute', 'common'])
const [opened, handler] = useDisclosure(false)
@@ -97,13 +98,20 @@ const AttributeModalBody = forwardRef(
// #region Handlers
const form = useForm({
resolver: zodResolver(formSchema),
+ mode: 'all',
+
+ defaultValues: {
+ id: generateId('attributeSupplement'),
+ ...parentRecord,
+ },
})
+ const formState = useFormState({ control: form.control })
const selectHandler = (e: string | null) => {
- console.log('selectHandler', e)
if (e === null) {
setSupplements(supplementDefaults)
setSelectedAttr(null)
+ form.resetField('attributeId')
if (supplementSchema !== null) {
setSupplementSchema(null)
}
@@ -128,7 +136,7 @@ const AttributeModalBody = forwardRef(
setSupplementSchema(item.dataSchema)
}
- return
+ // return
}
form.setValue('attributeId', item.value)
selectAttrRef.current && (selectAttrRef.current.value = '')
@@ -136,7 +144,7 @@ const AttributeModalBody = forwardRef(
}
const submitHandler = () => {
- //TODO: [IN-871] Create submit handler - convert tRPC organization.attachAttribute to be able to handle multiple items & accept org, serv, loc
+ saveAttributes.mutate(form.getValues())
}
// #endregion
@@ -191,11 +199,13 @@ const AttributeModalBody = forwardRef(
{supplements.boolean && }
{supplements.text && }
{supplements.data && selectedAttr?.formSchema && (
-
+
)}
{supplements.language && }
{supplements.geo && }
- {!needsSupplement && }
+
handler.open()} {...props} />
@@ -211,4 +221,5 @@ export const AttributeModal = createPolymorphicComponent<'button', AttributeModa
export interface AttributeModalProps extends ButtonProps {
restrictCategories?: string[]
attachesTo?: ApiOutput['fieldOpt']['attributesByCategory'][number]['canAttachTo']
+ parentRecord: { organizationId: string } | { serviceId: string } | { locationId: string }
}
diff --git a/packages/ui/modals/dataPortal/Attributes/schema.ts b/packages/ui/modals/dataPortal/Attributes/schema.ts
index d672a5466f..75354a7c5f 100644
--- a/packages/ui/modals/dataPortal/Attributes/schema.ts
+++ b/packages/ui/modals/dataPortal/Attributes/schema.ts
@@ -7,7 +7,9 @@ import { generateId } from '@weareinreach/db/lib/idGen'
export const formSchema = z.object({
id: prefixedId('attributeSupplement').default(generateId('attributeSupplement')),
attributeId: prefixedId('attribute'),
- value: z.string(),
+ organizationId: prefixedId('organization').optional(),
+ serviceId: prefixedId('orgService').optional(),
+ locationId: prefixedId('orgLocation').optional(),
countryId: z.string().optional(),
govDistId: z.string().optional(),
languageId: z.string().optional(),
From 7439194a244ff3ac9302b7e33fda7962cb369d5c Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 19 Mar 2024 11:01:10 -0400
Subject: [PATCH 23/61] use Section.* components
---
packages/ui/modals/Service/index.tsx | 119 ++++++++++++---------------
1 file changed, 51 insertions(+), 68 deletions(-)
diff --git a/packages/ui/modals/Service/index.tsx b/packages/ui/modals/Service/index.tsx
index 079f6090f4..b517d16938 100644
--- a/packages/ui/modals/Service/index.tsx
+++ b/packages/ui/modals/Service/index.tsx
@@ -18,13 +18,8 @@ import { forwardRef, type JSX, type ReactNode } from 'react'
import { serviceModalEvent } from '@weareinreach/analytics/events'
import { supplementSchema } from '@weareinreach/api/schemas/attributeSupplement'
import { AlertMessage } from '~ui/components/core/AlertMessage'
-import {
- type AttributeTagProps,
- Badge,
- BadgeGroup,
- type CommunityTagProps,
- type ServiceTagProps,
-} from '~ui/components/core/Badge'
+import { type AttributeTagProps, Badge, BadgeGroup, type CommunityTagProps } from '~ui/components/core/Badge'
+import { Section } from '~ui/components/core/Section'
import { ContactInfo, hasContactInfo, Hours } from '~ui/components/data-display'
import { type PassedDataObject } from '~ui/components/data-display/ContactInfo/types'
import { getFreeText, useSlug } from '~ui/hooks'
@@ -115,44 +110,6 @@ const ServiceModalBody = forwardRef(({ ser
)
- const SubSection = ({ title, children, li }: SubsectionProps) => (
-
- {title && {t(`service.${title}`)}}
- {li ? (
-
- {typeof li === 'string' ? (
-
- {li}
-
- ) : (
- li.map((item, i) => (
-
- {item}
-
- ))
- )}
-
- ) : (
- children
- )}
-
- )
-
- const SectionDivider = ({ title, children }: SectionProps) => {
- if (!children || (Array.isArray(children) && children.length === 0)) return <>>
-
- return (
-
-
-
- {t(`service.${title}`)}
-
-
- {children}
-
- )
- }
-
const contactData: PassedDataObject = {
phones: [],
emails: [],
@@ -320,9 +277,9 @@ const ServiceModalBody = forwardRef(({ ser
if (description.length > 0)
subsections[namespace].push(
-
+
{description}
-
+
)
break
}
@@ -364,33 +321,59 @@ const ServiceModalBody = forwardRef(({ ser
if (eligibility.age)
eligibilityItems.push(
-
+
{eligibility.age}
-
+
)
if (eligibility.requirements.length > 0)
- eligibilityItems.push()
+ eligibilityItems.push(
+
+
+ {eligibility.requirements.map((text, i) => (
+ {text}
+ ))}
+
+
+ )
if (eligibility.freeText.length > 0)
eligibilityItems.push(
-
+
{eligibility.freeText}
-
+
)
- const languages = lang.length === 0 ? undefined :
+ const languages =
+ lang.length === 0 ? undefined : (
+
+
+ {lang.map((lang, i) => (
+ {lang}
+ ))}
+
+
+ )
const extraInfo: JSX.Element[] = []
if (miscWithIcons.length > 0)
extraInfo.push(
-
+
-
+
)
- if (misc.length > 0) extraInfo.push()
+ if (misc.length > 0)
+ extraInfo.push(
+
+
+ {misc.map((text, i) => (
+ {text}
+ ))}
+
+
+ )
return (
<>
@@ -415,30 +398,30 @@ const ServiceModalBody = forwardRef(({ ser
{serviceBadges}
{(hasContactInfo(contactData) || Boolean(hours.length)) && (
-
+
{hasContactInfo(contactData) && (
)}
{Boolean(hours.length) && }
-
+
)}
{(Boolean(clientsServed.srvfocus.length) || Boolean(clientsServed.targetPop.length)) && (
-
+
{Boolean(clientsServed.srvfocus.length) && (
-
+
-
+
)}
{Boolean(clientsServed.targetPop.length) && (
- {clientsServed.targetPop}
+ {clientsServed.targetPop}
)}
-
+
)}
- {cost}
- {eligibilityItems}
- {languages}
- {extraInfo}
- {publicTransit}
+ {cost}
+ {eligibilityItems}
+ {languages}
+ {extraInfo}
+ {publicTransit}
Date: Tue, 19 Mar 2024 13:57:11 -0400
Subject: [PATCH 24/61] add sections
---
.../data-portal/ServiceEditDrawer/index.tsx | 123 +++++++++---------
1 file changed, 60 insertions(+), 63 deletions(-)
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index 82b52ba734..8fa506bf92 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -4,8 +4,8 @@ import {
type ButtonProps,
createPolymorphicComponent,
Drawer,
+ Group,
List,
- Modal,
Stack,
Text,
Title,
@@ -19,6 +19,8 @@ import { Textarea, TextInput } from 'react-hook-form-mantine'
import { Badge } from '~ui/components/core/Badge'
import { Breadcrumb } from '~ui/components/core/Breadcrumb'
+import { Button } from '~ui/components/core/Button'
+import { Section } from '~ui/components/core/Section'
import { ServiceSelect } from '~ui/components/data-portal/ServiceSelect'
import { useCustomVariant } from '~ui/hooks'
import { Icon } from '~ui/icon'
@@ -37,7 +39,7 @@ const _ServiceEditDrawer = forwardRef
const [serviceModalOpened, serviceModalHandler] = useDisclosure(false)
const { classes } = useStyles()
const variants = useCustomVariant()
- const { t } = useTranslation(['country', 'gov-dist'])
+ const { t } = useTranslation(['common', 'gov-dist'])
// #region Get existing data/populate form
const { data, isLoading } = api.service.forServiceEditDrawer.useQuery(serviceId, {
refetchOnWindowFocus: false,
@@ -79,23 +81,14 @@ const _ServiceEditDrawer = forwardRef
const array = serviceAreaObj[country]
const countryDetails = geoMap.get(country)
if (!countryDetails) continue
- if (!Array.isArray(array)) {
- serviceAreaObj[country] = [
-
-
- All of {t(countryDetails.tsKey, { ns: countryDetails.tsNs })}
-
- ,
- ]
- } else {
- array.push(
-
-
- All of {t(countryDetails.tsKey, { ns: countryDetails.tsNs })}
-
-
- )
- }
+ const item = (
+
+
+ All of {t(countryDetails.tsKey, { ns: countryDetails.tsNs })}
+
+
+ )
+ Array.isArray(array) ? array.push(item) : (serviceAreaObj[country] = [item])
}
}
if (districts?.length) {
@@ -108,34 +101,22 @@ const _ServiceEditDrawer = forwardRef
const parent = govDist.parent?.id ?? ''
const parentDist = geoMap.get(parent)
if (!distIdRegex.test(parent) || !parentDist) {
- Array.isArray(array)
- ? array.push(
-
- {t(govDist.tsKey, { ns: govDist.tsNs })}
-
- )
- : (serviceAreaObj[country] = [
-
- {t(govDist.tsKey, { ns: govDist.tsNs })}
- ,
- ])
+ const item = (
+
+ {t(govDist.tsKey, { ns: govDist.tsNs })}
+
+ )
+ Array.isArray(array) ? array.push(item) : (serviceAreaObj[country] = [item])
continue
}
- Array.isArray(array)
- ? array.push(
-
-
- {t(parentDist.tsKey, { ns: parentDist.tsNs })} - {t(govDist.tsKey, { ns: govDist.tsNs })}
-
-
- )
- : (serviceAreaObj[country] = [
-
-
- {t(parentDist.tsKey, { ns: parentDist.tsNs })} - {t(govDist.tsKey, { ns: govDist.tsNs })}
-
- ,
- ])
+ const item = (
+
+
+ {t(parentDist.tsKey, { ns: parentDist.tsNs })} - {t(govDist.tsKey, { ns: govDist.tsNs })}
+
+
+ )
+ Array.isArray(array) ? array.push(item) : (serviceAreaObj[country] = [item])
continue
}
}
@@ -161,12 +142,18 @@ const _ServiceEditDrawer = forwardRef
-
+
+
+ }>
+ Save
+
+
}
+ label='Service Name'
name='name.text'
control={form.control}
fontSize='h2'
@@ -175,33 +162,43 @@ const _ServiceEditDrawer = forwardRef
}
+ label='Description'
name='description.text'
control={form.control}
data-isDirty={dirtyFields.description}
autosize
/>
-
-
- {activeServices.map((serviceId) => {
- const service = allServices?.find((s) => s.id === serviceId)
- if (!service) return null
- return (
-
- {t(service.tsKey, { ns: service.tsNs })}
-
- )
- })}
-
-
+
+ Services
+
+
+ {activeServices.map((serviceId) => {
+ const service = allServices?.find((s) => s.id === serviceId)
+ if (!service) return null
+ return (
+
+ {t(service.tsKey, { ns: service.tsNs })}
+
+ )
+ })}
+
+
+
{/* */}
+
+ Coverage Area
-
- Coverage Area
-
{serviceAreas()}
{/* {Boolean(geoMap?.size) && } */}
- {/* */}
+ {t('service.get-help')}
+
+ {t('service.clients-served')}
+
+ {t('service.cost')}
+ {t('service.eligibility')}
+ {t('service.languages')}
+ {t('service.extra-info')}
From 0f1afe6e8c689d32d7a5a5740852769a6fe9f821 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 21 Mar 2024 12:32:01 -0400
Subject: [PATCH 25/61] patch for z.never()
---
.github/renovate.json | 2 +-
package.json | 3 ++-
patches/json-schema-to-zod@2.0.14.patch | 28 +++++++++++++++++++++++++
pnpm-lock.yaml | 10 ++++++---
4 files changed, 38 insertions(+), 5 deletions(-)
create mode 100644 patches/json-schema-to-zod@2.0.14.patch
diff --git a/.github/renovate.json b/.github/renovate.json
index 9366e4aed1..0f9dc84ca0 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -6,7 +6,7 @@
"packageRules": [
{
"groupName": "patched packages",
- "matchPackageNames": ["@crowdin/ota-client", "trpc-panel", "msw-storybook-addon"],
+ "matchPackageNames": ["@crowdin/ota-client", "trpc-panel", "msw-storybook-addon", "json-schema-to-zod"],
"matchUpdateTypes": ["major", "minor", "patch"]
},
{
diff --git a/package.json b/package.json
index 4df9bd0f1b..07d5c6b75f 100644
--- a/package.json
+++ b/package.json
@@ -104,7 +104,8 @@
"patchedDependencies": {
"@crowdin/ota-client@1.0.0": "patches/@crowdin__ota-client@1.0.0.patch",
"social-links@1.14.0": "patches/social-links@1.14.0.patch",
- "trpc-panel@1.3.4": "patches/trpc-panel@1.3.4.patch"
+ "trpc-panel@1.3.4": "patches/trpc-panel@1.3.4.patch",
+ "json-schema-to-zod@2.0.14": "patches/json-schema-to-zod@2.0.14.patch"
},
"peerDependencyRules": {
"allowedVersions": {
diff --git a/patches/json-schema-to-zod@2.0.14.patch b/patches/json-schema-to-zod@2.0.14.patch
new file mode 100644
index 0000000000..02438a9cae
--- /dev/null
+++ b/patches/json-schema-to-zod@2.0.14.patch
@@ -0,0 +1,28 @@
+diff --git a/dist/cjs/parsers/parseNot.js b/dist/cjs/parsers/parseNot.js
+index 43c2d410d17ab08e194e1227ea2536666d8d6508..5c300332e6b94b8737c5c0fc3ae87d3631e92bc0 100644
+--- a/dist/cjs/parsers/parseNot.js
++++ b/dist/cjs/parsers/parseNot.js
+@@ -3,9 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.parseNot = void 0;
+ const parseSchema_js_1 = require("./parseSchema.js");
+ const parseNot = (schema, refs) => {
+- return `z.any().refine((value) => !${(0, parseSchema_js_1.parseSchema)(schema.not, {
+- ...refs,
+- path: [...refs.path, "not"],
+- })}.safeParse(value).success, "Invalid input: Should NOT be valid against schema")`;
++ return `z.never()`;
+ };
+ exports.parseNot = parseNot;
+diff --git a/dist/esm/parsers/parseNot.js b/dist/esm/parsers/parseNot.js
+index 4aa11ba9febf80de210c07b72c0991d447f03838..6acaeb0e48751c14ba2ee05dd49767ec99f80774 100644
+--- a/dist/esm/parsers/parseNot.js
++++ b/dist/esm/parsers/parseNot.js
+@@ -1,7 +1,4 @@
+ import { parseSchema } from "./parseSchema.js";
+ export const parseNot = (schema, refs) => {
+- return `z.any().refine((value) => !${parseSchema(schema.not, {
+- ...refs,
+- path: [...refs.path, "not"],
+- })}.safeParse(value).success, "Invalid input: Should NOT be valid against schema")`;
++ return `z.never()`;
+ };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dfd39e117d..3a91ed6ba7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -34,6 +34,9 @@ patchedDependencies:
'@crowdin/ota-client@1.0.0':
hash: refrge56ym5gomc3tkglzjdymy
path: patches/@crowdin__ota-client@1.0.0.patch
+ json-schema-to-zod@2.0.14:
+ hash: m6gxyy33n5omrqal5wlfjbvve4
+ path: patches/json-schema-to-zod@2.0.14.patch
social-links@1.14.0:
hash: vsl4v34ksjh5tzibzra6h65ytm
path: patches/social-links@1.14.0.patch
@@ -982,7 +985,7 @@ importers:
version: 1.6.6
json-schema-to-zod:
specifier: 2.0.14
- version: 2.0.14
+ version: 2.0.14(patch_hash=m6gxyy33n5omrqal5wlfjbvve4)
kysely:
specifier: 0.27.3
version: 0.27.3
@@ -1299,7 +1302,7 @@ importers:
version: 3.3.4
json-schema-to-zod:
specifier: 2.0.14
- version: 2.0.14
+ version: 2.0.14(patch_hash=m6gxyy33n5omrqal5wlfjbvve4)
just-compact:
specifier: 3.2.0
version: 3.2.0
@@ -18689,10 +18692,11 @@ packages:
valid-url: 1.0.9
dev: true
- /json-schema-to-zod@2.0.14:
+ /json-schema-to-zod@2.0.14(patch_hash=m6gxyy33n5omrqal5wlfjbvve4):
resolution: {integrity: sha512-Pp9wg1/AcMw5KA1RA7t6ybUTIes1yX0vp8PeE48cPnddHb+ZZWbAKPaFXVf4Pif4XSbo9u9i/hIzBcS1UHK/TA==}
hasBin: true
dev: false
+ patched: true
/json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
From 3336155fa71e9ab57e695166ce4e8850642fdf09 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 21 Mar 2024 12:40:03 -0400
Subject: [PATCH 26/61] schema update
---
.../db/generated/attributeSupplementSchema.ts | 7 +-
...2024-03-21_attribute-supplement-schemas.ts | 95 +++++++++++++++++++
packages/db/prisma/data-migrations/index.ts | 1 +
packages/db/zod_util/attributeSupplement.ts | 7 +-
4 files changed, 106 insertions(+), 4 deletions(-)
create mode 100644 packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts
diff --git a/packages/db/generated/attributeSupplementSchema.ts b/packages/db/generated/attributeSupplementSchema.ts
index 0f13462e24..88bb6c7d30 100644
--- a/packages/db/generated/attributeSupplementSchema.ts
+++ b/packages/db/generated/attributeSupplementSchema.ts
@@ -6,14 +6,15 @@ export const attributeSupplementSchema = {
access_value: z.union([z.string(), z.null()]).optional(),
instructions: z.string(),
}),
+ currency: z.object({ cost: z.number(), currency: z.union([z.string(), z.null()]).optional() }).strict(),
incompatibleData: z.array(z.record(z.any())),
number: z.object({ num: z.number() }),
numMax: z.object({ max: z.number() }),
numMin: z.object({ min: z.number() }),
numMinMaxOrRange: z.union([
- z.object({ min: z.number() }),
- z.object({ max: z.number() }),
- z.object({ max: z.number(), min: z.number() }),
+ z.object({ max: z.never(), min: z.number() }).strict(),
+ z.object({ max: z.number(), min: z.never() }).strict(),
+ z.object({ max: z.number(), min: z.number() }).strict(),
]),
numRange: z.object({ max: z.number(), min: z.number() }),
otherDescribe: z.object({ other: z.string() }),
diff --git a/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts b/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts
new file mode 100644
index 0000000000..83ede3629e
--- /dev/null
+++ b/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts
@@ -0,0 +1,95 @@
+import { z } from 'zod'
+import { zodToJsonSchema } from 'zod-to-json-schema'
+
+import { prisma } from '~db/client'
+import { formatMessage } from '~db/prisma/common'
+import { type MigrationJob } from '~db/prisma/dataMigrationRunner'
+import { createLogger, type JobDef, jobPostRunner } from '~db/prisma/jobPreRun'
+import { JsonInputOrNull } from '~db/zod_util'
+import { type FieldAttributes, FieldType } from '~db/zod_util/attributeSupplement'
+
+/** Define the job metadata here. */
+const jobDef: JobDef = {
+ jobId: '2024-03-21_attribute-supplement-schemas',
+ title: 'attribute supplement schemas',
+ createdBy: 'Joe Karow',
+ /** Optional: Longer description for the job */
+ description: undefined,
+}
+
+const schemas = {
+ currency: z.object({
+ cost: z.number(),
+ currency: z.string().nullish(),
+ }),
+ numMinMaxOrRange: z
+ .union([
+ z.object({ min: z.number(), max: z.never() }),
+ z.object({ min: z.never(), max: z.number() }),
+ z.object({ min: z.number(), max: z.number() }),
+ ])
+ .refine(({ min, max }) => (min && max ? min < max : true), {
+ message: 'min must be less than max',
+ }),
+}
+/**
+ * Job export - this variable MUST be UNIQUE
+ */
+export const job20240321_attribute_supplement_schemas = {
+ title: `[${jobDef.jobId}] ${jobDef.title}`,
+ task: async (_ctx, task) => {
+ /** Create logging instance */
+ createLogger(task, jobDef.jobId)
+ const log = (...args: Parameters) => (task.output = formatMessage(...args))
+ /**
+ * Start defining your data migration from here.
+ *
+ * To log output, use `task.output = 'Message to log'`
+ *
+ * This will be written to `stdout` and to a log file in `/prisma/migration-logs/`
+ */
+
+ // Do stuff
+
+ const newSchemas = await prisma.attributeSupplementDataSchema.createMany({
+ data: [
+ {
+ id: 'asds_01HSGTSP6SKA5NZS9J42Z8S5BT',
+ tag: 'currency',
+ name: 'Currency',
+ definition: [
+ {
+ key: 'cost',
+ name: 'cost',
+ type: FieldType.number,
+ label: 'Cost',
+ },
+ {
+ key: 'currency',
+ name: 'currency',
+ type: FieldType.text,
+ label: 'Currency',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.currency)),
+ },
+ ],
+ skipDuplicates: true,
+ })
+ log(`Created ${newSchemas.count} Attribute Supplement Schema records.`)
+ const updateMinMax = await prisma.attributeSupplementDataSchema.update({
+ where: { id: 'asds_01GYX872BWWCGTZREHDT2AFF9D' },
+ data: {
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.numMinMaxOrRange)),
+ },
+ })
+ log(`Updated Attribute Supplement Schema: ${updateMinMax.name}.`)
+ /**
+ * DO NOT REMOVE BELOW
+ *
+ * This writes a record to the DB to register that this migration has run successfully.
+ */
+ await jobPostRunner(jobDef)
+ },
+ def: jobDef,
+} satisfies MigrationJob
diff --git a/packages/db/prisma/data-migrations/index.ts b/packages/db/prisma/data-migrations/index.ts
index 3ee27a277c..5b9ff1dbaf 100644
--- a/packages/db/prisma/data-migrations/index.ts
+++ b/packages/db/prisma/data-migrations/index.ts
@@ -11,4 +11,5 @@ export * from './2024-02-23_add-missing-website'
export * from './2024-03-08_update-alerts-and-org-urls/index'
export * from './2024-03-11_hide-locations'
export * from './2024-03-15_update-dead-links/index'
+export * from './2024-03-21_attribute-supplement-schemas'
// codegen:end
diff --git a/packages/db/zod_util/attributeSupplement.ts b/packages/db/zod_util/attributeSupplement.ts
index b0d120251a..e869bfffe3 100644
--- a/packages/db/zod_util/attributeSupplement.ts
+++ b/packages/db/zod_util/attributeSupplement.ts
@@ -79,7 +79,10 @@ export const accessInstructions = {
access_type: z.literal(''),
...commonAccessInstructions,
}),
-
+ publicTransport: z.object({
+ access_type: z.literal('publicTransit'),
+ ...commonAccessInstructions,
+ }),
getAll: function () {
return z.discriminatedUnion('access_type', [
this.email,
@@ -91,6 +94,7 @@ export const accessInstructions = {
this.sms,
this.whatsapp,
this.blank,
+ this.publicTransport,
])
},
}
@@ -104,6 +108,7 @@ export type AccessInstructions = {
sms: z.infer
whatsapp: z.infer
blank: z.infer
+ publicTransport: z.infer
getAll: () => z.infer>
}
From a7a2105083251698f3d4ef304584274f47ecaa12 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 21 Mar 2024 12:40:48 -0400
Subject: [PATCH 27/61] break in to pieces
---
packages/ui/modals/Service/ModalText.tsx | 16 ++
packages/ui/modals/Service/index.tsx | 300 ++---------------------
packages/ui/modals/Service/processor.tsx | 251 +++++++++++++++++++
packages/ui/modals/Service/styles.ts | 17 ++
4 files changed, 298 insertions(+), 286 deletions(-)
create mode 100644 packages/ui/modals/Service/ModalText.tsx
create mode 100644 packages/ui/modals/Service/processor.tsx
create mode 100644 packages/ui/modals/Service/styles.ts
diff --git a/packages/ui/modals/Service/ModalText.tsx b/packages/ui/modals/Service/ModalText.tsx
new file mode 100644
index 0000000000..e0f448def1
--- /dev/null
+++ b/packages/ui/modals/Service/ModalText.tsx
@@ -0,0 +1,16 @@
+import { Text } from '@mantine/core'
+import { type ReactNode } from 'react'
+
+import { useStyles } from './styles'
+
+export const ModalText = ({ children }: ModalTextprops) => {
+ const { classes } = useStyles()
+ return (
+
+ {children}
+
+ )
+}
+type ModalTextprops = {
+ children: ReactNode
+}
diff --git a/packages/ui/modals/Service/index.tsx b/packages/ui/modals/Service/index.tsx
index 46298b4e55..91b798fe45 100644
--- a/packages/ui/modals/Service/index.tsx
+++ b/packages/ui/modals/Service/index.tsx
@@ -2,7 +2,6 @@ import {
Box,
type ButtonProps,
createPolymorphicComponent,
- createStyles,
List,
Modal,
Stack,
@@ -13,37 +12,17 @@ import {
import { useDisclosure, useMediaQuery } from '@mantine/hooks'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
-import { forwardRef, type JSX, type ReactNode } from 'react'
+import { forwardRef, type ReactNode } from 'react'
import { serviceModalEvent } from '@weareinreach/analytics/events'
-import { supplementSchema } from '@weareinreach/api/schemas/attributeSupplement'
-import { AlertMessage } from '~ui/components/core/AlertMessage'
import { Badge } from '~ui/components/core/Badge'
import { Section } from '~ui/components/core/Section'
import { ContactInfo, hasContactInfo, Hours } from '~ui/components/data-display'
-import { type PassedDataObject } from '~ui/components/data-display/ContactInfo/types'
-import { getFreeText, useSlug } from '~ui/hooks'
-import { isValidIcon } from '~ui/icon'
+import { useSlug } from '~ui/hooks/useSlug'
import { trpc as api } from '~ui/lib/trpcClient'
+import { processAccessInstructions, processAttributes } from './processor'
import { ModalTitle, type ModalTitleProps } from '../ModalTitle'
-
-const useStyles = createStyles((theme) => ({
- sectionDivider: {
- backgroundColor: theme.other.colors.primary.lightGray,
- padding: 12,
- },
- timezone: {
- ...theme.other.utilityFonts.utility4,
- color: theme.other.colors.secondary.darkGray,
- },
- blackText: {
- color: '#000000',
- margin: 0,
- whiteSpace: 'pre-line',
- },
-}))
-
/**
* TODO: [IN-797] Service Modal updates
*
@@ -60,7 +39,6 @@ const ServiceModalBody = forwardRef(({ ser
const { data, status } = api.service.forServiceModal.useQuery(serviceId)
const { data: orgId } = api.organization.getIdFromSlug.useQuery({ slug })
const { t, i18n } = useTranslation(orgId?.id ? ['common', 'attribute', orgId.id] : ['common', 'attribute'])
- const { classes } = useStyles()
const [opened, handler] = useDisclosure(false)
const theme = useMantineTheme()
const isMobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`)
@@ -104,19 +82,6 @@ const ServiceModalBody = forwardRef(({ ser
)
}
- const ModalText = ({ children }: ModalTextprops) => (
-
- {children}
-
- )
-
- const contactData: PassedDataObject = {
- phones: [],
- emails: [],
- websites: [],
- socialMedia: [],
- }
-
if (data && status === 'success') {
const { serviceName, services, hours, accessDetails, attributes, description, locations } = data
@@ -127,203 +92,14 @@ const ServiceModalBody = forwardRef(({ ser
))}
)
+ const { getHelp, publicTransit } = processAccessInstructions({ accessDetails, locations, t })
+ const { eligibility, clientsServed, cost, lang, misc, miscWithIcons, atCapacity } = processAttributes({
+ attributes,
+ t,
+ locale: i18n.language,
+ })
- const baseDetails: AccessDetails = { publicTransit: [] }
-
- const { publicTransit } = accessDetails.reduce((details, { supplement }) => {
- const { data, text, id } = supplement
- const parsed = supplementSchema.accessInstructions.safeParse(data)
- if (parsed.success) {
- const { access_type, access_value } = parsed.data
- switch (access_type) {
- case 'publicTransit': {
- if (!text) break
- const { key, options } = getFreeText(text)
- details[access_type].push({t(key, options)})
- break
- }
- case 'email': {
- contactData.emails.push({
- id,
- title: null,
- description: null,
- email: parsed.data.access_value,
- // legacyDesc: parsed.data.instructions,
- // firstName: null,
- // lastName: null,
- primary: false,
- locationOnly: false,
- serviceOnly: false,
- })
- break
- }
- case 'phone': {
- const country = locations.find(({ location }) => Boolean(location.country))?.location?.country
- ?.cca2
- if (!country) break
- contactData.phones.push({
- id,
- number: parsed.data.access_value,
- phoneType: null,
- country,
- primary: false,
- locationOnly: false,
- ext: null,
- description: null,
- })
- break
- }
- case 'link':
- case 'file': {
- contactData.websites.push({
- id,
- description: null,
- isPrimary: false,
- // orgLocationId: null,
- orgLocationOnly: false,
- url: parsed.data.access_value,
- })
- }
- }
-
- const accessKey = CONTACTS.find((category) => category === access_type)
- if (accessKey) details[accessKey] ||= {access_value}
- }
- return details
- }, baseDetails)
-
- const attributeCategories: Attributes = {
- cost: [],
- lang: [],
- clientsServed: {
- srvfocus: [],
- targetPop: [],
- },
- eligibility: {
- requirements: [],
- freeText: [],
- },
- misc: [],
- miscWithIcons: [],
- }
-
- const { eligibility, clientsServed, cost, lang, misc, miscWithIcons, atCapacity } = attributes.reduce(
- (subsections, { attribute, supplement }) => {
- const { tsKey, icon, tsNs, id } = attribute
- /*
- Since the tsKeys follow a sort of pattern with the namespace being the first part of the
- string before the '.', would it be alright to check for the category that way?
- It avoids having to iterate through the categories array with:
- categories.find(({ category }) => tsKey.includes(category.tag))
- */
- const namespace = tsKey.split('.').shift() as string
-
- switch (namespace) {
- /** Clients served */
- case 'srvfocus': {
- if (typeof icon === 'string' && attribute._count.parents === 0) {
- subsections.clientsServed[namespace].push(
-
- {t(tsKey, { ns: tsNs })}
-
- )
- }
- break
- }
- /** Target Population & Eligibility Requirements */
- case 'eligibility': {
- const type = tsKey.split('.').pop() as string
- switch (type) {
- case 'elig-age': {
- const { data, id } = supplement
- const parsed = supplementSchema.age.safeParse(data)
- if (!parsed.success) break
- const { min, max } = parsed.data
- const context = min && max ? 'range' : min ? 'min' : 'max'
- subsections[namespace]['age'] = (
- {t('service.elig-age', { ns: 'common', context, min, max })}
- )
- break
- }
- case 'other-describe': {
- const { text, id } = supplement
- if (!text) break
- const { key, options } = getFreeText(text)
- subsections.clientsServed.targetPop.push({t(key, options)})
-
- break
- }
- }
-
- break
- }
- case 'cost': {
- if (!isValidIcon(icon)) break
- const costDetails: CostDetails = { description: [] }
-
- const { text, data, id } = supplement
- if (text) {
- const { key, options } = getFreeText(text)
- costDetails.description.push({t(key, options)})
- }
- const parsed = supplementSchema.cost.safeParse(data)
- if (parsed.success) {
- const { cost, currency } = parsed.data
- costDetails.price = new Intl.NumberFormat(i18n.language, {
- style: 'currency',
- currency: currency ?? undefined,
- }).format(cost)
- }
-
- const { price, description } = costDetails
- subsections[namespace].push(
-
- {t(tsKey, { price, ns: tsNs })}
-
- )
-
- if (description.length > 0)
- subsections[namespace].push(
-
- {description}
-
- )
- break
- }
-
- case 'lang': {
- const { language } = supplement
- if (!language) break
- const { languageName } = language
- subsections[namespace].push(languageName)
- break
- }
- case 'additional': {
- if (tsKey.includes('at-capacity'))
- subsections['atCapacity'] = (
-
- )
- else {
- isValidIcon(icon)
- ? subsections[`miscWithIcons`].push(
-
- {t(tsKey, { ns: tsNs })}
-
- )
- : subsections['misc'].push(t(tsKey, { ns: tsNs }))
- }
- break
- }
- default: {
- break
- }
- }
- return subsections
- },
- attributeCategories
- )
-
- const eligibilityItems: JSX.Element[] = []
+ const eligibilityItems: ReactNode[] = []
if (eligibility.age)
eligibilityItems.push(
@@ -361,7 +137,7 @@ const ServiceModalBody = forwardRef(({ ser
)
- const extraInfo: JSX.Element[] = []
+ const extraInfo: ReactNode[] = []
if (miscWithIcons.length > 0)
extraInfo.push(
@@ -403,10 +179,10 @@ const ServiceModalBody = forwardRef(({ ser
)}
{serviceBadges}
- {(hasContactInfo(contactData) || Boolean(hours.length)) && (
+ {(hasContactInfo(getHelp) || Boolean(hours.length)) && (
- {hasContactInfo(contactData) && (
-
+ {hasContactInfo(getHelp) && (
+
)}
{Boolean(hours.length) && }
@@ -451,51 +227,3 @@ export const ServiceModal = createPolymorphicComponent<'button', ServiceModalPro
export interface ServiceModalProps extends ButtonProps {
serviceId: string
}
-
-type SubsectionProps = {
- title?: string
- children?: ReactNode
- li?: string[] | string
-}
-
-type SectionProps = {
- title?: string
- children?: ReactNode
-}
-
-type Attributes = {
- directEmail?: string
- directPhone?: string
- directWebsite?: string
- cost: JSX.Element[]
- lang: string[]
- clientsServed: {
- srvfocus: JSX.Element[]
- targetPop: JSX.Element[]
- }
- atCapacity?: JSX.Element
- eligibility: {
- age?: JSX.Element
- requirements: string[]
- freeText: JSX.Element[]
- }
- misc: string[]
- miscWithIcons: JSX.Element[]
-}
-
-type AccessDetails = {
- phone?: JSX.Element
- email?: JSX.Element
- website?: JSX.Element
- atCapacity?: JSX.Element
- publicTransit: JSX.Element[]
-}
-
-type CostDetails = {
- price?: number | string
- description: JSX.Element[]
-}
-
-type ModalTextprops = {
- children: ReactNode
-}
diff --git a/packages/ui/modals/Service/processor.tsx b/packages/ui/modals/Service/processor.tsx
new file mode 100644
index 0000000000..8ba9d50879
--- /dev/null
+++ b/packages/ui/modals/Service/processor.tsx
@@ -0,0 +1,251 @@
+import { type TFunction } from 'next-i18next'
+import { type ReactNode } from 'react'
+
+import { type ApiOutput } from '@weareinreach/api'
+import { attributeSupplementSchema } from '@weareinreach/db/generated/attributeSupplementSchema'
+import { accessInstructions } from '@weareinreach/db/zod_util/attributeSupplement'
+import { AlertMessage } from '~ui/components/core/AlertMessage'
+import { Badge } from '~ui/components/core/Badge'
+import { Section } from '~ui/components/core/Section'
+import { type PassedDataObject } from '~ui/components/data-display/ContactInfo/types'
+import { getFreeText } from '~ui/hooks/useFreeText'
+import { isValidIcon } from '~ui/icon'
+
+import { ModalText } from './ModalText'
+
+export const processAccessInstructions = ({
+ accessDetails,
+ locations,
+ t,
+}: {
+ accessDetails: ApiOutput['service']['forServiceModal']['accessDetails']
+ locations: ApiOutput['service']['forServiceModal']['locations']
+ t: TFunction
+}): AccessInstructionsOutput => {
+ const output: AccessInstructionsOutput = {
+ getHelp: {
+ phones: [],
+ emails: [],
+ websites: [],
+ socialMedia: [],
+ },
+ publicTransit: null,
+ }
+
+ for (const { supplement } of accessDetails) {
+ const { data, text, id } = supplement
+ const parsed = accessInstructions.getAll().safeParse(data)
+ if (parsed.success) {
+ const { access_type, access_value } = parsed.data
+ switch (access_type) {
+ case 'publicTransit': {
+ if (!text) break
+ const { key, options } = getFreeText(text)
+ output.publicTransit = {t(key, options)}
+ break
+ }
+ case 'email': {
+ if (access_value)
+ output.getHelp.emails.push({
+ id,
+ title: null,
+ description: null,
+ email: access_value,
+ // legacyDesc: parsed.data.instructions,
+ // firstName: null,
+ // lastName: null,
+ primary: false,
+ locationOnly: false,
+ serviceOnly: false,
+ })
+ break
+ }
+ case 'phone': {
+ const country = locations.find(({ location }) => Boolean(location.country))?.location?.country?.cca2
+ if (!country) break
+ if (access_value)
+ output.getHelp.phones.push({
+ id,
+ number: access_value,
+ phoneType: null,
+ country,
+ primary: false,
+ locationOnly: false,
+ ext: null,
+ description: null,
+ })
+ break
+ }
+ case 'link':
+ case 'file': {
+ if (access_value)
+ output.getHelp.websites.push({
+ id,
+ description: null,
+ isPrimary: false,
+ // orgLocationId: null,
+ orgLocationOnly: false,
+ url: access_value,
+ })
+ }
+ }
+ }
+ }
+
+ return output
+}
+
+export const processAttributes = ({
+ attributes,
+ locale = 'en',
+ t,
+}: {
+ attributes: ApiOutput['service']['forServiceModal']['attributes']
+ locale: string
+ t: TFunction
+}): AttributesOutput => {
+ const output: AttributesOutput = {
+ clientsServed: {
+ srvfocus: [],
+ targetPop: [],
+ },
+ cost: [],
+ eligibility: {
+ requirements: [],
+ freeText: [],
+ },
+ lang: [],
+ misc: [],
+ miscWithIcons: [],
+ }
+ for (const { attribute, supplement } of attributes) {
+ const { tsKey, icon, tsNs, id } = attribute
+ const namespace = tsKey.split('.').shift() as string
+
+ switch (namespace) {
+ /** Clients served */
+ case 'srvfocus': {
+ if (typeof icon === 'string' && attribute._count.parents === 0) {
+ output.clientsServed.srvfocus.push(
+
+ {t(tsKey, { ns: tsNs })}
+
+ )
+ }
+ break
+ }
+ /** Target Population & Eligibility Requirements */
+ case 'eligibility': {
+ const type = tsKey.split('.').pop() as string
+ switch (type) {
+ case 'elig-age': {
+ const { data, id } = supplement
+ const parsed = attributeSupplementSchema.numMinMaxOrRange.safeParse(data)
+ if (!parsed.success) break
+ const { min, max } = parsed.data
+ const context = min && max ? 'range' : min ? 'min' : 'max'
+ output.eligibility.age = (
+ {t('service.elig-age', { ns: 'common', context, min, max })}
+ )
+ break
+ }
+ case 'other-describe': {
+ const { text, id } = supplement
+ if (!text) break
+ const { key, options } = getFreeText(text)
+ output.clientsServed.targetPop.push({t(key, options)})
+
+ break
+ }
+ }
+
+ break
+ }
+ case 'cost': {
+ if (!isValidIcon(icon)) break
+ const costDetails: {
+ price?: number | string
+ description: ReactNode[]
+ } = { description: [] }
+
+ const { text, data, id } = supplement
+ if (text) {
+ const { key, options } = getFreeText(text)
+ costDetails.description.push({t(key, options)})
+ }
+ const parsed = attributeSupplementSchema.currency.safeParse(data)
+ if (parsed.success) {
+ const { cost, currency } = parsed.data
+ costDetails.price = new Intl.NumberFormat(locale, {
+ style: 'currency',
+ currency: currency ?? undefined,
+ }).format(cost)
+ }
+
+ const { price, description } = costDetails
+ output.cost.push(
+
+ {t(tsKey, { price, ns: tsNs })}
+
+ )
+
+ if (description.length > 0)
+ output.cost.push(
+
+ {description}
+
+ )
+ break
+ }
+
+ case 'lang': {
+ const { language } = supplement
+ if (!language) break
+ const { languageName } = language
+ output.lang.push(languageName)
+ break
+ }
+ case 'additional': {
+ if (tsKey.includes('at-capacity'))
+ output.atCapacity =
+ else {
+ isValidIcon(icon)
+ ? output.miscWithIcons.push(
+
+ {t(tsKey, { ns: tsNs })}
+
+ )
+ : output.misc.push(t(tsKey, { ns: tsNs }))
+ }
+ break
+ }
+ default: {
+ break
+ }
+ }
+ }
+ return output
+}
+interface AccessInstructionsOutput {
+ getHelp: PassedDataObject
+ publicTransit: ReactNode
+}
+interface AttributesOutput {
+ directEmail?: string
+ directPhone?: string
+ directWebsite?: string
+ cost: ReactNode[]
+ lang: string[]
+ clientsServed: {
+ srvfocus: ReactNode[]
+ targetPop: ReactNode[]
+ }
+ atCapacity?: ReactNode
+ eligibility: {
+ age?: ReactNode
+ requirements: string[]
+ freeText: ReactNode[]
+ }
+ misc: string[]
+ miscWithIcons: ReactNode[]
+}
diff --git a/packages/ui/modals/Service/styles.ts b/packages/ui/modals/Service/styles.ts
new file mode 100644
index 0000000000..4a73d34a0e
--- /dev/null
+++ b/packages/ui/modals/Service/styles.ts
@@ -0,0 +1,17 @@
+import { createStyles } from '@mantine/core'
+
+export const useStyles = createStyles((theme) => ({
+ sectionDivider: {
+ backgroundColor: theme.other.colors.primary.lightGray,
+ padding: 12,
+ },
+ timezone: {
+ ...theme.other.utilityFonts.utility4,
+ color: theme.other.colors.secondary.darkGray,
+ },
+ blackText: {
+ color: '#000000',
+ margin: 0,
+ whiteSpace: 'pre-line',
+ },
+}))
From 946363d322f81792cb29f8939aede1c755963f7e Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 21 Mar 2024 18:03:12 -0400
Subject: [PATCH 28/61] add i18next as peerdep
---
packages/api/package.json | 2 ++
pnpm-lock.yaml | 3 +++
2 files changed, 5 insertions(+)
diff --git a/packages/api/package.json b/packages/api/package.json
index d3bdd6ae33..39e9f40f24 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -61,6 +61,7 @@
"@weareinreach/eslint-config": "0.100.0",
"dotenv-cli": "7.4.1",
"eslint": "8.57.0",
+ "i18next": "23.10.1",
"inquirer-search-list": "1.2.6",
"just-pascal-case": "3.2.0",
"next": "14.1.4",
@@ -71,6 +72,7 @@
"typescript": "5.4.3"
},
"peerDependencies": {
+ "i18next": "23.10.1",
"next": ">=13"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3a91ed6ba7..052c3e44fb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -789,6 +789,9 @@ importers:
eslint:
specifier: 8.57.0
version: 8.57.0
+ i18next:
+ specifier: 23.10.1
+ version: 23.10.1
inquirer-search-list:
specifier: 1.2.6
version: 1.2.6
From 368212f2055741e8026df48014d4dffd0805b7a7 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 21 Mar 2024 18:11:32 -0400
Subject: [PATCH 29/61] enable new turbo ui
---
turbo.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/turbo.json b/turbo.json
index a6f2d50b5d..c36da9ba55 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,5 +1,6 @@
{
"$schema": "https://turborepo.org/schema.json",
+ "experimentalUI": true,
"globalDependencies": ["./packages/config/tsconfig/base.json"],
"globalDotEnv": [".env"],
"globalEnv": ["NODE_ENV"],
From f0a70dcc5ff789a2fcd9855c96c68abdc3c73de8 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Fri, 22 Mar 2024 11:13:27 -0400
Subject: [PATCH 30/61] sonarcloud workspace binding
---
.vscode/settings.json | 6 ++++++
apps/app/.vscode/settings.json | 6 ++++++
apps/web/.vscode/settings.json | 6 ++++++
lambdas/.vscode/settings.json | 6 ++++++
packages/analytics/.vscode/settings.json | 6 ++++++
packages/api/.vscode/settings.json | 6 ++++++
packages/auth/.vscode/settings.json | 6 ++++++
packages/config/.vscode/settings.json | 6 ++++++
packages/crowdin/.vscode/settings.json | 6 ++++++
packages/db/.vscode/settings.json | 6 ++++++
packages/env/.vscode/settings.json | 6 ++++++
packages/eslint-config/.vscode/settings.json | 6 ++++++
packages/ui/.vscode/settings.json | 4 ++++
packages/util/.vscode/settings.json | 6 ++++++
14 files changed, 82 insertions(+)
create mode 100644 .vscode/settings.json
create mode 100644 apps/app/.vscode/settings.json
create mode 100644 apps/web/.vscode/settings.json
create mode 100644 lambdas/.vscode/settings.json
create mode 100644 packages/analytics/.vscode/settings.json
create mode 100644 packages/api/.vscode/settings.json
create mode 100644 packages/auth/.vscode/settings.json
create mode 100644 packages/config/.vscode/settings.json
create mode 100644 packages/crowdin/.vscode/settings.json
create mode 100644 packages/db/.vscode/settings.json
create mode 100644 packages/env/.vscode/settings.json
create mode 100644 packages/eslint-config/.vscode/settings.json
create mode 100644 packages/util/.vscode/settings.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/apps/app/.vscode/settings.json b/apps/app/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/apps/app/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/apps/web/.vscode/settings.json b/apps/web/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/apps/web/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/lambdas/.vscode/settings.json b/lambdas/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/lambdas/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/analytics/.vscode/settings.json b/packages/analytics/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/analytics/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/api/.vscode/settings.json b/packages/api/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/api/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/auth/.vscode/settings.json b/packages/auth/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/auth/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/config/.vscode/settings.json b/packages/config/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/config/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/crowdin/.vscode/settings.json b/packages/crowdin/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/crowdin/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/db/.vscode/settings.json b/packages/db/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/db/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/env/.vscode/settings.json b/packages/env/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/env/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/eslint-config/.vscode/settings.json b/packages/eslint-config/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/eslint-config/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
diff --git a/packages/ui/.vscode/settings.json b/packages/ui/.vscode/settings.json
index a2c3de751c..331acd2d09 100644
--- a/packages/ui/.vscode/settings.json
+++ b/packages/ui/.vscode/settings.json
@@ -2,5 +2,9 @@
"i18n-ally.localesPaths": "../../apps/app/public/locales",
"[json]": {
"editor.codeActionsOnSave": { "source.fixAll": "never" }
+ },
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
}
}
diff --git a/packages/util/.vscode/settings.json b/packages/util/.vscode/settings.json
new file mode 100644
index 0000000000..7c77f0a9b1
--- /dev/null
+++ b/packages/util/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach"
+ }
+}
From b8d5ef1807d08509598c1f5d8cb7d8b417289c79 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Mon, 25 Mar 2024 14:56:35 -0400
Subject: [PATCH 31/61] service attribute processing
---
.../service/formatters/modalAndEditDrawer.ts | 382 ++++++++++++++++++
1 file changed, 382 insertions(+)
create mode 100644 packages/api/router/service/formatters/modalAndEditDrawer.ts
diff --git a/packages/api/router/service/formatters/modalAndEditDrawer.ts b/packages/api/router/service/formatters/modalAndEditDrawer.ts
new file mode 100644
index 0000000000..807a55dae1
--- /dev/null
+++ b/packages/api/router/service/formatters/modalAndEditDrawer.ts
@@ -0,0 +1,382 @@
+import { type TOptions } from 'i18next'
+import { type z } from 'zod'
+
+import { type Prisma, prisma } from '@weareinreach/db'
+import { attributeSupplementSchema } from '@weareinreach/db/generated/attributeSupplementSchema'
+import { accessInstructions } from '@weareinreach/db/zod_util/attributeSupplement'
+import { globalWhere } from '~api/selects/global'
+
+const getFreeText = (freeTextRecord: NonNullable) => {
+ const { tsKey } = freeTextRecord
+ const { key: dbKey } = tsKey
+ const deconstructedKey = dbKey.split('.')
+ const ns = deconstructedKey[0]
+ if (!deconstructedKey.length || !ns) throw new Error('Invalid key')
+ const key = deconstructedKey.join('.')
+ const options = { ns, defaultValue: tsKey.text } satisfies TOptions
+ return { key, options }
+}
+export const attributeSelect = (showAll?: boolean) =>
+ ({
+ ...(showAll
+ ? {}
+ : ({
+ where: {
+ active: true,
+ attribute: { active: true },
+ },
+ } as const)),
+ select: {
+ attribute: {
+ select: {
+ id: true,
+ tag: true,
+ tsKey: true,
+ tsNs: true,
+ icon: true,
+ iconBg: true,
+ showOnLocation: true,
+ categories: { select: { category: { select: { tag: true, ns: true } } } },
+ _count: {
+ select: {
+ parents: true,
+ children: true,
+ },
+ },
+ },
+ },
+ active: true,
+ countryId: true,
+ data: true,
+ govDistId: true,
+ id: true,
+ language: { select: { id: true, languageName: true } },
+ languageId: true,
+ text: { select: { tsKey: { select: { key: true, text: true, ns: true } } } },
+ boolean: true,
+ },
+ }) as const
+export const locationSelect = (showAll?: boolean) =>
+ ({
+ ...(showAll
+ ? {}
+ : ({
+ where: {
+ location: globalWhere.isPublic(),
+ },
+ } as const)),
+ select: { location: { select: { country: { select: { cca2: true } } } } },
+ }) as const
+
+export const transformToProps = (data: ReturnedData): TransformOutput => {
+ const { attributes, locations } = data
+ const output: TransformOutput = {
+ accessInstructions: {
+ publicTransit: undefined,
+ email: [],
+ phone: [],
+ website: [],
+ },
+ attributeSections: {
+ clientsServed: {
+ focused: [],
+ targetPopulation: [],
+ },
+ eligibility: {
+ age: undefined,
+ },
+ cost: {
+ description: [],
+ badged: [],
+ },
+ language: [],
+ atCapacity: false,
+ misc: [],
+ miscWithIcons: [],
+ },
+ }
+ type AttributeToProcess = (typeof attributes)[number]['attribute']
+ type SupplementToProcess = Omit<(typeof attributes)[number], 'attribute'>
+ const processAccessInstruction = (
+ data: z.infer>,
+ supplement: SupplementToProcess
+ ) => {
+ const { access_type, access_value } = data
+ switch (access_type) {
+ case 'publicTransit': {
+ if (!supplement.text) break
+ output.accessInstructions.publicTransit = {
+ key: supplement.id,
+ children: getFreeText(supplement.text),
+ }
+ break
+ }
+ case 'email': {
+ if (access_value)
+ output.accessInstructions.email.push({
+ id: supplement.id,
+ title: null,
+ description: null,
+ email: access_value,
+ primary: false,
+ locationOnly: false,
+ serviceOnly: false,
+ })
+ break
+ }
+ case 'phone': {
+ const country = locations.find(({ location }) => Boolean(location.country))?.location?.country?.cca2
+ if (!country) break
+ if (access_value)
+ output.accessInstructions.phone.push({
+ id: supplement.id,
+ number: access_value,
+ phoneType: null,
+ country,
+ primary: false,
+ locationOnly: false,
+ ext: null,
+ description: null,
+ })
+ break
+ }
+ case 'link':
+ case 'file': {
+ if (access_value)
+ output.accessInstructions.website.push({
+ id: supplement.id,
+ description: null,
+ isPrimary: false,
+ orgLocationOnly: false,
+ url: access_value,
+ })
+ }
+ }
+ }
+
+ const handleSrvFocus = (attribute: AttributeToProcess, supplement: SupplementToProcess) => {
+ if (typeof attribute.icon === 'string' && attribute._count.parents === 0) {
+ output.attributeSections.clientsServed.focused.push({
+ key: supplement.id,
+ icon: attribute.icon,
+ children: {
+ tsKey: attribute.tsKey,
+ tOptions: { ns: attribute.tsNs },
+ },
+ })
+ }
+ }
+ const handleEligibility = (attribute: AttributeToProcess, supplement: SupplementToProcess) => {
+ const type = attribute.tsKey.split('.').pop() as string
+ switch (type) {
+ case 'elig-age': {
+ const parsed = attributeSupplementSchema.numMinMaxOrRange.safeParse(supplement.data)
+ if (!parsed.success) break
+ const { min, max } = parsed.data
+ const context = (function () {
+ switch (true) {
+ case Boolean(min) && Boolean(max): {
+ return 'range'
+ }
+ case Boolean(min): {
+ return 'min'
+ }
+ default: {
+ return 'max'
+ }
+ }
+ })()
+
+ output.attributeSections.eligibility.age = {
+ key: supplement.id,
+ children: {
+ key: 'service.elig-age',
+ options: { ns: 'common', context, min, max },
+ },
+ }
+
+ break
+ }
+ case 'other-describe': {
+ if (!supplement.text) break
+ output.attributeSections.clientsServed.targetPopulation.push({
+ key: supplement.id,
+ children: getFreeText(supplement.text),
+ })
+ break
+ }
+ }
+ }
+ const handleCost = (attribute: AttributeToProcess, supplement: SupplementToProcess) => {
+ if (!attribute.icon) return
+ if (supplement.text) {
+ output.attributeSections.cost.description.push({
+ key: supplement.id,
+ children: getFreeText(supplement.text),
+ })
+ }
+ const parsed = attributeSupplementSchema.currency.safeParse(supplement.data)
+ if (parsed.success) {
+ output.attributeSections.cost.badged.push({
+ key: supplement.id,
+ icon: attribute.icon,
+ style: { justifyContent: 'start' },
+ children: {
+ tsKey: attribute.tsKey,
+ tOptions: { ns: attribute.tsNs },
+ miscInterpolation: {
+ data: parsed.data.cost,
+ currency: parsed.data.currency ?? undefined,
+ style: 'currency',
+ },
+ },
+ })
+ }
+ }
+ const handleLanguage = (attribute: AttributeToProcess, supplement: SupplementToProcess) => {
+ if (!supplement.language) return
+ output.attributeSections.language.push(supplement.language.languageName)
+ }
+ const handleAdditional = (attribute: AttributeToProcess, supplement: SupplementToProcess) => {
+ if (attribute.tsKey.includes('at-capacity')) output.attributeSections.atCapacity = true
+ else {
+ typeof attribute.icon === 'string'
+ ? output.attributeSections.miscWithIcons.push({
+ key: supplement.id,
+ icon: attribute.icon,
+ children: {
+ tsKey: attribute.tsKey,
+ tOptions: { ns: attribute.tsNs },
+ },
+ })
+ : output.attributeSections.misc.push({
+ tsKey: attribute.tsKey,
+ tOptions: { ns: attribute.tsNs },
+ })
+ }
+ }
+ const processAttribute = (attribute: AttributeToProcess, supplement: SupplementToProcess) => {
+ const namespace = attribute.tsKey.split('.').shift() as string
+
+ switch (namespace) {
+ /** Clients served */
+ case 'srvfocus': {
+ handleSrvFocus(attribute, supplement)
+ break
+ }
+ /** Target Population & Eligibility Requirements */
+ case 'eligibility': {
+ handleEligibility(attribute, supplement)
+ break
+ }
+ case 'cost': {
+ handleCost(attribute, supplement)
+ break
+ }
+
+ case 'lang': {
+ handleLanguage(attribute, supplement)
+ break
+ }
+ case 'additional': {
+ handleAdditional(attribute, supplement)
+ break
+ }
+ default: {
+ break
+ }
+ }
+ }
+
+ for (const { attribute, ...supplement } of attributes) {
+ const flatAttribs = attribute.categories.map(({ category }) => category.tag)
+ if (flatAttribs.includes('service-access-instructions')) {
+ // process access instruction
+ const parsed = accessInstructions.getAll().safeParse(supplement.data)
+ if (parsed.success) {
+ processAccessInstruction(parsed.data, supplement)
+ }
+ } else {
+ // process attribute
+ processAttribute(attribute, supplement)
+ }
+ }
+ return output
+}
+
+const testTxn = async () =>
+ await prisma.orgService.findFirstOrThrow({
+ where: { id: '' },
+ select: { attributes: attributeSelect(), locations: locationSelect() },
+ })
+type ReturnedData = Prisma.PromiseReturnType
+type TransformOutput = {
+ accessInstructions: {
+ publicTransit?: ModalTextProps
+ email: EmailProps[]
+ phone: PhoneProps[]
+ website: WebsiteProps[]
+ }
+ attributeSections: {
+ clientsServed: {
+ focused: AttributeBadgeProps[]
+ targetPopulation: ModalTextProps[]
+ }
+ eligibility: {
+ age?: ModalTextProps
+ }
+ cost: {
+ badged: AttributeBadgeProps[]
+ description: ModalTextProps[]
+ }
+ language: string[]
+ atCapacity: boolean
+ miscWithIcons: AttributeBadgeProps[]
+ misc: ChildrenT[]
+ }
+}
+type ModalTextProps = {
+ key: string
+ children: {
+ key: string
+ options: TOptions
+ }
+}
+type EmailProps = {
+ id: string
+ title: null
+ description: null
+ email: string
+ primary: boolean
+ locationOnly: boolean
+ serviceOnly: boolean
+}
+type PhoneProps = {
+ id: string
+ number: string
+ phoneType: null
+ country: string
+ primary: boolean
+ locationOnly: boolean
+ ext: null
+ description: null
+}
+type WebsiteProps = {
+ id: string
+ description: null
+ isPrimary: false
+ orgLocationOnly: boolean
+ url: string
+}
+type AttributeBadgeProps = {
+ key: string
+ icon: string
+ children: ChildrenT
+ style?: Record
+}
+type ChildrenT = {
+ tsKey: string
+ tOptions: TOptions
+ miscInterpolation?: InterpolateNumber
+}
+type InterpolateNumber = Intl.NumberFormatOptions & { data: number }
From cb7a6f08f9b27cefdd2099000306a7b4ce674680 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Mon, 25 Mar 2024 14:56:57 -0400
Subject: [PATCH 32/61] update criteria
---
packages/api/formatters/attributes.ts | 12 +++++++-----
.../query.forServiceEditDrawer.handler.ts | 5 +++--
.../service/query.forServiceModal.handler.ts | 19 ++-----------------
3 files changed, 12 insertions(+), 24 deletions(-)
diff --git a/packages/api/formatters/attributes.ts b/packages/api/formatters/attributes.ts
index ce8fc579b9..30adf304fe 100644
--- a/packages/api/formatters/attributes.ts
+++ b/packages/api/formatters/attributes.ts
@@ -16,11 +16,13 @@ export const formatAttributes = {
select: {
id: true,
tag: true,
- // tsKey: true,
- // tsNs: true,
- // icon: true,
- // iconBg: true,
- categories: { select: { category: { select: { tag: true, ns: true } } } },
+ tsKey: true,
+ tsNs: true,
+ icon: true,
+ iconBg: true,
+ showOnLocation: true,
+ categories: { select: { category: { select: { tag: true, icon: true, ns: true } } } },
+ _count: { select: { parents: true, children: true } },
},
},
active: true,
diff --git a/packages/api/router/service/query.forServiceEditDrawer.handler.ts b/packages/api/router/service/query.forServiceEditDrawer.handler.ts
index 6921fbe8fb..0a6ec8ba08 100644
--- a/packages/api/router/service/query.forServiceEditDrawer.handler.ts
+++ b/packages/api/router/service/query.forServiceEditDrawer.handler.ts
@@ -1,7 +1,6 @@
import { prisma } from '@weareinreach/db'
import { formatAttributes } from '~api/formatters/attributes'
import { formatHours } from '~api/formatters/hours'
-import { globalSelect } from '~api/selects/global'
import { type TRPCHandlerParams } from '~api/types/handler'
import { type TForServiceEditDrawerSchema } from './query.forServiceEditDrawer.schema'
@@ -20,7 +19,9 @@ export const forServiceEditDrawer = async ({ input }: TRPCHandlerParams) => {
const result = await prisma.orgService.findUniqueOrThrow({
@@ -36,22 +35,8 @@ export const forServiceModal = async ({ input }: TRPCHandlerParams
Date: Mon, 25 Mar 2024 14:58:30 -0400
Subject: [PATCH 33/61] extract processing to separate fns
---
.../data-portal/ServiceEditDrawer/index.tsx | 91 +++++++++++--------
1 file changed, 53 insertions(+), 38 deletions(-)
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index 8fa506bf92..158cee8d10 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -11,11 +11,11 @@ import {
Title,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
-import compact from 'just-compact'
import { useTranslation } from 'next-i18next'
-import { forwardRef, type ReactNode, useEffect, useMemo } from 'react'
+import { forwardRef, type ReactNode } from 'react'
import { useForm } from 'react-hook-form'
import { Textarea, TextInput } from 'react-hook-form-mantine'
+import invariant from 'tiny-invariant'
import { Badge } from '~ui/components/core/Badge'
import { Breadcrumb } from '~ui/components/core/Breadcrumb'
@@ -25,6 +25,7 @@ import { ServiceSelect } from '~ui/components/data-portal/ServiceSelect'
import { useCustomVariant } from '~ui/hooks'
import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
+import { processAccessInstructions, processAttributes } from '~ui/modals/Service/processor'
import { DataViewer } from '~ui/other/DataViewer'
import { FormSchema, type TFormSchema } from './schemas'
@@ -36,12 +37,11 @@ const isObject = (x: unknown): x is object => typeof x === 'object'
const _ServiceEditDrawer = forwardRef(
({ serviceId, ...props }, ref) => {
const [drawerOpened, drawerHandler] = useDisclosure(true)
- const [serviceModalOpened, serviceModalHandler] = useDisclosure(false)
const { classes } = useStyles()
const variants = useCustomVariant()
const { t } = useTranslation(['common', 'gov-dist'])
// #region Get existing data/populate form
- const { data, isLoading } = api.service.forServiceEditDrawer.useQuery(serviceId, {
+ const { data } = api.service.forServiceEditDrawer.useQuery(serviceId, {
refetchOnWindowFocus: false,
})
const form = useForm({
@@ -76,48 +76,53 @@ const _ServiceEditDrawer = forwardRef
const countryIdRegex = /^ctry_.*/
const distIdRegex = /^gdst_.*/
- if (countries?.length) {
- for (const country of countries) {
- const array = serviceAreaObj[country]
- const countryDetails = geoMap.get(country)
- if (!countryDetails) continue
- const item = (
-
+ const processCountry = (country: string) => {
+ serviceAreaObj[country] ??= []
+ const array = serviceAreaObj[country]
+ invariant(array)
+ const countryDetails = geoMap.get(country)
+ if (!countryDetails) return
+ const item = (
+
+
+ All of {t(countryDetails.tsKey, { ns: countryDetails.tsNs })}
+
+
+ )
+ array.push(item)
+ }
+ const processDistrict = (district: string) => {
+ const govDist = geoMap.get(district)
+ const country = govDist?.parent?.parent?.id ?? govDist?.parent?.id ?? ''
+ if (!countryIdRegex.test(country) || !govDist) return
+ serviceAreaObj[country] ??= []
+ const array = serviceAreaObj[country]
+ invariant(array)
+ const parent = govDist.parent?.id ?? ''
+ const parentDist = geoMap.get(parent)
+ const item =
+ !distIdRegex.test(parent) || !parentDist ? (
+
+ {t(govDist.tsKey, { ns: govDist.tsNs })}
+
+ ) : (
+
- All of {t(countryDetails.tsKey, { ns: countryDetails.tsNs })}
+ {t(parentDist.tsKey, { ns: parentDist.tsNs })} - {t(govDist.tsKey, { ns: govDist.tsNs })}
)
- Array.isArray(array) ? array.push(item) : (serviceAreaObj[country] = [item])
+ array.push(item)
+ }
+
+ if (countries?.length) {
+ for (const country of countries) {
+ processCountry(country)
}
}
if (districts?.length) {
for (const district of districts) {
- const govDist = geoMap.get(district)
- if (!govDist) continue
- const country = govDist.parent?.parent?.id ?? govDist.parent?.id ?? ''
- if (!countryIdRegex.test(country)) continue
- const array = serviceAreaObj[country]
- const parent = govDist.parent?.id ?? ''
- const parentDist = geoMap.get(parent)
- if (!distIdRegex.test(parent) || !parentDist) {
- const item = (
-
- {t(govDist.tsKey, { ns: govDist.tsNs })}
-
- )
- Array.isArray(array) ? array.push(item) : (serviceAreaObj[country] = [item])
- continue
- }
- const item = (
-
-
- {t(parentDist.tsKey, { ns: parentDist.tsNs })} - {t(govDist.tsKey, { ns: govDist.tsNs })}
-
-
- )
- Array.isArray(array) ? array.push(item) : (serviceAreaObj[country] = [item])
- continue
+ processDistrict(district)
}
}
return Object.entries(serviceAreaObj)?.map(([key, value]) => {
@@ -136,6 +141,16 @@ const _ServiceEditDrawer = forwardRef
// #endregion
+ if (!data) return null
+
+ // const { getHelp, publicTransit } = data
+ // ? processAccessInstructions({
+ // accessDetails: data?.accessDetails,
+ // locations: data?.locations,
+ // t,
+ // })
+ // : { getHelp: null, publicTransit: null }
+
return (
<>
From 1e99747dbeaf5fc12cb8607a949dd805690b44fc Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 27 Mar 2024 15:21:47 -0400
Subject: [PATCH 34/61] unify service attrib select criteria, update api & mock
data
---
packages/api/formatters/attributes.ts | 104 +++++++++++++---
.../query.forServiceEditDrawer.handler.ts | 4 +-
.../service/query.forServiceModal.handler.ts | 117 ++++++++----------
.../json/service.forServiceEditDrawer.json | 2 +-
.../json/service.forServiceInfoCard.json | 2 +-
.../json/service.forServiceModal.json | 2 +-
6 files changed, 139 insertions(+), 92 deletions(-)
diff --git a/packages/api/formatters/attributes.ts b/packages/api/formatters/attributes.ts
index 30adf304fe..fd27db61d7 100644
--- a/packages/api/formatters/attributes.ts
+++ b/packages/api/formatters/attributes.ts
@@ -1,3 +1,5 @@
+import { type Simplify } from 'type-fest'
+
import { type Prisma } from '@weareinreach/db'
export const formatAttributes = {
@@ -27,10 +29,24 @@ export const formatAttributes = {
},
active: true,
countryId: true,
+ country: {
+ select: {
+ cca2: true,
+ id: true,
+ name: true,
+ },
+ },
data: true,
govDistId: true,
+ govDist: { select: { tsKey: true, tsNs: true, abbrev: true, id: true } },
id: true,
languageId: true,
+ language: {
+ select: {
+ languageName: true,
+ nativeName: true,
+ },
+ },
text: { select: { tsKey: { select: { key: true, text: true, ns: true } } } },
boolean: true,
},
@@ -84,54 +100,102 @@ export const formatAttributes = {
}
type ReturnedData = {
+ boolean: boolean | null
attribute: {
id: string
+ _count: {
+ children: number
+ parents: number
+ }
tag: string
- // tsKey: string
- // tsNs: string
+ tsKey: string
+ tsNs: string
categories: {
category: {
tag: string
ns: string
+ icon: string | null
}
}[]
- // icon: string | null
- // iconBg: string | null
+ icon: string | null
+ iconBg: string | null
+ showOnLocation: boolean | null
}
- boolean: boolean | null
- id: string
+ country: {
+ id: string
+ name: string
+ cca2: string
+ } | null
+ govDist: {
+ id: string
+ tsKey: string
+ tsNs: string
+ abbrev: string | null
+ } | null
+ language: {
+ languageName: string
+ nativeName: string
+ } | null
data: Prisma.JsonValue
+ id: string
active: boolean
text: {
tsKey: {
key: string
- text: string
ns: string
+ text: string
}
} | null
- countryId: string | null
govDistId: string | null
+ countryId: string | null
languageId: string | null
}[]
type DataOutput = {
- text: {
- key: string
- text: string
- ns: string
- } | null
+ // ids
+ attributeId: string
+ supplementId: string
+ // attribute
+ category: string
+ _count: {
+ children: number
+ parents: number
+ }
+ tag: string
+ tsKey: string
+ tsNs: string
+ icon: string | null
+ iconBg: string | null
+ showOnLocation: boolean | null
+ // supplement
boolean: boolean | null
+ country: {
+ id: string
+ name: string
+ cca2: string
+ } | null
+ govDist: {
+ id: string
+ tsKey: string
+ tsNs: string
+ abbrev: string | null
+ } | null
+ language: {
+ languageName: string
+ nativeName: string
+ } | null
data: Prisma.JsonValue
active: boolean
- countryId: string | null
govDistId: string | null
+ countryId: string | null
languageId: string | null
- category: string
- tag: string
- attributeId: string
- supplementId: string
+ text: {
+ key: string
+ ns: string
+ text: string
+ } | null
}
type ReturnSegmented = {
- attributes: DataOutput[]
- accessDetails: DataOutput[]
+ attributes: Simplify[]
+ accessDetails: Simplify[]
}
diff --git a/packages/api/router/service/query.forServiceEditDrawer.handler.ts b/packages/api/router/service/query.forServiceEditDrawer.handler.ts
index 0a6ec8ba08..a5b6effdfc 100644
--- a/packages/api/router/service/query.forServiceEditDrawer.handler.ts
+++ b/packages/api/router/service/query.forServiceEditDrawer.handler.ts
@@ -39,7 +39,7 @@ export const forServiceEditDrawer = async ({ input }: TRPCHandlerParams phone.id),
emails: emails.map(({ email }) => email.id),
- locations: locations.map(({ orgLocationId }) => orgLocationId),
+ // locations: locations.map(({ orgLocationId }) => orgLocationId),
services: services.map(({ tag }) => tag.id),
hours: formatHours.process(hours),
serviceAreas: serviceAreas
diff --git a/packages/api/router/service/query.forServiceModal.handler.ts b/packages/api/router/service/query.forServiceModal.handler.ts
index 70c181f902..8b46dbab5f 100644
--- a/packages/api/router/service/query.forServiceModal.handler.ts
+++ b/packages/api/router/service/query.forServiceModal.handler.ts
@@ -1,4 +1,5 @@
import { prisma } from '@weareinreach/db'
+import { formatAttributes } from '~api/formatters/attributes'
import { globalWhere } from '~api/selects/global'
import { type TRPCHandlerParams } from '~api/types/handler'
@@ -25,56 +26,52 @@ export const forServiceModal = async ({ input }: TRPCHandlerParams
- attribute.categories.every(({ category }) => category.tag !== 'service-access-instructions')
- )
- .map(({ attribute, ...supplement }) => ({
- attribute,
- supplement,
- })),
- accessDetails: result.attributes
- .filter(({ attribute }) =>
- attribute.categories.some(({ category }) => category.tag === 'service-access-instructions')
- )
- .map(({ attribute, ...supplement }) => ({
- attribute,
- supplement,
- })),
+ attributes,
+ accessDetails,
}
return transformed
}
diff --git a/packages/ui/mockData/json/service.forServiceEditDrawer.json b/packages/ui/mockData/json/service.forServiceEditDrawer.json
index bb71586888..23067c3218 100644
--- a/packages/ui/mockData/json/service.forServiceEditDrawer.json
+++ b/packages/ui/mockData/json/service.forServiceEditDrawer.json
@@ -1 +1 @@
-{"id":"osvc_01GVH3VEVPF1KEKBTRVTV70WGV","published":true,"deleted":false,"name":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.name","text":"Get rapid HIV testing","ns":"org-data","crowdinId":773224},"description":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.description","text":"Whitman-Walker provides walk-in HIV testing at multiple locations in DC. Walk-in HIV testing includes a confidential, rapid HIV test and risk-reduction counseling. The counseling provides clients with education on their options for having safer sex. Whitman-Walker uses the INSTI® HIV-1/HIV-2 Rapid Antibody Test and results take one minute.","ns":"org-data","crowdinId":773222},"phones":["ophn_01GVH3VEVC36PW0Z9GDV0ZERV1","ophn_01GVH3VEVCFKT3NWQ79STYVDKR"],"emails":[],"locations":["oloc_01GVH3VEVBRCFA2AHNTWCXQA2B","oloc_01GVH3VEVBSA85T6VR2C38BJPT"],"services":["svtg_01GW2HHFBRPBXSYN12DWNEAJJ7"],"hours":{},"serviceAreas":{"id":"svar_01GW2HT9F1JKT1MCAJ3P7XBDHP","countries":[],"districts":["gdst_01GW2HJ5A278S2G84AB3N9FCW0"]},"attributes":[{"attributeId":"attr_01GW2HHFVA06WHRSM241ZF0FY0","supplementId":"atts_01E4ENGMG266R5BH78D7B2MB7M","tag":"hiv-aids","category":"community","active":true,"countryId":null,"data":null,"govDistId":null,"languageId":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","supplementId":"atts_01E4ENGMG2XWR5JQ1JMBN2SQVM","tag":"cost-free","category":"cost","active":true,"countryId":null,"data":null,"govDistId":null,"languageId":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","supplementId":"atts_01E4ENGMG2J94M4S9DQTE57GWN","tag":"has-confidentiality-policy","category":"additional-information","active":true,"countryId":null,"data":null,"govDistId":null,"languageId":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","supplementId":"atts_01E4ENGMG20KXGB20JYGZ4X938","tag":"time-walk-in","category":"additional-information","active":true,"countryId":null,"data":null,"govDistId":null,"languageId":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","supplementId":"atts_01GW2HT9F13VVJCJ8W2WE86R6N","tag":"incompatible-info","category":"system","active":false,"countryId":null,"data":{"json":[{"community-lgbt":"true"},{"lang-all-languages-by-interpreter":"Language access services are available, including ASL interpreting."}]},"govDistId":null,"languageId":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","supplementId":"atts_01GW2HT9F15B2HJK144B3NZHQK","tag":"lang-offered","category":"languages","active":true,"countryId":null,"data":null,"govDistId":null,"languageId":"lang_0000000000N3K70GZXE29Z03A4","boolean":null,"text":null}],"accessDetails":[{"attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","supplementId":"atts_01GW2HT9F01W2M7FBSKSXAQ9R4","tag":"accesslink","category":"service-access-instructions","active":true,"countryId":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4234"},"access_type":"link","access_value":"https://www.whitman-walker.org/hiv-sti-testing","instructions":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","access_value_ES":"https://www.whitman-walker.org/hiv-sti-testing","instructions_ES":"Visita el sitio web para obtener más información sobre los horarios y lugares de prueba de Whitman-Walker."}},"govDistId":null,"languageId":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F01W2M7FBSKSXAQ9R4","text":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","supplementId":"atts_01GW2HT9F09GFRWM3JK2A43AWG","tag":"accessphone","category":"service-access-instructions","active":true,"countryId":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4235"},"access_type":"phone","access_value":"202-745-7000","instructions":"Contact the Main Office about services offered in multiple languages upon request.","access_value_ES":"202-745-7000","instructions_ES":"Comunícate con la oficina principal sobre los servicios que se ofrecen en varios idiomas si lo solicitas."}},"govDistId":null,"languageId":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F09GFRWM3JK2A43AWG","text":"Contact the Main Office about services offered in multiple languages upon request.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0SPS3EBCQ710RCNTA","tag":"accesslocation","category":"service-access-instructions","active":true,"countryId":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4231"},"access_type":"location","access_value":"2301 M. Luther King Jr., Washington DC 20020","instructions":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","access_value_ES":"2301 M. Luther King Jr., Washington DC 20020","instructions_ES":"Centro Max Robinson:NO hay pruebas disponibles sin cita previa. Lunes:08:30-12:30, 13:30-17:30; Martes:08:30 - 12:30, 13:30 - 17:30; Miércoles:08:30 - 12:30, 13:30 - 17:30; Jueves:08:30 - 12:30, 13:30 - 17:30; Viernes:08:30 - 12:30, 14:15 - 17:30."}},"govDistId":null,"languageId":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0SPS3EBCQ710RCNTA","text":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0638MD74PJ3SCWNXC","tag":"accesslocation","category":"service-access-instructions","active":true,"countryId":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4233"},"access_type":"location","access_value":"1525 14th St, NW Washington, DC 20005","instructions":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","access_value_ES":"1525 14th St, NW Washington, DC 20005","instructions_ES":"Whitman-Walker en 1525:NO hay pruebas disponibles. Lunes-Jueves:08:30-12:30 y 13:30-17:30; Viernes:08:30- 12:30 y 14:30 -17:30."}},"govDistId":null,"languageId":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0638MD74PJ3SCWNXC","text":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","ns":"org-data"}}]}
+{"id":"osvc_01GVH3VEVPF1KEKBTRVTV70WGV","published":true,"deleted":false,"locations":[{"orgLocationId":"oloc_01GVH3VEVBRCFA2AHNTWCXQA2B","location":{"country":{"cca2":"US"}}},{"orgLocationId":"oloc_01GVH3VEVBSA85T6VR2C38BJPT","location":{"country":{"cca2":"US"}}}],"name":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.name","text":"Get rapid HIV testing","ns":"org-data","crowdinId":773224},"description":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.description","text":"Whitman-Walker provides walk-in HIV testing at multiple locations in DC. Walk-in HIV testing includes a confidential, rapid HIV test and risk-reduction counseling. The counseling provides clients with education on their options for having safer sex. Whitman-Walker uses the INSTI® HIV-1/HIV-2 Rapid Antibody Test and results take one minute.","ns":"org-data","crowdinId":773222},"phones":["ophn_01GVH3VEVC36PW0Z9GDV0ZERV1","ophn_01GVH3VEVCFKT3NWQ79STYVDKR"],"emails":[],"services":["svtg_01GW2HHFBRPBXSYN12DWNEAJJ7"],"hours":{},"serviceAreas":{"id":"svar_01GW2HT9F1JKT1MCAJ3P7XBDHP","countries":[],"districts":["gdst_01GW2HJ5A278S2G84AB3N9FCW0"]},"attributes":[{"attributeId":"attr_01GW2HHFVA06WHRSM241ZF0FY0","supplementId":"atts_01E4ENGMG266R5BH78D7B2MB7M","tag":"hiv-aids","tsKey":"community.hiv-aids","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"community","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","supplementId":"atts_01E4ENGMG2XWR5JQ1JMBN2SQVM","tag":"cost-free","tsKey":"cost.cost-free","tsNs":"attribute","icon":"carbon:piggy-bank","iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"cost","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","supplementId":"atts_01E4ENGMG2J94M4S9DQTE57GWN","tag":"has-confidentiality-policy","tsKey":"additional.has-confidentiality-policy","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","supplementId":"atts_01E4ENGMG20KXGB20JYGZ4X938","tag":"time-walk-in","tsKey":"additional.time-walk-in","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","supplementId":"atts_01GW2HT9F13VVJCJ8W2WE86R6N","tag":"incompatible-info","tsKey":"sys.incompatible-info","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"system","active":false,"countryId":null,"country":null,"data":{"json":[{"community-lgbt":"true"},{"lang-all-languages-by-interpreter":"Language access services are available, including ASL interpreting."}]},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","supplementId":"atts_01GW2HT9F15B2HJK144B3NZHQK","tag":"lang-offered","tsKey":"lang.lang-offered","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"languages","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":"lang_0000000000N3K70GZXE29Z03A4","language":{"languageName":"English","nativeName":"English"},"boolean":null,"text":null}],"accessDetails":[{"attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","supplementId":"atts_01GW2HT9F01W2M7FBSKSXAQ9R4","tag":"accesslink","tsKey":"serviceaccess.accesslink","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4234"},"access_type":"link","access_value":"https://www.whitman-walker.org/hiv-sti-testing","instructions":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","access_value_ES":"https://www.whitman-walker.org/hiv-sti-testing","instructions_ES":"Visita el sitio web para obtener más información sobre los horarios y lugares de prueba de Whitman-Walker."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F01W2M7FBSKSXAQ9R4","text":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","supplementId":"atts_01GW2HT9F09GFRWM3JK2A43AWG","tag":"accessphone","tsKey":"serviceaccess.accessphone","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4235"},"access_type":"phone","access_value":"202-745-7000","instructions":"Contact the Main Office about services offered in multiple languages upon request.","access_value_ES":"202-745-7000","instructions_ES":"Comunícate con la oficina principal sobre los servicios que se ofrecen en varios idiomas si lo solicitas."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F09GFRWM3JK2A43AWG","text":"Contact the Main Office about services offered in multiple languages upon request.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0SPS3EBCQ710RCNTA","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4231"},"access_type":"location","access_value":"2301 M. Luther King Jr., Washington DC 20020","instructions":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","access_value_ES":"2301 M. Luther King Jr., Washington DC 20020","instructions_ES":"Centro Max Robinson:NO hay pruebas disponibles sin cita previa. Lunes:08:30-12:30, 13:30-17:30; Martes:08:30 - 12:30, 13:30 - 17:30; Miércoles:08:30 - 12:30, 13:30 - 17:30; Jueves:08:30 - 12:30, 13:30 - 17:30; Viernes:08:30 - 12:30, 14:15 - 17:30."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0SPS3EBCQ710RCNTA","text":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0638MD74PJ3SCWNXC","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4233"},"access_type":"location","access_value":"1525 14th St, NW Washington, DC 20005","instructions":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","access_value_ES":"1525 14th St, NW Washington, DC 20005","instructions_ES":"Whitman-Walker en 1525:NO hay pruebas disponibles. Lunes-Jueves:08:30-12:30 y 13:30-17:30; Viernes:08:30- 12:30 y 14:30 -17:30."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0638MD74PJ3SCWNXC","text":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","ns":"org-data"}}]}
\ No newline at end of file
diff --git a/packages/ui/mockData/json/service.forServiceInfoCard.json b/packages/ui/mockData/json/service.forServiceInfoCard.json
index f999911369..546dbec06c 100644
--- a/packages/ui/mockData/json/service.forServiceInfoCard.json
+++ b/packages/ui/mockData/json/service.forServiceInfoCard.json
@@ -1 +1 @@
-[{"id":"osvc_01GVH3VEWK33YAKZMQ2W3GT4QK","serviceName":{"tsKey":{"text":"Access PEP and PrEP"},"tsNs":"org-data","defaultText":"Access PEP and PrEP"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEW3CZ8P9VS6A5MA0R7Z","serviceName":{"tsKey":{"text":"Receive behavioral health services"},"tsNs":"org-data","defaultText":"Receive behavioral health services"},"serviceCategories":["mental-health.CATEGORYNAME"],"offersRemote":true},{"id":"osvc_01GVH3VEWFZ5FHZ6S7BXQY1W55","serviceName":{"tsKey":{"text":"Get the COVID-19 vaccine"},"tsNs":"org-data","defaultText":"Get the COVID-19 vaccine"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEWD5ZQY1JZM16Y5M9NG","serviceName":{"tsKey":{"text":"Get legal help for transgender people to replace and update name/gender marker on immigration documents"},"tsNs":"org-data","defaultText":"Get legal help for transgender people to replace and update name/gender marker on immigration documents"},"serviceCategories":["legal.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEVY24KAYTWY2ZSFZNBX","serviceName":{"tsKey":{"text":"Get free individual and group psychotherapy for LGBTQ young people (ages 13-24)"},"tsNs":"org-data","defaultText":"Get free individual and group psychotherapy for LGBTQ young people (ages 13-24)"},"serviceCategories":["community-support.CATEGORYNAME","mental-health.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEVVHBRF1FFXZGMMYG7D","serviceName":{"tsKey":{"text":"Access youth and family support services"},"tsNs":"org-data","defaultText":"Access youth and family support services"},"serviceCategories":["community-support.CATEGORYNAME","medical.CATEGORYNAME","mental-health.CATEGORYNAME"],"offersRemote":true},{"id":"osvc_01GVH3VEWHDC6F5FCQHB0H5GD6","serviceName":{"tsKey":{"text":"Get gender affirming hormone therapy"},"tsNs":"org-data","defaultText":"Get gender affirming hormone therapy"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEVR4SRPFQD2SJF1MCJJ","serviceName":{"tsKey":{"text":"Receive gender affirming care and services"},"tsNs":"org-data","defaultText":"Receive gender affirming care and services"},"serviceCategories":["legal.CATEGORYNAME","medical.CATEGORYNAME","mental-health.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEW2ND36DB0XWAH1PQY0","serviceName":{"tsKey":{"text":"Get dental health services for HIV-positive individuals"},"tsNs":"org-data","defaultText":"Get dental health services for HIV-positive individuals"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEVSNF9NH79R7HC9FHY6","serviceName":{"tsKey":{"text":"Get HIV care for newly diagnosed patients"},"tsNs":"org-data","defaultText":"Get HIV care for newly diagnosed patients"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEWM65579T29F19QXP8E","serviceName":{"tsKey":{"text":"Get help with navigating health insurance options"},"tsNs":"org-data","defaultText":"Get help with navigating health insurance options"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":true},{"id":"osvc_01GVH3VEVZY7K2TYY1ZE7WXRRC","serviceName":{"tsKey":{"text":"Get legal help with immigration services"},"tsNs":"org-data","defaultText":"Get legal help with immigration services"},"serviceCategories":["legal.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VEVPF1KEKBTRVTV70WGV","serviceName":{"tsKey":{"text":"Get rapid HIV testing"},"tsNs":"org-data","defaultText":"Get rapid HIV testing"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false}]
\ No newline at end of file
+[{"id":"osvc_01GVH3VDMNH6PJFW50BVWN0N9R","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMNH6PJFW50BVWN0N9R.name","tsNs":"org-data","defaultText":"Get emergency shelter for youth ages 18-24"},"serviceCategories":["housing.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDMSN34BACQDMY6S5GPM","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMSN34BACQDMY6S5GPM.name","tsNs":"org-data","defaultText":"Get education and employment services for youth ages 24 and under"},"serviceCategories":["education-and-employment.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDMZYAPMQWQ5F3YWM8FW","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMZYAPMQWQ5F3YWM8FW.name","tsNs":"org-data","defaultText":"Get housing and support services for youth ages 18-24 with HIV"},"serviceCategories":["housing.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDN19JS30RV26PH04ZA8","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDN19JS30RV26PH04ZA8.name","tsNs":"org-data","defaultText":"Get supportive housing for LGBTQ youth ages 18-24"},"serviceCategories":["housing.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDN4M572FCVMDZTCNYT0","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDN4M572FCVMDZTCNYT0.name","tsNs":"org-data","defaultText":"Get homeless support services at a drop-in center for ages 24 and under"},"serviceCategories":["housing.CATEGORYNAME","hygiene-and-clothing.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDN73VP7ZAFMPC67HSWN","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDN73VP7ZAFMPC67HSWN.name","tsNs":"org-data","defaultText":"Get free medical care for youth ages 25 and under"},"serviceCategories":["medical.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDN9470A0E49NNYP9JX6","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDN9470A0E49NNYP9JX6.name","tsNs":"org-data","defaultText":"Get emergency shelter for children ages 17 and younger"},"serviceCategories":["housing.CATEGORYNAME"],"offersRemote":false},{"id":"osvc_01GVH3VDNCMFMKMGSA8EGA8NPB","serviceName":{"tsKey":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDNCMFMKMGSA8EGA8NPB.name","tsNs":"org-data","defaultText":"Call a crisis help line for youth"},"serviceCategories":["housing.CATEGORYNAME","mental-health.CATEGORYNAME"],"offersRemote":true}]
\ No newline at end of file
diff --git a/packages/ui/mockData/json/service.forServiceModal.json b/packages/ui/mockData/json/service.forServiceModal.json
index 66d55b3b28..3c8c715ecd 100644
--- a/packages/ui/mockData/json/service.forServiceModal.json
+++ b/packages/ui/mockData/json/service.forServiceModal.json
@@ -1 +1 @@
-{"id":"osvc_01GVH3VDMSN34BACQDMY6S5GPM","services":[{"tag":{"tsKey":"education-and-employment.career-counseling"}}],"serviceName":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMSN34BACQDMY6S5GPM.name","ns":"org-data","tsKey":{"text":"Get education and employment services for youth ages 24 and under"}},"locations":[{"location":{"country":{"cca2":"US"}}}],"attributes":[{"attribute":{"id":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","tsKey":"cost.cost-free","tsNs":"attribute","icon":"carbon:piggy-bank","iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"cost","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01E4ENGJDYSQVYQQG5K7ZHPRGS","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFVE9NE0NMDPK4X8WBNB","tsKey":"community.teens","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"community","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01E4ENGJDYXPSMZJYMYTSRQSBG","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFVAKWSPFVAN9CYQE982","tsKey":"community.homeless","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"community","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01E4ENGJDYGA7WJPW6TE0XECN3","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFVCKH2AQ2E1CKA1A8HP","tsKey":"community.lgbtq-youth","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"community","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01E4ENGJDY1GX5QJYCSVJ98HKM","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFV3BADK80TG0DXXFPMM","tsKey":"additional.has-confidentiality-policy","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"additional-information","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01E4ENGJDY7F991XESQ0GGGZTR","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","tsKey":"eligibility.time-appointment-required","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"eligibility-requirements","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01E4ENGJDYZTZ70RZDSX8X24WX","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFVJ8K180CNX339BTXM2","tsKey":"lang.lang-offered","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"languages","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01GW2HT8C1N900BKNRTY39R58H","country":null,"language":{"languageName":"English","nativeName":"English"},"text":null,"govDist":null,"boolean":null,"data":null}},{"attribute":{"id":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","tsKey":"eligibility.elig-age","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"eligibility-requirements","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01GW2HT8C1J8AQAEHVGANCYRPB","country":null,"language":null,"text":null,"govDist":null,"boolean":null,"data":{"json":{"json":{"max":24}}}}}],"hours":[],"description":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMSN34BACQDMY6S5GPM.description","ns":"org-data","tsKey":{"text":"Larkin Street Academy Services offers job readiness, college readiness, computer classes, job placement and retention, internships, tutoring, GED tutoring and classes, secondary and post-secondary school enrollment and support, mindfulness, visual and performing arts. Offices are open Monday through Thursday, 9:00 AM - 16:00 PM, appointments only."}},"accessDetails":[{"attribute":{"id":"attr_01GW2HHFVMH6AE94EXN7T5A87C","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"service-access-instructions","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01GW2HT8BWQ0WZ804A34QV7P0J","country":null,"language":null,"text":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.attribute.atts_01GW2HT8BWQ0WZ804A34QV7P0J","ns":"org-data","tsKey":{"text":"The above are drop-in service hours for education. Drop-in hours for employment services are Monday, Tuesday:10 a.m. to noon, and 2:30 to 4:30 p.m. Wednesday:10 a.m. to noon, and 1 to 2 p.m. Thursday:10 a.m. to noon, and 1 to 3 p.m. Friday:10 a.m. to 1 p.m."}},"govDist":null,"boolean":null,"data":{"json":{"_id":{"$oid":"5e7e4bd9d54f1760921a3aff"},"access_type":"location","access_value":"134 Golden Gate Ave, San Francisco, CA 94102","instructions":"The above are drop-in service hours for education. Drop-in hours for employment services are Monday, Tuesday:10 a.m. to noon, and 2:30 to 4:30 p.m. Wednesday:10 a.m. to noon, and 1 to 2 p.m. Thursday:10 a.m. to noon, and 1 to 3 p.m. Friday:10 a.m. to 1 p.m.","access_value_ES":"134 Golden Gate Ave, San Francisco, CA 94102","instructions_ES":"Visita para más información."}}}},{"attribute":{"id":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","tsKey":"serviceaccess.accessphone","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"categories":[{"category":{"tag":"service-access-instructions","icon":null}}],"_count":{"parents":0,"children":0}},"supplement":{"id":"atts_01GW2HT8BWZG5BTQ57DAQHJZ5Z","country":null,"language":null,"text":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.attribute.atts_01GW2HT8BWZG5BTQ57DAQHJZ5Z","ns":"org-data","tsKey":{"text":"Call for more information."}},"govDist":null,"boolean":null,"data":{"json":{"_id":{"$oid":"5e7e4bd9d54f1760921a3b00"},"access_type":"phone","access_value":"415-673-0911","instructions":"Call for more information.","access_value_ES":"415-673-0911","instructions_ES":"Llama para más información."}}}}]}
+{"id":"osvc_01GVH3VDMSN34BACQDMY6S5GPM","services":[{"tag":{"tsKey":"education-and-employment.career-counseling"}}],"serviceName":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMSN34BACQDMY6S5GPM.name","ns":"org-data","tsKey":{"text":"Get education and employment services for youth ages 24 and under"}},"locations":[{"location":{"country":{"cca2":"US"}}}],"attributes":[{"attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","supplementId":"atts_01GW2HT8C1J8AQAEHVGANCYRPB","tag":"elig-age","tsKey":"eligibility.elig-age","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"eligibility-requirements","active":true,"countryId":null,"country":null,"data":{"json":{"max":24}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","supplementId":"atts_01E4ENGJDYSQVYQQG5K7ZHPRGS","tag":"cost-free","tsKey":"cost.cost-free","tsNs":"attribute","icon":"carbon:piggy-bank","iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"cost","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVE9NE0NMDPK4X8WBNB","supplementId":"atts_01E4ENGJDYXPSMZJYMYTSRQSBG","tag":"teens","tsKey":"community.teens","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"community","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVAKWSPFVAN9CYQE982","supplementId":"atts_01E4ENGJDYGA7WJPW6TE0XECN3","tag":"homeless","tsKey":"community.homeless","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"community","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVCKH2AQ2E1CKA1A8HP","supplementId":"atts_01E4ENGJDY1GX5QJYCSVJ98HKM","tag":"lgbtq-youth","tsKey":"community.lgbtq-youth","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"community","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","supplementId":"atts_01E4ENGJDY7F991XESQ0GGGZTR","tag":"has-confidentiality-policy","tsKey":"additional.has-confidentiality-policy","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","supplementId":"atts_01E4ENGJDYZTZ70RZDSX8X24WX","tag":"time-appointment-required","tsKey":"eligibility.time-appointment-required","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"eligibility-requirements","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","supplementId":"atts_01GW2HT8C1N900BKNRTY39R58H","tag":"lang-offered","tsKey":"lang.lang-offered","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"languages","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":"lang_0000000000N3K70GZXE29Z03A4","language":{"languageName":"English","nativeName":"English"},"boolean":null,"text":null}],"hours":[],"description":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.osvc_01GVH3VDMSN34BACQDMY6S5GPM.description","ns":"org-data","tsKey":{"text":"Larkin Street Academy Services offers job readiness, college readiness, computer classes, job placement and retention, internships, tutoring, GED tutoring and classes, secondary and post-secondary school enrollment and support, mindfulness, visual and performing arts. Offices are open Monday through Thursday, 9:00 AM - 16:00 PM, appointments only."}},"accessDetails":[{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT8BWQ0WZ804A34QV7P0J","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bd9d54f1760921a3aff"},"access_type":"location","access_value":"134 Golden Gate Ave, San Francisco, CA 94102","instructions":"The above are drop-in service hours for education. Drop-in hours for employment services are Monday, Tuesday:10 a.m. to noon, and 2:30 to 4:30 p.m. Wednesday:10 a.m. to noon, and 1 to 2 p.m. Thursday:10 a.m. to noon, and 1 to 3 p.m. Friday:10 a.m. to 1 p.m.","access_value_ES":"134 Golden Gate Ave, San Francisco, CA 94102","instructions_ES":"Visita para más información."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.attribute.atts_01GW2HT8BWQ0WZ804A34QV7P0J","text":"The above are drop-in service hours for education. Drop-in hours for employment services are Monday, Tuesday:10 a.m. to noon, and 2:30 to 4:30 p.m. Wednesday:10 a.m. to noon, and 1 to 2 p.m. Thursday:10 a.m. to noon, and 1 to 3 p.m. Friday:10 a.m. to 1 p.m.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","supplementId":"atts_01GW2HT8BWZG5BTQ57DAQHJZ5Z","tag":"accessphone","tsKey":"serviceaccess.accessphone","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bd9d54f1760921a3b00"},"access_type":"phone","access_value":"415-673-0911","instructions":"Call for more information.","access_value_ES":"415-673-0911","instructions_ES":"Llama para más información."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V3RCCBMFD55PWHR8AEC0.attribute.atts_01GW2HT8BWZG5BTQ57DAQHJZ5Z","text":"Call for more information.","ns":"org-data"}}]}
\ No newline at end of file
From 03a07a0c5dc0d39fd43768ab7841fd3515a60bd9 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 27 Mar 2024 15:23:17 -0400
Subject: [PATCH 35/61] allow passed data, widen param type
---
packages/ui/components/data-display/Hours.tsx | 8 ++++++--
packages/ui/hooks/useFreeText.ts | 17 +++++++++++++----
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/packages/ui/components/data-display/Hours.tsx b/packages/ui/components/data-display/Hours.tsx
index 914dc8db25..4ce5434dea 100644
--- a/packages/ui/components/data-display/Hours.tsx
+++ b/packages/ui/components/data-display/Hours.tsx
@@ -2,6 +2,7 @@ import { createStyles, List, rem, Skeleton, Stack, Table, Text, Title } from '@m
import { Interval } from 'luxon'
import { useTranslation } from 'next-i18next'
+import { type ApiOutput } from '@weareinreach/api'
import { HoursDrawer } from '~ui/components/data-portal/HoursDrawer'
import { useCustomVariant } from '~ui/hooks/useCustomVariant'
import { useLocalizedDays } from '~ui/hooks/useLocalizedDays'
@@ -39,11 +40,13 @@ const nullObj = {
6: [],
}
-export const Hours = ({ parentId, label = 'regular', edit }: HoursProps) => {
+export const Hours = ({ parentId, label = 'regular', edit, data: passedData }: HoursProps) => {
const { t, i18n } = useTranslation('common')
const variants = useCustomVariant()
const { classes } = useStyles()
- const { data, isLoading } = api.orgHours.forHoursDisplay.useQuery(parentId)
+ const { data, isLoading } = passedData
+ ? { data: passedData, isLoading: false }
+ : api.orgHours.forHoursDisplay.useQuery(parentId)
const dayMap = useLocalizedDays(i18n.resolvedLanguage)
if (!data && !isLoading) return null
@@ -106,4 +109,5 @@ export interface HoursProps {
parentId: string
label?: keyof typeof labelKeys
edit?: boolean
+ data?: ApiOutput['orgHours']['forHoursDisplay']
}
diff --git a/packages/ui/hooks/useFreeText.ts b/packages/ui/hooks/useFreeText.ts
index 4b1f846893..b40277143a 100644
--- a/packages/ui/hooks/useFreeText.ts
+++ b/packages/ui/hooks/useFreeText.ts
@@ -3,8 +3,16 @@ import { useTranslation } from 'next-i18next'
import { type DB } from '@weareinreach/api/prisma/types'
+const isNestedFreeText = (item: unknown): item is NestedFreeText => {
+ if (!item || typeof item !== 'object') return false
+ if ('tsKey' in item) return true
+ return false
+}
+
export const getFreeText: GetFreeText = (freeTextRecord, tOptions) => {
- const { key: dbKey, tsKey } = freeTextRecord
+ const { key: dbKey, tsKey } = isNestedFreeText(freeTextRecord)
+ ? freeTextRecord
+ : { key: freeTextRecord.key, tsKey: { text: freeTextRecord.text } }
const deconstructedKey = dbKey.split('.')
const ns = deconstructedKey[0]
if (!deconstructedKey.length || !ns) throw new Error('Invalid key')
@@ -20,13 +28,14 @@ export const useFreeText: UseFreeText = (freeTextRecord, tOptions) => {
return t(key, options)
}
-export interface UseFreeTextProps extends Pick, Partial> {
+export interface NestedFreeText extends Pick, Partial> {
tsKey: Pick & Partial>
}
+export type TranslationKeyRecord = Pick
export type GetFreeText = (
- freeTextRecord: UseFreeTextProps,
+ freeTextRecord: NestedFreeText | TranslationKeyRecord,
tOptions?: TOptions
) => { key: string; options: TOptions }
-export type UseFreeText = (freeTextRecord: UseFreeTextProps, tOptions?: TOptions) => string
+export type UseFreeText = (freeTextRecord: NestedFreeText, tOptions?: TOptions) => string
From 3f274e6c70460cc0e5f52821cbfa23891e2b2036 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 27 Mar 2024 15:24:26 -0400
Subject: [PATCH 36/61] display data
---
.../data-display/ContactInfo/index.tsx | 3 +-
.../ServiceEditDrawer/index.stories.tsx | 2 +
.../data-portal/ServiceEditDrawer/index.tsx | 77 +++++++++++++++----
packages/ui/modals/Service/index.stories.tsx | 1 -
packages/ui/modals/Service/processor.tsx | 34 +++++---
5 files changed, 90 insertions(+), 27 deletions(-)
diff --git a/packages/ui/components/data-display/ContactInfo/index.tsx b/packages/ui/components/data-display/ContactInfo/index.tsx
index d5d22b9c09..0c809072bc 100644
--- a/packages/ui/components/data-display/ContactInfo/index.tsx
+++ b/packages/ui/components/data-display/ContactInfo/index.tsx
@@ -47,7 +47,8 @@ export const ContactInfo = ({
return {items}
}
-export const hasContactInfo = (data: PassedDataObject) => {
+export const hasContactInfo = (data: PassedDataObject | null | undefined): data is PassedDataObject => {
+ if (!data) return false
const { websites, phones, emails, socialMedia } = data
return Boolean(websites.length || phones.length || emails.length || socialMedia.length)
}
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
index 5bbc2c1fd1..159611f216 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
@@ -4,6 +4,7 @@ import { Button } from '~ui/components/core/Button'
import { component } from '~ui/mockData/component'
import { fieldOpt } from '~ui/mockData/fieldOpt'
import { organization } from '~ui/mockData/organization'
+import { orgHours } from '~ui/mockData/orgHours'
import { service } from '~ui/mockData/service'
import { ServiceEditDrawer } from './index'
@@ -31,6 +32,7 @@ export default {
fieldOpt.govDistsByCountry,
fieldOpt.countryGovDistMap,
component.ServiceSelect,
+ orgHours.forHoursDisplay,
],
},
args: {
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index 158cee8d10..08179869b9 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -21,6 +21,8 @@ import { Badge } from '~ui/components/core/Badge'
import { Breadcrumb } from '~ui/components/core/Breadcrumb'
import { Button } from '~ui/components/core/Button'
import { Section } from '~ui/components/core/Section'
+import { ContactInfo, hasContactInfo } from '~ui/components/data-display/ContactInfo'
+import { Hours } from '~ui/components/data-display/Hours'
import { ServiceSelect } from '~ui/components/data-portal/ServiceSelect'
import { useCustomVariant } from '~ui/hooks'
import { Icon } from '~ui/icon'
@@ -39,7 +41,7 @@ const _ServiceEditDrawer = forwardRef
const [drawerOpened, drawerHandler] = useDisclosure(true)
const { classes } = useStyles()
const variants = useCustomVariant()
- const { t } = useTranslation(['common', 'gov-dist'])
+ const { t, i18n } = useTranslation(['common', 'gov-dist'])
// #region Get existing data/populate form
const { data } = api.service.forServiceEditDrawer.useQuery(serviceId, {
refetchOnWindowFocus: false,
@@ -143,13 +145,19 @@ const _ServiceEditDrawer = forwardRef
if (!data) return null
- // const { getHelp, publicTransit } = data
- // ? processAccessInstructions({
- // accessDetails: data?.accessDetails,
- // locations: data?.locations,
- // t,
- // })
- // : { getHelp: null, publicTransit: null }
+ const { getHelp, publicTransit } = data
+ ? processAccessInstructions({
+ accessDetails: data?.accessDetails,
+ locations: data?.locations,
+ t,
+ })
+ : { getHelp: null, publicTransit: null }
+
+ const attributes = processAttributes({
+ attributes: data.attributes,
+ locale: i18n.resolvedLanguage ?? 'en',
+ t,
+ })
return (
<>
@@ -206,14 +214,55 @@ const _ServiceEditDrawer = forwardRef
{serviceAreas()}
{/* {Boolean(geoMap?.size) && } */}
- {t('service.get-help')}
+
+ {hasContactInfo(getHelp) && (
+
+ )}
+ {Boolean(data.hours) && }
+
- {t('service.clients-served')}
+
+ {attributes.clientsServed.srvfocus}
+
+
+ {attributes.clientsServed.targetPop}
+
+
+ {attributes.cost}
+
+ {attributes.eligibility.age}
+
+
+ {attributes.eligibility.requirements.map((text, i) => (
+ {text}
+ ))}
+
+
+
+ {attributes.eligibility.freeText}
+
+
+
+
+
+ {attributes.lang.map((lang, i) => (
+ {lang}
+ ))}
+
+
+
+
+
+ {attributes.miscWithIcons}
+
+
+
+ {attributes.misc.map((text, i) => (
+ {text}
+ ))}
+
+
- {t('service.cost')}
- {t('service.eligibility')}
- {t('service.languages')}
- {t('service.extra-info')}
diff --git a/packages/ui/modals/Service/index.stories.tsx b/packages/ui/modals/Service/index.stories.tsx
index df7df9b1b9..e2919b20f1 100644
--- a/packages/ui/modals/Service/index.stories.tsx
+++ b/packages/ui/modals/Service/index.stories.tsx
@@ -1,7 +1,6 @@
import { type Meta } from '@storybook/react'
import { Button } from '~ui/components/core/Button'
-import { getTRPCMock } from '~ui/lib/getTrpcMock'
import { organization } from '~ui/mockData/organization'
import { savedList } from '~ui/mockData/savedList'
import { service } from '~ui/mockData/service'
diff --git a/packages/ui/modals/Service/processor.tsx b/packages/ui/modals/Service/processor.tsx
index 8ba9d50879..c140ccbcd7 100644
--- a/packages/ui/modals/Service/processor.tsx
+++ b/packages/ui/modals/Service/processor.tsx
@@ -13,13 +13,25 @@ import { isValidIcon } from '~ui/icon'
import { ModalText } from './ModalText'
+type AccessDetailsAPI =
+ | ApiOutput['service']['forServiceModal']['accessDetails']
+ | ApiOutput['service']['forServiceEditDrawer']['accessDetails']
+
+type LocationsAPI =
+ | ApiOutput['service']['forServiceModal']['locations']
+ | ApiOutput['service']['forServiceEditDrawer']['locations']
+
+type AttributesAPI =
+ | ApiOutput['service']['forServiceModal']['attributes']
+ | ApiOutput['service']['forServiceEditDrawer']['attributes']
+
export const processAccessInstructions = ({
accessDetails,
locations,
t,
}: {
- accessDetails: ApiOutput['service']['forServiceModal']['accessDetails']
- locations: ApiOutput['service']['forServiceModal']['locations']
+ accessDetails: AccessDetailsAPI
+ locations: LocationsAPI
t: TFunction
}): AccessInstructionsOutput => {
const output: AccessInstructionsOutput = {
@@ -32,8 +44,8 @@ export const processAccessInstructions = ({
publicTransit: null,
}
- for (const { supplement } of accessDetails) {
- const { data, text, id } = supplement
+ for (const item of accessDetails) {
+ const { data, text, supplementId: id } = item
const parsed = accessInstructions.getAll().safeParse(data)
if (parsed.success) {
const { access_type, access_value } = parsed.data
@@ -100,7 +112,7 @@ export const processAttributes = ({
locale = 'en',
t,
}: {
- attributes: ApiOutput['service']['forServiceModal']['attributes']
+ attributes: AttributesAPI
locale: string
t: TFunction
}): AttributesOutput => {
@@ -118,8 +130,8 @@ export const processAttributes = ({
misc: [],
miscWithIcons: [],
}
- for (const { attribute, supplement } of attributes) {
- const { tsKey, icon, tsNs, id } = attribute
+ for (const attribute of attributes) {
+ const { tsKey, icon, tsNs, supplementId: id } = attribute
const namespace = tsKey.split('.').shift() as string
switch (namespace) {
@@ -139,7 +151,7 @@ export const processAttributes = ({
const type = tsKey.split('.').pop() as string
switch (type) {
case 'elig-age': {
- const { data, id } = supplement
+ const { data } = attribute
const parsed = attributeSupplementSchema.numMinMaxOrRange.safeParse(data)
if (!parsed.success) break
const { min, max } = parsed.data
@@ -150,7 +162,7 @@ export const processAttributes = ({
break
}
case 'other-describe': {
- const { text, id } = supplement
+ const { text } = attribute
if (!text) break
const { key, options } = getFreeText(text)
output.clientsServed.targetPop.push({t(key, options)})
@@ -168,7 +180,7 @@ export const processAttributes = ({
description: ReactNode[]
} = { description: [] }
- const { text, data, id } = supplement
+ const { text, data } = attribute
if (text) {
const { key, options } = getFreeText(text)
costDetails.description.push({t(key, options)})
@@ -199,7 +211,7 @@ export const processAttributes = ({
}
case 'lang': {
- const { language } = supplement
+ const { language } = attribute
if (!language) break
const { languageName } = language
output.lang.push(languageName)
From f2d8b1ac2d7e61a574cd639f7557ca5f65c112e7 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 28 Mar 2024 15:49:04 -0400
Subject: [PATCH 37/61] implement Add Attribute modal
---
.../ServiceEditDrawer/index.stories.tsx | 5 +-
.../data-portal/ServiceEditDrawer/index.tsx | 89 ++++---------------
2 files changed, 21 insertions(+), 73 deletions(-)
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
index 159611f216..96531c3276 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
@@ -2,7 +2,7 @@ import { type Meta, type StoryObj } from '@storybook/react'
import { Button } from '~ui/components/core/Button'
import { component } from '~ui/mockData/component'
-import { fieldOpt } from '~ui/mockData/fieldOpt'
+import { allFieldOptHandlers } from '~ui/mockData/fieldOpt'
import { organization } from '~ui/mockData/organization'
import { orgHours } from '~ui/mockData/orgHours'
import { service } from '~ui/mockData/service'
@@ -29,10 +29,9 @@ export default {
service.getNames,
service.forServiceEditDrawer,
service.getOptions,
- fieldOpt.govDistsByCountry,
- fieldOpt.countryGovDistMap,
component.ServiceSelect,
orgHours.forHoursDisplay,
+ ...allFieldOptHandlers,
],
},
args: {
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index 08179869b9..77fa24c0d4 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -27,6 +27,7 @@ import { ServiceSelect } from '~ui/components/data-portal/ServiceSelect'
import { useCustomVariant } from '~ui/hooks'
import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
+import { AttributeModal } from '~ui/modals/dataPortal/Attributes'
import { processAccessInstructions, processAttributes } from '~ui/modals/Service/processor'
import { DataViewer } from '~ui/other/DataViewer'
@@ -167,9 +168,20 @@ const _ServiceEditDrawer = forwardRef
- }>
- Save
-
+
+ }
+ parentRecord={{ serviceId: data.id }}
+ attachesTo={['SERVICE']}
+ >
+ Add Attribute
+
+ }>
+ Save
+
+
@@ -218,7 +230,10 @@ const _ServiceEditDrawer = forwardRef
{hasContactInfo(getHelp) && (
)}
- {Boolean(data.hours) && }
+ {publicTransit}
+ {Boolean(Object.values(data.hours).length) && (
+
+ )}
@@ -284,69 +299,3 @@ export const ServiceEditDrawer = createPolymorphicComponent<'button', ServiceEdi
interface ServiceEditDrawerProps extends ButtonProps {
serviceId: string
}
-
-interface FreeText {
- id?: string
- key: string
- ns: string
- tsKey: {
- text: string | null
- crowdinId: number | null
- }
-}
-interface Attribute {
- attribute: {
- categories?: string[]
- id: string
- tsKey: string
- tsNs: string
- icon?: string | null
- }
- supplement: {
- id: string
- active?: boolean
- data: unknown
- boolean?: boolean | null
- countryId?: string | null
- govDistId?: string | null
- languageId?: string | null
- text: FreeText | null
- }
-}
-interface FormData {
- id: string
- published: boolean
- deleted: boolean
- serviceName: FreeText | null
- description: FreeText | null
- hours: {
- id: string
- dayIndex: number
- start: Date
- end: Date
- closed: boolean
- tz: string | null
- }[]
- phones: string[]
- emails: string[]
- locations: string[]
- services: {
- id: string
- primaryCategoryId: string
- }[]
- serviceAreas: {
- id: string
- countries: string[]
- districts: string[]
- } | null
- attributes: Attribute[]
- accessDetails: {
- id?: string
- attribute: { id: string; tsKey: string; tsNs: string }
- supplement: {
- id: string
- data: unknown
- text: { id?: string; key: string; ns: string; tsKey: { text: string; crowdinId: number | null } } | null
- }
- }[]
-}
From 925dc17f57cc4021f4dd5dad86529ee6f9e095e4 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 28 Mar 2024 17:00:13 -0400
Subject: [PATCH 38/61] fix useRouter import
---
packages/ui/components/core/UserAvatar.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/ui/components/core/UserAvatar.tsx b/packages/ui/components/core/UserAvatar.tsx
index 887d5cbd30..ca81041617 100644
--- a/packages/ui/components/core/UserAvatar.tsx
+++ b/packages/ui/components/core/UserAvatar.tsx
@@ -1,6 +1,6 @@
import { Avatar, createStyles, Group, rem, Skeleton, Stack, Text, useMantineTheme } from '@mantine/core'
import { DateTime } from 'luxon'
-import router from 'next/router'
+import { useRouter } from 'next/router'
import { type User } from 'next-auth'
import { useSession } from 'next-auth/react'
import { useTranslation } from 'next-i18next'
@@ -35,6 +35,7 @@ export const UserAvatar = ({
const { t, i18n } = useTranslation()
const { data: session, status } = useSession()
const theme = useMantineTheme()
+ const router = useRouter()
const subText = () => {
if (!user && useLoggedIn && subheading !== undefined) {
From a3ff5464f8f21bfb048e9153152fab3bf6e0d099 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Thu, 28 Mar 2024 17:00:45 -0400
Subject: [PATCH 39/61] launch service drawer
---
packages/ui/components/sections/ServicesInfo.tsx | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/packages/ui/components/sections/ServicesInfo.tsx b/packages/ui/components/sections/ServicesInfo.tsx
index 3672d45f77..3b00be0023 100644
--- a/packages/ui/components/sections/ServicesInfo.tsx
+++ b/packages/ui/components/sections/ServicesInfo.tsx
@@ -5,6 +5,7 @@ import { useTranslation } from 'next-i18next'
import { transformer } from '@weareinreach/util/transformer'
import { Link } from '~ui/components/core'
import { Badge } from '~ui/components/core/Badge'
+import { ServiceEditDrawer } from '~ui/components/data-portal/ServiceEditDrawer'
import { useCustomVariant } from '~ui/hooks/useCustomVariant'
import { useEditMode } from '~ui/hooks/useEditMode'
import { useScreenSize } from '~ui/hooks/useScreenSize'
@@ -73,21 +74,11 @@ const ServiceSection = ({ category, services, hideRemoteBadges }: ServiceSection
)
return isEditMode ? (
-
+
{children}
-
+
) : (
Date: Thu, 28 Mar 2024 17:01:11 -0400
Subject: [PATCH 40/61] remove data viewer
---
apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx | 2 +-
.../ui/components/data-portal/ServiceEditDrawer/index.tsx | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
index 0f8a061972..729979693c 100644
--- a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
+++ b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
@@ -131,7 +131,7 @@ const OrgLocationPage: NextPage
+ onClick: async () =>
router.push({
pathname: '/org/[slug]/edit',
query: { slug: data.organization.slug },
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index 77fa24c0d4..4118e5a544 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -29,7 +29,6 @@ import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
import { AttributeModal } from '~ui/modals/dataPortal/Attributes'
import { processAccessInstructions, processAttributes } from '~ui/modals/Service/processor'
-import { DataViewer } from '~ui/other/DataViewer'
import { FormSchema, type TFormSchema } from './schemas'
import { useStyles } from './styles'
@@ -39,7 +38,7 @@ const isObject = (x: unknown): x is object => typeof x === 'object'
const _ServiceEditDrawer = forwardRef(
({ serviceId, ...props }, ref) => {
- const [drawerOpened, drawerHandler] = useDisclosure(true)
+ const [drawerOpened, drawerHandler] = useDisclosure(false)
const { classes } = useStyles()
const variants = useCustomVariant()
const { t, i18n } = useTranslation(['common', 'gov-dist'])
@@ -283,7 +282,6 @@ const _ServiceEditDrawer = forwardRef
-
>
From a480ccabdc69a9b249dc28120db0ae6a50e290a2 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 11:00:45 -0400
Subject: [PATCH 41/61] temp disable gtag addition
---
apps/app/src/pages/_app.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/apps/app/src/pages/_app.tsx b/apps/app/src/pages/_app.tsx
index b66767f379..896d34973b 100644
--- a/apps/app/src/pages/_app.tsx
+++ b/apps/app/src/pages/_app.tsx
@@ -9,7 +9,7 @@ import { type AppProps, type NextWebVitalsMetric } from 'next/app'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import { useRouter } from 'next/router'
-import Script from 'next/script'
+// import Script from 'next/script'
import { type Session } from 'next-auth'
import { appWithTranslation } from 'next-i18next'
import { DefaultSeo, type DefaultSeoProps } from 'next-seo'
@@ -78,11 +78,13 @@ const MyApp = (appProps: AppPropsWithGridSwitch) => {
-
+ */}
From d0e570fc6b8559a7670b49038c227573569cbc89 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 11:02:24 -0400
Subject: [PATCH 42/61] `slug` isn't being passed to page props??
---
apps/app/src/pages/org/[slug]/index.tsx | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/apps/app/src/pages/org/[slug]/index.tsx b/apps/app/src/pages/org/[slug]/index.tsx
index 29897c1f7b..d8f6b247fb 100644
--- a/apps/app/src/pages/org/[slug]/index.tsx
+++ b/apps/app/src/pages/org/[slug]/index.tsx
@@ -10,7 +10,6 @@ import { useEffect, useRef, useState } from 'react'
import { trpcServerClient } from '@weareinreach/api/trpc'
import { AlertMessage } from '@weareinreach/ui/components/core/AlertMessage'
-// import { GoogleMap } from '@weareinreach/ui/components/core/GoogleMap'
import { Toolbar } from '@weareinreach/ui/components/core/Toolbar'
import { ContactSection } from '@weareinreach/ui/components/sections/ContactSection'
import { ListingBasicInfo } from '@weareinreach/ui/components/sections/ListingBasicInfo'
@@ -40,11 +39,12 @@ const useStyles = createStyles((theme) => ({
}))
const OrganizationPage = ({
- slug,
+ slug: passedSlug,
organizationId: orgId,
}: InferGetStaticPropsType) => {
const router = useRouter<'/org/[slug]'>()
- const { data, status } = api.organization.forOrgPage.useQuery({ slug }, { enabled: !!slug })
+ const slug = passedSlug ?? router.query.slug
+ const { data, status } = api.organization.forOrgPage.useQuery({ slug })
// const { query } = router
const { t } = useTranslation(formatNS(orgId))
const [activeTab, setActiveTab] = useState('services')
@@ -155,7 +155,7 @@ const OrganizationPage = ({
<>
{isTablet && }
- {width && (
+ {Boolean(width) && (
id)}
width={width}
@@ -244,7 +244,6 @@ export const getStaticProps = async ({
}: GetStaticPropsContext>) => {
if (!params) return { notFound: true }
const { slug } = params
-
const ssg = await trpcServerClient({ session: null })
try {
const redirect = await ssg.organization.slugRedirect.fetch(slug)
From b86ba236333fc539d362976479413ac3dd39390c Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 11:03:36 -0400
Subject: [PATCH 43/61] temp remove service multi select popover
---
.../src/pages/org/[slug]/[orgLocationId]/edit/index.tsx | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
index 729979693c..8264d58078 100644
--- a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
+++ b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
@@ -131,11 +131,12 @@ const OrgLocationPage: NextPage
+ onClick: async () => {
router.push({
pathname: '/org/[slug]/edit',
query: { slug: data.organization.slug },
- }),
+ })
+ },
}}
organizationId={data.organization.id}
saved={Boolean(isSaved)}
@@ -195,13 +196,13 @@ const OrgLocationPage: NextPage
{'Associate service(s) to this location'}
-
+ />*/}
{'Associated services'}
From 7c1137bc883557b5dd8a32994106a7f75bb5a19e Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 13:16:40 -0400
Subject: [PATCH 44/61] revert paths
---
.../{edit/index.tsx => edit.tsx} | 0
.../[orgLocationId]/edit/[orgServiceId].tsx | 298 ------------------
apps/app/src/types/nextjs-routes.d.ts | 1 -
3 files changed, 299 deletions(-)
rename apps/app/src/pages/org/[slug]/[orgLocationId]/{edit/index.tsx => edit.tsx} (100%)
delete mode 100644 apps/app/src/pages/org/[slug]/[orgLocationId]/edit/[orgServiceId].tsx
diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
similarity index 100%
rename from apps/app/src/pages/org/[slug]/[orgLocationId]/edit/index.tsx
rename to apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/[orgServiceId].tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/[orgServiceId].tsx
deleted file mode 100644
index ae142e0d69..0000000000
--- a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit/[orgServiceId].tsx
+++ /dev/null
@@ -1,298 +0,0 @@
-import { Grid, Stack } from '@mantine/core'
-import dynamic from 'next/dynamic'
-import { useRouter } from 'next/router'
-import { useTranslation } from 'next-i18next'
-import { type GetServerSideProps } from 'nextjs-routes'
-import { type ReactNode, Suspense, useEffect, useState } from 'react'
-import { /*type Path,*/ useFieldArray, useForm } from 'react-hook-form'
-import { Textarea, TextInput } from 'react-hook-form-mantine'
-// import { type Merge } from 'type-fest'
-import { z } from 'zod'
-
-import { prefixedId } from '@weareinreach/api/schemas/idPrefix'
-import { trpcServerClient } from '@weareinreach/api/trpc'
-import { checkServerPermissions } from '@weareinreach/auth'
-import { generateId } from '@weareinreach/db/lib/idGen'
-import { Badge } from '@weareinreach/ui/components/core/Badge'
-import { Section } from '@weareinreach/ui/components/core/Section'
-import { InlineTextInput } from '@weareinreach/ui/components/data-portal/InlineTextInput'
-import { ServiceSelect } from '@weareinreach/ui/components/data-portal/ServiceSelect'
-import { api } from '~app/utils/api'
-import { getServerSideTranslations } from '~app/utils/i18n'
-import { Button } from '~ui/components/core/Button'
-
-const DevTool = dynamic(() => import('@hookform/devtools').then((mod) => mod.DevTool), { ssr: false })
-
-const FreetextObject = z
- .object({
- text: z.string().nullable(),
- key: z.string().nullish(),
- ns: z.string().nullish(),
- })
- .nullish()
-
-// type MapValue = A extends Map ? V : never
-
-const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
-type Literal = z.infer
-type Json = Literal | { [key: string]: Json } | Json[]
-const JsonSchema: z.ZodType = z.lazy(() =>
- z.union([literalSchema, z.array(JsonSchema), z.record(JsonSchema)])
-)
-
-const FormSchema = z.object({
- name: FreetextObject,
- description: FreetextObject,
- services: prefixedId('serviceTag').array(),
- attributes: z
- .object({
- text: z
- .object({
- key: z.string(),
- text: z.string(),
- ns: z.string(),
- })
- .nullable(),
- boolean: z.boolean().nullable(),
- data: z.any(),
- active: z.boolean(),
- countryId: z.string().nullable(),
- govDistId: z.string().nullable(),
- languageId: z.string().nullable(),
- category: z.string(),
- attributeId: z.string(),
- supplementId: z.string(),
- })
- .array(),
- published: z.boolean(),
- deleted: z.boolean(),
-})
-const isObject = (x: unknown): x is object => typeof x === 'object'
-
-type FormSchemaType = z.infer
-const EditServicePage = () => {
- const { t } = useTranslation()
- const router = useRouter<'/org/[slug]/[orgLocationId]/edit/[orgServiceId]'>()
- const { data: attributeMap } = api.attribute.map.useQuery()
- const { data } = api.page.serviceEdit.useQuery({ id: router.query.orgServiceId ?? '' })
- const { data: allServices } = api.service.getOptions.useQuery()
- const form = useForm({
- values: data ? { ...data, services: data.services.map(({ id }) => id) } : undefined,
- })
- const attribFields = useFieldArray({ control: form.control, name: 'attributes', keyName: '_rhfId' })
-
- console.log(`🚀 ~ EditServicePage ~ attribFields:`, attribFields.fields)
-
- const dirtyFields = {
- name: isObject(form.formState.dirtyFields.name) ? form.formState.dirtyFields.name.text : false,
- description: isObject(form.formState.dirtyFields.description)
- ? form.formState.dirtyFields.description.text
- : false,
- services: form.formState.dirtyFields.services ?? false,
- }
- const dataAttributes = form.watch('attributes') ?? []
- const activeServices = form.watch('services') ?? []
-
- type AttrSectionKeys = 'clientsServed' | 'cost' | 'eligibility' | 'languages' | 'additionalInfo'
- // type AttrSectionVals = Merge<
- // FormSchemaType['attributes'][number],
- // { _rhfName: Path; _rhfLabel: string }
- // >
-
- const attributeBase: {
- [key in AttrSectionKeys]: ReactNode[]
- } = {
- clientsServed: [],
- cost: [],
- eligibility: [],
- languages: [],
- additionalInfo: [],
- }
- const [attributes, setAttributes] = useState(attributeBase)
-
- console.log(`🚀 ~ EditServicePage ~ attributes:`, attributes)
-
- useEffect(() => {
- if (!attributeMap) return
- const attrToSet = attributeBase
-
- for (const [i, item] of dataAttributes.entries()) {
- const attribDef = attributeMap.byId.get(item.attributeId)
-
- console.log(`🚀 ~ useEffect ~ attribDef:`, attribDef)
-
- if (!attribDef) continue
-
- const attribNs = attribDef.tsKey.split('.').length
- ? (attribDef.tsKey.split('.').shift() as string)
- : attribDef.tsKey
- console.log(`🚀 ~ useEffect ~ attribNs:`, attribNs)
-
- switch (attribNs) {
- case 'tpop': {
- // attrToSet.clientsServed.push({
- // ...item,
- // _rhfName: `attributes.${i}.text.text`,
- // _rhfLabel: 'Target Population',
- // })
- attrToSet.clientsServed.push(
- }
- name={`attributes.${i}.text.text`}
- control={form.control}
- label='Target Population'
- data-isDirty={form.getFieldState(`attributes.${i}.text.text`).isDirty}
- autosize
- />
- )
- break
- }
- case 'cost': {
- if (attribDef.tag === 'cost-free')
- // attrToSet.cost.push({
- // ...item,
- // _rhfName: `attributes.${i}`,
- // _rhfLabel: 'Cost',
- // })
- break
- }
- }
- }
- setAttributes(attrToSet)
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [dataAttributes, attributeMap])
-
- return (
- <>
-
-
-
-
- }
- name='name.text'
- control={form.control}
- fontSize='h2'
- data-isDirty={dirtyFields.name}
- />
-
-
- }
- name='description.text'
- control={form.control}
- data-isDirty={dirtyFields.description}
- autosize
- />
-
-
-
-
-
- {activeServices.map((serviceId) => {
- const service = allServices?.find((s) => s.id === serviceId)
- if (!service) return null
- return (
- {t(service.tsKey, { ns: service.tsNs })}
- )
- })}
-
-
-
- {t('service.get-help')}
-
- {attributes.clientsServed.length ? (
- // attributes.clientsServed.map(({ _rhfName, _rhfLabel, ...item }) => (
- // }
- // name={_rhfName}
- // control={form.control}
- // label={_rhfLabel}
- // data-isDirty={form.getFieldState(_rhfName).isDirty}
- // autosize
- // />
- // ))
- attributes.clientsServed
- ) : (
-
- )}
-
- {t('service.cost')}
- {t('service.eligibility')}
- {t('service.languages')}
- {t('service.extra-info')}
-
-
- {/* @ts-expect-error Hush, devtool. */}
-
- >
- )
-}
-
-export default EditServicePage
-
-export const getServerSideProps: GetServerSideProps = async ({ locale, params, req, res }) => {
- const urlParams = z
- .object({
- slug: z.string(),
- orgLocationId: prefixedId('orgLocation'),
- orgServiceId: prefixedId('orgService'),
- })
- .safeParse(params)
- if (!urlParams.success) return { notFound: true }
- const { slug, orgLocationId: _, orgServiceId } = urlParams.data
- const session = await checkServerPermissions({
- ctx: { req, res },
- permissions: ['dataPortalBasic'],
- has: 'some',
- })
- if (!session) {
- return {
- redirect: {
- destination: '/',
- permanent: false,
- },
- }
- }
- const ssg = await trpcServerClient({ session })
- const { id: orgId } = await ssg.organization.getIdFromSlug.fetch({ slug })
- const [i18n] = await Promise.all([
- getServerSideTranslations(locale, ['common', 'services', 'attribute']),
- ssg.page.serviceEdit.prefetch({ id: orgServiceId }),
- ssg.component.ServiceSelect.prefetch(),
- ssg.service.getOptions.prefetch(),
- ])
- const props = {
- organizationId: orgId,
- session,
- trpcState: ssg.dehydrate(),
- ...i18n,
- }
-
- return {
- props,
- }
-}
diff --git a/apps/app/src/types/nextjs-routes.d.ts b/apps/app/src/types/nextjs-routes.d.ts
index 8ff841f0fb..96bc95ed54 100644
--- a/apps/app/src/types/nextjs-routes.d.ts
+++ b/apps/app/src/types/nextjs-routes.d.ts
@@ -30,7 +30,6 @@ declare module "nextjs-routes" {
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
| StaticRoute<"/api/trpc-playground">
| StaticRoute<"/">
- | DynamicRoute<"/org/[slug]/[orgLocationId]/edit/[orgServiceId]", { "slug": string; "orgLocationId": string; "orgServiceId": string }>
| DynamicRoute<"/org/[slug]/[orgLocationId]/edit", { "slug": string; "orgLocationId": string }>
| DynamicRoute<"/org/[slug]/[orgLocationId]", { "slug": string; "orgLocationId": string }>
| DynamicRoute<"/org/[slug]/edit", { "slug": string }>
From dddc731b098260e867a19840fd2adb185465e466 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 13:53:28 -0400
Subject: [PATCH 45/61] fix PageContent props
---
apps/app/src/pages/_app.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/app/src/pages/_app.tsx b/apps/app/src/pages/_app.tsx
index 896d34973b..db029c6449 100644
--- a/apps/app/src/pages/_app.tsx
+++ b/apps/app/src/pages/_app.tsx
@@ -52,7 +52,7 @@ export function reportWebVitals(stats: NextWebVitalsMetric) {
appEvent.webVitals(stats)
}
-const PageContent = ({ Component, ...pageProps }: AppPropsWithGridSwitch) => {
+const PageContent = ({ Component, pageProps }: AppPropsWithGridSwitch) => {
const router = useRouter()
const autoResetState = Component.autoResetState ? { key: router.asPath } : {}
return Component.omitGrid ? (
From 1a80bcb539b639e563ca073ecf645f080f683b98 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 14:01:12 -0400
Subject: [PATCH 46/61] fix group spacing
---
packages/ui/components/sections/ServicesInfo.tsx | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/ui/components/sections/ServicesInfo.tsx b/packages/ui/components/sections/ServicesInfo.tsx
index 3b00be0023..8df0044cf2 100644
--- a/packages/ui/components/sections/ServicesInfo.tsx
+++ b/packages/ui/components/sections/ServicesInfo.tsx
@@ -74,7 +74,12 @@ const ServiceSection = ({ category, services, hideRemoteBadges }: ServiceSection
)
return isEditMode ? (
-
+
{children}
From 4e8860c66accdd9723c80d14c11a3b4458e602a7 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 17:10:13 -0400
Subject: [PATCH 47/61] Add Coverage modal
---
packages/api/router/serviceArea/index.ts | 9 +
.../serviceArea/mutation.addToArea.handler.ts | 50 +++
.../serviceArea/mutation.addToArea.schema.ts | 26 ++
packages/api/router/serviceArea/schemas.ts | 1 +
packages/ui/mockData/serviceArea.ts | 5 +
packages/ui/modals/CoverageArea/hooks.ts | 7 +-
.../ui/modals/CoverageArea/index.stories.tsx | 1 +
packages/ui/modals/CoverageArea/index.tsx | 385 +++++++-----------
packages/ui/modals/CoverageArea/schema.ts | 27 --
9 files changed, 234 insertions(+), 277 deletions(-)
create mode 100644 packages/api/router/serviceArea/mutation.addToArea.handler.ts
create mode 100644 packages/api/router/serviceArea/mutation.addToArea.schema.ts
delete mode 100644 packages/ui/modals/CoverageArea/schema.ts
diff --git a/packages/api/router/serviceArea/index.ts b/packages/api/router/serviceArea/index.ts
index eeb1829503..c792ebf279 100644
--- a/packages/api/router/serviceArea/index.ts
+++ b/packages/api/router/serviceArea/index.ts
@@ -19,4 +19,13 @@ export const serviceAreaRouter = defineRouter({
const handler = await importHandler(namespaced('update'), () => import('./mutation.update.handler'))
return handler(opts)
}),
+ addToArea: permissionedProcedure('updateOrgService')
+ .input(schema.ZAddToAreaSchema)
+ .mutation(async (opts) => {
+ const handler = await importHandler(
+ namespaced('addToArea'),
+ () => import('./mutation.addToArea.handler')
+ )
+ return handler(opts)
+ }),
})
diff --git a/packages/api/router/serviceArea/mutation.addToArea.handler.ts b/packages/api/router/serviceArea/mutation.addToArea.handler.ts
new file mode 100644
index 0000000000..90fed8378e
--- /dev/null
+++ b/packages/api/router/serviceArea/mutation.addToArea.handler.ts
@@ -0,0 +1,50 @@
+import { TRPCError } from '@trpc/server'
+
+import { generateId, getAuditedClient } from '@weareinreach/db'
+import { handleError } from '~api/lib/errorHandler'
+import { type TRPCHandlerParams } from '~api/types/handler'
+
+import { type TAddToAreaSchema } from './mutation.addToArea.schema'
+
+export const addToArea = async ({ ctx, input }: TRPCHandlerParams) => {
+ try {
+ const prisma = getAuditedClient(ctx.actorId)
+
+ const { id: serviceAreaId } =
+ typeof input.serviceArea === 'string'
+ ? { id: input.serviceArea as string }
+ : await prisma.serviceArea.create({
+ data: {
+ id: generateId('serviceArea'),
+ ...input.serviceArea,
+ },
+ select: { id: true },
+ })
+ if (input.countryId) {
+ const result = await prisma.serviceAreaCountry.create({
+ data: {
+ serviceAreaId,
+ countryId: input.countryId,
+ },
+ })
+ if (result) {
+ return { result: 'added' }
+ }
+ } else if (input.govDistId) {
+ const result = await prisma.serviceAreaDist.create({
+ data: {
+ serviceAreaId,
+ govDistId: input.govDistId,
+ },
+ })
+ if (result) {
+ return { result: 'added' }
+ }
+ } else {
+ throw new TRPCError({ code: 'BAD_REQUEST' })
+ }
+ } catch (error) {
+ handleError(error)
+ }
+}
+export default addToArea
diff --git a/packages/api/router/serviceArea/mutation.addToArea.schema.ts b/packages/api/router/serviceArea/mutation.addToArea.schema.ts
new file mode 100644
index 0000000000..b789dc412a
--- /dev/null
+++ b/packages/api/router/serviceArea/mutation.addToArea.schema.ts
@@ -0,0 +1,26 @@
+import { type Simplify } from 'type-fest'
+import { z } from 'zod'
+
+import { prefixedId } from '~api/schemas/idPrefix'
+
+const organization = z.object({ organizationId: prefixedId('organization') })
+const orgLocation = z.object({ orgLocationId: prefixedId('orgLocation') })
+const orgService = z.object({ orgServiceId: prefixedId('orgService') })
+
+const serviceArea = z.union([prefixedId('serviceArea'), organization, orgLocation, orgService])
+
+export const ZAddToAreaSchema = z
+ .object({
+ serviceArea,
+ countryId: prefixedId('country').optional(),
+ govDistId: prefixedId('govDist').optional(),
+ })
+ .refine(
+ (data) =>
+ (typeof data.countryId === 'string' || typeof data.govDistId === 'string') &&
+ !(typeof data.countryId === 'string' && typeof data.govDistId === 'string'),
+ {
+ message: 'Only one of countryId or govDistId must be provided',
+ }
+ )
+export type TAddToAreaSchema = Simplify>
diff --git a/packages/api/router/serviceArea/schemas.ts b/packages/api/router/serviceArea/schemas.ts
index 917e0a58a5..76e97c2b96 100644
--- a/packages/api/router/serviceArea/schemas.ts
+++ b/packages/api/router/serviceArea/schemas.ts
@@ -1,4 +1,5 @@
// codegen:start {preset: barrel, include: ./*.schema.ts}
+export * from './mutation.addToArea.schema'
export * from './mutation.update.schema'
export * from './query.getServiceArea.schema'
// codegen:end
diff --git a/packages/ui/mockData/serviceArea.ts b/packages/ui/mockData/serviceArea.ts
index 1503c2c6e4..8cb64f459d 100644
--- a/packages/ui/mockData/serviceArea.ts
+++ b/packages/ui/mockData/serviceArea.ts
@@ -22,4 +22,9 @@ export const serviceArea = {
},
}),
}),
+ addToArea: getTRPCMock({
+ path: ['serviceArea', 'addToArea'],
+ type: 'mutation',
+ response: () => ({ result: 'added' }),
+ }),
} satisfies MockHandlerObject<'serviceArea'>
diff --git a/packages/ui/modals/CoverageArea/hooks.ts b/packages/ui/modals/CoverageArea/hooks.ts
index 8280909c3f..83663eda9a 100644
--- a/packages/ui/modals/CoverageArea/hooks.ts
+++ b/packages/ui/modals/CoverageArea/hooks.ts
@@ -1,7 +1,12 @@
import { useState } from 'react'
+import { type Simplify } from 'type-fest'
export const useServiceAreaSelections = () => {
- const [selected, setSelected] = useState({ country: null, govDist: null, subDist: null })
+ const [selected, setSelected] = useState>({
+ country: null,
+ govDist: null,
+ subDist: null,
+ })
const setVal = {
country: (value: string) => setSelected({ country: value, govDist: null, subDist: null }),
govDist: (value: string) => setSelected((prev) => ({ ...prev, govDist: value, subDist: null })),
diff --git a/packages/ui/modals/CoverageArea/index.stories.tsx b/packages/ui/modals/CoverageArea/index.stories.tsx
index baa3794681..8f24396705 100644
--- a/packages/ui/modals/CoverageArea/index.stories.tsx
+++ b/packages/ui/modals/CoverageArea/index.stories.tsx
@@ -23,6 +23,7 @@ export default {
fieldOpt.govDists,
serviceArea.getServiceArea,
serviceArea.update,
+ serviceArea.addToArea,
],
rqDevtools: true,
whyDidYouRender: { collapseGroups: true },
diff --git a/packages/ui/modals/CoverageArea/index.tsx b/packages/ui/modals/CoverageArea/index.tsx
index 6a0cacec76..94b95ebabb 100644
--- a/packages/ui/modals/CoverageArea/index.tsx
+++ b/packages/ui/modals/CoverageArea/index.tsx
@@ -1,30 +1,20 @@
-import { zodResolver } from '@hookform/resolvers/zod'
import {
- Badge,
Box,
Button,
type ButtonProps,
- CloseButton,
createPolymorphicComponent,
- Grid,
- Group,
Modal,
Select,
Stack,
- Text,
Title,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
-import { compareArrayVals } from 'crud-object-diff'
-import compact from 'just-compact'
import { type TFunction, useTranslation } from 'next-i18next'
-import { forwardRef } from 'react'
-import { useForm } from 'react-hook-form'
+import { forwardRef, useEffect } from 'react'
import { trpc as api } from '~ui/lib/trpcClient'
import { useServiceAreaSelections } from './hooks'
-import { ServiceAreaForm, type ZServiceAreaForm } from './schema'
import { useStyles } from './styles'
import { ModalTitle } from '../ModalTitle'
@@ -38,267 +28,164 @@ const reduceDistType = (data: { tsNs: string; tsKey: string }[] | undefined, t:
return [...valueSet].sort().join('/')
}
-const CoverageAreaModal = forwardRef(({ id, ...props }, ref) => {
- const { classes } = useStyles()
- const { t, i18n } = useTranslation(['common', 'gov-dist'])
- const countryTranslation = new Intl.DisplayNames(i18n.language, { type: 'region' })
- const [opened, { open, close }] = useDisclosure(true) //TODO: remove `true` when done with dev
+const CoverageAreaModal = forwardRef(
+ ({ serviceArea, onSuccessAction, ...props }, ref) => {
+ const { classes } = useStyles()
+ const { t, i18n } = useTranslation(['common', 'gov-dist'])
+ const countryTranslation = new Intl.DisplayNames(i18n.language, { type: 'region' })
+ const [modalOpened, modalHandler] = useDisclosure(false)
- const [selected, setVal] = useServiceAreaSelections()
+ const [selected, setVal] = useServiceAreaSelections()
- const { data: dataCountry } = api.fieldOpt.countries.useQuery(
- { activeForOrgs: true },
- {
- select: (data) =>
- data.map(({ id, cca2 }) => ({ value: id, label: countryTranslation.of(cca2), cca2 })) ?? [],
- placeholderData: [],
- }
- )
- const { data: dataDistrict } = api.fieldOpt.govDists.useQuery(
- { countryId: selected.country ?? '', parentsOnly: true },
- {
- enabled: selected.country !== null,
+ useEffect(() => {
+ if (modalOpened === true) {
+ setVal.blank()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [modalOpened])
+
+ const { data: dataCountry } = api.fieldOpt.countries.useQuery(
+ { activeForOrgs: true },
+ {
+ select: (data) =>
+ data.map(({ id, cca2 }) => ({ value: id, label: countryTranslation.of(cca2), cca2 })) ?? [],
+ placeholderData: [],
+ }
+ )
+ const { data: dataDistrict } = api.fieldOpt.govDists.useQuery(
+ { countryId: selected.country ?? '', parentsOnly: true },
+ {
+ enabled: selected.country !== null,
+ select: (data) =>
+ data?.map(({ id, tsKey, tsNs, ...rest }) => ({
+ value: id,
+ label: t(tsKey, { ns: tsNs }),
+ tsKey,
+ tsNs,
+ parent: null,
+ ...rest,
+ })) ?? [],
+ placeholderData: [],
+ }
+ )
+ const { data: dataSubDist } = api.fieldOpt.getSubDistricts.useQuery(selected.govDist ?? '', {
+ enabled: selected.govDist !== null,
select: (data) =>
data?.map(({ id, tsKey, tsNs, ...rest }) => ({
value: id,
label: t(tsKey, { ns: tsNs }),
tsKey,
tsNs,
- parent: null,
...rest,
})) ?? [],
placeholderData: [],
+ })
+
+ const placeHolders = {
+ first: t('select.base', { item: 'Country' }),
+ second: t('select.base', {
+ item: reduceDistType(
+ dataDistrict?.map(({ govDistType }) => govDistType),
+ t
+ ),
+ }),
+ third: t('select.base', {
+ item: reduceDistType(
+ dataSubDist?.map(({ govDistType }) => govDistType),
+ t
+ ),
+ }),
}
- )
- const { data: dataSubDist } = api.fieldOpt.getSubDistricts.useQuery(selected.govDist ?? '', {
- enabled: selected.govDist !== null,
- select: (data) =>
- data?.map(({ id, tsKey, tsNs, ...rest }) => ({
- value: id,
- label: t(tsKey, { ns: tsNs }),
- tsKey,
- tsNs,
- ...rest,
- })) ?? [],
- placeholderData: [],
- })
- const apiUtils = api.useUtils()
- const updateServiceArea = api.serviceArea.update.useMutation()
-
- const form = useForm({
- resolver: zodResolver(ServiceAreaForm),
- defaultValues: async () => {
- const data = await apiUtils.serviceArea.getServiceArea.fetch(id)
- const formatted = {
- id: data?.id ?? id,
- countries: data?.countries ?? [],
- districts: data?.districts ?? [],
- }
- return formatted
- },
- })
-
- const serviceAreaCountries = form.watch('countries')
- const serviceAreaDistricts = form.watch('districts')
-
- const placeHolders = {
- first: t('select.base', { item: 'Country' }),
- second: t('select.base', {
- item: reduceDistType(
- dataDistrict?.map(({ govDistType }) => govDistType),
- t
- ),
- }),
- third: t('select.base', {
- item: reduceDistType(
- dataSubDist?.map(({ govDistType }) => govDistType),
- t
- ),
- }),
- }
-
- const handleAdd = () => {
- switch (true) {
- case !!selected.subDist:
- case !!selected.govDist: {
- const itemId = selected.subDist ?? selected.govDist
- const valToAdd = selected.subDist
- ? dataSubDist?.find(({ value }) => value === itemId)
- : dataDistrict?.find(({ value }) => value === itemId)
- if (!valToAdd) return
- form.setValue(
- 'districts',
- [
- ...serviceAreaDistricts,
- {
- id: valToAdd.value,
- tsKey: valToAdd.tsKey,
- tsNs: valToAdd.tsNs,
- parent: valToAdd.parent,
- country: valToAdd.country,
- },
- ],
- {
- shouldValidate: true,
- }
- )
- setVal.blank()
- break
- }
- case !!selected.country: {
- const valToAdd = dataCountry?.find(({ value }) => value === selected.country)
- if (!valToAdd) return
- form.setValue('countries', [...serviceAreaCountries, { id: valToAdd?.value, cca2: valToAdd?.cca2 }], {
- shouldValidate: true,
+ const addServiceArea = api.serviceArea.addToArea.useMutation({
+ onSuccess: (data) => {
+ if (onSuccessAction instanceof Function) {
+ onSuccessAction()
+ }
+ if (data?.result) {
+ modalHandler.close()
+ }
+ },
+ })
+
+ const canAdd = !!selected.country
+ const handleAdd = () => {
+ if (selected.govDist || selected.subDist) {
+ const distToAdd = selected.subDist ?? selected.govDist
+ if (!distToAdd) {
+ throw new Error('Missing district')
+ }
+ addServiceArea.mutate({
+ serviceArea,
+ govDistId: distToAdd,
})
- setVal.blank()
- break
+ } else if (selected.country) {
+ addServiceArea.mutate({ serviceArea, countryId: selected.country })
}
}
- }
-
- const activeAreas = compact(
- [
- serviceAreaCountries?.map((country) => (
-
-
- {countryTranslation.of(country.cca2)}
-
- form.setValue(
- 'countries',
- serviceAreaCountries?.filter(({ id }) => id !== country.id)
- )
- }
- />
-
-
- )),
-
- // Display -> Country / District / Sub-District
- serviceAreaDistricts?.map((govDist) => {
- const { id, tsKey, tsNs, country, parent } = govDist
-
- const displayName = compact([
- country.cca2,
- parent ? t(parent.tsKey, { ns: parent.tsNs }) : null,
- t(tsKey, { ns: tsNs }),
- ]).join(' → ')
-
- return (
-
-
- {displayName}
-
- form.setValue(
- 'districts',
- serviceAreaDistricts?.filter(({ id }) => id !== govDist.id)
- )
- }
- />
-
-
- )
- }),
- ].flat()
- )
-
- const handleSave = () => {
- const initialData = {
- id: form.formState.defaultValues?.id,
- countries: compact(form.formState.defaultValues?.countries?.map((country) => country?.id) ?? []),
- districts: compact(form.formState.defaultValues?.districts?.map((district) => district?.id) ?? []),
- }
- const data = form.getValues()
- const currentData = {
- id: data.id,
- countries: data.countries.map((country) => country.id),
- districts: data.districts.map((district) => district.id),
- }
- const changes = {
- id: data.id,
- countries: compareArrayVals([initialData.countries, currentData.countries]),
- districts: compareArrayVals([initialData.districts, currentData.districts]),
- }
- updateServiceArea.mutate(changes)
- }
-
- return (
- <>
- }
- onClose={close}
- opened={opened}
- >
-
-
- {t('portal-module.service-area')}
- ({ ...theme.other.utilityFonts.utility4, color: 'black' })}>
- {`${t('organization')}: `}
-
-
-
- {activeAreas}
-
-
- theme.other.utilityFonts.utility1}>
- {t('add', {
- item: '$t(portal-module.service-area)',
- })}
-
-
-
-
+ return (
+ <>
+ }
+ onClose={modalHandler.close}
+ opened={modalOpened}
+ >
+
+
+
+ {t('add', {
+ item: '$t(portal-module.service-area)',
+ })}
+
+
+
+
+
+ {selected.country && !!dataDistrict?.length && (
- {selected.country && !!dataDistrict?.length && (
-
- )}
- {selected.govDist && !!dataSubDist?.length && (
-
- )}
-
-
-
-
-
-
+ )}
+ {selected.govDist && !!dataSubDist?.length && (
+
+ )}
+
+
+
-
-
-
-
- >
- )
-})
+
+
+ >
+ )
+ }
+)
CoverageAreaModal.displayName = 'coverageArea'
-export const CoverageArea = createPolymorphicComponent(CoverageAreaModal)
+export const CoverageArea = createPolymorphicComponent<'button', Props>(CoverageAreaModal)
interface Props extends ButtonProps {
- id: string
+ serviceArea: string | NewServiceArea
+ onSuccessAction?: () => void
}
+
+type NewServiceArea = { organizationId: string } | { orgLocationId: string } | { orgServiceId: string }
diff --git a/packages/ui/modals/CoverageArea/schema.ts b/packages/ui/modals/CoverageArea/schema.ts
deleted file mode 100644
index 292b2b9506..0000000000
--- a/packages/ui/modals/CoverageArea/schema.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { z } from 'zod'
-
-const Country = z.object({
- id: z.string(),
- cca2: z.string(),
-})
-
-const GovDist = z.object({
- id: z.string(),
- country: z.object({ cca2: z.string() }),
- tsKey: z.string(),
- tsNs: z.string(),
- parent: z
- .object({
- tsKey: z.string(),
- tsNs: z.string(),
- })
- .nullable(),
-})
-
-export const ServiceAreaForm = z.object({
- id: z.string(),
- countries: Country.array(),
- districts: GovDist.array(),
-})
-
-export type ZServiceAreaForm = z.infer
From ea43160214da3ecdab1a3f001a7d2fa55e0b7d05 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 18:34:23 -0400
Subject: [PATCH 48/61] remove placeholder
---
.../data-portal/PhoneNumberEntry/withHookForm.tsx | 7 +++++--
packages/ui/modals/CoverageArea/index.tsx | 1 -
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/ui/components/data-portal/PhoneNumberEntry/withHookForm.tsx b/packages/ui/components/data-portal/PhoneNumberEntry/withHookForm.tsx
index 274eff68f0..73c0074a7e 100644
--- a/packages/ui/components/data-portal/PhoneNumberEntry/withHookForm.tsx
+++ b/packages/ui/components/data-portal/PhoneNumberEntry/withHookForm.tsx
@@ -29,13 +29,16 @@ export const PhoneNumberEntry = ({
label = 'Phone Number',
required,
}: PhoneNumberEntryProps) => {
- const { data: countryList } = api.fieldOpt.countries.useQuery(
+ const { data: countryData } = api.fieldOpt.countries.useQuery(
{ activeForOrgs: true },
{
- initialData: [],
select: (data) => transformCountryList(data),
}
)
+ const countryList = useMemo(() => {
+ if (!countryData) return []
+ return countryData
+ }, [countryData])
const validCountries = countryList.map(({ data }) => data.cca2)
const {
diff --git a/packages/ui/modals/CoverageArea/index.tsx b/packages/ui/modals/CoverageArea/index.tsx
index 94b95ebabb..16f4861785 100644
--- a/packages/ui/modals/CoverageArea/index.tsx
+++ b/packages/ui/modals/CoverageArea/index.tsx
@@ -49,7 +49,6 @@ const CoverageAreaModal = forwardRef(
{
select: (data) =>
data.map(({ id, cca2 }) => ({ value: id, label: countryTranslation.of(cca2), cca2 })) ?? [],
- placeholderData: [],
}
)
const { data: dataDistrict } = api.fieldOpt.govDists.useQuery(
From 8b3abfcd35252a722e834ea0abfca7420bd30610 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 18:35:15 -0400
Subject: [PATCH 49/61] styling
---
.../data-portal/ServiceEditDrawer/styles.ts | 1 -
.../data-portal/ServiceSelect/index.tsx | 32 ++++++++++---------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/styles.ts b/packages/ui/components/data-portal/ServiceEditDrawer/styles.ts
index ebc45d47ca..fd0aa69294 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/styles.ts
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/styles.ts
@@ -13,7 +13,6 @@ export const useStyles = createStyles((theme) => ({
},
badgeGroup: {
width: '100%',
- cursor: 'pointer',
backgroundColor: theme.fn.lighten(theme.other.colors.secondary.teal, 0.9),
borderRadius: rem(8),
padding: rem(4),
diff --git a/packages/ui/components/data-portal/ServiceSelect/index.tsx b/packages/ui/components/data-portal/ServiceSelect/index.tsx
index 93fe71e8dc..7309096982 100644
--- a/packages/ui/components/data-portal/ServiceSelect/index.tsx
+++ b/packages/ui/components/data-portal/ServiceSelect/index.tsx
@@ -43,22 +43,24 @@ export const ServiceSelect = ({
const serviceGroups = data ? (
-
+
{data.map((category) => (
-
- {t(category.tsKey)}
- {category.services.map((service) => (
-
- ))}
+
+ {t(category.tsKey)}
+
+ {category.services.map((service) => (
+
+ ))}
+
))}
From 761d57b77168e9eff73a916faf1edaf01ae187d2 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 18:36:16 -0400
Subject: [PATCH 50/61] api routes & mock data
---
packages/api/router/fieldOpt/index.ts | 4 ++
.../router/fieldOpt/query.ccaMap.handler.ts | 28 +++++++++++
.../router/fieldOpt/query.ccaMap.schema.ts | 4 ++
.../router/fieldOpt/query.govDists.handler.ts | 1 +
packages/api/router/fieldOpt/schemas.ts | 1 +
packages/api/router/serviceArea/index.ts | 9 ++++
.../mutation.delFromArea.handler.ts | 46 +++++++++++++++++++
.../mutation.delFromArea.schema.ts | 19 ++++++++
packages/api/router/serviceArea/schemas.ts | 1 +
.../ServiceEditDrawer/index.stories.tsx | 4 ++
packages/ui/mockData/fieldOpt.ts | 11 +++++
.../ui/mockData/json/fieldOpt.ccaMap.json | 1 +
.../json/service.forServiceEditDrawer.json | 2 +-
packages/ui/mockData/serviceArea.ts | 5 ++
14 files changed, 135 insertions(+), 1 deletion(-)
create mode 100644 packages/api/router/fieldOpt/query.ccaMap.handler.ts
create mode 100644 packages/api/router/fieldOpt/query.ccaMap.schema.ts
create mode 100644 packages/api/router/serviceArea/mutation.delFromArea.handler.ts
create mode 100644 packages/api/router/serviceArea/mutation.delFromArea.schema.ts
create mode 100644 packages/ui/mockData/json/fieldOpt.ccaMap.json
diff --git a/packages/api/router/fieldOpt/index.ts b/packages/api/router/fieldOpt/index.ts
index 9a16c851ab..a6c367195d 100644
--- a/packages/api/router/fieldOpt/index.ts
+++ b/packages/api/router/fieldOpt/index.ts
@@ -84,4 +84,8 @@ export const fieldOptRouter = defineRouter({
const handler = await importHandler(namespaced('orgBadges'), () => import('./query.orgBadges.handler'))
return handler(opts)
}),
+ ccaMap: publicProcedure.input(schema.ZCcaMapSchema).query(async (opts) => {
+ const handler = await importHandler(namespaced('ccaMap'), () => import('./query.ccaMap.handler'))
+ return handler(opts)
+ }),
})
diff --git a/packages/api/router/fieldOpt/query.ccaMap.handler.ts b/packages/api/router/fieldOpt/query.ccaMap.handler.ts
new file mode 100644
index 0000000000..dc21f59d49
--- /dev/null
+++ b/packages/api/router/fieldOpt/query.ccaMap.handler.ts
@@ -0,0 +1,28 @@
+import { prisma } from '@weareinreach/db'
+import { handleError } from '~api/lib/errorHandler'
+import { type TRPCHandlerParams } from '~api/types/handler'
+
+import { type TCcaMapSchema } from './query.ccaMap.schema'
+
+export const ccaMap = async ({ input }: TRPCHandlerParams) => {
+ try {
+ const data = await prisma.country.findMany({
+ where: input.activeForOrgs
+ ? {
+ activeForOrgs: input.activeForOrgs ?? true,
+ }
+ : {},
+ select: {
+ id: true,
+ cca2: true,
+ },
+ })
+ const byId = new Map(data.map(({ id, cca2 }) => [id, cca2]))
+ const byCCA = new Map(data.map(({ id, cca2 }) => [cca2, id]))
+
+ return { byId, byCCA }
+ } catch (error) {
+ handleError(error)
+ }
+}
+export default ccaMap
diff --git a/packages/api/router/fieldOpt/query.ccaMap.schema.ts b/packages/api/router/fieldOpt/query.ccaMap.schema.ts
new file mode 100644
index 0000000000..94e2b924fb
--- /dev/null
+++ b/packages/api/router/fieldOpt/query.ccaMap.schema.ts
@@ -0,0 +1,4 @@
+import { z } from 'zod'
+
+export const ZCcaMapSchema = z.object({ activeForOrgs: z.boolean().optional() })
+export type TCcaMapSchema = z.infer
diff --git a/packages/api/router/fieldOpt/query.govDists.handler.ts b/packages/api/router/fieldOpt/query.govDists.handler.ts
index 501af8271d..16233d696b 100644
--- a/packages/api/router/fieldOpt/query.govDists.handler.ts
+++ b/packages/api/router/fieldOpt/query.govDists.handler.ts
@@ -16,6 +16,7 @@ export const govDists = async ({ input }: TRPCHandlerParams) =>
country: { select: { cca2: true } },
govDistType: { select: { tsKey: true, tsNs: true } },
},
+ orderBy: { tsKey: 'asc' },
})
return results
} catch (error) {
diff --git a/packages/api/router/fieldOpt/schemas.ts b/packages/api/router/fieldOpt/schemas.ts
index 7b506a54dd..54901ae681 100644
--- a/packages/api/router/fieldOpt/schemas.ts
+++ b/packages/api/router/fieldOpt/schemas.ts
@@ -1,6 +1,7 @@
// codegen:start {preset: barrel, include: ./*.schema.ts}
export * from './query.attributeCategories.schema'
export * from './query.attributesByCategory.schema'
+export * from './query.ccaMap.schema'
export * from './query.countries.schema'
export * from './query.getSubDistricts.schema'
export * from './query.govDists.schema'
diff --git a/packages/api/router/serviceArea/index.ts b/packages/api/router/serviceArea/index.ts
index c792ebf279..8f13203bf3 100644
--- a/packages/api/router/serviceArea/index.ts
+++ b/packages/api/router/serviceArea/index.ts
@@ -28,4 +28,13 @@ export const serviceAreaRouter = defineRouter({
)
return handler(opts)
}),
+ delFromArea: permissionedProcedure('updateOrgService')
+ .input(schema.ZDelFromAreaSchema)
+ .mutation(async (opts) => {
+ const handler = await importHandler(
+ namespaced('delFromArea'),
+ () => import('./mutation.delFromArea.handler')
+ )
+ return handler(opts)
+ }),
})
diff --git a/packages/api/router/serviceArea/mutation.delFromArea.handler.ts b/packages/api/router/serviceArea/mutation.delFromArea.handler.ts
new file mode 100644
index 0000000000..3d39000148
--- /dev/null
+++ b/packages/api/router/serviceArea/mutation.delFromArea.handler.ts
@@ -0,0 +1,46 @@
+import { TRPCError } from '@trpc/server'
+
+import { getAuditedClient } from '@weareinreach/db'
+import { handleError } from '~api/lib/errorHandler'
+import { type TRPCHandlerParams } from '~api/types/handler'
+
+import { type TDelFromAreaSchema } from './mutation.delFromArea.schema'
+
+export const delFromArea = async ({ ctx, input }: TRPCHandlerParams) => {
+ try {
+ const prisma = getAuditedClient(ctx.actorId)
+
+ if (input.countryId) {
+ const { serviceAreaId, countryId } = input
+ const delCountry = await prisma.serviceAreaCountry.delete({
+ where: {
+ serviceAreaId_countryId: {
+ serviceAreaId,
+ countryId,
+ },
+ },
+ })
+ if (delCountry) {
+ return { result: 'deleted' }
+ }
+ } else if (input.govDistId) {
+ const { serviceAreaId, govDistId } = input
+ const delGovDist = await prisma.serviceAreaDist.delete({
+ where: {
+ serviceAreaId_govDistId: {
+ serviceAreaId,
+ govDistId,
+ },
+ },
+ })
+ if (delGovDist) {
+ return { result: 'deleted' }
+ }
+ }
+
+ throw new TRPCError({ code: 'BAD_REQUEST' })
+ } catch (error) {
+ handleError(error)
+ }
+}
+export default delFromArea
diff --git a/packages/api/router/serviceArea/mutation.delFromArea.schema.ts b/packages/api/router/serviceArea/mutation.delFromArea.schema.ts
new file mode 100644
index 0000000000..708c359b6f
--- /dev/null
+++ b/packages/api/router/serviceArea/mutation.delFromArea.schema.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod'
+
+import { prefixedId } from '~api/schemas/idPrefix'
+
+export const ZDelFromAreaSchema = z
+ .object({
+ serviceAreaId: prefixedId('serviceArea'),
+ countryId: z.string().optional(),
+ govDistId: z.string().optional(),
+ })
+ .refine(
+ (data) =>
+ (typeof data.countryId === 'string' || typeof data.govDistId === 'string') &&
+ !(typeof data.countryId === 'string' && typeof data.govDistId === 'string'),
+ {
+ message: 'Only one of countryId or govDistId must be provided',
+ }
+ )
+export type TDelFromAreaSchema = z.infer
diff --git a/packages/api/router/serviceArea/schemas.ts b/packages/api/router/serviceArea/schemas.ts
index 76e97c2b96..5d18aba68c 100644
--- a/packages/api/router/serviceArea/schemas.ts
+++ b/packages/api/router/serviceArea/schemas.ts
@@ -1,5 +1,6 @@
// codegen:start {preset: barrel, include: ./*.schema.ts}
export * from './mutation.addToArea.schema'
+export * from './mutation.delFromArea.schema'
export * from './mutation.update.schema'
export * from './query.getServiceArea.schema'
// codegen:end
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
index 96531c3276..e2c04861a7 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.stories.tsx
@@ -6,6 +6,7 @@ import { allFieldOptHandlers } from '~ui/mockData/fieldOpt'
import { organization } from '~ui/mockData/organization'
import { orgHours } from '~ui/mockData/orgHours'
import { service } from '~ui/mockData/service'
+import { serviceArea } from '~ui/mockData/serviceArea'
import { ServiceEditDrawer } from './index'
@@ -31,6 +32,8 @@ export default {
service.getOptions,
component.ServiceSelect,
orgHours.forHoursDisplay,
+ serviceArea.addToArea,
+ serviceArea.delFromArea,
...allFieldOptHandlers,
],
},
@@ -38,6 +41,7 @@ export default {
component: Button,
children: 'Open Drawer',
variant: 'inlineInvertedUtil1',
+ serviceId: 'osvc_123456789000000',
},
} satisfies Meta
diff --git a/packages/ui/mockData/fieldOpt.ts b/packages/ui/mockData/fieldOpt.ts
index c558b7b864..90c4d0faf1 100644
--- a/packages/ui/mockData/fieldOpt.ts
+++ b/packages/ui/mockData/fieldOpt.ts
@@ -234,6 +234,17 @@ export const fieldOpt = {
}
},
}),
+ ccaMap: getTRPCMock({
+ path: ['fieldOpt', 'ccaMap'],
+ response: async ({ activeForOrgs }) => {
+ const { default: data } = await import('./json/fieldOpt.ccaMap.json')
+ const dataToUse = activeForOrgs ? data.true : data.false
+ return {
+ byId: new Map(Object.entries(dataToUse.byId)),
+ byCCA: new Map(Object.entries(dataToUse.byCCA)),
+ }
+ },
+ }),
} satisfies MockHandlerObject<'fieldOpt'>
export const allFieldOptHandlers = Object.values(fieldOpt)
diff --git a/packages/ui/mockData/json/fieldOpt.ccaMap.json b/packages/ui/mockData/json/fieldOpt.ccaMap.json
new file mode 100644
index 0000000000..91daec8f6d
--- /dev/null
+++ b/packages/ui/mockData/json/fieldOpt.ccaMap.json
@@ -0,0 +1 @@
+{"true":{"byId":{"ctry_01GW2HHDKBRDF1DMR5DA9DAT7K":"PW","ctry_01GW2HHDK67GZQVGA3NZ8PE5SS":"AS","ctry_01GW2HHDKCRS9KW4FG2WR2GG06":"UM","ctry_01GW2HHDKFJ4Q7PBTTN4GSMPV0":"MP","ctry_01GW2HHDK8HTCM0MWQXBJRXEYB":"MH","ctry_01GW2HHDK9M26M80SG63T21SVH":"US","ctry_01GW2HHDKB9DG2T2YZM5MFFVX9":"MX","ctry_01GW2HHDKAWXWYHAAESAA5HH94":"CA","ctry_01GW2HHDK9DG12Y7RQMVEE5XSQ":"VI","ctry_01GW2HHDKGZ2XQ8Q9D8GX564MJ":"GU","ctry_01GW2HHDK7PACTC9GJ2XBMVPKY":"PR"},"byCCA":{"PW":"ctry_01GW2HHDKBRDF1DMR5DA9DAT7K","AS":"ctry_01GW2HHDK67GZQVGA3NZ8PE5SS","UM":"ctry_01GW2HHDKCRS9KW4FG2WR2GG06","MP":"ctry_01GW2HHDKFJ4Q7PBTTN4GSMPV0","MH":"ctry_01GW2HHDK8HTCM0MWQXBJRXEYB","US":"ctry_01GW2HHDK9M26M80SG63T21SVH","MX":"ctry_01GW2HHDKB9DG2T2YZM5MFFVX9","CA":"ctry_01GW2HHDKAWXWYHAAESAA5HH94","VI":"ctry_01GW2HHDK9DG12Y7RQMVEE5XSQ","GU":"ctry_01GW2HHDKGZ2XQ8Q9D8GX564MJ","PR":"ctry_01GW2HHDK7PACTC9GJ2XBMVPKY"}},"false":{"byId":{"ctry_01GW2HHDK76HGWY2MNZTFSTT61":"NL","ctry_01GW2HHDK88EQB8BSKDWEPG76N":"VG","ctry_01GW2HHDKBRDF1DMR5DA9DAT7K":"PW","ctry_01GW2HHDK6BPY9VBW9WR5HDVA5":"GT","ctry_01GW2HHDK67CD2NGY24P7G22SF":"BD","ctry_01GW2HHDK7JEDGYF9BQBZVDW0X":"PA","ctry_01GW2HHDK7372NTNTPP3V9BPGV":"NI","ctry_01GW2HHDK7HX3TFSB0HX6YDCRR":"RE","ctry_01GW2HHDK70QBRG8S7ZXZP89W9":"SL","ctry_01GW2HHDK8SPF3A2D6NW2XPTHM":"BB","ctry_01GW2HHDK8JM827C3VY37CM7TF":"IL","ctry_01GW2HHDK87660QN220M6T4S2K":"EH","ctry_01GW2HHDK87VQBC3WXQDJJZ6XM":"AI","ctry_01GW2HHDK8ZYZHYB9CAVW6C4ER":"RW","ctry_01GW2HHDK8NAY2T3GHNV1HVD3M":"TR","ctry_01GW2HHDK8VJ7Z7R0D99K5E4AN":"MZ","ctry_01GW2HHDK8AXT584ZTWBN739SN":"NG","ctry_01GW2HHDK8A3W54F0AW64VV2GD":"RU","ctry_01GW2HHDK8K7BJH8JW34Q7JK12":"CU","ctry_01GW2HHDK8NAN0FT6100XB79PX":"JE","ctry_01GW2HHDK8WHEVKX1JE6V2E5JJ":"CR","ctry_01GW2HHDK8MZVCAHNP3EBTHDGH":"PM","ctry_01GW2HHDK8HE6ZXBJEN5GGJYBA":"SV","ctry_01GW2HHDK96FCPGE9HBHC01VDE":"HN","ctry_01GW2HHDK9114P6V7VFFNFTMNP":"MO","ctry_01GW2HHDKAKGTGCFFE8HQGZXF4":"MK","ctry_01GW2HHDKATDZGNR55QRS6Y5NX":"AF","ctry_01GW2HHDKA6G8FACKBG654B237":"BW","ctry_01GW2HHDKAFPAWV4NQHE9VT4BA":"AQ","ctry_01GW2HHDKAR1506PSRF3PB1HYX":"MR","ctry_01GW2HHDKA77DT55ZPJ8XKM8P3":"QA","ctry_01GW2HHDKAMRVA2E0TZBFPX7VF":"ES","ctry_01GW2HHDKA60427HJJGXNGC3EV":"YE","ctry_01GW2HHDKBHVDM3WCVS2THFMHT":"SO","ctry_01GW2HHDKBCAGQ1RJGC3ZP6Z5S":"SE","ctry_01GW2HHDKBKDT96T91NCRFZE8A":"LI","ctry_01GW2HHDKBM1E4N9900Q4N942Y":"GS","ctry_01GW2HHDKBJ9NNQ06K5V64NAB8":"MA","ctry_01GW2HHDKBN3B46NQY3YEACJ0K":"MG","ctry_01GW2HHDKBX833SWMMRHFEXGDG":"UA","ctry_01GW2HHDKBEYBGKWE7BCHXZHJ6":"IR","ctry_01GW2HHDKBRG2CC67WN648YK7S":"PL","ctry_01GW2HHDK65BRH9H4P9YZH3P3F":"IT","ctry_01GW2HHDK6ENG0N2YD2ZXD043N":"DZ","ctry_01GW2HHDKBDJH4JA3QZRZCDB1C":"TD","ctry_01GW2HHDKBARW56WYXTBMBX9Z8":"VU","ctry_01GW2HHDKBRRZ84X368SH12W7Q":"PE","ctry_01GW2HHDKBT6AMAW7JQ18MAEGF":"SZ","ctry_01GW2HHDKB96KWQYA4T0D8VXTV":"MQ","ctry_01GW2HHDKBTPBSDQE1XHBMBXJE":"LU","ctry_01GW2HHDKBS9SYM4Z85HKVCW7S":"DJ","ctry_01GW2HHDKBP9EB7HW41MH04MCA":"HT","ctry_01GW2HHDKBCYJXSMFM5N5VW3Q5":"SY","ctry_01GW2HHDKBW4RFHHBG7R71M5B5":"GI","ctry_01GW2HHDKB1FFT4ZEBATSQXWRB":"IS","ctry_01GW2HHDKC0MEFFZTGK0RWCRQA":"FJ","ctry_01GW2HHDKCQX4EJEVCEME3EEBS":"KP","ctry_01GW2HHDKCK11551EN1JD1AMT6":"GN","ctry_01GW2HHDKC0N6MV90J9VWJ6CQN":"KN","ctry_01GW2HHDKCBYW7V5T1DWEBPFRV":"BH","ctry_01GW2HHDKC6BNE39T2DV3QC4NJ":"LT","ctry_01GW2HHDKCJV96MQND976G21HR":"GE","ctry_01GW2HHDK67GZQVGA3NZ8PE5SS":"AS","ctry_01GW2HHDKCGEP7FXQHWTQWB904":"RS","ctry_01GW2HHDKCFG7G5S12F71S6QG5":"ML","ctry_01GW2HHDKC2Q96XZGFSTMMQPNB":"MN","ctry_01GW2HHDKCVDAHZVSVC2YD68XN":"DM","ctry_01GW2HHDKCBG5RYMSXJ7NE0EQH":"GL","ctry_01GW2HHDKC2S12HZKRBMRC51WB":"ET","ctry_01GW2HHDKC77VK7AC2YXTJFP9H":"LY","ctry_01GW2HHDKCGYGA9KDYNQ3CK7H7":"NA","ctry_01GW2HHDKCZNJN6F1J1JTQRZ25":"MD","ctry_01GW2HHDKCS87MJP31FAC9S4NB":"BO","ctry_01GW2HHDKDEHGJVDQC1VDXFM0P":"CW","ctry_01GW2HHDKDHB62D7NFGE7RRNDN":"ID","ctry_01GW2HHDKD14G6PFAPNEMTW5F5":"BY","ctry_01GW2HHDKC5TFN4HP9KQ7QCQR9":"SB","ctry_01GW2HHDKCZ5TVB7QPAQNRS362":"TF","ctry_01GW2HHDKDHEQGH45498FKFDM8":"LR","ctry_01GW2HHDKDD06KYX7CY9JJR1SQ":"MT","ctry_01GW2HHDKD311V08MQJWG7CK03":"MF","ctry_01GW2HHDKDNEGQ31JPB4V9BBMK":"ZA","ctry_01GW2HHDKDMWST2K7SVAAEP7K5":"TG","ctry_01GW2HHDKD3SFPCG8ED0EP3MX3":"AM","ctry_01GW2HHDKD9T5SF5CAMVPA1M4F":"SH","ctry_01GW2HHDKDSS3YW7W5994KW3ZT":"AL","ctry_01GW2HHDKDE152HYH5H1TD8CK6":"AD","ctry_01GW2HHDKD48PB3V9R7ZWXZ3KP":"SS","ctry_01GW2HHDKDXS2FKHD8YPRKBZPY":"GG","ctry_01GW2HHDKDWZR3XW85F2G82SME":"CZ","ctry_01GW2HHDKDT4XD73Q57W6631FN":"GP","ctry_01GW2HHDKDK9BZQS106Q7H8XJR":"GD","ctry_01GW2HHDKDDHRPPGSEFG2P69F0":"PK","ctry_01GW2HHDKD09MHFTWE9SKVMMJ0":"VA","ctry_01GW2HHDKEYSJWN1W0AYDS0NH4":"WF","ctry_01GW2HHDKEZMTF03P4JYAFZRN1":"CM","ctry_01GW2HHDKEVZK4MDJTHQ025AQG":"JM","ctry_01GW2HHDKEFVF6N0VM03AGGZF7":"TM","ctry_01GW2HHDKEGN1AYW58P63XQPZ0":"BN","ctry_01GW2HHDKEW7A1ZRA4EFNWN6FB":"GH","ctry_01GW2HHDKE2127XE2CXSCB0SWG":"KW","ctry_01GW2HHDKE6ZTA1B0KNFYXA7XZ":"ER","ctry_01GW2HHDKENXBKBZTFJ4PA6Z2C":"ZW","ctry_01GW2HHDKE97QHYGJXTAGWENKK":"TZ","ctry_01GW2HHDKEM266QFKJYJVHC53T":"EE","ctry_01GW2HHDKEZN2DQG8Y7XVGHA0G":"BZ","ctry_01GW2HHDKE6SRWQT7YKMEV7N5Y":"SA","ctry_01GW2HHDKEKF565F1E5ZPTXFHE":"NZ","ctry_01GW2HHDKE747AN7G49Z1R39X7":"CO","ctry_01GW2HHDKEZED648DCY8RGH5NA":"MY","ctry_01GW2HHDKE7QG3QATZ72P7YT7V":"AG","ctry_01GW2HHDKE6FEQT1R83S60KVPT":"BM","ctry_01GW2HHDKEH1KYC4T8236ZSY3C":"AT","ctry_01GW2HHDKFTYX6T84QB1WZYYAY":"WS","ctry_01GW2HHDKF91Z0RAW6TC6P5TAP":"UG","ctry_01GW2HHDKFT9BZY0XPP6DESZX8":"NP","ctry_01GW2HHDKF47X5KGDW8RWF841A":"PY","ctry_01GW2HHDKFCKFTFJP7MARGNHRZ":"AU","ctry_01GW2HHDKF8Y6KNW8X28APN60W":"FO","ctry_01GW2HHDKFGDMKVKEAVVCVHXG4":"KG","ctry_01GW2HHDKFK3ME6G5ZNBVT26XS":"PF","ctry_01GW2HHDKFWZA5DF33HK20AMQK":"EC","ctry_01GW2HHDKF1ZKQGXHGKH67QXYA":"HK","ctry_01GW2HHDKF1NR34T2G6FMKQCYD":"TL","ctry_01GW2HHDKFYB0C2FBC8KQ50M36":"VC","ctry_01GW2HHDKFX9C6AWNQ3F32P95P":"VN","ctry_01GW2HHDKF939YJGVTR7H30KPX":"IE","ctry_01GW2HHDKFPPCXV7F8BNVZZ81G":"FI","ctry_01GW2HHDKFEWN5X3FJ1QFQ55NW":"JP","ctry_01GW2HHDKGHKX9XHGFYN7FAF95":"NF","ctry_01GW2HHDKGTHZA8H042BMMGF3J":"BR","ctry_01GW2HHDKGA35BCNN91RB2DFX7":"ME","ctry_01GW2HHDKGRJCTDX2GK4ZZDKAS":"GY","ctry_01GW2HHDKG21ZM449SKC9HB5YK":"SK","ctry_01GW2HHDKGB3V0H2DKQA84VA5K":"MC","ctry_01GW2HHDKGEYB1AM0P2DVYBS7A":"CN","ctry_01GW2HHDKGHT708N7RM8QDDEBK":"AW","ctry_01GW2HHDKG8SKA53C146FV5E0G":"LA","ctry_01GW2HHDKGJG43HR2M2T7JQ5DC":"VE","ctry_01GW2HHDKGRRZM6X62GJ3M62Z4":"PS","ctry_01GW2HHDKGQMAVVKJ3SPPCS689":"SX","ctry_01GW2HHDKGGQ8SZPJH74RXNCP6":"HU","ctry_01GW2HHDKGGTVBR39ZYJVVDGNY":"DE","ctry_01GW2HHDKG1TWFSYE4QQ6CRFX7":"TO","ctry_01GW2HHDKGM9N3RGKASW4677KV":"IQ","ctry_01GW2HHDKGVAGE7SGTXWD5V2EP":"TH","ctry_01GW2HHDKHHXXT7NMR9TQ0TY97":"FM","ctry_01GW2HHDKHCQVPDAC0H3PQV53N":"BE","ctry_01GW2HHDKH6NG85CKMSESSPHQK":"KR","ctry_01GW2HHDKH7Z155XNFE328RJK5":"DK","ctry_01GW2HHDKH3SMD6TVK8MPGE6DD":"OM","ctry_01GW2HHDK9B08AAMK0WGCVR313":"NU","ctry_01GW2HHDK95AW3T41X8R552F6R":"SI","ctry_01GW2HHDK9HZ6M9Q2PJEX8T6HA":"PG","ctry_01GW2HHDKH5GV00V6MM2F7CZ3P":"GF","ctry_01GW2HHDKJ4620D1AB421E7JYX":"SN","ctry_01GW2HHDKHCN2Q3JMYRQ7HJ5VH":"MS","ctry_01GW2HHDKCRS9KW4FG2WR2GG06":"UM","ctry_01GW2HHDK6TWQ3BN3PG06DQ3HM":"FR","ctry_01GW2HHDK7QA15RA6W4YR0YTPQ":"BT","ctry_01GW2HHDK73TNXH3DFC5BV244P":"ST","ctry_01GW2HHDK79QNCV8EVYND8E6MY":"NR","ctry_01GW2HHDK7Z5N9Q2JPZNXP8VPE":"GA","ctry_01GW2HHDK7HMC32S6PHPMFACZX":"NC","ctry_01GW2HHDKCW140K2TYTEE750YT":"CF","ctry_01GW2HHDK74HTQCYV1EVM79B1G":"NO","ctry_01GW2HHDKCGAXYVWHRWSD40516":"DO","ctry_01GW2HHDK7BMH7NEWWCXXE4XTG":"TV","ctry_01GW2HHDK7H0GWV1MRQ6EF6E8N":"IN","ctry_01GW2HHDKD20967VJAPNPBARXM":"CG","ctry_01GW2HHDKDXBNPG01NBMPKYFZ2":"AE","ctry_01GW2HHDKE2EJHZ22HF2GWSB4H":"KM","ctry_01GW2HHDKE1JW9G222WXWADT5M":"FK","ctry_01GW2HHDKEE1FJ2XTH7MA2AZP1":"TC","ctry_01GW2HHDKFTPR1W2P2M9E081J5":"GB","ctry_01GW2HHDKHMX2CYG3GWYA23W4B":"TW","ctry_01GW2HHDKFVJ4QNETXPR3PGZES":"HM","ctry_01GW2HHDKG19TTZ3XA7NKMZ3TJ":"IO","ctry_01GW2HHDKHF02X3NEPGZ12ZFZV":"SC","ctry_01GW2HHDKFJ4Q7PBTTN4GSMPV0":"MP","ctry_01GW2HHDK8FK98CXW90F5HFRJH":"CK","ctry_01GW2HHDKJRNN53ENTSS4Z001K":"PN","ctry_01GW2HHDK8HTCM0MWQXBJRXEYB":"MH","ctry_01GW2HHDK7AMX8NEKVMBA64YJS":"CD","ctry_01GW2HHDK7T16K1ZJ1PZX1E1ZV":"KH","ctry_01GW2HHDK7DF62BBZYJCBFGVYY":"MU","ctry_01GW2HHDK796T1HRBRQMRV2ZGH":"SM","ctry_01GW2HHDK99MQM7GZ8EN4WZ9FK":"MM","ctry_01GW2HHDK9ZK9PGCD8MGCD8YN9":"EG","ctry_01GW2HHDK9NG4F38ZGH49JE311":"LB","ctry_01GW2HHDK9D4RQ30ZEV336H36F":"KE","ctry_01GW2HHDK95TZ43CS71K1P62JD":"GQ","ctry_01GW2HHDK9SZZ6W0GPYA0STVYG":"HR","ctry_01GW2HHDKAE2X1VJCYX05SQ15T":"KZ","ctry_01GW2HHDKA32KTYFEBFF1FRX6G":"XK","ctry_01GW2HHDK90V1XFYZ6AWB05J58":"GM","ctry_01GW2HHDKAQTGEVED1E27QW61N":"PH","ctry_01GW2HHDK9JPEM2C72VSS71G4H":"BQ","ctry_01GW2HHDKH7K0Z1YPRYG3CMEMW":"BS","ctry_01GW2HHDK9M26M80SG63T21SVH":"US","ctry_01GW2HHDKAYQQC5ZYAY81QFF4G":"AR","ctry_01GW2HHDK9W45HXETV96K8CZAZ":"PT","ctry_01GW2HHDK94QVSXT8Q12EDB4XP":"TT","ctry_01GW2HHDKAHQVZWP2NHE3A02B5":"TJ","ctry_01GW2HHDKA3CZ3WF381DJZ5D0Z":"AZ","ctry_01GW2HHDKAGCFGK8W1ZFE62QKQ":"RO","ctry_01GW2HHDKAMPFS09WBC3XZHQKX":"CL","ctry_01GW2HHDKAZ27CDARTX8QS2JMN":"CY","ctry_01GW2HHDKAJ64YAST64W2BJQSV":"CV","ctry_01GW2HHDKAWJN8K8E3EYVZDENE":"BG","ctry_01GW2HHDKHRXXQZHPZYDV3SNVZ":"MW","ctry_01GW2HHDKHXN8ZSHAGKGENMQ4N":"GW","ctry_01GW2HHDKB9DG2T2YZM5MFFVX9":"MX","ctry_01GW2HHDKAWXWYHAAESAA5HH94":"CA","ctry_01GW2HHDKAZYVYFHDZNZDE4HPB":"UY","ctry_01GW2HHDKDSY01QJC6KX2BRXH8":"BV","ctry_01GW2HHDKEG4RY89ACRQMNT8SB":"SJ","ctry_01GW2HHDKFRPGM3P3HD5747R23":"TK","ctry_01GW2HHDKFFE67B6CGZGV25R1C":"IM","ctry_01GW2HHDKHV33E0R9ZQSE302T3":"CX","ctry_01GW2HHDKFPXSJ18WSJ8GEZKJ0":"ZM","ctry_01GW2HHDKFPY9T4YYDFWGBZP5P":"LC","ctry_01GW2HHDKGQRTDPFBTD9GJT3BN":"AX","ctry_01GW2HHDKHRGA9ME9MF56RDYYC":"CI","ctry_01GW2HHDKHA0DCHV26S9FDT5P4":"KY","ctry_01GW2HHDK6FGT7BES1NPX66JTQ":"SG","ctry_01GW2HHDKGG7JZ2RN968FJNWJ8":"TN","ctry_01GW2HHDKGB5RY2JAQVVF4RC4X":"BI","ctry_01GW2HHDKGFE1AY05PY62XEWPM":"CH","ctry_01GW2HHDKGTGG62NESH7TDS364":"BJ","ctry_01GW2HHDKGR9X8QJBDSK84PAG1":"KI","ctry_01GW2HHDKH7NVGT1JBPB5B9SP3":"SD","ctry_01GW2HHDKH6KGE8D69GPF7SSAJ":"UZ","ctry_01GW2HHDKHD6PPNGQWWGD59BQT":"BF","ctry_01GW2HHDKHZNVZP299TK5QC9X6":"LK","ctry_01GW2HHDKHMY5MATBT7VET2W95":"NE","ctry_01GW2HHDKHZP2AGK419VGS2YQ0":"SR","ctry_01GW2HHDKH6H61ZD7Q4D1EVHP5":"AO","ctry_01GW2HHDKH74J20AXR1GWGX6ZQ":"LS","ctry_01GW2HHDK66TC7PJG0EVPFMBFP":"CC","ctry_01GW2HHDK6FR8HMD3W523Q3WRA":"MV","ctry_01GW2HHDK9DG12Y7RQMVEE5XSQ":"VI","ctry_01GW2HHDK6V0QGJ9GRNQCW6A29":"BA","ctry_01GW2HHDK69KVF1HPHBRBTSSBE":"LV","ctry_01GW2HHDK6GRHQQEYGGJX4CQ1Z":"GR","ctry_01GW2HHDK6BG4CBH38VKSZ9M4X":"YT","ctry_01GW2HHDK6Q6BB2C5DWKAYKVP9":"JO","ctry_01GW2HHDKH0MMEJM9R74Z359R6":"BL","ctry_01GW2HHDKGZ2XQ8Q9D8GX564MJ":"GU","ctry_01GW2HHDK7PACTC9GJ2XBMVPKY":"PR"},"byCCA":{"NL":"ctry_01GW2HHDK76HGWY2MNZTFSTT61","VG":"ctry_01GW2HHDK88EQB8BSKDWEPG76N","PW":"ctry_01GW2HHDKBRDF1DMR5DA9DAT7K","GT":"ctry_01GW2HHDK6BPY9VBW9WR5HDVA5","BD":"ctry_01GW2HHDK67CD2NGY24P7G22SF","PA":"ctry_01GW2HHDK7JEDGYF9BQBZVDW0X","NI":"ctry_01GW2HHDK7372NTNTPP3V9BPGV","RE":"ctry_01GW2HHDK7HX3TFSB0HX6YDCRR","SL":"ctry_01GW2HHDK70QBRG8S7ZXZP89W9","BB":"ctry_01GW2HHDK8SPF3A2D6NW2XPTHM","IL":"ctry_01GW2HHDK8JM827C3VY37CM7TF","EH":"ctry_01GW2HHDK87660QN220M6T4S2K","AI":"ctry_01GW2HHDK87VQBC3WXQDJJZ6XM","RW":"ctry_01GW2HHDK8ZYZHYB9CAVW6C4ER","TR":"ctry_01GW2HHDK8NAY2T3GHNV1HVD3M","MZ":"ctry_01GW2HHDK8VJ7Z7R0D99K5E4AN","NG":"ctry_01GW2HHDK8AXT584ZTWBN739SN","RU":"ctry_01GW2HHDK8A3W54F0AW64VV2GD","CU":"ctry_01GW2HHDK8K7BJH8JW34Q7JK12","JE":"ctry_01GW2HHDK8NAN0FT6100XB79PX","CR":"ctry_01GW2HHDK8WHEVKX1JE6V2E5JJ","PM":"ctry_01GW2HHDK8MZVCAHNP3EBTHDGH","SV":"ctry_01GW2HHDK8HE6ZXBJEN5GGJYBA","HN":"ctry_01GW2HHDK96FCPGE9HBHC01VDE","MO":"ctry_01GW2HHDK9114P6V7VFFNFTMNP","MK":"ctry_01GW2HHDKAKGTGCFFE8HQGZXF4","AF":"ctry_01GW2HHDKATDZGNR55QRS6Y5NX","BW":"ctry_01GW2HHDKA6G8FACKBG654B237","AQ":"ctry_01GW2HHDKAFPAWV4NQHE9VT4BA","MR":"ctry_01GW2HHDKAR1506PSRF3PB1HYX","QA":"ctry_01GW2HHDKA77DT55ZPJ8XKM8P3","ES":"ctry_01GW2HHDKAMRVA2E0TZBFPX7VF","YE":"ctry_01GW2HHDKA60427HJJGXNGC3EV","SO":"ctry_01GW2HHDKBHVDM3WCVS2THFMHT","SE":"ctry_01GW2HHDKBCAGQ1RJGC3ZP6Z5S","LI":"ctry_01GW2HHDKBKDT96T91NCRFZE8A","GS":"ctry_01GW2HHDKBM1E4N9900Q4N942Y","MA":"ctry_01GW2HHDKBJ9NNQ06K5V64NAB8","MG":"ctry_01GW2HHDKBN3B46NQY3YEACJ0K","UA":"ctry_01GW2HHDKBX833SWMMRHFEXGDG","IR":"ctry_01GW2HHDKBEYBGKWE7BCHXZHJ6","PL":"ctry_01GW2HHDKBRG2CC67WN648YK7S","IT":"ctry_01GW2HHDK65BRH9H4P9YZH3P3F","DZ":"ctry_01GW2HHDK6ENG0N2YD2ZXD043N","TD":"ctry_01GW2HHDKBDJH4JA3QZRZCDB1C","VU":"ctry_01GW2HHDKBARW56WYXTBMBX9Z8","PE":"ctry_01GW2HHDKBRRZ84X368SH12W7Q","SZ":"ctry_01GW2HHDKBT6AMAW7JQ18MAEGF","MQ":"ctry_01GW2HHDKB96KWQYA4T0D8VXTV","LU":"ctry_01GW2HHDKBTPBSDQE1XHBMBXJE","DJ":"ctry_01GW2HHDKBS9SYM4Z85HKVCW7S","HT":"ctry_01GW2HHDKBP9EB7HW41MH04MCA","SY":"ctry_01GW2HHDKBCYJXSMFM5N5VW3Q5","GI":"ctry_01GW2HHDKBW4RFHHBG7R71M5B5","IS":"ctry_01GW2HHDKB1FFT4ZEBATSQXWRB","FJ":"ctry_01GW2HHDKC0MEFFZTGK0RWCRQA","KP":"ctry_01GW2HHDKCQX4EJEVCEME3EEBS","GN":"ctry_01GW2HHDKCK11551EN1JD1AMT6","KN":"ctry_01GW2HHDKC0N6MV90J9VWJ6CQN","BH":"ctry_01GW2HHDKCBYW7V5T1DWEBPFRV","LT":"ctry_01GW2HHDKC6BNE39T2DV3QC4NJ","GE":"ctry_01GW2HHDKCJV96MQND976G21HR","AS":"ctry_01GW2HHDK67GZQVGA3NZ8PE5SS","RS":"ctry_01GW2HHDKCGEP7FXQHWTQWB904","ML":"ctry_01GW2HHDKCFG7G5S12F71S6QG5","MN":"ctry_01GW2HHDKC2Q96XZGFSTMMQPNB","DM":"ctry_01GW2HHDKCVDAHZVSVC2YD68XN","GL":"ctry_01GW2HHDKCBG5RYMSXJ7NE0EQH","ET":"ctry_01GW2HHDKC2S12HZKRBMRC51WB","LY":"ctry_01GW2HHDKC77VK7AC2YXTJFP9H","NA":"ctry_01GW2HHDKCGYGA9KDYNQ3CK7H7","MD":"ctry_01GW2HHDKCZNJN6F1J1JTQRZ25","BO":"ctry_01GW2HHDKCS87MJP31FAC9S4NB","CW":"ctry_01GW2HHDKDEHGJVDQC1VDXFM0P","ID":"ctry_01GW2HHDKDHB62D7NFGE7RRNDN","BY":"ctry_01GW2HHDKD14G6PFAPNEMTW5F5","SB":"ctry_01GW2HHDKC5TFN4HP9KQ7QCQR9","TF":"ctry_01GW2HHDKCZ5TVB7QPAQNRS362","LR":"ctry_01GW2HHDKDHEQGH45498FKFDM8","MT":"ctry_01GW2HHDKDD06KYX7CY9JJR1SQ","MF":"ctry_01GW2HHDKD311V08MQJWG7CK03","ZA":"ctry_01GW2HHDKDNEGQ31JPB4V9BBMK","TG":"ctry_01GW2HHDKDMWST2K7SVAAEP7K5","AM":"ctry_01GW2HHDKD3SFPCG8ED0EP3MX3","SH":"ctry_01GW2HHDKD9T5SF5CAMVPA1M4F","AL":"ctry_01GW2HHDKDSS3YW7W5994KW3ZT","AD":"ctry_01GW2HHDKDE152HYH5H1TD8CK6","SS":"ctry_01GW2HHDKD48PB3V9R7ZWXZ3KP","GG":"ctry_01GW2HHDKDXS2FKHD8YPRKBZPY","CZ":"ctry_01GW2HHDKDWZR3XW85F2G82SME","GP":"ctry_01GW2HHDKDT4XD73Q57W6631FN","GD":"ctry_01GW2HHDKDK9BZQS106Q7H8XJR","PK":"ctry_01GW2HHDKDDHRPPGSEFG2P69F0","VA":"ctry_01GW2HHDKD09MHFTWE9SKVMMJ0","WF":"ctry_01GW2HHDKEYSJWN1W0AYDS0NH4","CM":"ctry_01GW2HHDKEZMTF03P4JYAFZRN1","JM":"ctry_01GW2HHDKEVZK4MDJTHQ025AQG","TM":"ctry_01GW2HHDKEFVF6N0VM03AGGZF7","BN":"ctry_01GW2HHDKEGN1AYW58P63XQPZ0","GH":"ctry_01GW2HHDKEW7A1ZRA4EFNWN6FB","KW":"ctry_01GW2HHDKE2127XE2CXSCB0SWG","ER":"ctry_01GW2HHDKE6ZTA1B0KNFYXA7XZ","ZW":"ctry_01GW2HHDKENXBKBZTFJ4PA6Z2C","TZ":"ctry_01GW2HHDKE97QHYGJXTAGWENKK","EE":"ctry_01GW2HHDKEM266QFKJYJVHC53T","BZ":"ctry_01GW2HHDKEZN2DQG8Y7XVGHA0G","SA":"ctry_01GW2HHDKE6SRWQT7YKMEV7N5Y","NZ":"ctry_01GW2HHDKEKF565F1E5ZPTXFHE","CO":"ctry_01GW2HHDKE747AN7G49Z1R39X7","MY":"ctry_01GW2HHDKEZED648DCY8RGH5NA","AG":"ctry_01GW2HHDKE7QG3QATZ72P7YT7V","BM":"ctry_01GW2HHDKE6FEQT1R83S60KVPT","AT":"ctry_01GW2HHDKEH1KYC4T8236ZSY3C","WS":"ctry_01GW2HHDKFTYX6T84QB1WZYYAY","UG":"ctry_01GW2HHDKF91Z0RAW6TC6P5TAP","NP":"ctry_01GW2HHDKFT9BZY0XPP6DESZX8","PY":"ctry_01GW2HHDKF47X5KGDW8RWF841A","AU":"ctry_01GW2HHDKFCKFTFJP7MARGNHRZ","FO":"ctry_01GW2HHDKF8Y6KNW8X28APN60W","KG":"ctry_01GW2HHDKFGDMKVKEAVVCVHXG4","PF":"ctry_01GW2HHDKFK3ME6G5ZNBVT26XS","EC":"ctry_01GW2HHDKFWZA5DF33HK20AMQK","HK":"ctry_01GW2HHDKF1ZKQGXHGKH67QXYA","TL":"ctry_01GW2HHDKF1NR34T2G6FMKQCYD","VC":"ctry_01GW2HHDKFYB0C2FBC8KQ50M36","VN":"ctry_01GW2HHDKFX9C6AWNQ3F32P95P","IE":"ctry_01GW2HHDKF939YJGVTR7H30KPX","FI":"ctry_01GW2HHDKFPPCXV7F8BNVZZ81G","JP":"ctry_01GW2HHDKFEWN5X3FJ1QFQ55NW","NF":"ctry_01GW2HHDKGHKX9XHGFYN7FAF95","BR":"ctry_01GW2HHDKGTHZA8H042BMMGF3J","ME":"ctry_01GW2HHDKGA35BCNN91RB2DFX7","GY":"ctry_01GW2HHDKGRJCTDX2GK4ZZDKAS","SK":"ctry_01GW2HHDKG21ZM449SKC9HB5YK","MC":"ctry_01GW2HHDKGB3V0H2DKQA84VA5K","CN":"ctry_01GW2HHDKGEYB1AM0P2DVYBS7A","AW":"ctry_01GW2HHDKGHT708N7RM8QDDEBK","LA":"ctry_01GW2HHDKG8SKA53C146FV5E0G","VE":"ctry_01GW2HHDKGJG43HR2M2T7JQ5DC","PS":"ctry_01GW2HHDKGRRZM6X62GJ3M62Z4","SX":"ctry_01GW2HHDKGQMAVVKJ3SPPCS689","HU":"ctry_01GW2HHDKGGQ8SZPJH74RXNCP6","DE":"ctry_01GW2HHDKGGTVBR39ZYJVVDGNY","TO":"ctry_01GW2HHDKG1TWFSYE4QQ6CRFX7","IQ":"ctry_01GW2HHDKGM9N3RGKASW4677KV","TH":"ctry_01GW2HHDKGVAGE7SGTXWD5V2EP","FM":"ctry_01GW2HHDKHHXXT7NMR9TQ0TY97","BE":"ctry_01GW2HHDKHCQVPDAC0H3PQV53N","KR":"ctry_01GW2HHDKH6NG85CKMSESSPHQK","DK":"ctry_01GW2HHDKH7Z155XNFE328RJK5","OM":"ctry_01GW2HHDKH3SMD6TVK8MPGE6DD","NU":"ctry_01GW2HHDK9B08AAMK0WGCVR313","SI":"ctry_01GW2HHDK95AW3T41X8R552F6R","PG":"ctry_01GW2HHDK9HZ6M9Q2PJEX8T6HA","GF":"ctry_01GW2HHDKH5GV00V6MM2F7CZ3P","SN":"ctry_01GW2HHDKJ4620D1AB421E7JYX","MS":"ctry_01GW2HHDKHCN2Q3JMYRQ7HJ5VH","UM":"ctry_01GW2HHDKCRS9KW4FG2WR2GG06","FR":"ctry_01GW2HHDK6TWQ3BN3PG06DQ3HM","BT":"ctry_01GW2HHDK7QA15RA6W4YR0YTPQ","ST":"ctry_01GW2HHDK73TNXH3DFC5BV244P","NR":"ctry_01GW2HHDK79QNCV8EVYND8E6MY","GA":"ctry_01GW2HHDK7Z5N9Q2JPZNXP8VPE","NC":"ctry_01GW2HHDK7HMC32S6PHPMFACZX","CF":"ctry_01GW2HHDKCW140K2TYTEE750YT","NO":"ctry_01GW2HHDK74HTQCYV1EVM79B1G","DO":"ctry_01GW2HHDKCGAXYVWHRWSD40516","TV":"ctry_01GW2HHDK7BMH7NEWWCXXE4XTG","IN":"ctry_01GW2HHDK7H0GWV1MRQ6EF6E8N","CG":"ctry_01GW2HHDKD20967VJAPNPBARXM","AE":"ctry_01GW2HHDKDXBNPG01NBMPKYFZ2","KM":"ctry_01GW2HHDKE2EJHZ22HF2GWSB4H","FK":"ctry_01GW2HHDKE1JW9G222WXWADT5M","TC":"ctry_01GW2HHDKEE1FJ2XTH7MA2AZP1","GB":"ctry_01GW2HHDKFTPR1W2P2M9E081J5","TW":"ctry_01GW2HHDKHMX2CYG3GWYA23W4B","HM":"ctry_01GW2HHDKFVJ4QNETXPR3PGZES","IO":"ctry_01GW2HHDKG19TTZ3XA7NKMZ3TJ","SC":"ctry_01GW2HHDKHF02X3NEPGZ12ZFZV","MP":"ctry_01GW2HHDKFJ4Q7PBTTN4GSMPV0","CK":"ctry_01GW2HHDK8FK98CXW90F5HFRJH","PN":"ctry_01GW2HHDKJRNN53ENTSS4Z001K","MH":"ctry_01GW2HHDK8HTCM0MWQXBJRXEYB","CD":"ctry_01GW2HHDK7AMX8NEKVMBA64YJS","KH":"ctry_01GW2HHDK7T16K1ZJ1PZX1E1ZV","MU":"ctry_01GW2HHDK7DF62BBZYJCBFGVYY","SM":"ctry_01GW2HHDK796T1HRBRQMRV2ZGH","MM":"ctry_01GW2HHDK99MQM7GZ8EN4WZ9FK","EG":"ctry_01GW2HHDK9ZK9PGCD8MGCD8YN9","LB":"ctry_01GW2HHDK9NG4F38ZGH49JE311","KE":"ctry_01GW2HHDK9D4RQ30ZEV336H36F","GQ":"ctry_01GW2HHDK95TZ43CS71K1P62JD","HR":"ctry_01GW2HHDK9SZZ6W0GPYA0STVYG","KZ":"ctry_01GW2HHDKAE2X1VJCYX05SQ15T","XK":"ctry_01GW2HHDKA32KTYFEBFF1FRX6G","GM":"ctry_01GW2HHDK90V1XFYZ6AWB05J58","PH":"ctry_01GW2HHDKAQTGEVED1E27QW61N","BQ":"ctry_01GW2HHDK9JPEM2C72VSS71G4H","BS":"ctry_01GW2HHDKH7K0Z1YPRYG3CMEMW","US":"ctry_01GW2HHDK9M26M80SG63T21SVH","AR":"ctry_01GW2HHDKAYQQC5ZYAY81QFF4G","PT":"ctry_01GW2HHDK9W45HXETV96K8CZAZ","TT":"ctry_01GW2HHDK94QVSXT8Q12EDB4XP","TJ":"ctry_01GW2HHDKAHQVZWP2NHE3A02B5","AZ":"ctry_01GW2HHDKA3CZ3WF381DJZ5D0Z","RO":"ctry_01GW2HHDKAGCFGK8W1ZFE62QKQ","CL":"ctry_01GW2HHDKAMPFS09WBC3XZHQKX","CY":"ctry_01GW2HHDKAZ27CDARTX8QS2JMN","CV":"ctry_01GW2HHDKAJ64YAST64W2BJQSV","BG":"ctry_01GW2HHDKAWJN8K8E3EYVZDENE","MW":"ctry_01GW2HHDKHRXXQZHPZYDV3SNVZ","GW":"ctry_01GW2HHDKHXN8ZSHAGKGENMQ4N","MX":"ctry_01GW2HHDKB9DG2T2YZM5MFFVX9","CA":"ctry_01GW2HHDKAWXWYHAAESAA5HH94","UY":"ctry_01GW2HHDKAZYVYFHDZNZDE4HPB","BV":"ctry_01GW2HHDKDSY01QJC6KX2BRXH8","SJ":"ctry_01GW2HHDKEG4RY89ACRQMNT8SB","TK":"ctry_01GW2HHDKFRPGM3P3HD5747R23","IM":"ctry_01GW2HHDKFFE67B6CGZGV25R1C","CX":"ctry_01GW2HHDKHV33E0R9ZQSE302T3","ZM":"ctry_01GW2HHDKFPXSJ18WSJ8GEZKJ0","LC":"ctry_01GW2HHDKFPY9T4YYDFWGBZP5P","AX":"ctry_01GW2HHDKGQRTDPFBTD9GJT3BN","CI":"ctry_01GW2HHDKHRGA9ME9MF56RDYYC","KY":"ctry_01GW2HHDKHA0DCHV26S9FDT5P4","SG":"ctry_01GW2HHDK6FGT7BES1NPX66JTQ","TN":"ctry_01GW2HHDKGG7JZ2RN968FJNWJ8","BI":"ctry_01GW2HHDKGB5RY2JAQVVF4RC4X","CH":"ctry_01GW2HHDKGFE1AY05PY62XEWPM","BJ":"ctry_01GW2HHDKGTGG62NESH7TDS364","KI":"ctry_01GW2HHDKGR9X8QJBDSK84PAG1","SD":"ctry_01GW2HHDKH7NVGT1JBPB5B9SP3","UZ":"ctry_01GW2HHDKH6KGE8D69GPF7SSAJ","BF":"ctry_01GW2HHDKHD6PPNGQWWGD59BQT","LK":"ctry_01GW2HHDKHZNVZP299TK5QC9X6","NE":"ctry_01GW2HHDKHMY5MATBT7VET2W95","SR":"ctry_01GW2HHDKHZP2AGK419VGS2YQ0","AO":"ctry_01GW2HHDKH6H61ZD7Q4D1EVHP5","LS":"ctry_01GW2HHDKH74J20AXR1GWGX6ZQ","CC":"ctry_01GW2HHDK66TC7PJG0EVPFMBFP","MV":"ctry_01GW2HHDK6FR8HMD3W523Q3WRA","VI":"ctry_01GW2HHDK9DG12Y7RQMVEE5XSQ","BA":"ctry_01GW2HHDK6V0QGJ9GRNQCW6A29","LV":"ctry_01GW2HHDK69KVF1HPHBRBTSSBE","GR":"ctry_01GW2HHDK6GRHQQEYGGJX4CQ1Z","YT":"ctry_01GW2HHDK6BG4CBH38VKSZ9M4X","JO":"ctry_01GW2HHDK6Q6BB2C5DWKAYKVP9","BL":"ctry_01GW2HHDKH0MMEJM9R74Z359R6","GU":"ctry_01GW2HHDKGZ2XQ8Q9D8GX564MJ","PR":"ctry_01GW2HHDK7PACTC9GJ2XBMVPKY"}}}
\ No newline at end of file
diff --git a/packages/ui/mockData/json/service.forServiceEditDrawer.json b/packages/ui/mockData/json/service.forServiceEditDrawer.json
index 23067c3218..f06e874914 100644
--- a/packages/ui/mockData/json/service.forServiceEditDrawer.json
+++ b/packages/ui/mockData/json/service.forServiceEditDrawer.json
@@ -1 +1 @@
-{"id":"osvc_01GVH3VEVPF1KEKBTRVTV70WGV","published":true,"deleted":false,"locations":[{"orgLocationId":"oloc_01GVH3VEVBRCFA2AHNTWCXQA2B","location":{"country":{"cca2":"US"}}},{"orgLocationId":"oloc_01GVH3VEVBSA85T6VR2C38BJPT","location":{"country":{"cca2":"US"}}}],"name":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.name","text":"Get rapid HIV testing","ns":"org-data","crowdinId":773224},"description":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.description","text":"Whitman-Walker provides walk-in HIV testing at multiple locations in DC. Walk-in HIV testing includes a confidential, rapid HIV test and risk-reduction counseling. The counseling provides clients with education on their options for having safer sex. Whitman-Walker uses the INSTI® HIV-1/HIV-2 Rapid Antibody Test and results take one minute.","ns":"org-data","crowdinId":773222},"phones":["ophn_01GVH3VEVC36PW0Z9GDV0ZERV1","ophn_01GVH3VEVCFKT3NWQ79STYVDKR"],"emails":[],"services":["svtg_01GW2HHFBRPBXSYN12DWNEAJJ7"],"hours":{},"serviceAreas":{"id":"svar_01GW2HT9F1JKT1MCAJ3P7XBDHP","countries":[],"districts":["gdst_01GW2HJ5A278S2G84AB3N9FCW0"]},"attributes":[{"attributeId":"attr_01GW2HHFVA06WHRSM241ZF0FY0","supplementId":"atts_01E4ENGMG266R5BH78D7B2MB7M","tag":"hiv-aids","tsKey":"community.hiv-aids","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"community","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","supplementId":"atts_01E4ENGMG2XWR5JQ1JMBN2SQVM","tag":"cost-free","tsKey":"cost.cost-free","tsNs":"attribute","icon":"carbon:piggy-bank","iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"cost","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","supplementId":"atts_01E4ENGMG2J94M4S9DQTE57GWN","tag":"has-confidentiality-policy","tsKey":"additional.has-confidentiality-policy","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","supplementId":"atts_01E4ENGMG20KXGB20JYGZ4X938","tag":"time-walk-in","tsKey":"additional.time-walk-in","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","supplementId":"atts_01GW2HT9F13VVJCJ8W2WE86R6N","tag":"incompatible-info","tsKey":"sys.incompatible-info","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"system","active":false,"countryId":null,"country":null,"data":{"json":[{"community-lgbt":"true"},{"lang-all-languages-by-interpreter":"Language access services are available, including ASL interpreting."}]},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","supplementId":"atts_01GW2HT9F15B2HJK144B3NZHQK","tag":"lang-offered","tsKey":"lang.lang-offered","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"languages","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":"lang_0000000000N3K70GZXE29Z03A4","language":{"languageName":"English","nativeName":"English"},"boolean":null,"text":null}],"accessDetails":[{"attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","supplementId":"atts_01GW2HT9F01W2M7FBSKSXAQ9R4","tag":"accesslink","tsKey":"serviceaccess.accesslink","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4234"},"access_type":"link","access_value":"https://www.whitman-walker.org/hiv-sti-testing","instructions":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","access_value_ES":"https://www.whitman-walker.org/hiv-sti-testing","instructions_ES":"Visita el sitio web para obtener más información sobre los horarios y lugares de prueba de Whitman-Walker."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F01W2M7FBSKSXAQ9R4","text":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","supplementId":"atts_01GW2HT9F09GFRWM3JK2A43AWG","tag":"accessphone","tsKey":"serviceaccess.accessphone","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4235"},"access_type":"phone","access_value":"202-745-7000","instructions":"Contact the Main Office about services offered in multiple languages upon request.","access_value_ES":"202-745-7000","instructions_ES":"Comunícate con la oficina principal sobre los servicios que se ofrecen en varios idiomas si lo solicitas."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F09GFRWM3JK2A43AWG","text":"Contact the Main Office about services offered in multiple languages upon request.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0SPS3EBCQ710RCNTA","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4231"},"access_type":"location","access_value":"2301 M. Luther King Jr., Washington DC 20020","instructions":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","access_value_ES":"2301 M. Luther King Jr., Washington DC 20020","instructions_ES":"Centro Max Robinson:NO hay pruebas disponibles sin cita previa. Lunes:08:30-12:30, 13:30-17:30; Martes:08:30 - 12:30, 13:30 - 17:30; Miércoles:08:30 - 12:30, 13:30 - 17:30; Jueves:08:30 - 12:30, 13:30 - 17:30; Viernes:08:30 - 12:30, 14:15 - 17:30."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0SPS3EBCQ710RCNTA","text":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0638MD74PJ3SCWNXC","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4233"},"access_type":"location","access_value":"1525 14th St, NW Washington, DC 20005","instructions":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","access_value_ES":"1525 14th St, NW Washington, DC 20005","instructions_ES":"Whitman-Walker en 1525:NO hay pruebas disponibles. Lunes-Jueves:08:30-12:30 y 13:30-17:30; Viernes:08:30- 12:30 y 14:30 -17:30."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0638MD74PJ3SCWNXC","text":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","ns":"org-data"}}]}
\ No newline at end of file
+{"id":"osvc_01GVH3VEVPF1KEKBTRVTV70WGV","published":true,"deleted":false,"locations":[{"orgLocationId":"oloc_01GVH3VEVBRCFA2AHNTWCXQA2B","location":{"country":{"cca2":"US"}}},{"orgLocationId":"oloc_01GVH3VEVBSA85T6VR2C38BJPT","location":{"country":{"cca2":"US"}}}],"name":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.name","text":"Get rapid HIV testing","ns":"org-data","crowdinId":773224},"description":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.description","text":"Whitman-Walker provides walk-in HIV testing at multiple locations in DC. Walk-in HIV testing includes a confidential, rapid HIV test and risk-reduction counseling. The counseling provides clients with education on their options for having safer sex. Whitman-Walker uses the INSTI® HIV-1/HIV-2 Rapid Antibody Test and results take one minute.","ns":"org-data","crowdinId":773222},"phones":["ophn_01GVH3VEVC36PW0Z9GDV0ZERV1","ophn_01GVH3VEVCFKT3NWQ79STYVDKR"],"emails":[],"services":["svtg_01GW2HHFBRPBXSYN12DWNEAJJ7"],"hours":{},"serviceAreas":{"id":"svar_01GW2HT9F1JKT1MCAJ3P7XBDHP","countries":["ctry_01GW2HHDK7PACTC9GJ2XBMVPKY"],"districts":["gdst_01GW2HJ5A278S2G84AB3N9FCW0"]},"attributes":[{"attributeId":"attr_01GW2HHFVA06WHRSM241ZF0FY0","supplementId":"atts_01E4ENGMG266R5BH78D7B2MB7M","tag":"hiv-aids","tsKey":"community.hiv-aids","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"community","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","supplementId":"atts_01E4ENGMG2XWR5JQ1JMBN2SQVM","tag":"cost-free","tsKey":"cost.cost-free","tsNs":"attribute","icon":"carbon:piggy-bank","iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"cost","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","supplementId":"atts_01E4ENGMG2J94M4S9DQTE57GWN","tag":"has-confidentiality-policy","tsKey":"additional.has-confidentiality-policy","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","supplementId":"atts_01E4ENGMG20KXGB20JYGZ4X938","tag":"time-walk-in","tsKey":"additional.time-walk-in","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"additional-information","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","supplementId":"atts_01GW2HT9F13VVJCJ8W2WE86R6N","tag":"incompatible-info","tsKey":"sys.incompatible-info","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"system","active":false,"countryId":null,"country":null,"data":{"json":[{"community-lgbt":"true"},{"lang-all-languages-by-interpreter":"Language access services are available, including ASL interpreting."}]},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":null},{"attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","supplementId":"atts_01GW2HT9F15B2HJK144B3NZHQK","tag":"lang-offered","tsKey":"lang.lang-offered","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"languages","active":true,"countryId":null,"country":null,"data":null,"govDistId":null,"govDist":null,"languageId":"lang_0000000000N3K70GZXE29Z03A4","language":{"languageName":"English","nativeName":"English"},"boolean":null,"text":null}],"accessDetails":[{"attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","supplementId":"atts_01GW2HT9F01W2M7FBSKSXAQ9R4","tag":"accesslink","tsKey":"serviceaccess.accesslink","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4234"},"access_type":"link","access_value":"https://www.whitman-walker.org/hiv-sti-testing","instructions":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","access_value_ES":"https://www.whitman-walker.org/hiv-sti-testing","instructions_ES":"Visita el sitio web para obtener más información sobre los horarios y lugares de prueba de Whitman-Walker."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F01W2M7FBSKSXAQ9R4","text":"Visit the website to learn more about Whitman-Walker's testing hours and locations.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","supplementId":"atts_01GW2HT9F09GFRWM3JK2A43AWG","tag":"accessphone","tsKey":"serviceaccess.accessphone","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4235"},"access_type":"phone","access_value":"202-745-7000","instructions":"Contact the Main Office about services offered in multiple languages upon request.","access_value_ES":"202-745-7000","instructions_ES":"Comunícate con la oficina principal sobre los servicios que se ofrecen en varios idiomas si lo solicitas."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F09GFRWM3JK2A43AWG","text":"Contact the Main Office about services offered in multiple languages upon request.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0SPS3EBCQ710RCNTA","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4231"},"access_type":"location","access_value":"2301 M. Luther King Jr., Washington DC 20020","instructions":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","access_value_ES":"2301 M. Luther King Jr., Washington DC 20020","instructions_ES":"Centro Max Robinson:NO hay pruebas disponibles sin cita previa. Lunes:08:30-12:30, 13:30-17:30; Martes:08:30 - 12:30, 13:30 - 17:30; Miércoles:08:30 - 12:30, 13:30 - 17:30; Jueves:08:30 - 12:30, 13:30 - 17:30; Viernes:08:30 - 12:30, 14:15 - 17:30."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0SPS3EBCQ710RCNTA","text":"Max Robinson Center - NO walk-in testing is available. Monday:08:30-12:30, 13:30-17:30; Tuesday:08:30 - 12:30, 13:30 - 17:30; Wednesday:08:30 - 12:30, 13:30 - 17:30; Thursday:08:30 - 12:30, 13:30 - 17:30; Friday:08:30 - 12:30, 14:15 - 17:30.","ns":"org-data"}},{"attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","supplementId":"atts_01GW2HT9F0638MD74PJ3SCWNXC","tag":"accesslocation","tsKey":"serviceaccess.accesslocation","tsNs":"attribute","icon":null,"iconBg":null,"showOnLocation":null,"_count":{"parents":0,"children":0},"category":"service-access-instructions","active":true,"countryId":null,"country":null,"data":{"json":{"_id":{"$oid":"5e7e4bdbd54f1760921a4233"},"access_type":"location","access_value":"1525 14th St, NW Washington, DC 20005","instructions":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","access_value_ES":"1525 14th St, NW Washington, DC 20005","instructions_ES":"Whitman-Walker en 1525:NO hay pruebas disponibles. Lunes-Jueves:08:30-12:30 y 13:30-17:30; Viernes:08:30- 12:30 y 14:30 -17:30."}},"govDistId":null,"govDist":null,"languageId":null,"language":null,"boolean":null,"text":{"key":"orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0638MD74PJ3SCWNXC","text":"Whitman-Walker at 1525 - NO walk-in testing is available. Monday-Thursday:08:30-12:30 & 13:30-17:30; Friday:08:30- 12:30 & 14:30 -17:30.","ns":"org-data"}}]}
\ No newline at end of file
diff --git a/packages/ui/mockData/serviceArea.ts b/packages/ui/mockData/serviceArea.ts
index 8cb64f459d..9641c16b26 100644
--- a/packages/ui/mockData/serviceArea.ts
+++ b/packages/ui/mockData/serviceArea.ts
@@ -27,4 +27,9 @@ export const serviceArea = {
type: 'mutation',
response: () => ({ result: 'added' }),
}),
+ delFromArea: getTRPCMock({
+ path: ['serviceArea', 'delFromArea'],
+ type: 'mutation',
+ response: () => ({ result: 'deleted' }),
+ }),
} satisfies MockHandlerObject<'serviceArea'>
From 1321b62fca0d622728f295de435c134e2b4d917d Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 18:38:33 -0400
Subject: [PATCH 51/61] service edit drawer work
---
.../pages/org/[slug]/[orgLocationId]/edit.tsx | 18 +--
.../data-portal/ServiceEditDrawer/index.tsx | 116 ++++++++++++++----
2 files changed, 102 insertions(+), 32 deletions(-)
diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
index 8264d58078..096b830a89 100644
--- a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
+++ b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
@@ -194,6 +194,10 @@ const OrgLocationPage: NextPage
+
+ {'Associated services'}
+
+
{'Associate service(s) to this location'}
{/**/}
-
- {'Associated services'}
-
-
@@ -248,14 +248,14 @@ export const getServerSideProps = async ({
if (!session) {
return {
redirect: {
- destination: '/',
+ destination: `/org/${slug}/${id}`,
permanent: false,
},
}
}
const ssg = await trpcServerClient({ session })
const { id: orgId } = await ssg.organization.getIdFromSlug.fetch({ slug })
- const [i18n] = await Promise.all([
+ const [i18n] = await Promise.allSettled([
getServerSideTranslations(locale, [
'common',
'services',
@@ -263,15 +263,19 @@ export const getServerSideProps = async ({
'phone-type',
'country',
'gov-dist',
+ orgId,
]),
ssg.location.forLocationPageEdits.prefetch({ id }),
ssg.location.getAlerts.prefetch({ id }),
])
+
+ const translations = i18n.status === 'fulfilled' ? i18n.value : {}
+
const props = {
organizationId: orgId,
session,
trpcState: ssg.dehydrate(),
- ...i18n,
+ ...translations,
}
return {
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index 4118e5a544..d89913acc0 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -1,5 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod'
import {
+ ActionIcon,
Box,
type ButtonProps,
createPolymorphicComponent,
@@ -9,6 +10,7 @@ import {
Stack,
Text,
Title,
+ Tooltip,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { useTranslation } from 'next-i18next'
@@ -27,6 +29,7 @@ import { ServiceSelect } from '~ui/components/data-portal/ServiceSelect'
import { useCustomVariant } from '~ui/hooks'
import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
+import { CoverageArea } from '~ui/modals/CoverageArea'
import { AttributeModal } from '~ui/modals/dataPortal/Attributes'
import { processAccessInstructions, processAttributes } from '~ui/modals/Service/processor'
@@ -36,12 +39,53 @@ import { InlineTextInput } from '../InlineTextInput'
const isObject = (x: unknown): x is object => typeof x === 'object'
+const ServiceAreaItem = ({
+ serviceId,
+ serviceAreaId,
+ countryId,
+ govDistId,
+ children,
+}: ServiceAreaItemProps) => {
+ const apiUtils = api.useUtils()
+ const removeServiceArea = api.serviceArea.delFromArea.useMutation({
+ onSuccess: () => apiUtils.service.forServiceEditDrawer.invalidate(serviceId),
+ })
+ console.log({ serviceAreaId, countryId, govDistId, serviceId })
+ if (!serviceAreaId || !(countryId || govDistId)) {
+ console.log('just returning children only')
+ return children
+ }
+
+ const actionHandler = () => {
+ removeServiceArea.mutate({ serviceAreaId, countryId, govDistId })
+ }
+
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+interface ServiceAreaItemProps {
+ serviceId: string
+ serviceAreaId?: string
+ countryId?: string
+ govDistId?: string
+ children: ReactNode
+}
+
const _ServiceEditDrawer = forwardRef
(
({ serviceId, ...props }, ref) => {
const [drawerOpened, drawerHandler] = useDisclosure(false)
const { classes } = useStyles()
const variants = useCustomVariant()
const { t, i18n } = useTranslation(['common', 'gov-dist'])
+ const apiUtils = api.useUtils()
// #region Get existing data/populate form
const { data } = api.service.forServiceEditDrawer.useQuery(serviceId, {
refetchOnWindowFocus: false,
@@ -71,30 +115,39 @@ const _ServiceEditDrawer = forwardRef
const { data: geoMap } = api.fieldOpt.countryGovDistMap.useQuery(undefined, {
refetchOnWindowFocus: false,
})
+ const { data: countryMap } = api.fieldOpt.ccaMap.useQuery(
+ { activeForOrgs: true },
+ { refetchOnWindowFocus: false }
+ )
+ const removeServiceArea = api.serviceArea.delFromArea.useMutation({
+ onSuccess: () => apiUtils.service.forServiceEditDrawer.invalidate(serviceId),
+ })
const serviceAreas = () => {
+ const countryTranslation = new Intl.DisplayNames(i18n.language, { type: 'region' })
const serviceAreaObj: Record = {}
const { countries, districts } = form.watch('serviceAreas') ?? {}
if (!geoMap) return null
const countryIdRegex = /^ctry_.*/
const distIdRegex = /^gdst_.*/
- const processCountry = (country: string) => {
- serviceAreaObj[country] ??= []
- const array = serviceAreaObj[country]
+ const processCountry = (countryId: string) => {
+ serviceAreaObj[countryId] ??= []
+ const array = serviceAreaObj[countryId]
invariant(array)
- const countryDetails = geoMap.get(country)
- if (!countryDetails) return
+ const cca2 = countryMap?.byId.get(countryId)
+ if (!cca2) return
+ const serviceAreaId = data?.serviceAreas?.id
const item = (
-
-
- All of {t(countryDetails.tsKey, { ns: countryDetails.tsNs })}
-
+
+
+ All of {countryTranslation.of(cca2)}
+
)
array.push(item)
}
- const processDistrict = (district: string) => {
- const govDist = geoMap.get(district)
+ const processDistrict = (govDistId: string) => {
+ const govDist = geoMap.get(govDistId)
const country = govDist?.parent?.parent?.id ?? govDist?.parent?.id ?? ''
if (!countryIdRegex.test(country) || !govDist) return
serviceAreaObj[country] ??= []
@@ -102,18 +155,19 @@ const _ServiceEditDrawer = forwardRef
invariant(array)
const parent = govDist.parent?.id ?? ''
const parentDist = geoMap.get(parent)
- const item =
- !distIdRegex.test(parent) || !parentDist ? (
-
- {t(govDist.tsKey, { ns: govDist.tsNs })}
-
- ) : (
-
+ const serviceAreaId = data?.serviceAreas?.id
+ const item = (
+
+
- {t(parentDist.tsKey, { ns: parentDist.tsNs })} - {t(govDist.tsKey, { ns: govDist.tsNs })}
+ {!distIdRegex.test(parent) || !parentDist
+ ? t(govDist.tsKey, { ns: govDist.tsNs })
+ : `${t(parentDist.tsKey, { ns: parentDist.tsNs })} - ${t(govDist.tsKey, { ns: govDist.tsNs })}`}
-
- )
+
+
+ )
+
array.push(item)
}
@@ -128,12 +182,12 @@ const _ServiceEditDrawer = forwardRef
}
}
return Object.entries(serviceAreaObj)?.map(([key, value]) => {
- const country = geoMap.get(key)
+ const country = countryMap?.byId.get(key)
if (!country) return null
return (
-
- {t(country.tsKey, { ns: country.tsNs })}
-
}>
+
+ {countryTranslation.of(country)}
+
{value}
@@ -158,6 +212,7 @@ const _ServiceEditDrawer = forwardRef
locale: i18n.resolvedLanguage ?? 'en',
t,
})
+ const coverageModalServiceArea = data.serviceAreas?.id ?? { orgServiceId: serviceId }
return (
<>
@@ -223,6 +278,17 @@ const _ServiceEditDrawer = forwardRef
Coverage Area
{serviceAreas()}
+ {
+ apiUtils.service.forServiceEditDrawer.invalidate(serviceId)
+ apiUtils.service.forServiceModal.invalidate(serviceId)
+ }}
+ component={Button}
+ variant={variants.Button.secondarySm}
+ >
+ Add new service area
+
{/* {Boolean(geoMap?.size) && } */}
From 183a602d99d6868abc9feef42af1da757ca7d2a2 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Tue, 2 Apr 2024 18:59:15 -0400
Subject: [PATCH 52/61] cleanup console.log
---
.../data-portal/ServiceEditDrawer/index.tsx | 20 ++++++++-----------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
index d89913acc0..3c8c0f214e 100644
--- a/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
+++ b/packages/ui/components/data-portal/ServiceEditDrawer/index.tsx
@@ -50,9 +50,7 @@ const ServiceAreaItem = ({
const removeServiceArea = api.serviceArea.delFromArea.useMutation({
onSuccess: () => apiUtils.service.forServiceEditDrawer.invalidate(serviceId),
})
- console.log({ serviceAreaId, countryId, govDistId, serviceId })
if (!serviceAreaId || !(countryId || govDistId)) {
- console.log('just returning children only')
return children
}
@@ -71,13 +69,6 @@ const ServiceAreaItem = ({
)
}
-interface ServiceAreaItemProps {
- serviceId: string
- serviceAreaId?: string
- countryId?: string
- govDistId?: string
- children: ReactNode
-}
const _ServiceEditDrawer = forwardRef(
({ serviceId, ...props }, ref) => {
@@ -119,9 +110,6 @@ const _ServiceEditDrawer = forwardRef
{ activeForOrgs: true },
{ refetchOnWindowFocus: false }
)
- const removeServiceArea = api.serviceArea.delFromArea.useMutation({
- onSuccess: () => apiUtils.service.forServiceEditDrawer.invalidate(serviceId),
- })
const serviceAreas = () => {
const countryTranslation = new Intl.DisplayNames(i18n.language, { type: 'region' })
const serviceAreaObj: Record = {}
@@ -363,3 +351,11 @@ export const ServiceEditDrawer = createPolymorphicComponent<'button', ServiceEdi
interface ServiceEditDrawerProps extends ButtonProps {
serviceId: string
}
+
+interface ServiceAreaItemProps {
+ serviceId: string
+ serviceAreaId?: string
+ countryId?: string
+ govDistId?: string
+ children: ReactNode
+}
From 87f36bc2c38c35d36c4783e5d374a9cf53953e4d Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 11:01:26 -0400
Subject: [PATCH 53/61] update attribute schemas
---
.../2024-04-03_access-instruction-schemas.ts | 284 ++++++++++++++++++
packages/db/prisma/data-migrations/index.ts | 1 +
.../json/fieldOpt.attributesByCategory.json | 2 +-
3 files changed, 286 insertions(+), 1 deletion(-)
create mode 100644 packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
diff --git a/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts b/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
new file mode 100644
index 0000000000..e42f6c196e
--- /dev/null
+++ b/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
@@ -0,0 +1,284 @@
+import { z } from 'zod'
+import { zodToJsonSchema } from 'zod-to-json-schema'
+
+import { prisma } from '~db/client'
+import { formatMessage } from '~db/prisma/common'
+import { type MigrationJob } from '~db/prisma/dataMigrationRunner'
+import { createLogger, type JobDef, jobPostRunner } from '~db/prisma/jobPreRun'
+import { JsonInputOrNull } from '~db/zod_util'
+import { type FieldAttributes, FieldType } from '~db/zod_util/attributeSupplement'
+
+/** Define the job metadata here. */
+const jobDef: JobDef = {
+ jobId: '2024-04-03_access-instruction-schemas',
+ title: 'access instruction schemas',
+ createdBy: 'Joe Karow',
+ /** Optional: Longer description for the job */
+ description: undefined,
+}
+
+const schemas = {
+ email: z.object({
+ access_type: z.literal('email').optional().default('email'),
+ access_value: z.string().email().nullish(),
+ instructions: z.string().optional(),
+ }),
+ file: z.object({
+ access_type: z.literal('file').optional().default('file'),
+ access_value: z.string().url().nullish(),
+ instructions: z.string().optional(),
+ }),
+ link: z.object({
+ access_type: z.literal('link').optional().default('link'),
+ access_value: z.string().url().nullish(),
+ instructions: z.string().optional(),
+ }),
+ location: z.object({
+ access_type: z.literal('location').optional().default('location'),
+ access_value: z.string().nullish(),
+ instructions: z.string().optional(),
+ }),
+ other: z.object({
+ access_type: z.literal('other').optional().default('other'),
+ access_value: z.string().nullish(),
+ instructions: z.string().optional(),
+ }),
+ phone: z.object({
+ access_type: z.literal('phone').optional().default('phone'),
+ access_value: z.string().nullish(),
+ instructions: z.string().optional(),
+ }),
+ sms: z.object({
+ access_type: z.literal('sms').optional().default('sms'),
+ sms_body: z.string().optional(),
+ access_value: z.string().nullish(),
+ instructions: z.string().optional(),
+ }),
+ whatsapp: z.object({
+ access_type: z.literal('whatsapp').optional().default('whatsapp'),
+ access_value: z.string().nullish(),
+ instructions: z.string().optional(),
+ }),
+ publicTransport: z.object({
+ access_type: z.literal('publicTransit').optional().default('publicTransit'),
+ access_value: z.string().nullish(),
+ instructions: z.string().optional(),
+ }),
+}
+
+/**
+ * Job export - this variable MUST be UNIQUE
+ */
+export const job20240403_access_instruction_schemas = {
+ title: `[${jobDef.jobId}] ${jobDef.title}`,
+ task: async (_ctx, task) => {
+ /** Create logging instance */
+ createLogger(task, jobDef.jobId)
+ const log = (...args: Parameters) => (task.output = formatMessage(...args))
+ /**
+ * Start defining your data migration from here.
+ *
+ * To log output, use `task.output = 'Message to log'`
+ *
+ * This will be written to `stdout` and to a log file in `/prisma/migration-logs/`
+ */
+
+ // Do stuff
+
+ const newSchemas = await prisma.attributeSupplementDataSchema.createMany({
+ data: [
+ {
+ id: 'asds_01HTJ6EZ419CVQCY4N8KAYYCMB',
+ tag: 'access-instruction-email',
+ name: 'Access Instruction - Email',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'Email Address',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.email)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42H4YZ68RM1WDSEK89',
+ tag: 'access-instruction-file',
+ name: 'Access Instruction - File',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'File URL',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.file)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42PTQZG4SPQDBHM8BN',
+ tag: 'access-instruction-link',
+ name: 'Access Instruction - Link',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'Link URL',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.link)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42YHJT3CY7SK8N2WW6',
+ tag: 'access-instruction-location',
+ name: 'Access Instruction - Location',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'Location',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.location)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42HTHRFAH0JDC2ZXG1',
+ tag: 'access-instruction-other',
+ name: 'Access Instruction - Other',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'Other',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.other)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42TRHK12DVNDK8ZT02',
+ tag: 'access-instruction-phone',
+ name: 'Access Instruction - Phone',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'Phone Number',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.phone)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42GGZJ0R4S73T5KCNK',
+ tag: 'access-instruction-sms',
+ name: 'Access Instruction - SMS',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'SMS Details',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.sms)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42V93736GW3DPM34V8',
+ tag: 'access-instruction-whatsapp',
+ name: 'Access Instruction - WhatsApp',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'WhatsApp Number',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.whatsapp)),
+ },
+ {
+ id: 'asds_01HTJ6EZ42KNM6A4BC02PXZFJH',
+ tag: 'access-instruction-publicTransport',
+ name: 'Access Instruction - Public Transport',
+ definition: [
+ {
+ key: 'access_value',
+ name: 'access_value',
+ type: FieldType.text,
+ label: 'Public Transport Details',
+ },
+ ],
+ schema: JsonInputOrNull.parse(zodToJsonSchema(schemas.publicTransport)),
+ },
+ ],
+ skipDuplicates: true,
+ })
+
+ log(`Created ${newSchemas.count} Access Instruction Schema records.`)
+
+ const updateData: UpdateData[] = [
+ {
+ where: 'attr_01GW2HHFVKFM4TDY4QRK4AR2ZW',
+ schemaId: 'asds_01HTJ6EZ419CVQCY4N8KAYYCMB',
+ },
+ {
+ where: 'attr_01GW2HHFVKMRHFD8SMDAZM3SSM',
+ schemaId: 'asds_01HTJ6EZ42H4YZ68RM1WDSEK89',
+ },
+ {
+ where: 'attr_01GW2HHFVMYXMS8ARA3GE7HZFD',
+ schemaId: 'asds_01HTJ6EZ42PTQZG4SPQDBHM8BN',
+ },
+ {
+ where: 'attr_01GW2HHFVMH6AE94EXN7T5A87C',
+ schemaId: 'asds_01HTJ6EZ42YHJT3CY7SK8N2WW6',
+ },
+ {
+ where: 'attr_01GW2HHFVMKTFWCKBVVFJ5GMY0',
+ schemaId: 'asds_01HTJ6EZ42TRHK12DVNDK8ZT02',
+ },
+ {
+ where: 'attr_01GW2HHFVMSX7T1WDNZ5QEHKWT',
+ schemaId: 'asds_01HTJ6EZ42KNM6A4BC02PXZFJH',
+ },
+ {
+ where: 'attr_01H6PRPT32KX1JPGJSHAF2D89C',
+ schemaId: 'asds_01HTJ6EZ42GGZJ0R4S73T5KCNK',
+ },
+ {
+ where: 'attr_01GW2HHFVMMF19AX2KPBTMV6P3',
+ schemaId: 'asds_01HTJ6EZ42HTHRFAH0JDC2ZXG1',
+ },
+ {
+ where: 'attr_01H6PRPTWRS80XFM77EMHKZ787',
+ schemaId: 'asds_01HTJ6EZ42V93736GW3DPM34V8',
+ },
+ ]
+
+ const updateDefinitions = await prisma.$transaction(
+ updateData.map(({ where, schemaId }) =>
+ prisma.attribute.update({
+ where: { id: where },
+ data: { requiredSchemaId: schemaId },
+ })
+ )
+ )
+ log(`Updated ${updateDefinitions.length} Attribute records.`)
+
+ /**
+ * DO NOT REMOVE BELOW
+ *
+ * This writes a record to the DB to register that this migration has run successfully.
+ */
+ await jobPostRunner(jobDef)
+ },
+ def: jobDef,
+} satisfies MigrationJob
+
+type UpdateData = {
+ where: string
+ schemaId: string
+}
diff --git a/packages/db/prisma/data-migrations/index.ts b/packages/db/prisma/data-migrations/index.ts
index 5b9ff1dbaf..f49dd92b67 100644
--- a/packages/db/prisma/data-migrations/index.ts
+++ b/packages/db/prisma/data-migrations/index.ts
@@ -12,4 +12,5 @@ export * from './2024-03-08_update-alerts-and-org-urls/index'
export * from './2024-03-11_hide-locations'
export * from './2024-03-15_update-dead-links/index'
export * from './2024-03-21_attribute-supplement-schemas'
+export * from './2024-04-03_access-instruction-schemas'
// codegen:end
diff --git a/packages/ui/mockData/json/fieldOpt.attributesByCategory.json b/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
index 7f269e70be..b77f210d66 100644
--- a/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
+++ b/packages/ui/mockData/json/fieldOpt.attributesByCategory.json
@@ -1 +1 @@
-[{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3YJ2AWADHVKG79BQ0","attributeName":"at-capacity","attributeKey":"additional.at-capacity","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4D5ZHFMAE7852GB4P","attributeName":"geo-near-public-transit","attributeKey":"additional.geo-near-public-transit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV48VQJBMFA05QCBBV9","attributeName":"geo-public-transit-description","attributeKey":"additional.geo-public-transit-description","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","attributeName":"has-confidentiality-policy","attributeKey":"additional.has-confidentiality-policy","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5Q7XN2ZNTYFR1AD3M","attributeName":"offers-remote-services","attributeKey":"additional.offers-remote-services","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:globe","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","attributeName":"time-walk-in","attributeKey":"additional.time-walk-in","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5FYXQNGTPAQB7G2TF","attributeName":"wheelchair-accessible","attributeKey":"additional.wheelchair-accessible","attributeNs":"attribute","interpolationValues":{"true":"Accessible","false":"Not Accessible"},"icon":"carbon:accessibility","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":true,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAMR6RDV6M69H4KN3T","attributeName":"info","attributeKey":"alerts.info","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:information-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAKP7C6JKJ342ZM35M","attributeName":"warn","attributeKey":"alerts.warn","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:warning-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGWKWB53HWAAHQ9AAZ","attributeName":"cost-fees","attributeKey":"cost.cost-fees","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"formSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}],"dataSchema":{"anyOf":[{"type":"object","required":["min"],"properties":{"min":{"type":"number"}}},{"type":"object","required":["max"],"properties":{"max":{"type":"number"}}},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}}}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","attributeName":"cost-free","attributeKey":"cost.cost-free","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P951P0V3CR807P8KRH82S1","attributeName":"elders","attributeKey":"crisis-support-community.elders","attributeNs":"attribute","interpolationValues":null,"icon":"🌳","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P8T277D0C8HFQA6N09FJWD","attributeName":"general-lgbtq","attributeKey":"crisis-support-community.general-lgbtq","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️🌈","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","attributeName":"elig-age","attributeKey":"eligibility.elig-age","attributeNs":"attribute","interpolationValues":{"max":"Under{{max}}","min":"{{min}} and older","range":"{{min}} -{{max}}"},"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"formSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}],"dataSchema":{"anyOf":[{"type":"object","required":["min"],"properties":{"min":{"type":"number"}}},{"type":"object","required":["max"],"properties":{"max":{"type":"number"}}},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}}}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJDKVF1HV7559CNZCY","attributeName":"other-describe","attributeKey":"eligibility.other-describe","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH9DPBZ968VXGE50E7","attributeName":"req-medical-insurance","attributeKey":"eligibility.req-medical-insurance","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHZ599M48CMSPGDCSC","attributeName":"req-photo-id","attributeKey":"eligibility.req-photo-id","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH0GQK0GAJR5D952V3","attributeName":"req-proof-of-age","attributeKey":"eligibility.req-proof-of-age","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHEVX4PMNN077ASQMG","attributeName":"req-proof-of-income","attributeKey":"eligibility.req-proof-of-income","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHGMVCAY1G5BWF1PFB","attributeName":"req-proof-of-residence","attributeKey":"eligibility.req-proof-of-residence","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJH8MADHYTHBV54CER","attributeName":"req-referral","attributeKey":"eligibility.req-referral","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","attributeName":"time-appointment-required","attributeKey":"eligibility.time-appointment-required","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJGDDWTR5D0C8BY357","attributeName":"all-languages-by-interpreter","attributeKey":"lang.all-languages-by-interpreter","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJF09GXY5N5CKMSANJ","attributeName":"american-sign-language","attributeKey":"lang.american-sign-language","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","attributeName":"lang-offered","attributeKey":"lang.lang-offered","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":true,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRH531R2HAV8DMDZSC","attributeName":"corp-law-firm","attributeKey":"userlawpractice.corp-law-firm","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVSE2074QZJ4SKEW74J","attributeName":"law-other","attributeKey":"userlawpractice.law-other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"otherDescribe","canAttachTo":["USER"],"formSchema":[{"key":"other","label":"Other","name":"other","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["other"],"properties":{"other":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRS8XEJ3TJBBEQJ707","attributeName":"law-school-clinic","attributeKey":"userlawpractice.law-school-clinic","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRFPRQCQHNJA6BM3XP","attributeName":"legal-nonprofit","attributeKey":"userlawpractice.legal-nonprofit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNPKMHYK12DDRVC1VJ","attributeName":"bipoc-led","attributeKey":"orgleader.bipoc-led","attributeNs":"attribute","interpolationValues":null,"icon":"🤎","iconBg":"#F1DD7F","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3JX2J7REFFT5NAMS","attributeName":"black-led","attributeKey":"orgleader.black-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":"#C77E54","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNHMF72WHVKRF6W4TA","attributeName":"immigrant-led","attributeKey":"orgleader.immigrant-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":"#79ADD7","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3RYX9JMXDZSQZM70","attributeName":"trans-led","attributeKey":"orgleader.trans-led","attributeNs":"attribute","interpolationValues":null,"icon":"️🏳️⚧️","iconBg":"#705890","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKFM4TDY4QRK4AR2ZW","attributeName":"accessemail","attributeKey":"serviceaccess.accessemail","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKMRHFD8SMDAZM3SSM","attributeName":"accessfile","attributeKey":"serviceaccess.accessfile","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","attributeName":"accesslink","attributeKey":"serviceaccess.accesslink","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","attributeName":"accesslocation","attributeKey":"serviceaccess.accesslocation","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","attributeName":"accessphone","attributeKey":"serviceaccess.accessphone","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"accessInstructions","canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["access_type","instructions"],"properties":{"access_type":{"enum":["email","file","link","location","other","phone"],"type":"string"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMSX7T1WDNZ5QEHKWT","attributeName":"accesspublictransit","attributeKey":"serviceaccess.accesspublictransit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMMF19AX2KPBTMV6P3","attributeName":"accesstext","attributeKey":"serviceaccess.accesstext","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPCVX8F3B7M30ZJEHW","attributeName":"asylum-seekers","attributeKey":"srvfocus.asylum-seekers","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ7SYGD3KM8WP9X50B","attributeName":"gender-nc","attributeKey":"srvfocus.gender-nc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVRMQFJ9AMA633SQQGV","attributeName":"hiv-comm","attributeKey":"srvfocus.hiv-comm","attributeNs":"attribute","interpolationValues":null,"icon":"💛","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPTK9555WHJHDBDA2J","attributeName":"immigrant-comm","attributeKey":"srvfocus.immigrant-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPJERY0GS9D7F56A23","attributeName":"resettled-refugees","attributeKey":"srvfocus.resettled-refugees","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ8AGBKBBZJWTHNP2F","attributeName":"spanish-speakers","attributeKey":"srvfocus.spanish-speakers","attributeNs":"attribute","interpolationValues":null,"icon":"🗣️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQX4M8DY1FSAYSJSSK","attributeName":"trans-fem","attributeKey":"srvfocus.trans-fem","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQEFWW42MBAD64BWXZ","attributeName":"trans-masc","attributeKey":"srvfocus.trans-masc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQVEGH6W3A2ANH1QZE","attributeName":"trans-youth-focus","attributeKey":"srvfocus.trans-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TK83N5E52PPP828SD88KP8","attributeName":"userserviceprovider.case-mananger","attributeKey":"userserviceprovider.case-mananger","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTTZ83PZR61M37R8R7","attributeName":"userserviceprovider.community-org","attributeKey":"userserviceprovider.community-org","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVSPXWJJPFG9DKXESEK","attributeName":"userserviceprovider.healthcare","attributeKey":"userserviceprovider.healthcare","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM092CFVG6H0MR148AVAP7","attributeName":"userserviceprovider.lawyer","attributeKey":"userserviceprovider.lawyer","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AJHVK8TSR8JNFANFNZ7","attributeName":"userserviceprovider.other","attributeKey":"userserviceprovider.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09EG0G84NXH40G5TESB5","attributeName":"userserviceprovider.paralegal","attributeKey":"userserviceprovider.paralegal","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09RAK024ZDZQ6FSY0TXB","attributeName":"userserviceprovider.social-worker","attributeKey":"userserviceprovider.social-worker","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTN6MSCMBW740Y7HN1","attributeName":"userserviceprovider.student-club","attributeKey":"userserviceprovider.student-club","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0A19DD6S97DNH76ZVP40","attributeName":"userserviceprovider.teacher","attributeKey":"userserviceprovider.teacher","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AA4CZXJJHMXHE1PHMVV","attributeName":"userserviceprovider.therapist-counselor","attributeKey":"userserviceprovider.therapist-counselor","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKM2PSHFWVFM0TWX1P","categoryName":"system","categoryDisplay":"System","attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","attributeName":"incompatible-info","attributeKey":"sys.incompatible-info","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"incompatibleData","canAttachTo":["LOCATION","ORGANIZATION","SERVICE","USER"],"formSchema":[{"key":"incompatible","label":"Incompatible","name":"incompatible","type":"text"}],"dataSchema":{"type":"array","items":{"type":"object","additionalProperties":{}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01HNG5BPYJADWX4YFVNENS3TRD","categoryName":"target-population","categoryDisplay":"Target Population","attributeId":"attr_01HNG5GDC5MXW30F32FWJNJ98C","attributeName":"tpop-other","attributeKey":"tpop.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null}]
+[{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3YJ2AWADHVKG79BQ0","attributeName":"at-capacity","attributeKey":"additional.at-capacity","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4D5ZHFMAE7852GB4P","attributeName":"geo-near-public-transit","attributeKey":"additional.geo-near-public-transit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV48VQJBMFA05QCBBV9","attributeName":"geo-public-transit-description","attributeKey":"additional.geo-public-transit-description","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV3BADK80TG0DXXFPMM","attributeName":"has-confidentiality-policy","attributeKey":"additional.has-confidentiality-policy","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5Q7XN2ZNTYFR1AD3M","attributeName":"offers-remote-services","attributeKey":"additional.offers-remote-services","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:globe","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV4TM7H5V6FHWA7S9JK","attributeName":"time-walk-in","attributeKey":"additional.time-walk-in","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFV3DJ380F351SKB0B74","categoryName":"additional-information","categoryDisplay":"Additional Information","attributeId":"attr_01GW2HHFV5FYXQNGTPAQB7G2TF","attributeName":"wheelchair-accessible","attributeKey":"additional.wheelchair-accessible","attributeNs":"attribute","interpolationValues":{"true":"Accessible","false":"Not Accessible"},"icon":"carbon:accessibility","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":true,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAMR6RDV6M69H4KN3T","attributeName":"info","attributeKey":"alerts.info","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:information-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GYSVX1N9T91BJYSHRDPCHJBS","categoryName":"alerts","categoryDisplay":"Alerts","attributeId":"attr_01GYSVX1NAKP7C6JKJ342ZM35M","attributeName":"warn","attributeKey":"alerts.warn","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:warning-filled","iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGWKWB53HWAAHQ9AAZ","attributeName":"cost-fees","attributeKey":"cost.cost-fees","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"formSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}],"dataSchema":{"anyOf":[{"type":"object","required":["min","max"],"properties":{"max":{"not":{}},"min":{"type":"number"}},"additionalProperties":false},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"not":{}}},"additionalProperties":false},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}},"additionalProperties":false}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVFKNMYPN8F86M0H576","categoryName":"cost","categoryDisplay":"Cost","attributeId":"attr_01GW2HHFVGDTNW9PDQNXK6TF1T","attributeName":"cost-free","attributeKey":"cost.cost-free","attributeNs":"attribute","interpolationValues":null,"icon":"carbon:piggy-bank","iconBg":null,"badgeRender":"ATTRIBUTE","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P951P0V3CR807P8KRH82S1","attributeName":"elders","attributeKey":"crisis-support-community.elders","attributeNs":"attribute","interpolationValues":null,"icon":"🌳","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01H6P8T277D0C8HFQA6N09FJWD","attributeName":"general-lgbtq","attributeKey":"crisis-support-community.general-lgbtq","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️🌈","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01H6P8SSY4C141YH7BAC1RW7KJ","categoryName":"crisis-support-community","categoryDisplay":"Crisis Support Community","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGSAZXGR4JAVHEK6ZC","attributeName":"elig-age","attributeKey":"eligibility.elig-age","attributeNs":"attribute","interpolationValues":{"max":"Under{{max}}","min":"{{min}} and older","range":"{{min}} -{{max}}"},"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"numMinMaxOrRange","canAttachTo":["SERVICE"],"formSchema":[{"key":"min","label":"Min","name":"min","type":"number"},{"key":"max","label":"Max","name":"max","type":"number"}],"dataSchema":{"anyOf":[{"type":"object","required":["min","max"],"properties":{"max":{"not":{}},"min":{"type":"number"}},"additionalProperties":false},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"not":{}}},"additionalProperties":false},{"type":"object","required":["min","max"],"properties":{"max":{"type":"number"},"min":{"type":"number"}},"additionalProperties":false}],"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJDKVF1HV7559CNZCY","attributeName":"other-describe","attributeKey":"eligibility.other-describe","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH9DPBZ968VXGE50E7","attributeName":"req-medical-insurance","attributeKey":"eligibility.req-medical-insurance","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHZ599M48CMSPGDCSC","attributeName":"req-photo-id","attributeKey":"eligibility.req-photo-id","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVH0GQK0GAJR5D952V3","attributeName":"req-proof-of-age","attributeKey":"eligibility.req-proof-of-age","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHEVX4PMNN077ASQMG","attributeName":"req-proof-of-income","attributeKey":"eligibility.req-proof-of-income","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVHGMVCAY1G5BWF1PFB","attributeName":"req-proof-of-residence","attributeKey":"eligibility.req-proof-of-residence","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVJH8MADHYTHBV54CER","attributeName":"req-referral","attributeKey":"eligibility.req-referral","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVGHPW1Y72SA8377623","categoryName":"eligibility-requirements","categoryDisplay":"Eligibility Requirements","attributeId":"attr_01GW2HHFVGJ5GD2WHNJDPSFNRW","attributeName":"time-appointment-required","attributeKey":"eligibility.time-appointment-required","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJGDDWTR5D0C8BY357","attributeName":"all-languages-by-interpreter","attributeKey":"lang.all-languages-by-interpreter","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJF09GXY5N5CKMSANJ","attributeName":"american-sign-language","attributeKey":"lang.american-sign-language","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVJQQ68XGSBXM976BDF","categoryName":"languages","categoryDisplay":"Languages","attributeId":"attr_01GW2HHFVJ8K180CNX339BTXM2","attributeName":"lang-offered","attributeKey":"lang.lang-offered","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":"LIST","requireText":false,"requireLanguage":true,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE","ORGANIZATION","LOCATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRH531R2HAV8DMDZSC","attributeName":"corp-law-firm","attributeKey":"userlawpractice.corp-law-firm","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVSE2074QZJ4SKEW74J","attributeName":"law-other","attributeKey":"userlawpractice.law-other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"otherDescribe","canAttachTo":["USER"],"formSchema":[{"key":"other","label":"Other","name":"other","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","required":["other"],"properties":{"other":{"type":"string"}}}},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRS8XEJ3TJBBEQJ707","attributeName":"law-school-clinic","attributeKey":"userlawpractice.law-school-clinic","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVRSN3W3GYZZ43WCW24","categoryName":"law-practice-options","categoryDisplay":"Law Practice Options","attributeId":"attr_01GW2HHFVRFPRQCQHNJA6BM3XP","attributeName":"legal-nonprofit","attributeKey":"userlawpractice.legal-nonprofit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNPKMHYK12DDRVC1VJ","attributeName":"bipoc-led","attributeKey":"orgleader.bipoc-led","attributeNs":"attribute","interpolationValues":null,"icon":"🤎","iconBg":"#F1DD7F","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3JX2J7REFFT5NAMS","attributeName":"black-led","attributeKey":"orgleader.black-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":"#C77E54","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVNHMF72WHVKRF6W4TA","attributeName":"immigrant-led","attributeKey":"orgleader.immigrant-led","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":"#79ADD7","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVMNHV2ZS5875JWCRJ7","categoryName":"organization-leadership","categoryDisplay":"Organization Leadership","attributeId":"attr_01GW2HHFVN3RYX9JMXDZSQZM70","attributeName":"trans-led","attributeKey":"orgleader.trans-led","attributeNs":"attribute","interpolationValues":null,"icon":"️🏳️⚧️","iconBg":"#705890","badgeRender":"LEADER","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKFM4TDY4QRK4AR2ZW","attributeName":"accessemail","attributeKey":"serviceaccess.accessemail","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"access-instruction-email","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"Email Address","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"email","default":"email"},"access_value":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVKMRHFD8SMDAZM3SSM","attributeName":"accessfile","attributeKey":"serviceaccess.accessfile","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"access-instruction-file","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"File URL","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"file","default":"file"},"access_value":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMYXMS8ARA3GE7HZFD","attributeName":"accesslink","attributeKey":"serviceaccess.accesslink","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"access-instruction-link","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"Link URL","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"link","default":"link"},"access_value":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMH6AE94EXN7T5A87C","attributeName":"accesslocation","attributeKey":"serviceaccess.accesslocation","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"access-instruction-location","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"Location","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"location","default":"location"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMKTFWCKBVVFJ5GMY0","attributeName":"accessphone","attributeKey":"serviceaccess.accessphone","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"access-instruction-phone","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"Phone Number","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"phone","default":"phone"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMSX7T1WDNZ5QEHKWT","attributeName":"accesspublictransit","attributeKey":"serviceaccess.accesspublictransit","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":"access-instruction-publicTransport","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"Public Transport Details","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"publicTransit","default":"publicTransit"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVKAMMGPD71H90XRJ38","categoryName":"service-access-instructions","categoryDisplay":"Service Access Instructions","attributeId":"attr_01GW2HHFVMMF19AX2KPBTMV6P3","attributeName":"accesstext","attributeKey":"serviceaccess.accesstext","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":"access-instruction-other","canAttachTo":["SERVICE"],"formSchema":[{"key":"access_value","label":"Other","name":"access_value","type":"text"}],"dataSchema":{"type":"object","$schema":"http://json-schema.org/draft-07/schema#","properties":{"access_type":{"type":"string","const":"other","default":"other"},"access_value":{"type":["string","null"]},"instructions":{"type":"string"}},"additionalProperties":false}},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPCVX8F3B7M30ZJEHW","attributeName":"asylum-seekers","attributeKey":"srvfocus.asylum-seekers","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVN72D7XEBZZJXCJQXQ","attributeName":"bipoc-comm","attributeKey":"srvfocus.bipoc-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️✊🏿","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ7SYGD3KM8WP9X50B","attributeName":"gender-nc","attributeKey":"srvfocus.gender-nc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVRMQFJ9AMA633SQQGV","attributeName":"hiv-comm","attributeKey":"srvfocus.hiv-comm","attributeNs":"attribute","interpolationValues":null,"icon":"💛","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPTK9555WHJHDBDA2J","attributeName":"immigrant-comm","attributeKey":"srvfocus.immigrant-comm","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQCZPA3Z5GW6J3MQHW","attributeName":"lgbtq-youth-focus","attributeKey":"srvfocus.lgbtq-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPJERY0GS9D7F56A23","attributeName":"resettled-refugees","attributeKey":"srvfocus.resettled-refugees","attributeNs":"attribute","interpolationValues":null,"icon":"️️🌎","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQ8AGBKBBZJWTHNP2F","attributeName":"spanish-speakers","attributeKey":"srvfocus.spanish-speakers","attributeNs":"attribute","interpolationValues":null,"icon":"🗣️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVPSYBCYF37B44WP6CZ","attributeName":"trans-comm","attributeKey":"srvfocus.trans-comm","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQX4M8DY1FSAYSJSSK","attributeName":"trans-fem","attributeKey":"srvfocus.trans-fem","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQEFWW42MBAD64BWXZ","attributeName":"trans-masc","attributeKey":"srvfocus.trans-masc","attributeNs":"attribute","interpolationValues":null,"icon":"🏳️⚧️","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVNXMNJNV47BF2BPM1R","categoryName":"service-focus","categoryDisplay":"Service Focus","attributeId":"attr_01GW2HHFVQVEGH6W3A2ANH1QZE","attributeName":"trans-youth-focus","attributeKey":"srvfocus.trans-youth-focus","attributeNs":"attribute","interpolationValues":null,"icon":"🌱","iconBg":null,"badgeRender":"COMMUNITY","requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["LOCATION","ORGANIZATION","SERVICE"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TK83N5E52PPP828SD88KP8","attributeName":"userserviceprovider.case-mananger","attributeKey":"userserviceprovider.case-mananger","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTTZ83PZR61M37R8R7","attributeName":"userserviceprovider.community-org","attributeKey":"userserviceprovider.community-org","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVSPXWJJPFG9DKXESEK","attributeName":"userserviceprovider.healthcare","attributeKey":"userserviceprovider.healthcare","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM092CFVG6H0MR148AVAP7","attributeName":"userserviceprovider.lawyer","attributeKey":"userserviceprovider.lawyer","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AJHVK8TSR8JNFANFNZ7","attributeName":"userserviceprovider.other","attributeKey":"userserviceprovider.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09EG0G84NXH40G5TESB5","attributeName":"userserviceprovider.paralegal","attributeKey":"userserviceprovider.paralegal","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM09RAK024ZDZQ6FSY0TXB","attributeName":"userserviceprovider.social-worker","attributeKey":"userserviceprovider.social-worker","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01GW2HHFVTN6MSCMBW740Y7HN1","attributeName":"userserviceprovider.student-club","attributeKey":"userserviceprovider.student-club","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0A19DD6S97DNH76ZVP40","attributeName":"userserviceprovider.teacher","attributeKey":"userserviceprovider.teacher","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVSQWE2Y2RF3DT2VEYX","categoryName":"service-provider-options","categoryDisplay":"Service Provider Options","attributeId":"attr_01H2TM0AA4CZXJJHMXHE1PHMVV","attributeName":"userserviceprovider.therapist-counselor","attributeKey":"userserviceprovider.therapist-counselor","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["USER"],"formSchema":null,"dataSchema":null},{"categoryId":"attc_01GW2HHFVKM2PSHFWVFM0TWX1P","categoryName":"system","categoryDisplay":"System","attributeId":"attr_01GW2HHFVK8KPRGKYFSSM5ECPQ","attributeName":"incompatible-info","attributeKey":"sys.incompatible-info","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":false,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":true,"dataSchemaName":"incompatibleData","canAttachTo":["LOCATION","ORGANIZATION","SERVICE","USER"],"formSchema":[{"key":"incompatible","label":"Incompatible","name":"incompatible","type":"text"}],"dataSchema":{"type":"array","items":{"type":"object","additionalProperties":{}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"categoryId":"attc_01HNG5BPYJADWX4YFVNENS3TRD","categoryName":"target-population","categoryDisplay":"Target Population","attributeId":"attr_01HNG5GDC5MXW30F32FWJNJ98C","attributeName":"tpop-other","attributeKey":"tpop.other","attributeNs":"attribute","interpolationValues":null,"icon":null,"iconBg":null,"badgeRender":null,"requireText":true,"requireLanguage":false,"requireGeo":false,"requireBoolean":false,"requireData":false,"dataSchemaName":null,"canAttachTo":["SERVICE"],"formSchema":null,"dataSchema":null}]
\ No newline at end of file
From aef1312c71688683f355b3b83ce60d05ac7e1871 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 11:04:45 -0400
Subject: [PATCH 54/61] remove devtool
---
apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx | 2 --
apps/app/src/pages/org/[slug]/edit.tsx | 2 --
packages/ui/modals/dataPortal/Attributes/index.tsx | 2 --
3 files changed, 6 deletions(-)
diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
index 096b830a89..55e0d4e6fc 100644
--- a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
+++ b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
@@ -1,4 +1,3 @@
-import { DevTool } from '@hookform/devtools'
import { createStyles, Grid, Stack, Tabs, Title } from '@mantine/core'
import { compareArrayVals } from 'crud-object-diff'
import { type InferGetServerSidePropsType, type NextPage } from 'next'
@@ -225,7 +224,6 @@ const OrgLocationPage: NextPage
-
>
)
diff --git a/apps/app/src/pages/org/[slug]/edit.tsx b/apps/app/src/pages/org/[slug]/edit.tsx
index d25cca9aea..24238f6fb3 100644
--- a/apps/app/src/pages/org/[slug]/edit.tsx
+++ b/apps/app/src/pages/org/[slug]/edit.tsx
@@ -1,4 +1,3 @@
-import { DevTool } from '@hookform/devtools'
import { Grid, Stack } from '@mantine/core'
import { useElementSize } from '@mantine/hooks'
import { t } from 'i18next'
@@ -127,7 +126,6 @@ const OrganizationPage: NextPageWithOptions
-
>
diff --git a/packages/ui/modals/dataPortal/Attributes/index.tsx b/packages/ui/modals/dataPortal/Attributes/index.tsx
index b642dfc2bd..1eb3332831 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.tsx
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import {
Box,
@@ -209,7 +208,6 @@ const AttributeModalBody = forwardRef(
handler.open()} {...props} />
-
)
}
From df5d9b5f203e9c7f2c177def5fd13a5ebf3f3dfd Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 11:08:09 -0400
Subject: [PATCH 55/61] misc cleanup
---
packages/ui/.storybook/main.ts | 1 -
packages/ui/components/sections/Navbar.tsx | 16 +---------------
packages/ui/hooks/useEditMode.ts | 6 +-----
3 files changed, 2 insertions(+), 21 deletions(-)
diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts
index d44faf6a5d..d8fb696a3d 100644
--- a/packages/ui/.storybook/main.ts
+++ b/packages/ui/.storybook/main.ts
@@ -113,7 +113,6 @@ const config: StorybookConfig = {
const plugin = new I18NextHMRPlugin({
localesDir: path.resolve(__dirname, '../../../apps/app/public/locales'),
})
- // @ts-expect-error It doesn't like the i18nHMRPlugin for some reason...
Array.isArray(config.plugins) ? config.plugins.push(plugin) : (config.plugins = [plugin])
}
diff --git a/packages/ui/components/sections/Navbar.tsx b/packages/ui/components/sections/Navbar.tsx
index a622f3a324..6734c8faa1 100644
--- a/packages/ui/components/sections/Navbar.tsx
+++ b/packages/ui/components/sections/Navbar.tsx
@@ -63,11 +63,7 @@ const EditModeBar = () => {
const apiUtils = api.useUtils()
const { unsaved, saveEvent } = useEditMode()
const { t } = useTranslation('common')
- const router = useRouter<
- | '/org/[slug]/edit'
- | '/org/[slug]/[orgLocationId]/edit'
- | '/org/[slug]/[orgLocationId]/edit/[orgServiceId]'
- >()
+ const router = useRouter<'/org/[slug]/edit' | '/org/[slug]/[orgLocationId]/edit'>()
const { orgLocationId, slug, orgServiceId } = router.query
const apiQuery = (() => {
@@ -128,9 +124,6 @@ const EditModeBar = () => {
case '/org/[slug]/[orgLocationId]/edit': {
return '/org/[slug]/[orgLocationId]'
}
- case '/org/[slug]/[orgLocationId]/edit/[orgServiceId]': {
- return '/org/[slug]/[orgLocationId]'
- }
default: {
return router.pathname
}
@@ -234,10 +227,3 @@ export const Navbar = () => {
>
)
}
-
-type NavbarProps = {
- editMode?: boolean
- editModeRef?: {
- handleEditSubmit: (handler: () => void) => void
- }
-}
diff --git a/packages/ui/hooks/useEditMode.ts b/packages/ui/hooks/useEditMode.ts
index 411bd67119..efff401d83 100644
--- a/packages/ui/hooks/useEditMode.ts
+++ b/packages/ui/hooks/useEditMode.ts
@@ -9,11 +9,7 @@ export const useEditMode = () => {
if (!ctx) {
throw new Error('useEditMode must be used within a EditModeProvider')
}
- const editPaths: (typeof router.pathname)[] = [
- '/org/[slug]/edit',
- '/org/[slug]/[orgLocationId]/edit',
- '/org/[slug]/[orgLocationId]/edit/[orgServiceId]',
- ]
+ const editPaths: (typeof router.pathname)[] = ['/org/[slug]/edit', '/org/[slug]/[orgLocationId]/edit']
const isEditMode = editPaths.includes(router.pathname)
From 507a096943f964b3d6ca21cd31bfc68b4b26ed93 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 11:41:50 -0400
Subject: [PATCH 56/61] sonarlint config
---
.vscode/settings.json | 6 ------
InReach.code-workspace | 6 ++++++
apps/app/.vscode/settings.json | 6 ------
apps/web/.vscode/settings.json | 6 ------
lambdas/.vscode/settings.json | 6 ------
packages/analytics/.vscode/settings.json | 6 ------
packages/api/.vscode/settings.json | 6 ------
packages/auth/.vscode/settings.json | 6 ------
packages/config/.vscode/settings.json | 6 ------
packages/crowdin/.vscode/settings.json | 6 ------
packages/db/.vscode/settings.json | 6 ------
packages/env/.vscode/settings.json | 6 ------
packages/eslint-config/.vscode/settings.json | 6 ------
packages/ui/.vscode/settings.json | 4 ----
packages/util/.vscode/settings.json | 6 ------
15 files changed, 6 insertions(+), 82 deletions(-)
delete mode 100644 .vscode/settings.json
delete mode 100644 apps/app/.vscode/settings.json
delete mode 100644 apps/web/.vscode/settings.json
delete mode 100644 lambdas/.vscode/settings.json
delete mode 100644 packages/analytics/.vscode/settings.json
delete mode 100644 packages/api/.vscode/settings.json
delete mode 100644 packages/auth/.vscode/settings.json
delete mode 100644 packages/config/.vscode/settings.json
delete mode 100644 packages/crowdin/.vscode/settings.json
delete mode 100644 packages/db/.vscode/settings.json
delete mode 100644 packages/env/.vscode/settings.json
delete mode 100644 packages/eslint-config/.vscode/settings.json
delete mode 100644 packages/util/.vscode/settings.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/InReach.code-workspace b/InReach.code-workspace
index 7cc0b6def4..23477ab00c 100644
--- a/InReach.code-workspace
+++ b/InReach.code-workspace
@@ -224,6 +224,12 @@
"typescript.tsdk": "✨ InReach (root)/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.workspaceSymbols.scope": "allOpenProjects",
+ "sonarlint.connectedMode.project": {
+ "connectionId": "inreach",
+ "projectKey": "weareinreach_InReach",
+ },
+ "sonarlint.output.showAnalyzerLogs": true,
+ "sonarlint.output.showVerboseLogs": false,
},
"launch": {
"configurations": [
diff --git a/apps/app/.vscode/settings.json b/apps/app/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/apps/app/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/apps/web/.vscode/settings.json b/apps/web/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/apps/web/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/lambdas/.vscode/settings.json b/lambdas/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/lambdas/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/analytics/.vscode/settings.json b/packages/analytics/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/analytics/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/api/.vscode/settings.json b/packages/api/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/api/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/auth/.vscode/settings.json b/packages/auth/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/auth/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/config/.vscode/settings.json b/packages/config/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/config/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/crowdin/.vscode/settings.json b/packages/crowdin/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/crowdin/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/db/.vscode/settings.json b/packages/db/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/db/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/env/.vscode/settings.json b/packages/env/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/env/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/eslint-config/.vscode/settings.json b/packages/eslint-config/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/eslint-config/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
diff --git a/packages/ui/.vscode/settings.json b/packages/ui/.vscode/settings.json
index 331acd2d09..a2c3de751c 100644
--- a/packages/ui/.vscode/settings.json
+++ b/packages/ui/.vscode/settings.json
@@ -2,9 +2,5 @@
"i18n-ally.localesPaths": "../../apps/app/public/locales",
"[json]": {
"editor.codeActionsOnSave": { "source.fixAll": "never" }
- },
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
}
}
diff --git a/packages/util/.vscode/settings.json b/packages/util/.vscode/settings.json
deleted file mode 100644
index 7c77f0a9b1..0000000000
--- a/packages/util/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sonarlint.connectedMode.project": {
- "connectionId": "inreach",
- "projectKey": "weareinreach_InReach"
- }
-}
From 20cd842a630c35f91972d2f431f3e09cefc4e7a9 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 12:11:41 -0400
Subject: [PATCH 57/61] cleanup
---
.../2024-03-21_attribute-supplement-schemas.ts | 2 +-
.../data-migrations/2024-04-03_access-instruction-schemas.ts | 2 +-
packages/ui/components/data-portal/ServiceSelect/index.tsx | 4 +---
packages/ui/modals/Service/index.tsx | 2 --
4 files changed, 3 insertions(+), 7 deletions(-)
diff --git a/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts b/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts
index 83ede3629e..b2c65b4c3c 100644
--- a/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts
+++ b/packages/db/prisma/data-migrations/2024-03-21_attribute-supplement-schemas.ts
@@ -6,7 +6,7 @@ import { formatMessage } from '~db/prisma/common'
import { type MigrationJob } from '~db/prisma/dataMigrationRunner'
import { createLogger, type JobDef, jobPostRunner } from '~db/prisma/jobPreRun'
import { JsonInputOrNull } from '~db/zod_util'
-import { type FieldAttributes, FieldType } from '~db/zod_util/attributeSupplement'
+import { FieldType } from '~db/zod_util/attributeSupplement'
/** Define the job metadata here. */
const jobDef: JobDef = {
diff --git a/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts b/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
index e42f6c196e..c8291ebbb3 100644
--- a/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
+++ b/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
@@ -6,7 +6,7 @@ import { formatMessage } from '~db/prisma/common'
import { type MigrationJob } from '~db/prisma/dataMigrationRunner'
import { createLogger, type JobDef, jobPostRunner } from '~db/prisma/jobPreRun'
import { JsonInputOrNull } from '~db/zod_util'
-import { type FieldAttributes, FieldType } from '~db/zod_util/attributeSupplement'
+import { FieldType } from '~db/zod_util/attributeSupplement'
/** Define the job metadata here. */
const jobDef: JobDef = {
diff --git a/packages/ui/components/data-portal/ServiceSelect/index.tsx b/packages/ui/components/data-portal/ServiceSelect/index.tsx
index 7309096982..6a434fa7ff 100644
--- a/packages/ui/components/data-portal/ServiceSelect/index.tsx
+++ b/packages/ui/components/data-portal/ServiceSelect/index.tsx
@@ -1,11 +1,10 @@
-import { Box, type BoxProps, createStyles, Drawer, Group, rem, Stack, Text, Title } from '@mantine/core'
+import { Box, type BoxProps, createStyles, Drawer, Group, rem, Stack, Title } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { useTranslation } from 'next-i18next'
import { type FieldValues, type UseControllerProps, useFormState } from 'react-hook-form'
import { Checkbox } from 'react-hook-form-mantine'
import { Breadcrumb } from '~ui/components/core/Breadcrumb'
-import { useCustomVariant } from '~ui/hooks/useCustomVariant'
import { trpc as api } from '~ui/lib/trpcClient'
const useStyles = createStyles((theme) => ({
@@ -38,7 +37,6 @@ export const ServiceSelect = ({
const { data } = api.component.ServiceSelect.useQuery()
const { classes } = useStyles()
const { t } = useTranslation('services')
- const variants = useCustomVariant()
const form = useFormState({ control, name })
const serviceGroups = data ? (
diff --git a/packages/ui/modals/Service/index.tsx b/packages/ui/modals/Service/index.tsx
index 91b798fe45..f5617a1a1d 100644
--- a/packages/ui/modals/Service/index.tsx
+++ b/packages/ui/modals/Service/index.tsx
@@ -32,8 +32,6 @@ import { ModalTitle, type ModalTitleProps } from '../ModalTitle'
* - Validate data display against finalized data structure.
*/
-const CONTACTS = ['phone', 'email', 'website'] as const
-
const ServiceModalBody = forwardRef(({ serviceId, ...props }, ref) => {
const slug = useSlug()
const { data, status } = api.service.forServiceModal.useQuery(serviceId)
From 4b8a4103685ef31049a9835df2876f7c995937fc Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 14:14:28 -0400
Subject: [PATCH 58/61] fix schema
---
.../db/generated/attributeSupplementSchema.ts | 68 ++++++++++++++++++-
.../2024-04-03_access-instruction-schemas.ts | 21 ++++--
2 files changed, 83 insertions(+), 6 deletions(-)
diff --git a/packages/db/generated/attributeSupplementSchema.ts b/packages/db/generated/attributeSupplementSchema.ts
index 88bb6c7d30..bc714e9bc4 100644
--- a/packages/db/generated/attributeSupplementSchema.ts
+++ b/packages/db/generated/attributeSupplementSchema.ts
@@ -1,19 +1,83 @@
import { z } from 'zod'
export const attributeSupplementSchema = {
+ 'access-instruction-email': z
+ .object({
+ access_type: z.literal('email').default('email'),
+ access_value: z.union([z.string().email(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-file': z
+ .object({
+ access_type: z.literal('file').default('file'),
+ access_value: z.union([z.string().url(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-link': z
+ .object({
+ access_type: z.literal('link').default('link'),
+ access_value: z.union([z.string().url(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-location': z
+ .object({
+ access_type: z.literal('location').default('location'),
+ access_value: z.union([z.string(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-other': z
+ .object({
+ access_type: z.literal('other').default('other'),
+ access_value: z.union([z.string(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-phone': z
+ .object({
+ access_type: z.literal('phone').default('phone'),
+ access_value: z.union([z.string(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-publicTransport': z
+ .object({
+ access_type: z.literal('publicTransit').default('publicTransit'),
+ access_value: z.union([z.string(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
accessInstructions: z.object({
access_type: z.enum(['email', 'file', 'link', 'location', 'other', 'phone']),
access_value: z.union([z.string(), z.null()]).optional(),
instructions: z.string(),
}),
+ 'access-instruction-sms': z
+ .object({
+ sms_body: z.string().optional(),
+ access_type: z.literal('sms').default('sms'),
+ access_value: z.union([z.string(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
+ 'access-instruction-whatsapp': z
+ .object({
+ access_type: z.literal('whatsapp').default('whatsapp'),
+ access_value: z.union([z.string(), z.null()]).optional(),
+ instructions: z.string().optional(),
+ })
+ .strict(),
currency: z.object({ cost: z.number(), currency: z.union([z.string(), z.null()]).optional() }).strict(),
incompatibleData: z.array(z.record(z.any())),
number: z.object({ num: z.number() }),
numMax: z.object({ max: z.number() }),
numMin: z.object({ min: z.number() }),
numMinMaxOrRange: z.union([
- z.object({ max: z.never(), min: z.number() }).strict(),
- z.object({ max: z.number(), min: z.never() }).strict(),
+ z.object({ max: z.never().optional(), min: z.number() }).strict(),
+ z.object({ max: z.number(), min: z.never().optional() }).strict(),
z.object({ max: z.number(), min: z.number() }).strict(),
]),
numRange: z.object({ max: z.number(), min: z.number() }),
diff --git a/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts b/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
index c8291ebbb3..c0abc9f4bc 100644
--- a/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
+++ b/packages/db/prisma/data-migrations/2024-04-03_access-instruction-schemas.ts
@@ -65,6 +65,15 @@ const schemas = {
instructions: z.string().optional(),
}),
}
+const numMinMaxOrRange = z
+ .union([
+ z.object({ min: z.number(), max: z.never().optional() }),
+ z.object({ min: z.never().optional(), max: z.number() }),
+ z.object({ min: z.number(), max: z.number() }),
+ ])
+ .refine(({ min, max }) => (min && max ? min < max : true), {
+ message: 'min must be less than max',
+ })
/**
* Job export - this variable MUST be UNIQUE
@@ -258,14 +267,18 @@ export const job20240403_access_instruction_schemas = {
},
]
- const updateDefinitions = await prisma.$transaction(
- updateData.map(({ where, schemaId }) =>
+ const updateDefinitions = await prisma.$transaction([
+ ...updateData.map(({ where, schemaId }) =>
prisma.attribute.update({
where: { id: where },
data: { requiredSchemaId: schemaId },
})
- )
- )
+ ),
+ prisma.attributeSupplementDataSchema.update({
+ where: { id: 'asds_01GYX872BWWCGTZREHDT2AFF9D' },
+ data: { schema: JsonInputOrNull.parse(zodToJsonSchema(numMinMaxOrRange)) },
+ }),
+ ])
log(`Updated ${updateDefinitions.length} Attribute records.`)
/**
From 965d28282cdcd2290ff771840e4f00bb55bc6032 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 14:38:03 -0400
Subject: [PATCH 59/61] fix sonarlint issues
---
packages/ui/mockData/fieldOpt.ts | 2 +-
packages/ui/modals/CoverageArea/index.tsx | 2 +-
.../dataPortal/Attributes/SelectionItem.tsx | 2 +-
.../modals/dataPortal/Attributes/fields.tsx | 41 ++++---------------
.../ui/modals/dataPortal/Attributes/index.tsx | 36 ++++++----------
5 files changed, 23 insertions(+), 60 deletions(-)
diff --git a/packages/ui/mockData/fieldOpt.ts b/packages/ui/mockData/fieldOpt.ts
index 90c4d0faf1..7ac50baa08 100644
--- a/packages/ui/mockData/fieldOpt.ts
+++ b/packages/ui/mockData/fieldOpt.ts
@@ -38,7 +38,7 @@ const queryAttributesByCategory: MockAPIHandler<'fieldOpt', 'attributesByCategor
})
}
- return attributesByCategory as ApiOutput['fieldOpt']['attributesByCategory']
+ return attributesByCategory
}
const queryLanguages: MockAPIHandler<'fieldOpt', 'languages'> = async (query) => {
diff --git a/packages/ui/modals/CoverageArea/index.tsx b/packages/ui/modals/CoverageArea/index.tsx
index 16f4861785..d2f511a053 100644
--- a/packages/ui/modals/CoverageArea/index.tsx
+++ b/packages/ui/modals/CoverageArea/index.tsx
@@ -25,7 +25,7 @@ const reduceDistType = (data: { tsNs: string; tsKey: string }[] | undefined, t:
prev.add(translated)
return prev
}, new Set())
- return [...valueSet].sort().join('/')
+ return [...valueSet].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
}
const CoverageAreaModal = forwardRef(
diff --git a/packages/ui/modals/dataPortal/Attributes/SelectionItem.tsx b/packages/ui/modals/dataPortal/Attributes/SelectionItem.tsx
index 6df9390cbb..9b120eeaf8 100644
--- a/packages/ui/modals/dataPortal/Attributes/SelectionItem.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/SelectionItem.tsx
@@ -23,7 +23,7 @@ export const SelectionItem = forwardRef(
>
{icon && isValidIcon(icon) && }
{icon && !isValidIcon(icon) && {icon}}
- {label && label}
+ {label}
)
diff --git a/packages/ui/modals/dataPortal/Attributes/fields.tsx b/packages/ui/modals/dataPortal/Attributes/fields.tsx
index 0701a79ecf..80dcd092ce 100644
--- a/packages/ui/modals/dataPortal/Attributes/fields.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/fields.tsx
@@ -105,16 +105,16 @@ const GeoItem = forwardRef(({ flag, label, ...prop
GeoItem.displayName = 'GeoItem'
const SuppGeo = ({ countryOnly }: SuppGeoProps) => {
- const { control, ...form } = useFormContext()
+ // const { control } = useFormContext()
const { t } = useTranslation(['country', 'gov-dist'])
// const [primaryList, setPrimaryList] = useState()
const [secondaryList, setSecondaryList] = useState()
const [tertiaryList, setTertiaryList] = useState<
NonNullable[number]['subDistricts'] | undefined
>()
- const [primarySearch, onPrimarySearch] = useState(null)
- const [secondarySearch, onSecondarySearch] = useState(null)
- const [tertiarySearch, onTertiarySearch] = useState(null)
+ const [primarySearch, setPrimarySearch] = useState(null)
+ const [secondarySearch, setSecondarySearch] = useState(null)
+ const [tertiarySearch, setTertiarySearch] = useState(null)
// const [finalValue, setFinalValue] = useState(null)
// const [fieldName, setFieldName] = useState | undefined>(
@@ -137,33 +137,6 @@ const SuppGeo = ({ countryOnly }: SuppGeoProps) => {
})
const primaryList = countryOnly ? countryList : distByCountryList
- // useEffect(() => {
- // if (form.values.supplement?.govDistId && secondaryList) {
- // const secondarySelected = secondaryList.find(({ id }) => id === form.values.supplement?.govDistId)
- // if (secondarySelected && secondarySelected.subDistricts.length) {
- // form.setFieldValue('supplement.subDistId', undefined)
- // onTertiarySearch('')
- // setTertiaryList(secondarySelected.subDistricts)
- // } else if (secondarySelected && !secondarySelected.subDistricts.length) {
- // setTertiaryList(undefined)
- // }
- // }
- // // eslint-disable-next-line react-hooks/exhaustive-deps
- // }, [form.values.supplement?.govDistId])
-
- // useEffect(() => {
- // if (form.values.supplement?.countryId && !countryOnly && primaryList) {
- // const primarySelected = primaryList.find(({ value }) => value === form.values.supplement?.countryId)
- // if (primarySelected && primarySelected.districts?.length) {
- // setSecondaryList(primarySelected.districts)
- // } else if (primarySelected && !primarySelected.districts?.length) {
- // onSecondarySearch('')
- // setSecondaryList(undefined)
- // }
- // }
- // // eslint-disable-next-line react-hooks/exhaustive-deps
- // }, [form.values.supplement?.countryId, countryOnly])
-
if (!primaryList && !countries.isSuccess) return <>Loading...>
return (
@@ -172,7 +145,7 @@ const SuppGeo = ({ countryOnly }: SuppGeoProps) => {
data={primaryList}
searchable
searchValue={primarySearch ?? undefined}
- onSearchChange={onPrimarySearch}
+ onSearchChange={setPrimarySearch}
itemComponent={GeoItem}
// control={control}
name='countryId'
@@ -186,7 +159,7 @@ const SuppGeo = ({ countryOnly }: SuppGeoProps) => {
}))}
searchable
searchValue={secondarySearch ?? undefined}
- onSearchChange={onSecondarySearch}
+ onSearchChange={setSecondarySearch}
itemComponent={GeoItem}
// control={control}
name='govDistId'
@@ -201,7 +174,7 @@ const SuppGeo = ({ countryOnly }: SuppGeoProps) => {
}))}
searchable
searchValue={tertiarySearch ?? undefined}
- onSearchChange={onTertiarySearch}
+ onSearchChange={setTertiarySearch}
itemComponent={GeoItem}
// {...form.getInputProps('supplement.subDistId')}
/>
diff --git a/packages/ui/modals/dataPortal/Attributes/index.tsx b/packages/ui/modals/dataPortal/Attributes/index.tsx
index 1eb3332831..4701b58ef4 100644
--- a/packages/ui/modals/dataPortal/Attributes/index.tsx
+++ b/packages/ui/modals/dataPortal/Attributes/index.tsx
@@ -1,21 +1,17 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
import { zodResolver } from '@hookform/resolvers/zod'
import {
Box,
type ButtonProps,
createPolymorphicComponent,
- Group,
Select as MantineSelect,
Modal,
Skeleton,
Stack,
- Text,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
-import { type JSONSchemaType } from 'ajv'
import { useTranslation } from 'next-i18next'
-import { forwardRef, useMemo, useRef, useState } from 'react'
-import { FormProvider, useForm, useFormState } from 'react-hook-form'
+import { forwardRef, type ReactNode, useMemo, useRef, useState } from 'react'
+import { FormProvider, useForm } from 'react-hook-form'
import { type ApiOutput } from '@weareinreach/api'
import { generateId } from '@weareinreach/db/lib/idGen'
@@ -43,7 +39,7 @@ const AttributeModalBody = forwardRef(
const selectAttrRef = useRef(null)
// #region tRPC
- const utils = api.useUtils()
+ // const utils = api.useUtils()
const [attrCat, setAttrCat] = useState()
const { data: attributesByCategory, ...attributesByCategoryApi } =
api.fieldOpt.attributesByCategory.useQuery(undefined, {
@@ -90,7 +86,6 @@ const AttributeModalBody = forwardRef(
null
)
const [supplements, setSupplements] = useState(supplementDefaults)
- const [supplementSchema, setSupplementSchema] = useState | null>(null)
const saveAttributes = api.organization.attachAttribute.useMutation()
// #endregion
@@ -104,16 +99,13 @@ const AttributeModalBody = forwardRef(
...parentRecord,
},
})
- const formState = useFormState({ control: form.control })
+ // const formState = useFormState({ control: form.control })
const selectHandler = (e: string | null) => {
if (e === null) {
setSupplements(supplementDefaults)
setSelectedAttr(null)
form.resetField('attributeId')
- if (supplementSchema !== null) {
- setSupplementSchema(null)
- }
return
}
const item = attributesByCategory?.find(({ value }) => value === e)
@@ -131,11 +123,6 @@ const AttributeModalBody = forwardRef(
data: requireData ?? false,
}
setSupplements(suppRequired)
- if (requireData && item.dataSchema) {
- setSupplementSchema(item.dataSchema)
- }
-
- // return
}
form.setValue('attributeId', item.value)
selectAttrRef.current && (selectAttrRef.current.value = '')
@@ -150,7 +137,14 @@ const AttributeModalBody = forwardRef(
// #region Title & Selected items display
const modalTitle =
- const needsSupplement = Object.values(supplements).includes(true)
+ // const needsSupplement = Object.values(supplements).includes(true)
+
+ const inputContainerWithSkeleton = (children: ReactNode) => (
+
+ {children}
+
+ )
+
return (
handler.close()}>
@@ -188,11 +182,7 @@ const AttributeModalBody = forwardRef(
ref={selectAttrRef}
clearable
onChange={selectHandler}
- inputContainer={(children) => (
-
- {children}
-
- )}
+ inputContainer={inputContainerWithSkeleton}
/>
{supplements.boolean && }
From 5f3a7be256df9f929b6ba5e14264aca3950dbba2 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 14:42:32 -0400
Subject: [PATCH 60/61] add sonarlint recommendation
---
InReach.code-workspace | 1 +
1 file changed, 1 insertion(+)
diff --git a/InReach.code-workspace b/InReach.code-workspace
index 23477ab00c..e2abad320e 100644
--- a/InReach.code-workspace
+++ b/InReach.code-workspace
@@ -19,6 +19,7 @@
"figma.figma-vscode-extension",
"yoavbls.pretty-ts-errors",
"quick-lint.quick-lint-js",
+ "sonarsource.sonarlint-vscode",
],
},
"folders": [
From f4aeb24db4098199dbd0ff05489321ec9efda671 Mon Sep 17 00:00:00 2001
From: Joe Karow <58997957+JoeKarow@users.noreply.github.com>
Date: Wed, 3 Apr 2024 14:54:16 -0400
Subject: [PATCH 61/61] fix sonar/deepsource issues
---
packages/ui/modals/LoginSignUp/index.tsx | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/ui/modals/LoginSignUp/index.tsx b/packages/ui/modals/LoginSignUp/index.tsx
index 8acbe9c95b..5629cff8d1 100644
--- a/packages/ui/modals/LoginSignUp/index.tsx
+++ b/packages/ui/modals/LoginSignUp/index.tsx
@@ -111,7 +111,7 @@ const LcrQuestion3 = (
i18nKey='sign-up.lcr-screen3'
components={{
Link: (
-
+
.
),
@@ -279,7 +279,7 @@ const SignUpModalBody = forwardRef((pro
i18nKey={error === '1' ? 'sign-up.lcr-error1' : 'sign-up.lcr-error2'}
components={{
Link: (
-
+
.
),
@@ -467,8 +467,8 @@ export const LoginBody = forwardRef(
const router = useRouter()
const loginErrors = new Map([[401, t('login.error-username-password')]])
const LoginSchema = z.object({
- email: z.string().email({ message: t('form-error-enter-valid-email') as string }),
- password: z.string().min(1, t('form-error-password-blank') as string),
+ email: z.string().email({ message: t('form-error-enter-valid-email') }),
+ password: z.string().min(1, t('form-error-password-blank')),
})
const form = useForm({
validate: zodResolver(LoginSchema),
@@ -503,13 +503,13 @@ export const LoginBody = forwardRef(
{hideTitle ? null : {t('log-in')}}