chore: features category
This commit is contained in:
+3
-3
@@ -15,7 +15,7 @@
|
||||
"simulator": true
|
||||
},
|
||||
"env": {
|
||||
"API_URL": "https://uber-stup.club"
|
||||
"API_URL": "http://5.181.0.112"
|
||||
},
|
||||
"channel": "development"
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
"buildType": "apk"
|
||||
},
|
||||
"env": {
|
||||
"API_URL": "https://uber-stup.club"
|
||||
"API_URL": "http://5.181.0.112"
|
||||
},
|
||||
"channel": "preview"
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
"buildType": "apk"
|
||||
},
|
||||
"env": {
|
||||
"API_URL": "https://uber-stup.club"
|
||||
"API_URL": "http://5.181.0.112"
|
||||
},
|
||||
"channel": "production"
|
||||
}
|
||||
|
||||
+16
-1
@@ -11,7 +11,7 @@ import type {
|
||||
import { getToken } from "../auth/tokenStorage";
|
||||
import { extractUsernameFromToken } from "../auth/jwtUtils";
|
||||
|
||||
const V1 = "https://uber-stup.club/api/v1";
|
||||
const V1 = "http://5.181.0.112/api/v1";
|
||||
|
||||
export const getJwtUsername = async (): Promise<string | null> => {
|
||||
const token = await getToken();
|
||||
@@ -113,6 +113,21 @@ export const logoutUser = async (): Promise<void> => {
|
||||
// PRODUCTS
|
||||
// ============================================
|
||||
|
||||
export interface Category {
|
||||
id: number;
|
||||
name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const getCategories = async (): Promise<Category[]> => {
|
||||
try {
|
||||
const { data } = await apiClient.get(`${V1}/categories`);
|
||||
return data.categories || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllProducts = async () => {
|
||||
try {
|
||||
const { data } = await apiClient.get(`${V1}/products`);
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios from "axios";
|
||||
import { getToken, getAdminToken } from "../auth/tokenStorage";
|
||||
|
||||
// Change this to your server IP/domain
|
||||
export const API_BASE_URL = "https://uber-stup.club";
|
||||
export const API_BASE_URL = "http://5.181.0.112";
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
} from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import { getAllProducts, getProductsByCategory } from "../../api/api";
|
||||
import { getAllProducts, getProductsByCategory, getCategories } from "../../api/api";
|
||||
import type { Category } from "../../api/api";
|
||||
import type { Product } from "../../api/api_types";
|
||||
import ProductCard from "../../components/ProductCard";
|
||||
import CategoryPill from "../../components/CategoryPill";
|
||||
@@ -33,25 +34,14 @@ const logoGrosSemi = require("../../../assets/logo-gros-semi.png");
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
||||
const CARD_WIDTH = SCREEN_WIDTH - 48;
|
||||
|
||||
const CATEGORIES = [
|
||||
{ label: "Tous", value: "tous" },
|
||||
{ label: "Weed&Hash", value: "weed&hash" },
|
||||
{ label: "Zipette&Co", value: "zipette&co" },
|
||||
{ label: "Gros&Semi", value: "gros&semi" },
|
||||
];
|
||||
|
||||
const CATEGORY_TITLES: Record<string, string> = {
|
||||
tous: "Tous les produits",
|
||||
"weed&hash": "Weed & Hash",
|
||||
"zipette&co": "Zipette & Co",
|
||||
"gros&semi": "Gros & Semi",
|
||||
};
|
||||
// Les catégories sont chargées dynamiquement depuis l'API
|
||||
|
||||
type Nav = NativeStackNavigationProp<ClientStackParamList>;
|
||||
|
||||
export default function ProductsScreen() {
|
||||
const { colors } = useTheme();
|
||||
const navigation = useNavigation<Nav>();
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState("tous");
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -60,6 +50,10 @@ export default function ProductsScreen() {
|
||||
const carouselRef = useRef<FlatList>(null);
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
||||
|
||||
useEffect(() => {
|
||||
getCategories().then(setCategories);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pulse = Animated.loop(
|
||||
Animated.sequence([
|
||||
@@ -190,7 +184,7 @@ export default function ProductsScreen() {
|
||||
|
||||
return (
|
||||
<ImageBackground
|
||||
source={selectedCategory === "gros&semi" ? logoGrosSemi : undefined}
|
||||
source={undefined}
|
||||
style={styles.container}
|
||||
imageStyle={styles.bgImage}
|
||||
>
|
||||
@@ -200,19 +194,24 @@ export default function ProductsScreen() {
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.filters}
|
||||
>
|
||||
{CATEGORIES.map((cat) => (
|
||||
<CategoryPill
|
||||
label="Tous"
|
||||
active={selectedCategory === "tous"}
|
||||
onPress={() => setSelectedCategory("tous")}
|
||||
/>
|
||||
{categories.map((cat) => (
|
||||
<CategoryPill
|
||||
key={cat.value}
|
||||
label={cat.label}
|
||||
active={selectedCategory === cat.value}
|
||||
onPress={() => setSelectedCategory(cat.value)}
|
||||
key={cat.id}
|
||||
label={cat.name}
|
||||
active={selectedCategory === cat.name}
|
||||
onPress={() => setSelectedCategory(cat.name)}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
<View style={styles.categoryHeader}>
|
||||
<Text style={styles.categoryTitle}>
|
||||
{CATEGORY_TITLES[selectedCategory]}
|
||||
{selectedCategory === "tous" ? "Tous les produits" : selectedCategory}
|
||||
</Text>
|
||||
</View>
|
||||
{error ? (
|
||||
@@ -258,18 +257,6 @@ export default function ProductsScreen() {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{selectedCategory === "gros&semi" && (
|
||||
<View style={styles.comingSoonWrapper}>
|
||||
<Animated.Text
|
||||
style={[
|
||||
styles.comingSoonText,
|
||||
{ transform: [{ scale: scaleAnim }] },
|
||||
]}
|
||||
>
|
||||
Prochainement
|
||||
</Animated.Text>
|
||||
</View>
|
||||
)}
|
||||
</ImageBackground>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user