@la-main-verte/shared-types 1.0.82 → 1.0.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@la-main-verte/shared-types",
3
- "version": "1.0.82",
3
+ "version": "1.0.86",
4
4
  "description": "Shared TypeScript interfaces for frontend of la-main-verte app",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Audience targeting shared types
3
+ * -------------------------------
4
+ * An Audience is a reusable, named set of targeting criteria. Membership is
5
+ * precomputed into a join table, so these shapes describe the *definition* of
6
+ * an audience, not its resolved members.
7
+ *
8
+ * Criteria are heterogeneous and open-ended: each `type` has its own `params`.
9
+ * The canonical runtime validation lives in the zod discriminated union at
10
+ * `server/domains/audience/criterion.schema.ts`; keep these types in sync with it.
11
+ */
12
+
13
+ /** How an audience combines its criteria. */
14
+ export type AudienceCombinator = 'all' | 'any'
15
+
16
+ /** Member within `radiusKm` of a point. `label` is a human-readable origin (e.g. a city name). */
17
+ export interface GeoRadiusCriterion {
18
+ type: 'geo_radius'
19
+ params: {
20
+ latitude: number
21
+ longitude: number
22
+ radiusKm: number
23
+ label?: string
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Member whose `countryCode` matches (ISO 3166-1 alpha-2, e.g. "CA", "US", "FR").
29
+ * @see documentation/geographic-targeting.md
30
+ */
31
+ export interface CountryCriterion {
32
+ type: 'country'
33
+ params: { countryCode: string }
34
+ }
35
+
36
+ /**
37
+ * Member whose `provinceCode` matches (ISO 3166-2 subdivision code, e.g. "CA-QC", "US-CA", "FR-IDF").
38
+ * @see documentation/geographic-targeting.md
39
+ */
40
+ export interface ProvinceCriterion {
41
+ type: 'province'
42
+ params: { provinceCode: string }
43
+ }
44
+
45
+ /** Member whose `city` matches (case-insensitive). */
46
+ export interface CityCriterion {
47
+ type: 'city'
48
+ params: { cityName: string }
49
+ }
50
+
51
+ /** Member owns at least one GardenMap of the given category (e.g. "greenhouse"). */
52
+ export interface GardenMapCategoryCriterion {
53
+ type: 'garden_map_category'
54
+ params: { category: string }
55
+ }
56
+
57
+ /**
58
+ * Behavioral: member who was active on at least `minDistinctDays` distinct
59
+ * calendar days (counted from SessionEvents) within the last `windowDays`.
60
+ * Time-relative — evaluated at recompute time, so the daily refresh cron keeps
61
+ * the trailing window honest.
62
+ */
63
+ export interface ActivityDistinctDaysCriterion {
64
+ type: 'activity_distinct_days'
65
+ params: { minDistinctDays: number; windowDays: number }
66
+ }
67
+
68
+ export type AudienceCriterion =
69
+ | GeoRadiusCriterion
70
+ | CountryCriterion
71
+ | ProvinceCriterion
72
+ | CityCriterion
73
+ | GardenMapCategoryCriterion
74
+ | ActivityDistinctDaysCriterion
75
+
76
+ export type AudienceCriterionType = AudienceCriterion['type']
77
+
78
+ export interface AudienceI {
79
+ id: number
80
+ name: string
81
+ description: string | null
82
+ combinator: AudienceCombinator
83
+ criteria: AudienceCriterion[]
84
+ createdAt: Date
85
+ updatedAt: Date
86
+ }
package/src/home.api.d.ts CHANGED
@@ -169,6 +169,8 @@ export namespace HOME {
169
169
 
170
170
  export interface UpcomingTaskI {
171
171
  task: TaskI
172
+ /** PlantSelection id used by task list views to group tasks per planted item. */
173
+ plantSelectionId?: number
172
174
  plantName: string
173
175
  plantSlug: string
174
176
  plantImageURL?: string | null
@@ -179,6 +181,8 @@ export namespace HOME {
179
181
  familyGroup?: TaskFamilyGroupI
180
182
  }
181
183
 
184
+ export type TaskListView = 'current' | 'completed' | 'earlier'
185
+
182
186
  export interface UpcomingTaskAdviceI {
183
187
  /** @deprecated Since 1.9.10, this field is always `''`. */
184
188
  content: string
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export * from './apiError'
8
8
  export * from './gardenMap'
9
9
  export * from './gardenOverview'
10
10
  export * from './alert'
11
+ export * from './audience'
11
12
  export * from './image'
12
13
  export * from './note'
13
14
  export * from './taggedItem'
package/src/member.d.ts CHANGED
@@ -1,72 +1,126 @@
1
- import type { NoteI } from './note'
2
- import type { SelectionI } from './selection'
3
-
4
- export interface MemberI {
5
- id: number
6
- email: string
7
- firstName: string
8
- lastName: string
9
- /** Virtual field to get the user's full name */
10
- fullName?: string | null
11
- city?: string
12
- hardinessZoneName?: string
13
- createdAt: Date
14
- updatedAt: Date
15
- lastFreezingDate?: Date
16
- firstFreezingDate?: Date
17
- /**
18
- * Number of unread Crisp messages
19
- */
20
- unreadMessagesCount?: number
21
- notes?: NoteI[]
22
- selections?: SelectionI[]
23
- userActivities?: UserActivityI[]
24
- /**
25
- * hasAnActiveSubscription is a VIRTUAL FIELD used to check if the user has an active subscription
26
- */
27
- hasAnActiveSubscription?: boolean
28
- /**
29
- * Acts as a one-way not identifiable token.
30
- * Crisp uses this token to identify the user conversation
31
- */
32
- userToken: string
33
- /**
34
- * HMAC signature used by Crisp identity verification.
35
- * Signed on the backend from the user email.
36
- */
37
- crispUserEmailSignature?: string
38
- /**
39
- * neverAddedATask <=> Has the use ever open the task form?
40
- * Yes it is misleading, ticket is open to change it.
41
- * It was too intense to force the user to add a task.
42
- */
43
- neverAddedATask?: boolean
44
- neverAddedAPlant?: boolean
45
- neverAddedANote?: boolean
46
- neverTriedSpacingSection?: boolean
47
- neverTriedAiAgent?: boolean
48
- neverDeletedAPlant?: boolean
49
- neverDeletedATask?: boolean
50
- neverEditedATask?: boolean
51
- finishedOnboarding: boolean
52
- hasDownloadedNativeApp?: boolean
53
- unitSystem: 'imperial' | 'metric'
54
- gardenZoneTimelessPreference?: boolean
55
- subscriptionEndDate?: Date
56
- /**
57
- * Last fertilizer ID chosen by the user.
58
- * Used as the default fertilizer for new garden zones.
59
- */
60
- lastFertilizerIdChosen?: number
61
- /**
62
- * Virtual field to get the app badge count
63
- * ----------------------------------------
64
- * For now, it's just the number of unread messages.
65
- */
66
- badgeCount?: number
67
- /**
68
- * Number of unread alerts, cached version of unread alerts (the table) count
69
- */
70
- unreadAlertsCount?: number
71
- isAdmin: boolean
72
- }
1
+ import type { NoteI } from './note'
2
+ import type { SelectionI } from './selection'
3
+
4
+ export interface MemberI {
5
+ id: number
6
+ email: string
7
+ firstName: string
8
+ lastName: string
9
+ /** Virtual field to get the user's full name */
10
+ fullName?: string | null
11
+ city?: string
12
+ hardinessZoneName?: string
13
+ createdAt: Date
14
+ updatedAt: Date
15
+ lastFreezingDate?: Date
16
+ firstFreezingDate?: Date
17
+ /**
18
+ * Number of unread Crisp messages
19
+ */
20
+ unreadMessagesCount?: number
21
+ notes?: NoteI[]
22
+ selections?: SelectionI[]
23
+ userActivities?: UserActivityI[]
24
+ /**
25
+ * hasAnActiveSubscription is a VIRTUAL FIELD used to check if the user has an active subscription
26
+ */
27
+ hasAnActiveSubscription?: boolean
28
+ /**
29
+ * Acts as a one-way not identifiable token.
30
+ * Crisp uses this token to identify the user conversation
31
+ */
32
+ userToken: string
33
+ /**
34
+ * HMAC signature used by Crisp identity verification.
35
+ * Signed on the backend from the user email.
36
+ */
37
+ crispUserEmailSignature?: string
38
+ /**
39
+ * neverAddedATask <=> Has the use ever open the task form?
40
+ * Yes it is misleading, ticket is open to change it.
41
+ * It was too intense to force the user to add a task.
42
+ */
43
+ neverAddedATask?: boolean
44
+ neverAddedAPlant?: boolean
45
+ neverAddedANote?: boolean
46
+ neverTriedSpacingSection?: boolean
47
+ neverTriedAiAgent?: boolean
48
+ neverDeletedAPlant?: boolean
49
+ neverDeletedATask?: boolean
50
+ neverEditedATask?: boolean
51
+ finishedOnboarding: boolean
52
+ hasDownloadedNativeApp?: boolean
53
+ unitSystem: 'imperial' | 'metric'
54
+ gardenZoneTimelessPreference?: boolean
55
+ subscriptionEndDate?: Date
56
+ /**
57
+ * Last fertilizer ID chosen by the user.
58
+ * Used as the default fertilizer for new garden zones.
59
+ */
60
+ lastFertilizerIdChosen?: number
61
+ /**
62
+ * Virtual field to get the app badge count
63
+ * ----------------------------------------
64
+ * For now, it's just the number of unread messages.
65
+ */
66
+ badgeCount?: number
67
+ /**
68
+ * Number of unread alerts, cached version of unread alerts (the table) count
69
+ */
70
+ unreadAlertsCount?: number
71
+ isAdmin: boolean
72
+ }
73
+
74
+ /**
75
+ * PrivateMemberI
76
+ * --------------
77
+ * The data a member is allowed to see about *themselves* — the shape
78
+ * returned to the front-end by GET/POST /users. (A future `PublicMemberI`
79
+ * will model the much smaller set of fields shown to other members.)
80
+ *
81
+ * Unlike `MemberI` (the full domain model), this is a deliberate
82
+ * allow-list of only the fields the client actually consumes. It exists
83
+ * to prevent leaking sensitive or internal Member columns: anything not
84
+ * listed here is never serialized to the client. Keep this in sync with
85
+ * `privateMember.presenter.ts` on the backend.
86
+ */
87
+ export interface PrivateMemberI {
88
+ id: number
89
+ email: string
90
+ city?: string
91
+ unitSystem: 'imperial' | 'metric'
92
+ gardenZoneTimelessPreference?: boolean
93
+ firstFreezingDate?: Date
94
+ lastFreezingDate?: Date
95
+ subscriptionEndDate?: Date
96
+ hasAnActiveSubscription?: boolean
97
+ hasDownloadedNativeApp?: boolean
98
+ isAdmin: boolean
99
+ /**
100
+ * App badge count (currently unread messages + unread alerts).
101
+ */
102
+ badgeCount?: number
103
+ /**
104
+ * One-way Crisp identification token. Not personally identifiable.
105
+ */
106
+ userToken: string
107
+ /**
108
+ * HMAC signature used by Crisp identity verification.
109
+ */
110
+ crispUserEmailSignature?: string
111
+ unreadMessagesCount?: number
112
+ unreadAlertsCount?: number
113
+ neverAddedANote?: boolean
114
+ neverAddedAPlant?: boolean
115
+ neverDeletedAPlant?: boolean
116
+ neverEditedATask?: boolean
117
+ neverTriedAiAgent?: boolean
118
+ neverTriedSpacingSection?: boolean
119
+ /**
120
+ * True when the member belongs to the "very active" audience (~14 days of
121
+ * distinct activity). Only present when GET /users is called with the
122
+ * `requestAppStoreReview` scope; the app uses it to trigger a one-time
123
+ * in-app store-review prompt.
124
+ */
125
+ requestAppStoreReview?: boolean
126
+ }
package/src/plant.d.ts CHANGED
@@ -1,189 +1,200 @@
1
- import type { TaxonFamilyI } from './taxonFamily'
2
- import type { SeedingInfoI } from './pages.api'
3
- import type { PlantFilterI } from './plantFilters'
4
- import type { RotationGroup } from './rotationGroup'
5
- /**
6
- * Range filter value for min/max filters
7
- */
8
- export interface RangeFilterValueI {
9
- min: number
10
- max: number
11
- }
12
-
13
- /**
14
- * Plant filter value types
15
- * Represents the possible values for a single filter
16
- */
17
- export type PlantFilterValueI = string | string[] | number | boolean | RangeFilterValueI
18
-
19
- /**
20
- * Plant attributes for filtering
21
- * Used in JSONB field and as query parameters
22
- * Supports multiple value types for different filter types
23
- */
24
- export type PlantAttributesI = Record<string, PlantFilterValueI>
25
-
26
- export interface PlantI {
27
- id: number
28
- name: string
29
- slug: string
30
- description: string
31
- descriptionSource?: string
32
- female: boolean
33
- family?: string | null
34
- taxonFamilyId?: number | null
35
- spaceBetweenSeedMin: number
36
- spaceBetweenSeedMax: number
37
- spaceBetweenAlleyMin: number
38
- spaceBetweenAlleyMax: number
39
- weeksInTransplant: number
40
- weeksDuringWhichYouCanSeedOutdoor: number
41
- weeksToMaturity: number
42
- daysToMaturity: number | null
43
- weeksToWaitAfterFreezingDate: number
44
- weeksToHarvest: number
45
- indoorSeeding: boolean
46
- outdoorSeeding: boolean
47
- azoteNeedsKgPerHa: number
48
- hibernate: boolean
49
- germinationTemperature: number | null
50
- minGerminationTemperature: number | null
51
- germinationNumberOfDays: string | null
52
- germinationSeedDepth: string | null
53
- cultivationInfo: string | null
54
- parentId: number | null
55
- memberId?: number
56
- memberFirstName?: string | null
57
- shared: boolean
58
- hexColor: string | null
59
- sunRequirements: 'partialShade' | 'fullSun' | 'fullShade' | null
60
- isHardy: boolean | null
61
- quantityForFiveInJardinVivrier?: number | null
62
- imageURL: string
63
- /**
64
- * Filename of the plant's 3D mesh asset stored on S3.
65
- * - Ideally a `.webp` file (e.g. `ail.webp`).
66
- * - Stored in S3 under the `images/meshes/` folder.
67
- * - By default named after the `Plant.slug` (e.g. `ail.webp` for the garlic plant).
68
- * - Only the filename is persisted; the public URL is exposed through the
69
- * virtual `meshURL` field.
70
- */
71
- meshFilename: string | null
72
- /**
73
- * Public ImageKit URL to the plant's 3D mesh asset.
74
- * Virtual field computed from `meshFilename`. The underlying file is
75
- * stored on S3 at `images/meshes/{meshFilename}` and rewritten to
76
- * ImageKit for CDN delivery and transformations.
77
- *
78
- * Falls back to `imageURL` when `meshFilename` is not set.
79
- *
80
- * Example: `https://ik.imagekit.io/lamainverte/meshes/ail.webp`
81
- */
82
- meshURL: string
83
- totalWeeksToMaturity: number
84
- hasNoInformation: boolean
85
- createdAt: Date
86
- updatedAt: Date
87
- translatedSunRequirements: 'Plein soleil' | 'Mi-ombre' | 'Ombre' | null
88
- parent?: PlantI
89
- children?: PlantI[]
90
- PlantInventories?: PlantInventoryI[]
91
- taggedItems?: TaggedItemI[]
92
- /**
93
- * List of selections that the plant is in.
94
- * --------------------------------------
95
- * Tends to be used for featured selections
96
- */
97
- Selections?: SelectionI[]
98
- tasks?: TaskI[]
99
- Images?: ImageI[]
100
- Member?: Partial<MemberI>
101
- notes?: NoteI[]
102
- /**
103
- * Nitrogen-derived rotation group enum (virtual field on the Plant model).
104
- * Source of truth for rotation classification derived from
105
- * `azoteNeedsKgPerHa` plus the regenerative override on `taxonFamily`.
106
- */
107
- rotationGroup?: RotationGroup | null
108
- /**
109
- * French label of `rotationGroup`, ready for display.
110
- */
111
- translatedRotationGroup?: string | null
112
- taxonFamily?: TaxonFamilyI
113
- seedingInfo: SeedingInfoI
114
- /**
115
- * Contextual search text for recommended plants
116
- * Provides additional context when suggesting plants for specific garden zones
117
- */
118
- contextualSearchText?: string
119
- }
120
-
121
- interface PlantInventoryI {
122
- id: number
123
- plantId: number
124
- supplierName: string
125
- plantName: string
126
- supplierURL: string
127
- imageLocation: string
128
- description: string
129
- inStock: boolean
130
- }
131
-
132
- export type AvailableGrowingConditionsI = Record<string, string[]>
133
-
134
- /**
135
- * For each attribute filter key (e.g. "fruit.color"), the list of values that
136
- * still yield at least one match under the current search + active filters.
137
- * Consumed by the frontend to mark filter tags as "unavailable".
138
- */
139
- export type AvailableAttributesI = Record<string, string[]>
140
-
141
- /**
142
- * Which filter surface the frontend should render for the current search.
143
- * - `specific`: family-specific filters (e.g. tomate attributes)
144
- * - `general`: global growing-conditions filters
145
- *
146
- * Mutually exclusive by design the two sets never appear together.
147
- */
148
- export type PlantFiltersModeI = 'specific' | 'general'
149
-
150
- /**
151
- * Backend-driven description of the filter UI for a given search response.
152
- * The frontend is a dumb renderer: it shows the filters listed here with the
153
- * given mode and marks tags whose value is not in `availability` as unavailable.
154
- */
155
- export interface PlantFiltersUII {
156
- mode: PlantFiltersModeI
157
- filters: PlantFilterI[]
158
- availability: Record<string, string[]>
159
- }
160
-
161
- export interface GrowingConditionsConfigI {
162
- filters: PlantFilterI[]
163
- }
164
-
165
- export interface GrowingConditionsFiltersI {
166
- sunRequirements?: 'partialShade' | 'fullShade'
167
- rotationGroup?: 'regenerative' | 'demanding' | 'moderately_demanding' | 'less_demanding'
168
- }
169
-
170
- export interface PlantSearchResultI {
171
- plants: PlantModelI[]
172
- selections: SelectionModelI[]
173
- total?: number
174
- familyName?: string
175
- filtersUI?: PlantFiltersUII
176
- }
177
-
178
- /**
179
- * Response shape for `GET /selections/:slug/gardenZones/:id/rotation-suggestions`.
180
- * - `advice`: short user-facing sentence explaining the recommendation
181
- * - `Plants`: parent plants matching the recommended rotation group, with
182
- * the avoided taxon families excluded. Empty when the zone has no usable
183
- * history yet.
184
- */
185
- export interface PlantRotationSuggestionsI {
186
- targetYear: number
187
- advice: string
188
- Plants: PlantI[]
189
- }
1
+ import type { TaxonFamilyI } from './taxonFamily'
2
+ import type { SeedingInfoI } from './pages.api'
3
+ import type { PlantFilterI } from './plantFilters'
4
+ import type { RotationGroup } from './rotationGroup'
5
+ /**
6
+ * Range filter value for min/max filters
7
+ */
8
+ export interface RangeFilterValueI {
9
+ min: number
10
+ max: number
11
+ }
12
+
13
+ /**
14
+ * Plant filter value types
15
+ * Represents the possible values for a single filter
16
+ */
17
+ export type PlantFilterValueI = string | string[] | number | boolean | RangeFilterValueI
18
+
19
+ /**
20
+ * Plant attributes for filtering
21
+ * Used in JSONB field and as query parameters
22
+ * Supports multiple value types for different filter types
23
+ */
24
+ /**
25
+ * A group of related attribute values, used for nested attribute objects.
26
+ * Ex: { growth: { type: 'déterminée' }}
27
+ */
28
+ export type PlantAttributeGroupI = Record<string, PlantFilterValueI>
29
+
30
+ /**
31
+ * Plant attributes for filtering
32
+ * Used in JSONB field and as query parameters
33
+ * Supports flat values and one level of nested groups.
34
+ */
35
+ export type PlantAttributesI = Record<string, PlantFilterValueI | PlantAttributeGroupI>
36
+
37
+ export interface PlantI {
38
+ id: number
39
+ name: string
40
+ slug: string
41
+ description: string
42
+ descriptionSource?: string
43
+ female: boolean
44
+ family?: string | null
45
+ taxonFamilyId?: number | null
46
+ spaceBetweenSeedMin: number
47
+ spaceBetweenSeedMax: number
48
+ spaceBetweenAlleyMin: number
49
+ spaceBetweenAlleyMax: number
50
+ weeksInTransplant: number
51
+ weeksDuringWhichYouCanSeedOutdoor: number
52
+ weeksToMaturity: number
53
+ daysToMaturity: number | null
54
+ weeksToWaitAfterFreezingDate: number
55
+ weeksToHarvest: number
56
+ indoorSeeding: boolean
57
+ outdoorSeeding: boolean
58
+ azoteNeedsKgPerHa: number
59
+ hibernate: boolean
60
+ germinationTemperature: number | null
61
+ minGerminationTemperature: number | null
62
+ germinationNumberOfDays: string | null
63
+ germinationSeedDepth: string | null
64
+ cultivationInfo: string | null
65
+ parentId: number | null
66
+ memberId?: number
67
+ memberFirstName?: string | null
68
+ shared: boolean
69
+ hexColor: string | null
70
+ sunRequirements: 'partialShade' | 'fullSun' | 'fullShade' | null
71
+ isHardy: boolean | null
72
+ quantityForFiveInJardinVivrier?: number | null
73
+ imageURL: string
74
+ /**
75
+ * Filename of the plant's 3D mesh asset stored on S3.
76
+ * - Ideally a `.webp` file (e.g. `ail.webp`).
77
+ * - Stored in S3 under the `images/meshes/` folder.
78
+ * - By default named after the `Plant.slug` (e.g. `ail.webp` for the garlic plant).
79
+ * - Only the filename is persisted; the public URL is exposed through the
80
+ * virtual `meshURL` field.
81
+ */
82
+ meshFilename: string | null
83
+ /**
84
+ * Public ImageKit URL to the plant's 3D mesh asset.
85
+ * Virtual field computed from `meshFilename`. The underlying file is
86
+ * stored on S3 at `images/meshes/{meshFilename}` and rewritten to
87
+ * ImageKit for CDN delivery and transformations.
88
+ *
89
+ * Falls back to `imageURL` when `meshFilename` is not set.
90
+ *
91
+ * Example: `https://ik.imagekit.io/lamainverte/meshes/ail.webp`
92
+ */
93
+ meshURL: string
94
+ totalWeeksToMaturity: number
95
+ hasNoInformation: boolean
96
+ createdAt: Date
97
+ updatedAt: Date
98
+ translatedSunRequirements: 'Plein soleil' | 'Mi-ombre' | 'Ombre' | null
99
+ parent?: PlantI
100
+ children?: PlantI[]
101
+ PlantInventories?: PlantInventoryI[]
102
+ taggedItems?: TaggedItemI[]
103
+ /**
104
+ * List of selections that the plant is in.
105
+ * --------------------------------------
106
+ * Tends to be used for featured selections
107
+ */
108
+ Selections?: SelectionI[]
109
+ tasks?: TaskI[]
110
+ Images?: ImageI[]
111
+ Member?: Partial<MemberI>
112
+ notes?: NoteI[]
113
+ /**
114
+ * Nitrogen-derived rotation group enum (virtual field on the Plant model).
115
+ * Source of truth for rotation classification — derived from
116
+ * `azoteNeedsKgPerHa` plus the regenerative override on `taxonFamily`.
117
+ */
118
+ rotationGroup?: RotationGroup | null
119
+ /**
120
+ * French label of `rotationGroup`, ready for display.
121
+ */
122
+ translatedRotationGroup?: string | null
123
+ taxonFamily?: TaxonFamilyI
124
+ seedingInfo: SeedingInfoI
125
+ /**
126
+ * Contextual search text for recommended plants
127
+ * Provides additional context when suggesting plants for specific garden zones
128
+ */
129
+ contextualSearchText?: string
130
+ }
131
+
132
+ interface PlantInventoryI {
133
+ id: number
134
+ plantId: number
135
+ supplierName: string
136
+ plantName: string
137
+ supplierURL: string
138
+ imageLocation: string
139
+ description: string
140
+ inStock: boolean
141
+ }
142
+
143
+ export type AvailableGrowingConditionsI = Record<string, string[]>
144
+
145
+ /**
146
+ * For each attribute filter key (e.g. "fruit.color"), the list of values that
147
+ * still yield at least one match under the current search + active filters.
148
+ * Consumed by the frontend to mark filter tags as "unavailable".
149
+ */
150
+ export type AvailableAttributesI = Record<string, string[]>
151
+
152
+ /**
153
+ * Which filter surface the frontend should render for the current search.
154
+ * - `specific`: family-specific filters (e.g. tomate attributes)
155
+ * - `general`: global growing-conditions filters
156
+ *
157
+ * Mutually exclusive by design — the two sets never appear together.
158
+ */
159
+ export type PlantFiltersModeI = 'specific' | 'general'
160
+
161
+ /**
162
+ * Backend-driven description of the filter UI for a given search response.
163
+ * The frontend is a dumb renderer: it shows the filters listed here with the
164
+ * given mode and marks tags whose value is not in `availability` as unavailable.
165
+ */
166
+ export interface PlantFiltersUII {
167
+ mode: PlantFiltersModeI
168
+ filters: PlantFilterI[]
169
+ availability: Record<string, string[]>
170
+ }
171
+
172
+ export interface GrowingConditionsConfigI {
173
+ filters: PlantFilterI[]
174
+ }
175
+
176
+ export interface GrowingConditionsFiltersI {
177
+ sunRequirements?: 'partialShade' | 'fullShade'
178
+ rotationGroup?: 'regenerative' | 'demanding' | 'moderately_demanding' | 'less_demanding'
179
+ }
180
+
181
+ export interface PlantSearchResultI {
182
+ plants: PlantModelI[]
183
+ selections: SelectionModelI[]
184
+ total?: number
185
+ familyName?: string
186
+ filtersUI?: PlantFiltersUII
187
+ }
188
+
189
+ /**
190
+ * Response shape for `GET /selections/:slug/gardenZones/:id/rotation-suggestions`.
191
+ * - `advice`: short user-facing sentence explaining the recommendation
192
+ * - `Plants`: parent plants matching the recommended rotation group, with
193
+ * the avoided taxon families excluded. Empty when the zone has no usable
194
+ * history yet.
195
+ */
196
+ export interface PlantRotationSuggestionsI {
197
+ targetYear: number
198
+ advice: string
199
+ Plants: PlantI[]
200
+ }
@@ -5,7 +5,6 @@ export interface PlantSelectionI {
5
5
  id: number
6
6
  plantId: number
7
7
  selectionId: number
8
- seedQuantity: number
9
8
  createdAt: Date
10
9
  updatedAt: Date
11
10
  Plant: PlantI
@@ -1,15 +1,15 @@
1
- import type { RotationGroup } from './rotationGroup'
2
-
3
- export interface TaxonFamilyI {
4
- id: number
5
- slug: string
6
- translation_key: string
7
- name: string
8
- latin_name: string | null
9
- kingdom: string
10
- description: string | null
11
- rotation_group: RotationGroup | null
12
- translated_rotation_group: string | null
13
- createdAt?: Date
14
- updatedAt?: Date
15
- }
1
+ import type { RotationGroup } from './rotationGroup'
2
+
3
+ export interface TaxonFamilyI {
4
+ id: number
5
+ slug: string
6
+ translation_key: string
7
+ name: string
8
+ latin_name: string | null
9
+ kingdom: string
10
+ description: string | null
11
+ rotation_group: RotationGroup | null
12
+ translated_rotation_group: string | null
13
+ createdAt?: Date
14
+ updatedAt?: Date
15
+ }
@@ -1,136 +1,134 @@
1
- import type { DeviceDataI } from './device.d'
2
- import type { MemberI as MemberDataI } from './member.d'
3
- import type { SelectionI } from './selection.d'
4
- import type { PlantI } from './plant.d'
5
-
6
- interface RequestHeaders {
7
- 'x-session-token': string
8
- 'x-native-app-version'?: string
9
- 'x-application-type'?: 'react-native' | 'web'
10
- }
11
-
12
- export namespace USERS {
13
- export namespace GET {
14
- export interface Request {
15
- headers: RequestHeaders
16
- params: {
17
- scopes: ('unreadNotificationsCount' | 'coordinates')[]
18
- }
19
- }
20
- export type Response = MemberDataI & {
21
- unreadAlertsCount?: number
22
- }
23
- }
24
- export namespace DEVICES {
25
- export interface Request {
26
- headers: RequestHeaders
27
- body: {
28
- /** @see DeviceDataI */
29
- userAgent?: DeviceDataI['userAgent']
30
- platform?: DeviceDataI['platform']
31
- version?: DeviceDataI['version']
32
- model?: DeviceDataI['model']
33
- name?: DeviceDataI['name']
34
- appVersion?: DeviceDataI['appVersion']
35
- pushKey?: DeviceDataI['pushKey']
36
- apnsToken?: DeviceDataI['apnsToken']
37
- fcmToken?: DeviceDataI['fcmToken']
38
- webPushEndpoint?: DeviceDataI['webPushEndpoint']
39
- webPushP256dh?: DeviceDataI['webPushP256dh']
40
- webPushAuth?: DeviceDataI['webPushAuth']
41
- webPushSubscription?: DeviceDataI['webPushSubscription']
42
- }
43
- }
44
- export type Response = DeviceDataI
45
- }
46
- export namespace RECOMMENDATIONS {
47
- export type ComponentType = 'PlantCarousel' | 'SelectionCarousel' | 'HeroCardsCarousel' | 'YourSelection'
48
-
49
- interface BaseRecommendation {
50
- id: string
51
- componentType: ComponentType
52
- title?: string
53
- subtitle?: string
54
- cardsVisible?: number
55
- }
56
-
57
- export interface PlantCarouselRecommendation extends BaseRecommendation {
58
- componentType: 'PlantCarousel'
59
- selection: SelectionI & { Plants?: PlantI[] }
60
- displayRanking?: boolean
61
- }
62
-
63
- export interface SelectionCarouselRecommendation extends BaseRecommendation {
64
- componentType: 'SelectionCarousel'
65
- selections: SelectionI[]
66
- displayRanking?: boolean
67
- }
68
-
69
- export interface HeroCardsCarouselRecommendation extends BaseRecommendation {
70
- componentType: 'HeroCardsCarousel'
71
- selections: SelectionI[]
72
- }
73
-
74
- export interface YourSelectionRecommendation extends BaseRecommendation {
75
- componentType: 'YourSelection'
76
- }
77
-
78
- export interface ThemedRecommendationI {
79
- id: string
80
- label: string
81
- icon: string
82
- title?: string
83
- subtitle?: string
84
- selections: SelectionI[]
85
- cardsVisible?: number
86
- displayRanking?: boolean
87
- }
88
-
89
- export type RecommendationI =
90
- | PlantCarouselRecommendation
91
- | SelectionCarouselRecommendation
92
- | HeroCardsCarouselRecommendation
93
- | YourSelectionRecommendation
94
-
95
- export interface Request {
96
- headers: RequestHeaders
97
- }
98
-
99
- export interface Response {
100
- recommendations: RecommendationI[]
101
- themedRecommendations: ThemedRecommendationI[]
102
- }
103
- }
104
- export namespace UPDATE {
105
- export interface Request {
106
- headers: RequestHeaders
107
- body: {
108
- city?: string
109
- lastFreezingDate?: string | Date
110
- firstFreezingDate?: string | Date
111
- moveDatesRequested?: boolean
112
- lastFertilizerIdChosen?: number
113
- lastOrganicMatterPercentage?: number
114
- unitSystem?: 'imperial' | 'metric'
115
- gardenZoneTimelessPreference?: boolean
116
- }
117
- }
118
- export type Response = MemberDataI
119
- }
120
- export namespace GIFT_CARDS {
121
- export namespace REDEEM {
122
- export interface Request {
123
- headers: RequestHeaders
124
- body: {
125
- code: string
126
- }
127
- }
128
- export interface Response {
129
- success: boolean
130
- message?: string
131
- error?: string
132
- error_message?: string
133
- }
134
- }
135
- }
136
- }
1
+ import type { DeviceDataI } from './device.d'
2
+ import type { PrivateMemberI } from './member.d'
3
+ import type { SelectionI } from './selection.d'
4
+ import type { PlantI } from './plant.d'
5
+
6
+ interface RequestHeaders {
7
+ 'x-session-token': string
8
+ 'x-native-app-version'?: string
9
+ 'x-application-type'?: 'react-native' | 'web'
10
+ }
11
+
12
+ export namespace USERS {
13
+ export namespace GET {
14
+ export interface Request {
15
+ headers: RequestHeaders
16
+ params: {
17
+ scopes: ('unreadNotificationsCount' | 'requestAppStoreReview')[]
18
+ }
19
+ }
20
+ export type Response = PrivateMemberI
21
+ }
22
+ export namespace DEVICES {
23
+ export interface Request {
24
+ headers: RequestHeaders
25
+ body: {
26
+ /** @see DeviceDataI */
27
+ userAgent?: DeviceDataI['userAgent']
28
+ platform?: DeviceDataI['platform']
29
+ version?: DeviceDataI['version']
30
+ model?: DeviceDataI['model']
31
+ name?: DeviceDataI['name']
32
+ appVersion?: DeviceDataI['appVersion']
33
+ pushKey?: DeviceDataI['pushKey']
34
+ apnsToken?: DeviceDataI['apnsToken']
35
+ fcmToken?: DeviceDataI['fcmToken']
36
+ webPushEndpoint?: DeviceDataI['webPushEndpoint']
37
+ webPushP256dh?: DeviceDataI['webPushP256dh']
38
+ webPushAuth?: DeviceDataI['webPushAuth']
39
+ webPushSubscription?: DeviceDataI['webPushSubscription']
40
+ }
41
+ }
42
+ export type Response = DeviceDataI
43
+ }
44
+ export namespace RECOMMENDATIONS {
45
+ export type ComponentType = 'PlantCarousel' | 'SelectionCarousel' | 'HeroCardsCarousel' | 'YourSelection'
46
+
47
+ interface BaseRecommendation {
48
+ id: string
49
+ componentType: ComponentType
50
+ title?: string
51
+ subtitle?: string
52
+ cardsVisible?: number
53
+ }
54
+
55
+ export interface PlantCarouselRecommendation extends BaseRecommendation {
56
+ componentType: 'PlantCarousel'
57
+ selection: SelectionI & { Plants?: PlantI[] }
58
+ displayRanking?: boolean
59
+ }
60
+
61
+ export interface SelectionCarouselRecommendation extends BaseRecommendation {
62
+ componentType: 'SelectionCarousel'
63
+ selections: SelectionI[]
64
+ displayRanking?: boolean
65
+ }
66
+
67
+ export interface HeroCardsCarouselRecommendation extends BaseRecommendation {
68
+ componentType: 'HeroCardsCarousel'
69
+ selections: SelectionI[]
70
+ }
71
+
72
+ export interface YourSelectionRecommendation extends BaseRecommendation {
73
+ componentType: 'YourSelection'
74
+ }
75
+
76
+ export interface ThemedRecommendationI {
77
+ id: string
78
+ label: string
79
+ icon: string
80
+ title?: string
81
+ subtitle?: string
82
+ selections: SelectionI[]
83
+ cardsVisible?: number
84
+ displayRanking?: boolean
85
+ }
86
+
87
+ export type RecommendationI =
88
+ | PlantCarouselRecommendation
89
+ | SelectionCarouselRecommendation
90
+ | HeroCardsCarouselRecommendation
91
+ | YourSelectionRecommendation
92
+
93
+ export interface Request {
94
+ headers: RequestHeaders
95
+ }
96
+
97
+ export interface Response {
98
+ recommendations: RecommendationI[]
99
+ themedRecommendations: ThemedRecommendationI[]
100
+ }
101
+ }
102
+ export namespace UPDATE {
103
+ export interface Request {
104
+ headers: RequestHeaders
105
+ body: {
106
+ city?: string
107
+ lastFreezingDate?: string | Date
108
+ firstFreezingDate?: string | Date
109
+ moveDatesRequested?: boolean
110
+ lastFertilizerIdChosen?: number
111
+ lastOrganicMatterPercentage?: number
112
+ unitSystem?: 'imperial' | 'metric'
113
+ gardenZoneTimelessPreference?: boolean
114
+ }
115
+ }
116
+ export type Response = PrivateMemberI
117
+ }
118
+ export namespace GIFT_CARDS {
119
+ export namespace REDEEM {
120
+ export interface Request {
121
+ headers: RequestHeaders
122
+ body: {
123
+ code: string
124
+ }
125
+ }
126
+ export interface Response {
127
+ success: boolean
128
+ message?: string
129
+ error?: string
130
+ error_message?: string
131
+ }
132
+ }
133
+ }
134
+ }