diff --git a/drizzle/0018_blushing_daimon_hellstrom.sql b/drizzle/0018_blushing_daimon_hellstrom.sql new file mode 100644 index 00000000..60357f25 --- /dev/null +++ b/drizzle/0018_blushing_daimon_hellstrom.sql @@ -0,0 +1 @@ +ALTER TABLE `place` MODIFY COLUMN `featuresId` int NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0018_snapshot.json b/drizzle/meta/0018_snapshot.json new file mode 100644 index 00000000..9fe15b7c --- /dev/null +++ b/drizzle/meta/0018_snapshot.json @@ -0,0 +1,1014 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "d3bfad8c-c019-4c9b-a4c7-ac10c6854fc3", + "prevId": "8d05399e-7f45-488d-a39f-74201ee934fc", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "session_sessionToken": { + "name": "session_sessionToken", + "columns": [ + "sessionToken" + ] + } + }, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {} + }, + "feature": { + "name": "feature", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "amountOfPeople": { + "name": "amountOfPeople", + "type": "enum('none','few','some','many','crowded')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "difficulty": { + "name": "difficulty", + "type": "enum('accessible','normal','smallEffort','hard','dangerous')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "groundType": { + "name": "groundType", + "type": "enum('sand','pebbles','rocks','concrete','dirt','pavimented')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasBus": { + "name": "hasBus", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasParking": { + "name": "hasParking", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parkingSpaces": { + "name": "parkingSpaces", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasToilet": { + "name": "hasToilet", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasRestaurant": { + "name": "hasRestaurant", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasDrinkingWater": { + "name": "hasDrinkingWater", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasShower": { + "name": "hasShower", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasLifeguard": { + "name": "hasLifeguard", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasLeisure": { + "name": "hasLeisure", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dimensions": { + "name": "dimensions", + "type": "tinytext", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price": { + "name": "price", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priceUnit": { + "name": "priceUnit", + "type": "enum('eur','eur/minute','eur/hour','eur/day')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isCovered": { + "name": "isCovered", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timeToArrive": { + "name": "timeToArrive", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "placeToArriveFrom": { + "name": "placeToArriveFrom", + "type": "enum('townCenter','parking','beach','road')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isFreeWithLocalStamp": { + "name": "isFreeWithLocalStamp", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "difficultyNotes": { + "name": "difficultyNotes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priceNotes": { + "name": "priceNotes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "feature_id": { + "name": "feature_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "feature_translation": { + "name": "feature_translation", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "feature_id": { + "name": "feature_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "difficultyNotes": { + "name": "difficultyNotes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priceNotes": { + "name": "priceNotes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "feature_translation_id": { + "name": "feature_translation_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "image": { + "name": "image", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "key": { + "name": "key", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "captureDate": { + "name": "captureDate", + "type": "date", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "alt": { + "name": "alt", + "type": "tinytext", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blurDataURL": { + "name": "blurDataURL", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "image_id": { + "name": "image_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "placeListToPlace": { + "name": "placeListToPlace", + "columns": { + "placeListId": { + "name": "placeListId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "placeId": { + "name": "placeId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "addedAt": { + "name": "addedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "placeListToPlace_placeId_placeListId_pk": { + "name": "placeListToPlace_placeId_placeListId_pk", + "columns": [ + "placeId", + "placeListId" + ] + } + }, + "uniqueConstraints": {} + }, + "placeList": { + "name": "placeList", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "placeList_id": { + "name": "placeList_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "placeCategory": { + "name": "placeCategory", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "icon": { + "name": "icon", + "type": "tinytext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "tinytext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hasVisitMission": { + "name": "hasVisitMission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "name": { + "name": "name", + "type": "tinytext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "namePlural": { + "name": "namePlural", + "type": "tinytext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nameGender": { + "name": "nameGender", + "type": "enum('masculine','feminine')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "placeCategory_id": { + "name": "placeCategory_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "placeCategory_translation": { + "name": "placeCategory_translation", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "placeCategory_id": { + "name": "placeCategory_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "tinytext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "namePlural": { + "name": "namePlural", + "type": "tinytext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nameGender": { + "name": "nameGender", + "type": "enum('masculine','feminine')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "placeCategory_translation_id": { + "name": "placeCategory_translation_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "place": { + "name": "place", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "mainImageId": { + "name": "mainImageId", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location": { + "name": "location", + "type": "POINT SRID 25831", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mainCategoryId": { + "name": "mainCategoryId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "featuresId": { + "name": "featuresId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verificationRequirementsId": { + "name": "verificationRequirementsId", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "tinytext", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_id": { + "name": "place_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "placeToPlaceCategory": { + "name": "placeToPlaceCategory", + "columns": { + "placeId": { + "name": "placeId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "categoryId": { + "name": "categoryId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "placeToPlaceCategory_categoryId_placeId_pk": { + "name": "placeToPlaceCategory_categoryId_placeId_pk", + "columns": [ + "categoryId", + "placeId" + ] + } + }, + "uniqueConstraints": {} + }, + "place_translation": { + "name": "place_translation", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "place_id": { + "name": "place_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "tinytext", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_translation_id": { + "name": "place_translation_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hashedPassword": { + "name": "hashedPassword", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "visitedPlaceListId": { + "name": "visitedPlaceListId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_id": { + "name": "user_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ] + } + } + }, + "verificationRequirement": { + "name": "verificationRequirement", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "isLocationRequired": { + "name": "isLocationRequired", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "maxLocationDistance": { + "name": "maxLocationDistance", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationRequirement_id": { + "name": "verificationRequirement_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "placeId": { + "name": "placeId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validatedOn": { + "name": "validatedOn", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "deviceLocation": { + "name": "deviceLocation", + "type": "POINT SRID 25831", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deviceLocationAccuracy": { + "name": "deviceLocationAccuracy", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verification_id": { + "name": "verification_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 80bd7b04..5d5a7a01 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -127,6 +127,13 @@ "when": 1707913502270, "tag": "0017_tricky_hellfire_club", "breakpoints": true + }, + { + "idx": 18, + "version": "5", + "when": 1708040528072, + "tag": "0018_blushing_daimon_hellstrom", + "breakpoints": true } ] } \ No newline at end of file diff --git a/e2e-tests/explore.test.ts b/e2e-tests/explore.test.ts index 184c6c48..b629fe1a 100644 --- a/e2e-tests/explore.test.ts +++ b/e2e-tests/explore.test.ts @@ -9,7 +9,11 @@ test('has title', async ({ page }) => { test('place link works', async ({ page }) => { await page.goto('/en/explore') - await page.getByRole('link', { name: 'Sa Tuna' }).click() + const link = page.getByRole('link', { name: 'Sa Riera Beach' }) + await link.scrollIntoViewIfNeeded() + await link.click() - await expect(page.getByRole('heading', { name: 'Sa Tuna' })).toBeVisible() + await expect( + page.getByRole('heading', { name: 'Sa Riera Beach' }) + ).toBeVisible() }) diff --git a/src/app/[locale]/(app)/explore/_components/features-block.tsx b/src/app/[locale]/(app)/explore/_components/features-block.tsx index d0680a8d..e41ede2a 100644 --- a/src/app/[locale]/(app)/explore/_components/features-block.tsx +++ b/src/app/[locale]/(app)/explore/_components/features-block.tsx @@ -5,13 +5,13 @@ import { useTranslations } from 'next-intl' import { FC, PropsWithChildren } from 'react' import { MarkdownContent } from '~/components/generic/markdown-content' import { IntlMessageKeys } from '~/helpers/types' -import { pick } from '~/helpers/utilities' import { Features } from '~/server/db/constants/features' import { featureDisplayGroups, getCompositeFeatureKey, + getCompositeFeatureValues, getIconForFeature, - getMoreInfoContent, + useFeatureDisplay, } from '~/server/db/constants/features-display-data' export const FeaturesBlock: FC<{ features: Features; className?: string }> = ({ @@ -20,118 +20,124 @@ export const FeaturesBlock: FC<{ features: Features; className?: string }> = ({ }) => { const t = useTranslations('data.features') + const { allValuesNull, allValuesNullInGroup, getMoreInfoContent } = + useFeatureDisplay(features) + + if (allValuesNull) return null + return ( - {featureDisplayGroups.map((group) => ( - - {group.featureDisplays.map((featureDisplay) => { - if ('hidden' in featureDisplay && featureDisplay.hidden) { - return null - } - - switch (featureDisplay.type) { - case 'number': - case 'text': { - const value = features[featureDisplay.key] - if (value === null || value === undefined) return null - - if ('showRaw' in featureDisplay && featureDisplay.showRaw) { - return ( - - ) - } else { - return ( - }`, - { - value, - } - )} - moreInfo={getMoreInfoContent(featureDisplay, features)} - /> - ) - } - } - case 'markdown': { - const value = features[featureDisplay.key] - if (value === null || value === undefined) return null - return ( - - ) - } - case 'enum': { - const value = features[featureDisplay.key] - if (value === null || value === undefined) return null - - return ( - }` - )} - moreInfo={getMoreInfoContent(featureDisplay, features)} - /> - ) - } - case 'boolean': { - const value = features[featureDisplay.key] - if (value === null || value === undefined) return null - - return ( - - ) - } - case 'composite': { - const rawValues = pick( - features, - featureDisplay.keys - ) as Parameters<(typeof featureDisplay)['transformValues']>[0] - - if ( - featureDisplay.showIf && - !featureDisplay.showIf(rawValues) - ) { + {featureDisplayGroups.map( + (group) => + !allValuesNullInGroup[group.key] && ( + + {group.featureDisplays.map((featureDisplay) => { + if ('hidden' in featureDisplay && featureDisplay.hidden) { return null } - const values = featureDisplay?.transformValues(rawValues) - - const key = getCompositeFeatureKey(featureDisplay.keys) - return ( - - ) - } - } - })} - - ))} + switch (featureDisplay.type) { + case 'number': + case 'text': { + const value = features[featureDisplay.key] + if (value === null || value === undefined) return null + + if ( + 'showRaw' in featureDisplay && + featureDisplay.showRaw + ) { + return ( + + ) + } else { + return ( + }`, + { + value, + } + )} + moreInfo={getMoreInfoContent(featureDisplay)} + /> + ) + } + } + case 'markdown': { + const value = features[featureDisplay.key] + if (value === null || value === undefined) return null + return ( + + ) + } + case 'enum': { + const value = features[featureDisplay.key] + if (value === null || value === undefined) return null + + return ( + }` + )} + moreInfo={getMoreInfoContent(featureDisplay)} + /> + ) + } + case 'boolean': { + const value = features[featureDisplay.key] + if (value === null || value === undefined) return null + + return ( + + ) + } + case 'composite': { + const values = getCompositeFeatureValues( + featureDisplay, + features + ) + + if (!values) return null + + const key = getCompositeFeatureKey(featureDisplay.keys) + return ( + + ) + } + } + })} + + ) + )} ) diff --git a/src/app/[locale]/(app)/explore/_components/place-details.tsx b/src/app/[locale]/(app)/explore/_components/place-details.tsx index 38958cb2..ba11fb2e 100644 --- a/src/app/[locale]/(app)/explore/_components/place-details.tsx +++ b/src/app/[locale]/(app)/explore/_components/place-details.tsx @@ -84,9 +84,7 @@ export const PlaceDetails: FC<{ className="mt-2" /> - {place.features && ( - - )} + {place.content ? ( diff --git a/src/app/[locale]/admin/places/__components/place-form.tsx b/src/app/[locale]/admin/places/__components/place-form.tsx index dacd8ceb..2c85b730 100644 --- a/src/app/[locale]/admin/places/__components/place-form.tsx +++ b/src/app/[locale]/admin/places/__components/place-form.tsx @@ -65,7 +65,7 @@ export const PlaceForm: FC<{ location: undefined, mainImageId: undefined, content: undefined, - features: undefined, + features: {}, }, }) diff --git a/src/schemas/places.ts b/src/schemas/places.ts index 0e00807f..4c93034d 100644 --- a/src/schemas/places.ts +++ b/src/schemas/places.ts @@ -49,7 +49,7 @@ export const createPlaceSchema = z.object({ ), mainImageId: z.number().int().optional().nullable(), content: z.string().optional(), - features: createInsertSchema(features).optional().nullable(), + features: createInsertSchema(features), }) export const editPlaceSchema = createPlaceSchema.extend({ diff --git a/src/server/api/router/admin/places.ts b/src/server/api/router/admin/places.ts index aba318f8..91600b9c 100644 --- a/src/server/api/router/admin/places.ts +++ b/src/server/api/router/admin/places.ts @@ -176,27 +176,20 @@ export const placesAdminRouter = router({ await db.transaction(async (tx) => { const placeId = Number(input.id) - let featuresId = ( + const featuresId = ( await tx .selectDistinct({ featuresId: places.featuresId }) .from(places) .where(eq(places.id, placeId)) )[0].featuresId - if (featuresId === null) { - const insertFeaturesResult = await tx - .insert(features) - .values({ ...input.features }) - featuresId = Number(insertFeaturesResult.insertId) - } else { - await tx - .update(features) - .set({ - ...input.features, - id: featuresId, - }) - .where(eq(features.id, featuresId)) - } + await tx + .update(features) + .set({ + id: featuresId, + ...input.features, + }) + .where(eq(features.id, featuresId)) await tx .update(places) diff --git a/src/server/db/constants/features-display-data.ts b/src/server/db/constants/features-display-data.ts index 19abfab9..040bc179 100644 --- a/src/server/db/constants/features-display-data.ts +++ b/src/server/db/constants/features-display-data.ts @@ -31,9 +31,12 @@ import { IconToolsKitchen2Off, IconWalk, } from '@tabler/icons-react' -import { InferInsertModel, InferSelectModel } from 'drizzle-orm' +import { useMemo } from 'react' import { Join } from 'ts-toolbelt/out/String/Join' +import { pick } from '~/helpers/utilities' import { + FeaturesInsert, + FeaturesSelect, PriceUnit, amountOfPeople, difficulty, @@ -41,7 +44,6 @@ import { placeToArriveFrom, priceUnit, } from '~/server/db/constants/features' -import { features } from '~/server/db/schema' const typeFeatureDisplay = (feature: F) => feature @@ -244,17 +246,6 @@ export const featureDisplayGroups = [ featureDisplays: AnyFeature[] }[] -export function getMoreInfoContent( - featureDisplay: AnyFeature, - features: Features | null | undefined -) { - if (!features) return null - if (!('moreInfoFeatureKey' in featureDisplay)) return null - if (!featureDisplay.moreInfoFeatureKey) return null - - return features[featureDisplay.moreInfoFeatureKey] -} - export function getIconForFeature( featureDisplay: F, value: string | boolean | null | undefined @@ -269,11 +260,87 @@ export function getCompositeFeatureKey>(keys: Keys) { return keys.join('-') as Join } +function getCompositeFeatureRawValues( + featureDisplay: T, + features: Features +) { + return pick(features, featureDisplay.keys) as Parameters< + NonNullable + >[0] +} + +function shouldShow( + featureDisplay: T, + rawValues: Parameters>[0] +) { + if (!featureDisplay.showIf) return true + return featureDisplay.showIf(rawValues) +} + +export function getCompositeFeatureValues( + featureDisplay: T, + features: Features +) { + const rawValues = getCompositeFeatureRawValues(featureDisplay, features) + if (!shouldShow(featureDisplay, rawValues)) return null + + if (!featureDisplay?.transformValues) return rawValues + return featureDisplay?.transformValues(rawValues) +} + +export function useFeatureDisplay(features: Features | null | undefined) { + const allValuesNullInGroup = useMemo(() => { + return Object.fromEntries( + featureDisplayGroups.map((group) => [ + group.key, + group.featureDisplays.every(featureDisplayIsEmpty), + ]) + ) as Record<(typeof featureDisplayGroups)[number]['key'], boolean> + }, [features]) + + const allValuesNull = useMemo(() => { + return featureDisplayGroups.every( + (group) => allValuesNullInGroup[group.key] + ) + }, [allValuesNullInGroup]) + + function getMoreInfoContent(featureDisplay: AnyFeature) { + if (!features) return null + if (!('moreInfoFeatureKey' in featureDisplay)) return null + if (!featureDisplay.moreInfoFeatureKey) return null + + return features[featureDisplay.moreInfoFeatureKey] + } + + function featureDisplayIsEmpty(featureDisplay: AnyFeature) { + if (!features) return true + + if ('hidden' in featureDisplay && featureDisplay.hidden) { + return true + } + + if (featureDisplay.type === 'composite') { + const rawValues = getCompositeFeatureRawValues(featureDisplay, features) + return !shouldShow(featureDisplay, rawValues) + } + + return ( + features[featureDisplay.key] === null || + features[featureDisplay.key] === undefined + ) + } + + return { + allValuesNull, + allValuesNullInGroup, + getMoreInfoContent, + featureDisplayIsEmpty, + } +} + // ------------------- types ------------------- -type Features = - | InferSelectModel - | InferInsertModel +type Features = FeaturesInsert | FeaturesSelect type FeatureKey = Exclude diff --git a/src/server/db/constants/features.ts b/src/server/db/constants/features.ts index 776a1d2e..340e176c 100644 --- a/src/server/db/constants/features.ts +++ b/src/server/db/constants/features.ts @@ -1,4 +1,4 @@ -import { InferSelectModel } from 'drizzle-orm' +import { InferInsertModel, InferSelectModel } from 'drizzle-orm' import { features } from '../schema' // Don't reorder these values, they are used to generate the database enum. @@ -45,4 +45,6 @@ export const placeToArriveFrom = [ ] as const export type PlaceToArriveFrom = (typeof placeToArriveFrom)[number] -export type Features = InferSelectModel +export type FeaturesSelect = InferSelectModel +export type FeaturesInsert = InferInsertModel +export type Features = FeaturesSelect diff --git a/src/server/db/schema/places.ts b/src/server/db/schema/places.ts index 02fbaf4b..3dca8429 100644 --- a/src/server/db/schema/places.ts +++ b/src/server/db/schema/places.ts @@ -31,7 +31,7 @@ export const { mainImageId: int('mainImageId'), location: pointType('location').notNull(), mainCategoryId: int('mainCategoryId').notNull(), - featuresId: int('featuresId'), + featuresId: int('featuresId').notNull(), verificationRequirementsId: int('verificationRequirementsId'), }, translatableColumns: {