newcandies 0.1.28 → 0.1.30

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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/templates/app-briefs/expo-router/flourish/src/app/_layout.tsx +1 -2
  3. package/templates/app-briefs/expo-router/flourish-auth/README.md +63 -110
  4. package/templates/app-briefs/expo-router/flourish-auth/app.json +1 -2
  5. package/templates/app-briefs/expo-router/flourish-auth/babel.config.js +0 -1
  6. package/templates/app-briefs/expo-router/flourish-auth/eas.json +0 -1
  7. package/templates/app-briefs/expo-router/flourish-auth/global.css +0 -1
  8. package/templates/app-briefs/expo-router/flourish-auth/metro.config.js +0 -1
  9. package/templates/app-briefs/expo-router/flourish-auth/package.json +1 -3
  10. package/templates/app-briefs/expo-router/flourish-auth/src/app/(tabs)/_layout.tsx +16 -13
  11. package/templates/app-briefs/expo-router/flourish-auth/src/app/(tabs)/explore.tsx +69 -11
  12. package/templates/app-briefs/expo-router/flourish-auth/src/app/(tabs)/index.tsx +46 -51
  13. package/templates/app-briefs/expo-router/flourish-auth/src/app/(tabs)/profile.tsx +99 -17
  14. package/templates/app-briefs/expo-router/flourish-auth/src/app/(tabs)/search.tsx +15 -14
  15. package/templates/app-briefs/expo-router/flourish-auth/src/app/_layout.tsx +5 -20
  16. package/templates/app-briefs/expo-router/flourish-auth/src/components/ui/screen.tsx +0 -1
  17. package/templates/app-briefs/expo-router/flourish-auth/src/components/ui/squircle.tsx +0 -1
  18. package/templates/app-briefs/expo-router/flourish-auth/src/components/ui/text.tsx +0 -1
  19. package/templates/app-briefs/expo-router/flourish-auth/src/lib/constants/index.ts +47 -0
  20. package/templates/app-briefs/expo-router/flourish-auth/src/lib/utils/index.ts +0 -1
  21. package/templates/app-briefs/expo-router/flourish-auth/src/types/plant.d.ts +0 -1
  22. package/templates/app-briefs/expo-router/flourish-auth/tsconfig.json +0 -1
  23. package/templates/app-briefs/expo-router/flourish-auth/src/app/(auth)/_layout.tsx +0 -14
  24. package/templates/app-briefs/expo-router/flourish-auth/src/app/(auth)/sign-in.tsx +0 -25
  25. package/templates/app-briefs/expo-router/flourish-auth/src/app/(auth)/sign-up.tsx +0 -26
  26. package/templates/app-briefs/expo-router/flourish-auth/src/app/(auth)/welcome.tsx +0 -22
  27. package/templates/app-briefs/expo-router/flourish-auth/src/app/plant/[id].tsx +0 -48
  28. package/templates/app-briefs/expo-router/flourish-auth/src/lib/stores/README.md +0 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newcandies",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "Scaffold Expo Router + Uniwind React Native apps with layered templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,9 +13,8 @@ export default function RootLayout() {
13
13
  <KeyboardProvider>
14
14
  <BottomSheetModalProvider>
15
15
  <Stack>
16
- <Stack>
17
16
  <Stack.Screen name="index" />
18
- </Stack>
17
+ </Stack>
19
18
  <Toaster />
20
19
  </BottomSheetModalProvider>
21
20
  </KeyboardProvider>
@@ -1,6 +1,6 @@
1
- # Flourish Auth - Expo Router Tab Navigation with Authentication
1
+ # Flourish Auth - Expo Router Authentication App Brief
2
2
 
3
- Learn to implement **Protected Routes** and authentication flows with Expo Router.
3
+ A beautiful plant care app built with React Native, Expo, and Expo Router for learning authentication flows.
4
4
 
5
5
  ## Getting Started
6
6
 
@@ -19,135 +19,88 @@ bun start
19
19
  ```
20
20
  src/
21
21
  ├── app/
22
- │ ├── _layout.tsx # Root layout (TODO: add Protected guards)
23
- │ ├── (auth)/ # Auth route group
24
- │ │ ├── _layout.tsx # Auth stack navigator
25
- │ │ ├── welcome.tsx # TODO: Build landing screen
26
- │ │ ├── sign-in.tsx # TODO: Build sign in form
27
- │ │ └── sign-up.tsx # TODO: Build sign up form
28
- ├── (tabs)/ # Tab group (from flourish)
29
- │ │ ├── _layout.tsx # Tab navigator (complete)
30
- ├── index.tsx # Home tab (complete)
31
- ├── explore.tsx # TODO
32
- │ ├── search.tsx # TODO
33
- │ │ └── profile.tsx # TODO: Add logout button
34
- │ └── plant/
35
- │ └── [id].tsx # TODO
36
- ├── components/ui/ # Complete
22
+ │ ├── _layout.tsx # Root layout with Stack
23
+ │ ├── (tabs)/ # Tab group
24
+ │ │ ├── _layout.tsx # Tab navigator config
25
+ │ │ ├── index.tsx # Home tab
26
+ │ │ ├── explore.tsx # Explore tab
27
+ │ │ ├── search.tsx # Search tab
28
+ │ └── profile.tsx # Profile tab
29
+ ├── components/ui/
30
+ │ ├── screen.tsx
31
+ │ ├── squircle.tsx
32
+ └── text.tsx
37
33
  ├── lib/
38
- │ ├── constants/ # Plant data (complete)
39
- ├── stores/
40
- │ │ └── README.md # TODO: Create auth.ts store
41
- │ └── utils/ # Complete
34
+ │ ├── constants/index.ts # Plant data
35
+ └── utils/index.ts # Date helpers
42
36
  └── types/
37
+ └── plant.d.ts
43
38
  ```
44
39
 
45
- ## Your Tasks
40
+ ## Your Task
46
41
 
47
- ### 1. Create Auth Store (`src/lib/stores/auth.ts`)
42
+ This app brief teaches **Expo Router Authentication Flows**. The base app has tab navigation already set up. Your task is to add authentication.
48
43
 
49
- ```typescript
50
- import { create } from 'zustand';
44
+ ### Screens to Build
51
45
 
52
- interface AuthState {
53
- isLoggedIn: boolean;
54
- login: () => void;
55
- logout: () => void;
56
- }
57
-
58
- const useAuth = create<AuthState>((set) => ({
59
- isLoggedIn: false,
60
- login: () => set({ isLoggedIn: true }),
61
- logout: () => set({ isLoggedIn: false }),
62
- }));
63
-
64
- export default useAuth;
65
- ```
66
-
67
- ### 2. Implement Protected Routes (`src/app/_layout.tsx`)
46
+ 1. **`(auth)/welcome.tsx`** - Welcome/landing screen
47
+ - App branding and logo
48
+ - "Sign In" and "Sign Up" buttons
49
+ - Navigate to respective auth screens
68
50
 
69
- ```tsx
70
- import useAuth from "~/lib/stores/auth";
71
-
72
- export default function RootLayout() {
73
- const { isLoggedIn } = useAuth();
74
-
75
- return (
76
- <Stack screenOptions={{ headerShown: false }}>
77
- {/* Show auth screens when NOT logged in */}
78
- <Stack.Protected guard={!isLoggedIn}>
79
- <Stack.Screen name="(auth)" />
80
- </Stack.Protected>
81
-
82
- {/* Show app screens when logged in */}
83
- <Stack.Protected guard={isLoggedIn}>
84
- <Stack.Screen name="(tabs)" />
85
- <Stack.Screen name="plant/[id]" />
86
- </Stack.Protected>
87
- </Stack>
88
- );
89
- }
90
- ```
51
+ 2. **`(auth)/sign-in.tsx`** - Sign in form
52
+ - Email and password inputs
53
+ - Sign in button
54
+ - Link to sign up page
55
+ - Back button to welcome
91
56
 
92
- ### 3. Build Auth Screens
57
+ 3. **`(auth)/sign-up.tsx`** - Sign up form
58
+ - Name, email, and password inputs
59
+ - Sign up button
60
+ - Link to sign in page
61
+ - Back button to welcome
93
62
 
94
- **welcome.tsx** - Landing page with:
95
- - App logo and tagline
96
- - "Get Started" → sign-up
97
- - "Sign In" link → sign-in
63
+ 4. **`(auth)/_layout.tsx`** - Auth group layout
64
+ - Stack navigator for auth screens
65
+ - Configure screen options
98
66
 
99
- **sign-in.tsx** - Login form with:
100
- - Email & password inputs
101
- - Sign In button → calls `login()`
102
- - Link to sign-up
67
+ ### Authentication Concepts
103
68
 
104
- **sign-up.tsx** - Registration form with:
105
- - Name, email & password inputs
106
- - Create Account button calls `login()`
107
- - Link to sign-in
69
+ You'll need to implement:
70
+ - Protected routes (redirect unauthenticated users)
71
+ - Auth state management (using Zustand or Context)
72
+ - Conditional navigation based on auth status
73
+ - Route groups for organization: `(auth)` and `(tabs)`
108
74
 
109
- ### 4. Add Logout to Profile
75
+ ### Navigation Flow
110
76
 
111
- ```tsx
112
- import useAuth from "~/lib/stores/auth";
113
-
114
- export default function Profile() {
115
- const { logout } = useAuth();
116
-
117
- return (
118
- // ... profile content ...
119
- <Pressable onPress={logout}>
120
- <Text>Sign Out</Text>
121
- </Pressable>
122
- );
123
- }
124
77
  ```
78
+ Unauthenticated:
79
+ welcome -> sign-in -> (tabs)
80
+ welcome -> sign-up -> (tabs)
125
81
 
126
- ## Key Concepts
82
+ Authenticated:
83
+ (tabs) directly
84
+ ```
127
85
 
128
- ### Route Groups
129
- - `(auth)` - Screens for unauthenticated users
130
- - `(tabs)` - Main app for authenticated users
86
+ ### Protecting Routes
131
87
 
132
- ### Stack.Protected
133
- Conditionally shows screens based on a guard boolean:
88
+ In `_layout.tsx`, check auth state and redirect:
134
89
  ```tsx
135
- <Stack.Protected guard={condition}>
136
- <Stack.Screen name="..." />
137
- </Stack.Protected>
90
+ // Redirect to welcome if not authenticated
91
+ if (!isAuthenticated && !isAuthRoute) {
92
+ return <Redirect href="/welcome" />;
93
+ }
138
94
  ```
139
95
 
140
- ### Auth Flow
141
- 1. User opens app → sees welcome (not logged in)
142
- 2. User signs in → `login()` sets `isLoggedIn: true`
143
- 3. Protected Routes automatically show `(tabs)`
144
- 4. User taps Sign Out → `logout()` → back to `(auth)`
96
+ ## Design System
145
97
 
146
- No manual navigation needed - Protected Routes handle it!
98
+ - 🌿 Green nature theme (`#234823` primary)
99
+ - ☀️ Golden accent (`#ffce47`)
100
+ - 🟢 Squircle cards with smooth corners
147
101
 
148
- ## Available Resources
102
+ ## Available Data
149
103
 
150
- - Plant data in `lib/constants`
151
- - UI components (Screen, Text, Squircle)
152
- - Tab navigation already configured
153
- - Home screen fully implemented
104
+ - `customPlants` - Array of 5 plants with full details
105
+ - `getWeekDays(date)` - Helper for calendar display
106
+ - `Plant` interface - TypeScript type for plants
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "expo": {
3
- "name": "Flourish Auth",
3
+ "name": "flourish-auth",
4
4
  "slug": "flourish-auth",
5
5
  "version": "1.0.0",
6
6
  "orientation": "portrait",
@@ -39,4 +39,3 @@
39
39
  }
40
40
  }
41
41
  }
42
-
@@ -5,4 +5,3 @@ module.exports = function (api) {
5
5
  plugins: ["react-native-reanimated/plugin"],
6
6
  };
7
7
  };
8
-
@@ -19,4 +19,3 @@
19
19
  "production": {}
20
20
  }
21
21
  }
22
-
@@ -12,4 +12,3 @@ module.exports = withRozenite(
12
12
  }),
13
13
  { enabled: process.env.WITH_ROZENITE === "true" }
14
14
  );
15
-
@@ -42,8 +42,7 @@
42
42
  "sonner-native": "latest",
43
43
  "tailwind-merge": "latest",
44
44
  "tailwindcss": "^4.1.16",
45
- "uniwind": "^1.0.0",
46
- "zustand": "^5.0.9"
45
+ "uniwind": "^1.0.0"
47
46
  },
48
47
  "devDependencies": {
49
48
  "@rozenite/metro": "^1.0.0-alpha.16",
@@ -52,4 +51,3 @@
52
51
  "typescript": "~5.9.2"
53
52
  }
54
53
  }
55
-
@@ -1,29 +1,35 @@
1
1
  import IonIcons from "@expo/vector-icons/Ionicons";
2
2
  import { Tabs } from "expo-router";
3
+ import React from "react";
3
4
  import { useCSSVariable } from "uniwind";
4
5
 
5
- function TabBarIcon(props: {
6
+ type TabBarIconType = {
6
7
  name: React.ComponentProps<typeof IonIcons>["name"];
7
8
  color: string;
8
- }) {
9
- return <IonIcons size={28} style={{ marginBottom: -4 }} {...props} />;
10
- }
9
+ };
11
10
 
12
- export default function TabLayout() {
13
- const backgroundColor = useCSSVariable("--color-muted") as string;
11
+ const TabBarIcon = (props: TabBarIconType) => {
12
+ return <IonIcons size={28} {...props} />;
13
+ };
14
+
15
+ const TabsLayout = () => {
14
16
  const primaryColor = useCSSVariable("--color-primary") as string;
15
- const tabBarActiveTintColor = useCSSVariable(
17
+ const tabBarInActiveTintColor = useCSSVariable(
16
18
  "--color-muted-foreground"
17
19
  ) as string;
18
20
 
21
+ const backgroundColor = useCSSVariable("--color-muted") as string;
22
+
19
23
  return (
20
24
  <Tabs
21
25
  screenOptions={{
22
26
  headerShown: false,
27
+
23
28
  tabBarActiveTintColor: primaryColor,
24
- tabBarInactiveTintColor: tabBarActiveTintColor,
29
+ tabBarInactiveTintColor: tabBarInActiveTintColor,
25
30
  tabBarStyle: {
26
31
  backgroundColor,
32
+ paddingTop: 16,
27
33
  },
28
34
  }}
29
35
  >
@@ -31,7 +37,6 @@ export default function TabLayout() {
31
37
  name="index"
32
38
  options={{
33
39
  title: "Home",
34
- headerShown: false,
35
40
  tabBarShowLabel: false,
36
41
  tabBarIcon: ({ color }) => (
37
42
  <TabBarIcon name="partly-sunny" color={color} />
@@ -42,7 +47,6 @@ export default function TabLayout() {
42
47
  name="explore"
43
48
  options={{
44
49
  title: "Explore",
45
- headerShown: false,
46
50
  tabBarShowLabel: false,
47
51
  tabBarIcon: ({ color }) => <TabBarIcon name="grid" color={color} />,
48
52
  }}
@@ -51,7 +55,6 @@ export default function TabLayout() {
51
55
  name="search"
52
56
  options={{
53
57
  title: "Search",
54
- headerShown: false,
55
58
  tabBarShowLabel: false,
56
59
  tabBarIcon: ({ color }) => <TabBarIcon name="search" color={color} />,
57
60
  }}
@@ -60,12 +63,12 @@ export default function TabLayout() {
60
63
  name="profile"
61
64
  options={{
62
65
  title: "Profile",
63
- headerShown: false,
64
66
  tabBarShowLabel: false,
65
67
  tabBarIcon: ({ color }) => <TabBarIcon name="person" color={color} />,
66
68
  }}
67
69
  />
68
70
  </Tabs>
69
71
  );
70
- }
72
+ };
71
73
 
74
+ export default TabsLayout;
@@ -1,24 +1,82 @@
1
- import { View } from "react-native";
1
+ import Ionicons from "@expo/vector-icons/Ionicons";
2
+ import { FlatList, Image, Pressable, ScrollView, View } from "react-native";
3
+ import { twMerge } from "tailwind-merge";
2
4
  import { Screen } from "~/components/ui/screen";
5
+ import { Squircle } from "~/components/ui/squircle";
3
6
  import Text from "~/components/ui/text";
7
+ import { categories, customPlants } from "~/lib/constants";
4
8
 
5
- export default function Explore() {
6
- // TODO: Implement the Explore screen
7
- // - Add category filters (Indoor, Outdoor, Low Water, Pet Safe, Beginner)
8
- // - Display plants in a vertical list with larger cards
9
- // - Navigate to plant detail on press
9
+ type ExploreCardProps = {
10
+ item: Plant;
11
+ };
12
+ const ExploreCard = ({ item }: ExploreCardProps) => {
13
+ return (
14
+ <Pressable className="mx-5">
15
+ <Squircle
16
+ cornerSmoothing={1}
17
+ className="overflow-hidden rounded-3xl bg-primary"
18
+ >
19
+ <Image
20
+ source={{ uri: item.coverImg }}
21
+ className="w-full h-48"
22
+ resizeMode="cover"
23
+ />
24
+ <View className="p-5">
25
+ <Text variant="subtitle-secondary">{item.locationType}</Text>
26
+ <Text variant="caption">{item.name}</Text>
27
+ </View>
28
+ </Squircle>
29
+ </Pressable>
30
+ );
31
+ };
10
32
 
33
+ const ListHeader = () => {
11
34
  return (
12
- <Screen>
35
+ <>
13
36
  <View className="px-5 pt-4 pb-4">
14
37
  <Text variant="display" className="text-primary">
15
38
  Explore
16
39
  </Text>
17
- <Text className="text-muted-foreground mt-2">
18
- Build this screen to browse plants by category
19
- </Text>
20
40
  </View>
41
+ <ScrollView
42
+ horizontal
43
+ showsHorizontalScrollIndicator={false}
44
+ contentContainerClassName="px-5 gap-2 pb-4"
45
+ >
46
+ {categories.map((category) => (
47
+ <Pressable key={category.id}>
48
+ <Squircle
49
+ cornerSmoothing={1}
50
+ className={twMerge(
51
+ "flex-row bg-primary items-center gap-2 px-3 py-2 rounded-xl",
52
+ category.bgColor
53
+ )}
54
+ >
55
+ <Ionicons
56
+ name={category.icon as any}
57
+ size={16}
58
+ color={category.bgColor}
59
+ />
60
+ <Text variant="caption-primary">{category.label}</Text>
61
+ </Squircle>
62
+ </Pressable>
63
+ ))}
64
+ </ScrollView>
65
+ </>
66
+ );
67
+ };
68
+
69
+ const Explore = () => {
70
+ return (
71
+ <Screen>
72
+ <FlatList
73
+ data={customPlants}
74
+ renderItem={({ item }) => <ExploreCard item={item} />}
75
+ contentContainerClassName="pb-8 gap-5 pt-2"
76
+ ListHeaderComponent={ListHeader}
77
+ />
21
78
  </Screen>
22
79
  );
23
- }
80
+ };
24
81
 
82
+ export default Explore;
@@ -1,51 +1,71 @@
1
- import { View, Image, Pressable, FlatList } from "react-native";
1
+ import { format } from "date-fns";
2
+ import { router } from "expo-router";
3
+ import { FlatList, Image, Pressable, View } from "react-native";
2
4
  import { Screen } from "~/components/ui/screen";
3
- import Text from "~/components/ui/text";
4
5
  import { Squircle } from "~/components/ui/squircle";
6
+ import Text from "~/components/ui/text";
5
7
  import { customPlants } from "~/lib/constants";
6
- import { useRouter } from "expo-router";
7
- import { format } from "date-fns";
8
8
  import { getWeekDays } from "~/lib/utils";
9
9
 
10
+ type PlantCardProps = {
11
+ item: Plant;
12
+ onPress?: () => void;
13
+ };
14
+
15
+ const PlantCard = ({ item, onPress }: PlantCardProps) => {
16
+ return (
17
+ <Pressable onPress={() => router.push(`/`)} className="flex-1">
18
+ <Squircle
19
+ className="bg-primary overflow-hidden rounded-3xl p-2"
20
+ cornerSmoothing={1}
21
+ >
22
+ <Squircle className="overflow-hidden rounded-2xl w-full aspect-square">
23
+ <Image
24
+ source={{ uri: item.coverImg }}
25
+ className="w-full h-full aspect-square"
26
+ />
27
+ </Squircle>
28
+ <View className="px-1 py-2">
29
+ <Text variant="body-secondary">{item.name}</Text>
30
+ </View>
31
+ </Squircle>
32
+ </Pressable>
33
+ );
34
+ };
35
+
10
36
  const now = new Date();
11
37
  const weekDays = getWeekDays(now);
12
38
 
13
39
  const HomeHeader = () => {
14
40
  return (
15
- <View className="px-5 pt-4 pb-4">
41
+ <View className="px-5 py-4">
16
42
  <View className="flex-row justify-between items-start mb-5">
17
43
  <View className="flex-row items-center gap-2">
18
- <Text variant="display" className="text-4xl">{format(now, "EEE")}</Text>
19
- <View className="w-3 h-3 rounded-full bg-accent" />
44
+ <Text variant="display">{format(now, "EEE")}</Text>
45
+ <View className="w-4 h-4 rounded-full bg-accent"></View>
20
46
  </View>
21
-
22
47
  <View className="items-end">
23
48
  <Text variant="subtitle">{format(now, "MMMM d")}</Text>
24
- <Text className="text-lg text-foreground/70">{format(now, "yyyy")}</Text>
49
+ <Text>{format(now, "yyyy")}</Text>
25
50
  </View>
26
51
  </View>
27
-
28
52
  <View className="flex-row justify-between">
29
53
  {weekDays.map((day, index) => (
30
54
  <View key={index} className="items-center">
31
55
  {day.isToday ? (
32
56
  <Squircle
33
- className="bg-primary px-2 py-2 items-center rounded-xl"
57
+ className="bg-primary p-2 items-center rounded-xl"
34
58
  cornerSmoothing={1}
35
59
  >
36
- <Text className="text-lg font-bold text-primary-foreground">
37
- {day.date}
38
- </Text>
39
- <Text className="text-xs font-medium text-secondary mt-1">
40
- {day.day}
41
- </Text>
60
+ <Text variant="subtitle-secondary">{day.date}</Text>
61
+ <Text variant="caption">{day.day}</Text>
42
62
  </Squircle>
43
63
  ) : (
44
- <View className="px-2 py-2 items-center">
45
- <Text className="text-lg font-medium text-primary/40">
64
+ <View className="p-2 items-center">
65
+ <Text variant="subtitle" className="text-primary/40">
46
66
  {day.date}
47
67
  </Text>
48
- <Text className="text-xs font-medium text-primary/30 mt-1">
68
+ <Text variant="caption" className="text-primary/30">
49
69
  {day.day}
50
70
  </Text>
51
71
  </View>
@@ -57,46 +77,21 @@ const HomeHeader = () => {
57
77
  );
58
78
  };
59
79
 
60
- const PlantCard = ({ item, onPress }: { item: Plant; onPress?: () => void }) => (
61
- <Pressable onPress={onPress} className="flex-1">
62
- <Squircle className="bg-primary overflow-hidden rounded-3xl p-2" cornerSmoothing={1}>
63
- <Squircle className="overflow-hidden rounded-2xl w-full aspect-square" cornerSmoothing={1}>
64
- <Image source={{ uri: item.coverImg }} className="w-full h-full" resizeMode="cover" />
65
- </Squircle>
66
- <View className="px-1 py-2">
67
- <Text variant="body-secondary" className="text-primary-foreground">{item.name}</Text>
68
- </View>
69
- </Squircle>
70
- </Pressable>
71
- );
72
-
73
- const ListHeader = () => (
74
- <>
75
- <HomeHeader />
76
- <View className="px-5 pb-4">
77
- <Text variant="subtitle" className="text-primary">Our Plants</Text>
78
- </View>
79
- </>
80
- );
81
-
82
- export default function Index() {
83
- const router = useRouter();
84
-
80
+ const Home = () => {
85
81
  return (
86
82
  <Screen>
87
83
  <FlatList
88
84
  data={customPlants}
85
+ renderItem={({ item }) => <PlantCard item={item} />}
89
86
  numColumns={2}
87
+ ListHeaderComponent={HomeHeader}
90
88
  keyExtractor={(item) => item.id}
91
- renderItem={({ item }) => (
92
- <PlantCard item={item} onPress={() => router.push(`/plant/${item.id}`)} />
93
- )}
94
- ListHeaderComponent={ListHeader}
95
- columnWrapperClassName="gap-4 px-5"
96
89
  contentContainerClassName="gap-4 pb-8"
90
+ columnWrapperClassName="gap-4 px-5"
97
91
  showsVerticalScrollIndicator={false}
98
92
  />
99
93
  </Screen>
100
94
  );
101
- }
95
+ };
102
96
 
97
+ export default Home;
@@ -1,27 +1,109 @@
1
- import { View } from "react-native";
1
+ import Ionicons from "@expo/vector-icons/Ionicons";
2
+ import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
3
+ import { Image, Pressable, ScrollView, View } from "react-native";
4
+ import { useCSSVariable } from "uniwind";
2
5
  import { Screen } from "~/components/ui/screen";
6
+ import { Squircle } from "~/components/ui/squircle";
3
7
  import Text from "~/components/ui/text";
8
+ import { userData } from "~/lib/constants";
4
9
 
5
- export default function Profile() {
6
- // TODO: Build the profile screen
7
- // - Import useAuth from ~/lib/stores/auth
8
- // - Get logout function from auth store
9
- // - Display user avatar and name
10
- // - Show plant stats (plants owned, waterings)
11
- // - Add "Currently..." status card
12
- // - Add Edit Profile button
13
- // - Add Sign Out button that calls logout()
10
+ const Profile = () => {
11
+ const [primaryColor, secondaryColor] = useCSSVariable([
12
+ "--color-primary",
13
+ "--color-secondary",
14
+ ]);
14
15
 
15
16
  return (
16
17
  <Screen>
17
- <View className="px-5 pt-4">
18
- <Text variant="display" className="text-primary">
19
- Profile
20
- </Text>
21
- <Text className="text-muted-foreground mt-2">
22
- Build this screen with user info and Sign Out button
18
+ <ScrollView
19
+ className="flex-1"
20
+ contentContainerClassName="pb-8"
21
+ showsVerticalScrollIndicator={false}
22
+ >
23
+ <View className="items-center mt-8 px-8">
24
+ <View className="relative w-36">
25
+ <Squircle className="absolute -bottom-1.5 -left-1.5 w-full aspect-3/4 bg-primary rounded-3xl -rotate-3" />
26
+ <Squircle
27
+ className="w-full aspect-3/4
28
+ overflow-hidden rounded-3xl z-10
29
+ "
30
+ >
31
+ <Image
32
+ source={{ uri: userData.avatar }}
33
+ className="w-full h-full"
34
+ resizeMode="cover"
35
+ />
36
+ </Squircle>
37
+ </View>
38
+ </View>
39
+ <Text variant="title" className="text-primary text-center my-4">
40
+ {userData.username}
23
41
  </Text>
42
+
43
+ <View className="px-6 mt-8 pb-4">
44
+ <View className="relative">
45
+ <View className="absolute -top-3 left-8 flex-row items-end gap-1 z-10">
46
+ <View className="size-4 rounded-full bg-primary" />
47
+ <View className="size-5 rounded-full bg-primary" />
48
+ </View>
49
+ <Squircle
50
+ className="absolute top-1 left-0 right-0 -bottom-2 bg-accent
51
+ rounded-3xl"
52
+ />
53
+
54
+ <Squircle className="bg-primary p-5 rounded-3xl z-10">
55
+ <View className="flex-row items-center gap-4">
56
+ <View className="w-12 h-12 rounded-full bg-secondary/20 items-center justify-center">
57
+ <Ionicons
58
+ name="chatbubble-outline"
59
+ size={26}
60
+ color={"white"}
61
+ />
62
+ </View>
63
+ <View className="flex-1">
64
+ <Text className="text-secondary/80">
65
+ {userData.plantLoveLabel}
66
+ </Text>
67
+ <Text className="text-primary-foreground">
68
+ {userData.plantLoveValue}
69
+ </Text>
70
+ </View>
71
+ </View>
72
+ </Squircle>
73
+ </View>
74
+ </View>
75
+
76
+ <View className="px-6 mt-6 gap-5">
77
+ <View className="flex-row items-center gap-4">
78
+ <MaterialCommunityIcons
79
+ name="flower-tulip"
80
+ size={28}
81
+ color={primaryColor as string}
82
+ />
83
+ <Text variant="title" className="text-primary">
84
+ {userData.plantsOwned} Plants
85
+ </Text>
86
+ </View>
87
+ <View className="flex-row items-center gap-4">
88
+ <MaterialCommunityIcons
89
+ name="water"
90
+ size={28}
91
+ color={primaryColor as string}
92
+ />
93
+ <Text variant="title" className="text-primary">
94
+ {userData.plantsWatered} Watered
95
+ </Text>
96
+ </View>
97
+ </View>
98
+ </ScrollView>
99
+
100
+ <View className="px-6 pb-6 pt-4">
101
+ <Pressable className="flex-row items-center justify-center gap-3 bg-primary py-4 px-6 rounded-full">
102
+ <Text variant="button">Edit Profile</Text>
103
+ </Pressable>
24
104
  </View>
25
105
  </Screen>
26
106
  );
27
- }
107
+ };
108
+
109
+ export default Profile;
@@ -1,24 +1,25 @@
1
- import { View } from "react-native";
1
+ import Ionicons from "@expo/vector-icons/Ionicons";
2
+ import { TextInput, View } from "react-native";
3
+ import { useCSSVariable } from "uniwind";
2
4
  import { Screen } from "~/components/ui/screen";
3
- import Text from "~/components/ui/text";
4
5
 
5
- export default function Search() {
6
- // TODO: Implement the Search screen
7
- // - Add a search input with icon
8
- // - Filter plants by name as user types
9
- // - Display matching results
6
+ const Search = () => {
7
+ const [mutedColor] = useCSSVariable(["--color-muted-foreground"]);
10
8
 
11
9
  return (
12
10
  <Screen>
13
11
  <View className="px-5 pt-4">
14
- <Text variant="display" className="text-primary">
15
- Search
16
- </Text>
17
- <Text className="text-muted-foreground mt-2">
18
- Build this screen to search for plants
19
- </Text>
12
+ <View className="flex-row items-center bg-muted/50 rounded-xl px-4 py-3 gap-3">
13
+ <Ionicons name="search" size={20} />
14
+ <TextInput
15
+ placeholder="Search plants..."
16
+ placeholderTextColor={mutedColor as string}
17
+ className="flex-1 text-base text-foreground"
18
+ />
19
+ </View>
20
20
  </View>
21
21
  </Screen>
22
22
  );
23
- }
23
+ };
24
24
 
25
+ export default Search;
@@ -8,31 +8,16 @@ import "../../global.css";
8
8
  import { StyleSheet } from "react-native";
9
9
 
10
10
  export default function RootLayout() {
11
- // TODO: Import useAuth from ~/lib/stores/auth
12
- // TODO: Get isLoggedIn from the auth store
13
-
14
11
  return (
15
12
  <GestureHandlerRootView style={styles.container}>
16
13
  <KeyboardProvider>
17
14
  <BottomSheetModalProvider>
18
- {/*
19
- TODO: Implement Protected Routes
20
-
21
- Use Stack.Protected to show different screens based on auth state:
22
-
23
- <Stack.Protected guard={!isLoggedIn}>
24
- <Stack.Screen name="(auth)" />
25
- </Stack.Protected>
26
-
27
- <Stack.Protected guard={isLoggedIn}>
28
- <Stack.Screen name="(tabs)" />
29
- <Stack.Screen name="plant/[id]" />
30
- </Stack.Protected>
31
- */}
32
- <Stack screenOptions={{ headerShown: false }}>
33
- <Stack.Screen name="(auth)" />
15
+ <Stack
16
+ screenOptions={{
17
+ headerShown: false,
18
+ }}
19
+ >
34
20
  <Stack.Screen name="(tabs)" />
35
- <Stack.Screen name="plant/[id]" />
36
21
  </Stack>
37
22
  <Toaster />
38
23
  </BottomSheetModalProvider>
@@ -17,4 +17,3 @@ export const Screen = ({ children, className }: ScreenProps) => {
17
17
  </SafeAreaView>
18
18
  );
19
19
  };
20
-
@@ -2,4 +2,3 @@ import { withUniwind } from "uniwind";
2
2
  import SquircleView from "react-native-fast-squircle";
3
3
 
4
4
  export const Squircle = withUniwind(SquircleView);
5
-
@@ -52,4 +52,3 @@ const Text = ({
52
52
  };
53
53
 
54
54
  export default Text;
55
-
@@ -146,3 +146,50 @@ export const customPlants: Plant[] = [
146
146
  },
147
147
  ];
148
148
 
149
+ export const categories = [
150
+ {
151
+ id: "indoor",
152
+ label: "Indoor",
153
+ icon: "home-outline",
154
+ bgColor: "bg-primary/15",
155
+ iconColor: "#234823",
156
+ },
157
+ {
158
+ id: "outdoor",
159
+ label: "Outdoor",
160
+ icon: "sunny-outline",
161
+ bgColor: "bg-accent/40",
162
+ iconColor: "#234823",
163
+ },
164
+ {
165
+ id: "low-water",
166
+ label: "Low Water",
167
+ icon: "water-outline",
168
+ bgColor: "bg-muted",
169
+ iconColor: "#234823",
170
+ },
171
+ {
172
+ id: "pet-safe",
173
+ label: "Pet Safe",
174
+ icon: "paw-outline",
175
+ bgColor: "bg-secondary",
176
+ iconColor: "#234823",
177
+ },
178
+ {
179
+ id: "beginner",
180
+ label: "Beginner",
181
+ icon: "leaf-outline",
182
+ bgColor: "bg-primary/10",
183
+ iconColor: "#234823",
184
+ },
185
+ ];
186
+
187
+ export const userData = {
188
+ username: "Olive",
189
+ level: 5,
190
+ avatar: customPlants[3].coverImg,
191
+ plantLoveLabel: "Currently",
192
+ plantLoveValue: "Talking to my plants",
193
+ plantsOwned: 12,
194
+ plantsWatered: 47,
195
+ };
@@ -5,4 +5,3 @@ export const getWeekDays = (from = new Date()) =>
5
5
  const date = addDays(from, i);
6
6
  return { day: format(date, "EEE").toUpperCase(), date: date.getDate(), isToday: isToday(date) };
7
7
  });
8
-
@@ -35,4 +35,3 @@ interface CareSchedule {
35
35
  seasonalTiming: string;
36
36
  instructions: string;
37
37
  }
38
-
@@ -14,4 +14,3 @@
14
14
  "./uniwind-types.d.ts"
15
15
  ]
16
16
  }
17
-
@@ -1,14 +0,0 @@
1
- import { Stack } from "expo-router";
2
-
3
- export default function AuthLayout() {
4
- // This is the auth stack navigator
5
- // It wraps the welcome, sign-in, and sign-up screens
6
-
7
- return (
8
- <Stack screenOptions={{ headerShown: false }}>
9
- <Stack.Screen name="welcome" />
10
- <Stack.Screen name="sign-in" />
11
- <Stack.Screen name="sign-up" />
12
- </Stack>
13
- );
14
- }
@@ -1,25 +0,0 @@
1
- import { View } from "react-native";
2
- import { Screen } from "~/components/ui/screen";
3
- import Text from "~/components/ui/text";
4
-
5
- export default function SignIn() {
6
- // TODO: Build the sign-in screen
7
- // - Add back button to go back
8
- // - Add email input field
9
- // - Add password input field
10
- // - Add "Sign In" button that calls login() from auth store
11
- // - Add link to sign-up screen
12
-
13
- return (
14
- <Screen>
15
- <View className="flex-1 px-6 justify-center">
16
- <Text variant="display" className="text-primary">
17
- Sign In
18
- </Text>
19
- <Text className="text-muted-foreground mt-2">
20
- Build this screen with email/password form
21
- </Text>
22
- </View>
23
- </Screen>
24
- );
25
- }
@@ -1,26 +0,0 @@
1
- import { View } from "react-native";
2
- import { Screen } from "~/components/ui/screen";
3
- import Text from "~/components/ui/text";
4
-
5
- export default function SignUp() {
6
- // TODO: Build the sign-up screen
7
- // - Add back button to go back
8
- // - Add name input field
9
- // - Add email input field
10
- // - Add password input field
11
- // - Add "Create Account" button that calls login() from auth store
12
- // - Add link to sign-in screen
13
-
14
- return (
15
- <Screen>
16
- <View className="flex-1 px-6 justify-center">
17
- <Text variant="display" className="text-primary">
18
- Sign Up
19
- </Text>
20
- <Text className="text-muted-foreground mt-2">
21
- Build this screen with registration form
22
- </Text>
23
- </View>
24
- </Screen>
25
- );
26
- }
@@ -1,22 +0,0 @@
1
- import { View } from "react-native";
2
- import { Screen } from "~/components/ui/screen";
3
- import Text from "~/components/ui/text";
4
-
5
- export default function Welcome() {
6
- // TODO: Build the welcome/landing screen
7
- // - Display app logo/icon (use MaterialCommunityIcons "flower")
8
- // - Show app name "Flourish" and tagline
9
- // - Add "Get Started" button → navigates to sign-up
10
- // - Add "Already have an account? Sign In" link → navigates to sign-in
11
-
12
- return (
13
- <Screen className="justify-center items-center">
14
- <Text variant="display" className="text-primary">
15
- Welcome
16
- </Text>
17
- <Text className="text-muted-foreground mt-2">
18
- Build this screen with login/signup buttons
19
- </Text>
20
- </Screen>
21
- );
22
- }
@@ -1,48 +0,0 @@
1
- import { View } from "react-native";
2
- import { Stack, useLocalSearchParams } from "expo-router";
3
- import { Screen } from "~/components/ui/screen";
4
- import Text from "~/components/ui/text";
5
- import { customPlants } from "~/lib/constants";
6
-
7
- export default function PlantDetails() {
8
- const { id } = useLocalSearchParams<{ id: string }>();
9
-
10
- const plant = customPlants.find((p) => p.id === id);
11
-
12
- if (!plant) {
13
- return (
14
- <>
15
- <Stack.Screen options={{ headerShown: false }} />
16
- <Screen className="flex-1 items-center justify-center bg-background">
17
- <Text>Plant not found</Text>
18
- </Screen>
19
- </>
20
- );
21
- }
22
-
23
- // TODO: Implement the Plant Details screen
24
- // - Add back button navigation
25
- // - Display plant image in a stacked card design
26
- // - Show plant name and scientific name
27
- // - Add quick info pills (water needs, light needs)
28
- // - Display "About this plant" card with description
29
- // - Show care details (water frequency, growth rate, maintenance)
30
-
31
- return (
32
- <>
33
- <Stack.Screen options={{ headerShown: false }} />
34
- <Screen className="flex-1 bg-background">
35
- <View className="px-5 pt-4">
36
- <Text variant="display">{plant.name}</Text>
37
- <Text className="text-muted-foreground mt-1">
38
- {plant.scientificName}
39
- </Text>
40
- <Text className="text-muted-foreground mt-4">
41
- Build this screen to show plant details
42
- </Text>
43
- </View>
44
- </Screen>
45
- </>
46
- );
47
- }
48
-
@@ -1,31 +0,0 @@
1
- # Stores
2
-
3
- This folder is where you'll create your Zustand stores for authentication.
4
-
5
- ## Your Task
6
-
7
- Create an `auth.ts` store here to manage authentication state.
8
-
9
- The store should have:
10
- - `isLoggedIn` - boolean state
11
- - `login()` - function to set logged in
12
- - `logout()` - function to set logged out
13
-
14
- ## Example Structure
15
-
16
- ```typescript
17
- import { create } from 'zustand';
18
-
19
- interface AuthState {
20
- isLoggedIn: boolean;
21
- login: () => void;
22
- logout: () => void;
23
- }
24
-
25
- const useAuth = create<AuthState>((set) => ({
26
- // Your implementation here
27
- }));
28
-
29
- export default useAuth;
30
- ```
31
-