-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ View Plants
+
+
+
+
+
+
-
-
-
- {viewingOption === 'myPlants' && (
-
- {filteredUserPlantList.length ? (
-
- {filteredUserPlantList.map(ownedPlant => (
-
handleUserPlantCardClick(ownedPlant)}
- />
- ))}
-
- ) : (
-
-
-
- )}
-
- )}
+
+
+
+
+ {viewingOption === 'all' && inAddMode ? (
+
+
+ {selectedPlants.length
+ ? `${selectedPlants.length} ${plantPluralityString} Selected`
+ : 'Select Plants'}
+
+
+ ) : null}
+
+
+
+
+ setViewingOption('myPlants')}
+ >
+ My Plants
+
+ setViewingOption('all')}
+ >
+ All
+
+
+ {/* Select/Cancel toggles Add Mode; appears in All plants only*/}
{viewingOption === 'all' &&
(inAddMode ? (
-
- {filteredPlantList.map((plant, key) => (
-
handlePlantCardClick(plant)}
- />
- ))}
-
-
-
-
+
+ Cancel
+
) : (
-
- {filteredPlantList.map((plant, key) => (
+
setInAddMode(true)}
+ >
+ Select
+
+ ))}
+
+ {viewingOption === 'myPlants' && (
+
+ {filteredUserPlantList.length ? (
+
+ {filteredUserPlantList.map(ownedPlant => (
handlePlantCardClick(plant)}
+ onClick={() => handleUserPlantCardClick(ownedPlant)}
+ // aspectRatio="168 / 200"
/>
))}
-
-
-
+
+ ) : (
+
+
- ))}
-
-
+ )}
+
+ )}
+ {viewingOption === 'all' && (
+ <>
+
+ {filteredPlantList.map((plant, key) => (
+ handlePlantCardClick(plant)}
+ // aspectRatio="168 / 200"
+ />
+ ))}
+
+ {inAddMode && (
+
+ {selectedPlants.length ? 'Add to My Garden' : 'Select Plants'}
+
+ )}
+ >
+ )}
+
);
}
diff --git a/app/view-plants/styles.ts b/app/view-plants/styles.ts
index 03af2dc..d28ffb6 100644
--- a/app/view-plants/styles.ts
+++ b/app/view-plants/styles.ts
@@ -1,15 +1,98 @@
import styled from 'styled-components';
+import { SmallRoundedButton } from '@/components/Button';
+import COLORS from '@/styles/colors';
export const FilterContainer = styled.div`
display: flex;
flex-direction: row;
gap: 8px;
+ margin-bottom: 20px;
`;
export const TopRowContainer = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
- margin-top: 8px;
- margin-bottom: 8px;
+ padding-left: 24px;
+ padding-right: 24px;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
+`;
+
+export const HeaderButton = styled.button<{
+ $isCurrentMode: boolean;
+}>`
+ background: none;
+ border: none;
+ color: ${COLORS.shrub};
+ font-family: inherit;
+ cursor: pointer;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ color: ${({ $isCurrentMode }) =>
+ $isCurrentMode ? ` ${COLORS.shrub}` : `${COLORS.midgray}`};
+ text-decoration: ${({ $isCurrentMode }) =>
+ $isCurrentMode ? ` underline ` : `none`};
+`;
+
+export const AddButton = styled.button<{ $backgroundColor: string }>`
+ position: fixed;
+
+ bottom: 0;
+ background-color: ${({ $backgroundColor }) => $backgroundColor};
+ color: white;
+ border-radius: 20px;
+ border: none;
+ font-family: inherit;
+ margin-bottom: 10px;
+ width: 170px;
+ height: 50px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 15px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+`;
+
+export const PlantGridContainer = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
+ /* grid-template-columns: repeat(auto-fill, 168px); */
+ gap: 8px;
+ max-width: 100%;
+ justify-content: center;
+`;
+
+export const SelectButton = styled(SmallRoundedButton)`
+ font-size: 0.75rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ width: 60px;
+ height: 25px;
+ padding: 0;
+`;
+
+export const ViewSelection = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 20px;
+`;
+
+export const NumberSelectedPlantsContainer = styled.div`
+ background-color: ${COLORS.shrub};
+ text-align: center;
+ width: 100%;
+ height: 16px;
+ padding: 0;
+`;
+
+export const NumberSelectedPlants = styled.p`
+ font-size: 0.625rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 16px;
+ color: #fff;
`;
diff --git a/components/Button.tsx b/components/Button.tsx
index 24f1127..1d22cc0 100644
--- a/components/Button.tsx
+++ b/components/Button.tsx
@@ -6,8 +6,12 @@ import styled from 'styled-components';
export const Button = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes
->(({ children, ...props }) => {
- return ;
+>(({ children, ...props }, ref) => {
+ return (
+
+ );
});
Button.displayName = 'Button';
diff --git a/components/DifficultyLevelBar/index.tsx b/components/DifficultyLevelBar.tsx
similarity index 68%
rename from components/DifficultyLevelBar/index.tsx
rename to components/DifficultyLevelBar.tsx
index 2c362f3..4f33a5d 100644
--- a/components/DifficultyLevelBar/index.tsx
+++ b/components/DifficultyLevelBar.tsx
@@ -1,7 +1,7 @@
'use client';
import { DifficultyLevelEnum } from '@/types/schema';
-import Icon from '../Icon';
+import Icon from './Icon';
export default function DifficultyLevelBar({
difficultyLevel,
@@ -9,11 +9,11 @@ export default function DifficultyLevelBar({
difficultyLevel: DifficultyLevelEnum;
}) {
if (difficultyLevel === 'EASY') {
- return ;
+ return ;
} else if (difficultyLevel === 'MODERATE') {
- return ;
+ return ;
} else {
// difficultyLevel === 'HARD'
- return ;
+ return ;
}
}
diff --git a/components/PlantCard/index.tsx b/components/PlantCard/index.tsx
index 53e70f2..5d328ae 100644
--- a/components/PlantCard/index.tsx
+++ b/components/PlantCard/index.tsx
@@ -1,16 +1,23 @@
-import React from 'react';
+import React, { memo } from 'react';
+import { P1 } from '@/styles/text';
import { Plant } from '@/types/schema';
+import { mapMonthToSeason, useTitleCase } from '@/utils/helpers';
+import DifficultyLevelBar from '../DifficultyLevelBar';
+import Icon from '../Icon';
import {
Attribute,
- Card,
+ AttributeContent,
+ CardContainer,
CardContent,
CardPic,
PlantAttributes,
+ PlantHeader,
+ PlantImage,
RoundCheck,
TopRight,
} from './styles';
-export default function PlantCard({
+const PlantCard = memo(function PlantCard({
plant,
canSelect,
isSelected = false,
@@ -22,35 +29,54 @@ export default function PlantCard({
onClick?: () => void;
}) {
return (
-
+
{canSelect && (
)}
-
+
- {plant.plant_name}
+
+ {plant.plant_name}
+
+
+
- {`${plant.harvest_start} - ${plant.harvest_end}`}
+
+
+ {useTitleCase(
+ mapMonthToSeason(plant.outdoors_start) || 'Unknown',
+ )}
+
+
+
+
+
+ {useTitleCase(plant.harvest_season)}
+
- {plant.water_frequency}
+
+ {plant.water_frequency}
-
+
+
{plant.sunlight_min_hours}
{plant.sunlight_max_hours
? ` - ${plant.sunlight_max_hours}`
: ''}{' '}
hours/day
-
+
-
+
);
-}
+});
+
+export default PlantCard;
diff --git a/components/PlantCard/styles.ts b/components/PlantCard/styles.ts
index 42eb286..056d3c7 100644
--- a/components/PlantCard/styles.ts
+++ b/components/PlantCard/styles.ts
@@ -1,26 +1,33 @@
import styled from 'styled-components';
import COLORS from '@/styles/colors';
-export const Card = styled.div<{ isSelected?: boolean }>`
+export const CardContainer = styled.div<{ $isSelected?: boolean }>`
position: relative;
- height: 40vh;
- width: 20vw;
+ width: 168px;
+ height: 200px;
display: flex;
flex-direction: column;
align-items: start;
border-radius: 12px;
background-color: white;
- box-shadow:
- 0 24px 38px 3px rgba(0, 0, 0, 0.14),
- 0 9px 46px 8px rgba(0, 0, 0, 0.12),
- 0 11px 15px -7px rgba(0, 0, 0, 0.2);
+ box-shadow: ${({ $isSelected }) =>
+ $isSelected
+ ? `
+ 0 24px 38px 3px rgb(148, 181, 6, 0.14),
+ 0 9px 46px 8px rgb(148, 181, 6, 0.12),
+ 0 11px 15px -7px rgb(148, 181, 6, 0.2)`
+ : `
+ 0 24px 38px 3px rgba(0, 0, 0, 0.14),
+ 0 9px 46px 8px rgba(0, 0, 0, 0.12),
+ 0 11px 15px -7px rgba(0, 0, 0, 0.2)`};
- border: ${({ isSelected }) =>
- isSelected ? `1px solid ${COLORS.sprout}` : 'none'};
+ border: ${({ $isSelected }) =>
+ $isSelected ? `1px solid ${COLORS.sprout}` : '1px solid transparent'};
+ backdrop-filter: blur(40px);
`;
export const CardPic = styled.div`
- height: 60%;
+ height: 92px;
width: 100%;
background-color: #f5f6f6;
display: flex;
@@ -33,34 +40,22 @@ export const CardPic = styled.div`
export const CardContent = styled.div`
display: flex;
flex-direction: column;
- padding-left: 1vw;
- height: 40%;
- row-gap: 1vh;
-
- > * {
- margin: 0;
- }
-
- > h2 {
- font-size: 1rem;
- margin-top: 1vh;
- }
+ padding: 12px 16px 8px 16px;
+ row-gap: 6px;
+ width: 100%;
`;
export const PlantAttributes = styled.div`
display: flex;
flex-direction: column;
- gap: 1vh;
+ gap: 4px;
`;
export const Attribute = styled.div`
display: flex;
flex-direction: row;
-
- > * {
- margin: 0;
- font-size: 0.625rem;
- }
+ align-items: center;
+ gap: 6px;
`;
export const RoundCheck = styled.input.attrs({ type: 'checkbox' })`
@@ -95,3 +90,24 @@ export const TopRight = styled.div`
right: 0;
padding: 10px 10px;
`;
+
+export const AttributeContent = styled.p`
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 300;
+ line-height: normal;
+ margin: 0;
+`;
+
+export const PlantHeader = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+export const PlantImage = styled.img`
+ width: 60px;
+ height: 60px;
+ font-size: 10px;
+`;
diff --git a/components/YourPlantDetails/index.tsx b/components/YourPlantDetails/index.tsx
index f717f6c..47f6245 100644
--- a/components/YourPlantDetails/index.tsx
+++ b/components/YourPlantDetails/index.tsx
@@ -40,7 +40,7 @@ export default function YourPlantDetails({
-
+
Planting Type: {useTitleCase(plantingType)}
diff --git a/lib/icons.tsx b/lib/icons.tsx
index aa1b7b0..a480153 100644
--- a/lib/icons.tsx
+++ b/lib/icons.tsx
@@ -135,7 +135,7 @@ export const IconSvgs = {
),
- plant_hand: (
+ plantHand: (
),
- easy_bar: (
+ easyBar: (
),
- moderate_bar: (
+ moderateBar: (
),
- hard_bar: (
+ hardBar: (
),
+ outdoorsSeason: (
+
+ ),
+ harvestSeason: (
+
+ ),
};
export type IconType = keyof typeof IconSvgs;
diff --git a/styles/colors.ts b/styles/colors.ts
index 454e1e5..89abdca 100644
--- a/styles/colors.ts
+++ b/styles/colors.ts
@@ -2,16 +2,19 @@ const COLORS = {
// background white
seed: '#FFFBF2',
- //greens
+ // greens
shrub: '#1F5A2A',
sprout: '#94B506',
sproutLight: '#F4F8E6',
- //grey
+ // grey
black: '#222222',
darkgray: '#555555',
midgray: '#888888',
lightgray: '#DDDDDD',
backgroundGrey: '#f9fafb',
+
+ // error
+ errorRed: '#D94E4E',
};
export default COLORS;
diff --git a/styles/containers.ts b/styles/containers.ts
new file mode 100644
index 0000000..de5ca49
--- /dev/null
+++ b/styles/containers.ts
@@ -0,0 +1,97 @@
+// from IJP's container.ts file
+import styled, { css } from 'styled-components';
+
+interface BoxProps {
+ $textAlign?: 'center' | 'start' | 'end' | 'justify';
+ $background?: string;
+ $border?: string;
+ $borderColor?: string;
+ $radius?: string;
+ $shadow?: string;
+ $w?: string;
+ $minW?: string;
+ $maxW?: string;
+ $h?: string;
+ $minH?: string;
+ $maxH?: string;
+ $p?: string;
+ $px?: string;
+ $py?: string;
+ $pl?: string;
+ $pr?: string;
+ $pt?: string;
+ $pb?: string;
+ $m?: string;
+ $mx?: string;
+ $my?: string;
+ $ml?: string;
+ $mt?: string;
+ $mr?: string;
+ $mb?: string;
+ $position?: string;
+}
+
+const BoxStyles = css`
+ position: ${({ $position }) => $position ?? 'static'};
+ text-align: ${({ $textAlign }) => $textAlign};
+
+ background: ${({ $background }) => $background};
+ border: ${({ $border }) => $border};
+ border-color: ${({ $borderColor }) => $borderColor};
+ border-radius: ${({ $radius }) => $radius};
+ box-shadow: ${({ $shadow }) => $shadow};
+
+ width: ${({ $w }) => $w ?? '100%'};
+ min-width: ${({ $minW }) => $minW};
+ max-width: ${({ $maxW }) => $maxW};
+ height: ${({ $h }) => $h ?? '100%'};
+ min-height: ${({ $minH }) => $minH};
+ max-height: ${({ $maxH }) => $maxH};
+
+ padding: ${({ $p }) => $p};
+ ${({ $py, $pt }) => ($pt || $py ? `padding-top: ${$pt || $py}` : null)};
+ ${({ $py, $pb }) => ($pb || $py ? `padding-bottom: ${$pb || $py}` : null)};
+ ${({ $px, $pl }) => ($pl || $px ? `padding-left: ${$pl || $px}` : null)};
+ ${({ $px, $pr }) => ($pr || $px ? `padding-right: ${$pr || $px}` : null)};
+
+ margin: ${({ $m }) => $m};
+ ${({ $my, $mt }) => ($mt || $my ? `margin-top: ${$mt || $my}` : null)};
+ ${({ $my, $mb }) => ($mb || $my ? `margin-bottom: ${$mb || $my}` : null)};
+ ${({ $mx, $ml }) => ($ml || $mx ? `margin-left: ${$ml || $mx}` : null)};
+ ${({ $mx, $mr }) => ($mr || $mx ? `margin-right: ${$mr || $mx}` : null)};
+`;
+
+export const Box = styled.div`
+ ${BoxStyles}
+`;
+
+type Justify = 'center' | 'start' | 'end' | 'between' | 'evenly';
+
+interface FlexProps extends BoxProps {
+ $gap?: string;
+ $direction?: 'column' | 'row';
+ $align?: 'center' | 'start' | 'end';
+ $justify?: Justify;
+ $wrap?: boolean;
+}
+
+const parseJustify = (justify: Justify) => {
+ if (justify === 'center') return 'center';
+ if (justify === 'start' || justify === 'end') return `flex-${justify}`;
+ return `space-${justify}`;
+};
+
+export const Flex = styled.div`
+ ${BoxStyles}
+ display: flex;
+ flex-direction: ${({ $direction }) => $direction || 'row'};
+ gap: ${({ $gap }) => $gap || '0px'};
+
+ align-items: ${({ $align }) =>
+ $align === 'center' ? $align : `flex-${$align}`};
+
+ justify-content: ${({ $justify }) =>
+ $justify ? parseJustify($justify) : null};
+
+ flex-wrap: ${({ $wrap }) => ($wrap ? 'wrap' : null)};
+`;
diff --git a/utils/helpers.ts b/utils/helpers.ts
index 2ae9941..2c9c986 100644
--- a/utils/helpers.ts
+++ b/utils/helpers.ts
@@ -1,4 +1,4 @@
-import { DropdownOption, Plant } from '@/types/schema';
+import { DropdownOption, Plant, SeasonEnum } from '@/types/schema';
// Helper function to process late/early month fields for checkGrowingSeason
function processPlantMonth(month: string) {
@@ -227,3 +227,26 @@ export function formatTimestamp(timestamp: string): string {
// Return in MM/DD/YYYY format
return `${month}/${day}/${year}`;
}
+
+const monthToSeason: Record = {
+ JANUARY: 'WINTER',
+ FEBRUARY: 'WINTER',
+ MARCH: 'SPRING',
+ APRIL: 'SPRING',
+ MAY: 'SPRING',
+ JUNE: 'SUMMER',
+ JULY: 'SUMMER',
+ AUGUST: 'SUMMER',
+ SEPTEMBER: 'FALL',
+ OCTOBER: 'FALL',
+ NOVEMBER: 'FALL',
+ DECEMBER: 'WINTER',
+};
+
+/* Maps a month to a season
+if valid month (e.g. 'LATE_JANUARY' or 'FEBRUARY'), return SeasonEnum
+else return null*/
+export function mapMonthToSeason(month: string): SeasonEnum | null {
+ month = processPlantMonth(month).toUpperCase();
+ return monthToSeason[month] || null;
+}