newcandies 0.1.21 → 0.1.22

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": "newcandies",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Scaffold Expo Router + Uniwind React Native apps with layered templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,3 @@
1
+ # API layer
2
+
3
+ Add your client and hooks here.
@@ -1,84 +1,15 @@
1
- import { Ionicons } from "@expo/vector-icons";
2
- import { FlashList } from "@shopify/flash-list";
3
- import { useRouter } from "expo-router";
4
- import { ActivityIndicator, RefreshControl, View } from "react-native";
5
- import { usePlants } from "~/api/plants/usePlants";
6
- import ItemSeparator from "~/components/screens/shared/item-separator";
7
- import PlantCard from "~/components/screens/shared/plant-card";
8
- import QueryState from "~/components/screens/shared/query-state";
9
1
  import { Screen } from "~/components/ui/screen";
10
2
  import Text from "~/components/ui/text";
11
3
 
12
4
  export default function Two() {
13
- const router = useRouter();
14
-
15
- const {
16
- plants,
17
- status,
18
- error,
19
- fetchNextPage,
20
- hasNextPage,
21
- isFetchingNextPage,
22
- refetch,
23
- isRefetching,
24
- } = usePlants({ pageSize: 10 });
25
-
26
- const renderItem = ({ item }: { item: Plant }) => (
27
- <PlantCard
28
- id={item.id}
29
- name={item.name}
30
- scientificName={item.scientificName}
31
- coverImg={item.coverImg}
32
- maintenance={item.maintenance}
33
- onPress={() => router.push(`/plant/${item.id}`)}
34
- />
35
- );
36
-
37
- const onEndReached = () => {
38
- if (hasNextPage && !isFetchingNextPage) {
39
- fetchNextPage();
40
- }
41
- };
42
-
43
- if (status === "error") {
44
- return (
45
- <QueryState
46
- isError={true}
47
- error={
48
- error instanceof Error ? error : new Error("Failed to load plants")
49
- }
50
- />
51
- );
52
- }
53
-
54
5
  return (
55
6
  <Screen>
56
- <View className="px-6 pt-5 pb-4 flex-row items-center gap-3">
57
- <Ionicons name="sunny" size={32} color="#234823" />
58
- <Text variant="display" className="text-primary">
59
- Plants
60
- </Text>
61
- </View>
62
- <FlashList
63
- data={plants}
64
- renderItem={renderItem}
65
- keyExtractor={(item: Plant) => item.id}
66
- className="px-4"
67
- showsVerticalScrollIndicator={false}
68
- ItemSeparatorComponent={ItemSeparator}
69
- onEndReached={onEndReached}
70
- onEndReachedThreshold={0.5}
71
- ListFooterComponent={
72
- isFetchingNextPage ? (
73
- <View className="py-4 items-center">
74
- <ActivityIndicator size="small" />
75
- </View>
76
- ) : null
77
- }
78
- refreshControl={
79
- <RefreshControl onRefresh={refetch} refreshing={isRefetching} />
80
- }
81
- />
7
+ <Text variant="title" className="px-4">
8
+ Explore tab
9
+ </Text>
10
+ <Text className="px-4 text-base text-muted-foreground">
11
+ Build your infinite list or discovery UI here.
12
+ </Text>
82
13
  </Screen>
83
14
  );
84
15
  }
@@ -1,46 +1,15 @@
1
- import { FlashList, ListRenderItem } from "@shopify/flash-list";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { useRouter } from "expo-router";
4
- import { getPlants } from "~/api/plants";
5
- import { HomeHeader } from "~/components/screens/home/home-header";
6
- import ItemSeparator from "~/components/screens/shared/item-separator";
7
- import PlantCard from "~/components/screens/shared/plant-card";
8
- import QueryState from "~/components/screens/shared/query-state";
9
1
  import { Screen } from "~/components/ui/screen";
2
+ import Text from "~/components/ui/text";
10
3
 
11
4
  export default function Index() {
12
- const router = useRouter();
13
- const { data, isPending, isError, error } = useQuery<PlantsResponse>({
14
- queryKey: ["plants"],
15
- queryFn: getPlants,
16
- });
17
-
18
- const renderItem = ({ item }: { item: Plant }) => (
19
- <PlantCard
20
- id={item.id}
21
- name={item.name}
22
- scientificName={item.scientificName}
23
- coverImg={item.coverImg}
24
- maintenance={item.maintenance}
25
- onPress={() => router.push(`/plant/${item.id}`)}
26
- />
27
- );
28
-
29
- if (isPending || isError) {
30
- return <QueryState isPending={isPending} isError={isError} error={error} />;
31
- }
32
-
33
5
  return (
34
6
  <Screen>
35
- <HomeHeader />
36
- <FlashList
37
- data={data?.plants || []}
38
- keyExtractor={(item: Plant) => item.id}
39
- ItemSeparatorComponent={ItemSeparator}
40
- showsVerticalScrollIndicator={false}
41
- renderItem={renderItem}
42
- className="px-4"
43
- />
7
+ <Text variant="title" className="px-4">
8
+ Sproutsy Home
9
+ </Text>
10
+ <Text className="px-4 text-base text-muted-foreground">
11
+ Replace this placeholder with your plant feed UI.
12
+ </Text>
44
13
  </Screen>
45
14
  );
46
15
  }
@@ -4,7 +4,6 @@ import { GestureHandlerRootView } from "react-native-gesture-handler";
4
4
  import { KeyboardProvider } from "react-native-keyboard-controller";
5
5
  import "react-native-reanimated";
6
6
  import { Toaster } from "sonner-native";
7
- import APIProvider from "~/api/api-provider";
8
7
  import "../../global.css";
9
8
  import { StyleSheet } from "react-native";
10
9
 
@@ -12,14 +11,13 @@ export default function RootLayout() {
12
11
  return (
13
12
  <GestureHandlerRootView style={styles.container}>
14
13
  <KeyboardProvider>
15
- <APIProvider>
16
- <BottomSheetModalProvider>
17
- <Stack>
18
- <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
19
- </Stack>
20
- <Toaster />
21
- </BottomSheetModalProvider>
22
- </APIProvider>
14
+ <BottomSheetModalProvider>
15
+ {/* TODO: Wrap your API/query providers here */}
16
+ <Stack>
17
+ <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
18
+ </Stack>
19
+ <Toaster />
20
+ </BottomSheetModalProvider>
23
21
  </KeyboardProvider>
24
22
  </GestureHandlerRootView>
25
23
  );
@@ -1,91 +1,21 @@
1
- import { Ionicons } from "@expo/vector-icons";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { Stack, useLocalSearchParams, useRouter } from "expo-router";
4
- import { Image, Pressable, ScrollView, View } from "react-native";
5
- import { getPlantById } from "~/api/plants";
6
- import QueryState from "~/components/screens/shared/query-state";
1
+ import { Stack, useLocalSearchParams } from "expo-router";
7
2
  import { Screen } from "~/components/ui/screen";
8
- import { Squircle } from "~/components/ui/squircle";
9
3
  import Text from "~/components/ui/text";
10
4
 
11
5
  export default function PlantDetails() {
12
6
  const { id } = useLocalSearchParams<{ id: string }>();
13
- const router = useRouter();
14
-
15
- const {
16
- data: plant,
17
- isPending,
18
- isError,
19
- error,
20
- } = useQuery<Plant>({
21
- queryKey: ["plant", id],
22
- queryFn: () => getPlantById(id!),
23
- enabled: !!id,
24
- });
25
-
26
- if (isPending || isError || !plant) {
27
- return (
28
- <QueryState
29
- isPending={isPending}
30
- isError={isError || !plant}
31
- error={error}
32
- errorMessage="Error"
33
- />
34
- );
35
- }
36
7
 
37
8
  return (
38
9
  <>
39
10
  <Stack.Screen options={{ headerShown: false }} />
40
- <Screen className="flex-1 relative">
41
- <ScrollView className="flex-1">
42
- <View className="bg-background">
43
- <Image
44
- source={{ uri: plant.coverImg }}
45
- className="w-full h-96"
46
- resizeMode="cover"
47
- />
48
- <Pressable
49
- onPress={() => router.back()}
50
- className="absolute top-4 left-4 w-10 h-10 rounded-full bg-white justify-center items-center"
51
- >
52
- <Ionicons name="chevron-back" size={24} color={"black"} />
53
- </Pressable>
54
- </View>
55
-
56
- <View className="p-6 bg-primary-foreground">
57
- <Text variant="display">{plant.name}</Text>
58
- <Text variant="subtitle" className="text-primary mb-4">
59
- {plant.scientificName}
60
- </Text>
61
-
62
- <Text variant="title" className="text-primary mb-6">
63
- ${(plant.price / 100).toFixed(2)}
64
- </Text>
65
-
66
- <View className="mb-6">
67
- <Text variant="subtitle" className="mb-2">
68
- About
69
- </Text>
70
- <Text className="leading-6">{plant.description}</Text>
71
- </View>
72
-
73
- {plant.specialCare && (
74
- <View className="mb-6">
75
- <Text variant="subtitle" className="mb-3">
76
- Special Care
77
- </Text>
78
- <Squircle
79
- cornerSmoothing={1}
80
- className="bg-white p-6"
81
- style={{ borderRadius: 16 }}
82
- >
83
- <Text className="leading-6">{plant.specialCare}</Text>
84
- </Squircle>
85
- </View>
86
- )}
87
- </View>
88
- </ScrollView>
11
+ <Screen className="flex-1 items-center justify-center bg-background px-6">
12
+ <Text variant="title" className="mb-2">
13
+ Plant details
14
+ </Text>
15
+ <Text className="text-center text-muted-foreground">
16
+ Use the ID param ({id ?? "?"}) to fetch the selected plant and display
17
+ its details here.
18
+ </Text>
89
19
  </Screen>
90
20
  </>
91
21
  );
@@ -1,15 +0,0 @@
1
- import { ReactNode } from "react";
2
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
- import { useTanStackQueryDevTools } from "@rozenite/tanstack-query-plugin";
4
-
5
- const queryClient = new QueryClient();
6
-
7
- const APIProvider = ({ children }: { children: ReactNode }) => {
8
- useTanStackQueryDevTools(queryClient);
9
-
10
- return (
11
- <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
12
- );
13
- };
14
-
15
- export default APIProvider;
@@ -1,8 +0,0 @@
1
- import axios from 'axios';
2
-
3
- console.log(process.env.EXPO_PUBLIC_API_URL, 'logging env');
4
-
5
- export const client = axios.create({
6
- baseURL: process.env.EXPO_PUBLIC_API_URL,
7
- timeout: 60000,
8
- });
@@ -1,18 +0,0 @@
1
- import { client } from "../client";
2
-
3
- export const getPlants = async () => {
4
- const response = await client.get("/plants/");
5
- return response.data;
6
- };
7
-
8
- export const listPlants = async (page: number = 1, pageSize: number = 20) => {
9
- const response = await client.get("/plants/", {
10
- params: { page, limit:pageSize },
11
- });
12
- return response.data;
13
- };
14
-
15
- export const getPlantById = async (id: string) => {
16
- const response = await client.get(`/plants/${id}`);
17
- return response.data.data;
18
- };
@@ -1,46 +0,0 @@
1
- import { useInfiniteQuery } from "@tanstack/react-query";
2
- import { listPlants } from ".";
3
-
4
- interface UsePlantsOptions {
5
- pageSize?: number;
6
- }
7
-
8
- export const usePlants = (options: UsePlantsOptions = {}) => {
9
- const { pageSize = 10 } = options;
10
-
11
- const {
12
- data,
13
- fetchNextPage,
14
- hasNextPage,
15
- isFetchingNextPage,
16
- refetch,
17
- isRefetching,
18
- isLoading,
19
- status,
20
- error,
21
- } = useInfiniteQuery({
22
- queryKey: ["plants-list", pageSize],
23
- queryFn: ({ pageParam = 1 }) => listPlants(pageParam, pageSize),
24
- initialPageParam: 1,
25
- getNextPageParam: (lastPage: PlantsResponse, _, lastPageParam) => {
26
- if (lastPage.metadata.currentPage >= lastPage.metadata.lastPage) {
27
- return undefined;
28
- }
29
- return lastPageParam + 1;
30
- },
31
- });
32
-
33
- const plants: Plant[] = data?.pages.flatMap((page) => page.plants) ?? [];
34
-
35
- return {
36
- plants,
37
- status,
38
- error,
39
- isLoading,
40
- refetch,
41
- isRefetching,
42
- isFetchingNextPage,
43
- hasNextPage,
44
- fetchNextPage,
45
- };
46
- };
@@ -1,38 +0,0 @@
1
- import { Image, View } from "react-native";
2
- import Text from "~/components/ui/text";
3
-
4
- const getDate = () => {
5
- const date = new Date();
6
- return {
7
- weekday: date.toLocaleString("en-US", { weekday: "long" }),
8
- };
9
- };
10
-
11
- export const HomeHeader = () => {
12
- const { weekday } = getDate();
13
-
14
- return (
15
- <View className="px-6 pt-2">
16
- <View className="flex-row items-center justify-between mb-6">
17
- <View className="flex-row items-center gap-3">
18
- <Image
19
- source={require("assets/images/sproutsies-logo.png")}
20
- style={{ width: 24, height: 24 }}
21
- resizeMode="contain"
22
- />
23
- <Image
24
- source={require("assets/images/sproutsy-main-logo.png")}
25
- style={{ height: 24, width: 124 }}
26
- resizeMode="contain"
27
- />
28
- </View>
29
- <View className="items-end">
30
- <Text variant="caption-primary" className="text-muted-foreground">
31
- Happy
32
- </Text>
33
- <Text variant="subtitle-primary">{weekday}</Text>
34
- </View>
35
- </View>
36
- </View>
37
- );
38
- };
@@ -1,7 +0,0 @@
1
- import { View } from "react-native";
2
-
3
- const ItemSeparator = () => {
4
- return <View className="h-4" />;
5
- };
6
-
7
- export default ItemSeparator;
@@ -1,70 +0,0 @@
1
- import { Ionicons } from "@expo/vector-icons";
2
- import { Image, Pressable, View } from "react-native";
3
- import { Squircle } from "~/components/ui/squircle";
4
- import Text from "~/components/ui/text";
5
-
6
- type PlantCardProps = {
7
- id: string;
8
- name: string;
9
- scientificName: string;
10
- coverImg: string;
11
- maintenance?: string;
12
- onPress?: () => void;
13
- };
14
-
15
- const PlantCard = ({
16
- name,
17
- scientificName,
18
- coverImg,
19
- maintenance,
20
- onPress,
21
- }: PlantCardProps) => {
22
- return (
23
- <Pressable onPress={onPress}>
24
- <Squircle cornerSmoothing={1} className="overflow-hidden rounded-3xl">
25
- <View className="bg-background">
26
- <Image
27
- source={{ uri: coverImg }}
28
- className="h-[356] w-full"
29
- resizeMode="cover"
30
- />
31
-
32
- <View className="flex-1 bg-card p-4 justify-end">
33
- <Text variant="subtitle" className="text-card-foreground mb-3">
34
- {name}
35
- </Text>
36
- <View className="flex-row justify-between items-center gap-2">
37
- <View className="flex-1 flex-row items-center gap-2">
38
- <View className="w-5 h-5 bg-overlay items-center justify-center rounded-full">
39
- <Ionicons name="flask" size={12} color="white" />
40
- </View>
41
- <View className="flex-1">
42
- <Text variant="caption">Scientific</Text>
43
- <Text variant="caption" numberOfLines={1}>
44
- {scientificName}
45
- </Text>
46
- </View>
47
- </View>
48
- {maintenance && (
49
- <Squircle
50
- cornerSmoothing={1}
51
- className="bg-overlay-muted px-3 py-1 flex-row items-center gap-2 rounded-xl"
52
- >
53
- <Ionicons name="shield-checkmark" size={12} color="white" />
54
- <View>
55
- <Text variant="caption">Care</Text>
56
- <Text className="text-card-foreground" variant="caption-primary">
57
- {maintenance.toLowerCase()}
58
- </Text>
59
- </View>
60
- </Squircle>
61
- )}
62
- </View>
63
- </View>
64
- </View>
65
- </Squircle>
66
- </Pressable>
67
- );
68
- };
69
-
70
- export default PlantCard;
@@ -1,50 +0,0 @@
1
- import { View, ActivityIndicator } from "react-native";
2
- import { Stack } from "expo-router";
3
- import { Screen } from "~/components/ui/screen";
4
- import Text from "~/components/ui/text";
5
-
6
- interface QueryStateProps {
7
- isPending?: boolean;
8
- isError?: boolean;
9
- error?: Error | null;
10
- errorMessage?: string;
11
- }
12
-
13
- const QueryState = ({
14
- isPending,
15
- isError,
16
- error,
17
- errorMessage = "Something went wrong",
18
- }: QueryStateProps) => {
19
- if (isPending) {
20
- return (
21
- <>
22
- <Stack.Screen options={{ headerShown: false }} />
23
- <Screen>
24
- <View className="flex-1 items-center justify-center">
25
- <ActivityIndicator size="large" />
26
- </View>
27
- </Screen>
28
- </>
29
- );
30
- }
31
-
32
- if (isError) {
33
- return (
34
- <>
35
- <Stack.Screen options={{ headerShown: false }} />
36
- <Screen>
37
- <View className="flex-1 items-center justify-center">
38
- <Text className="text-lg font-medium text-red-500">
39
- {error?.message || errorMessage}
40
- </Text>
41
- </View>
42
- </Screen>
43
- </>
44
- );
45
- }
46
-
47
- return null;
48
- };
49
-
50
- export default QueryState;