Skip to content

Commit

Permalink
Merge branch 'main' into azlyth/fly
Browse files Browse the repository at this point in the history
* main:
  Feature flags (#20)
  Wire up search provider/component (#17)
  • Loading branch information
azlyth committed Feb 10, 2025
2 parents 5529bc3 + a98ec14 commit a7be94c
Show file tree
Hide file tree
Showing 17 changed files with 480 additions and 14 deletions.
80 changes: 80 additions & 0 deletions db/pb_migrations/1739046672_created_feature_flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "bool4087400498",
"name": "enable",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_728725186",
"indexes": [],
"listRule": null,
"name": "feature_flags",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});

return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_728725186");

return app.delete(collection);
})
51 changes: 51 additions & 0 deletions db/pb_migrations/1739072396_updated_feature_flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_728725186")

// add field
collection.fields.addAt(3, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1843675174",
"max": 0,
"min": 0,
"name": "description",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))

// update field
collection.fields.addAt(1, new Field({
"hidden": false,
"id": "bool4087400498",
"name": "enabled",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))

return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_728725186")

// remove field
collection.fields.removeById("text1843675174")

// update field
collection.fields.addAt(1, new Field({
"hidden": false,
"id": "bool4087400498",
"name": "enable",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))

return app.save(collection)
})
20 changes: 20 additions & 0 deletions db/pb_migrations/1739072493_updated_feature_flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_728725186")

// update collection data
unmarshal({
"listRule": ""
}, collection)

return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_728725186")

// update collection data
unmarshal({
"listRule": null
}, collection)

return app.save(collection)
})
6 changes: 6 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@headlessui/react": "^2.2.0",
"@heroicons/react": "^2.2.0",
"next": "15.1.5",
"pocketbase": "^0.25.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hot-toast": "^2.5.1",
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/app/contexts/featureFlags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client"

import React, { createContext, useContext, ReactNode, useEffect, useState } from 'react';
import { FeatureFlag, loadFeatureFlags, defaultFeatureFlags } from '@/pocketbase/featureFlags';

const FeatureFlagsContext = createContext<FeatureFlag | undefined>(undefined);

interface FeatureFlagsProviderProps {
children: ReactNode;
initialFlags?: FeatureFlag;
}

export const FeatureFlagsProvider: React.FC<FeatureFlagsProviderProps> = ({
children,
initialFlags,
}) => {
const [flags, setFlags] = useState<FeatureFlag>(initialFlags || defaultFeatureFlags);

useEffect(() => {
if (!initialFlags) {
const loadFlags = async () => {
try {
const loadedFlags = await loadFeatureFlags();
setFlags(loadedFlags);
} catch (error) {
console.error('Failed to load feature flags:', error);
}
};

loadFlags();
}
}, [initialFlags]);

return (
<FeatureFlagsContext.Provider value={flags}>
{children}
</FeatureFlagsContext.Provider>
);
};

// Utility hook to check a specific feature flag
export const useFeatureFlag = (flagName: keyof FeatureFlag): boolean => {
const context = useContext(FeatureFlagsContext);
if (context === undefined) {
throw new Error('useFeatureFlags must be used within a FeatureFlagsProvider');
}
return context[flagName];
};
64 changes: 64 additions & 0 deletions frontend/src/app/contexts/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import { createContext, useContext, ReactNode, useState } from 'react';

interface SearchItemAttributes {
bean_type: string;
origin: string;
packaging_type: string;
processing_method: string;
roast_level: string;
variety: string;
}

export interface SearchItem {
id: number;
name: string;
description: string;
price: number;
quantity: number;
rating: number;
imageUrl: string;
attributes: SearchItemAttributes;
tags: string[];
}

interface SearchContextType {
searchItems: SearchItem[];
setSearchItems: (items: SearchItem[]) => void;
selectedItem: SearchItem | null;
setSelectedItem: (item: SearchItem | null) => void;
isLoading: boolean;
setIsLoading: (loading: boolean) => void;
}

const SearchContext = createContext<SearchContextType | undefined>(undefined);

export function SearchProvider({ children }: { children: ReactNode }) {
const [searchItems, setSearchItems] = useState<SearchItem[]>([]);
const [selectedItem, setSelectedItem] = useState<SearchItem | null>(null);
const [isLoading, setIsLoading] = useState(false);

return (
<SearchContext.Provider
value={{
searchItems,
setSearchItems,
selectedItem,
setSelectedItem,
isLoading,
setIsLoading,
}}
>
{children}
</SearchContext.Provider>
);
}

export function useSearch() {
const context = useContext(SearchContext);
if (context === undefined) {
throw new Error('useSearch must be used within a SearchProvider');
}
return context;
}
15 changes: 9 additions & 6 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Inter, DM_Sans } from "next/font/google";
import "./globals.css";
import { AuthProvider } from './contexts/auth';
import { CartProvider } from './contexts/cart';
import { FeatureFlagsProvider } from './contexts/featureFlags';
import { Toaster } from 'react-hot-toast';
import Header from '@/components/Header';

Expand Down Expand Up @@ -35,12 +36,14 @@ export default function RootLayout({
<body
className={`${inter.variable} ${dmSans.variable} font-sans antialiased`}
>
<AuthProvider>
<CartProvider>
<Header />
{children}
</CartProvider>
</AuthProvider>
<FeatureFlagsProvider>
<AuthProvider>
<CartProvider>
<Header />
{children}
</CartProvider>
</AuthProvider>
</FeatureFlagsProvider>
<Toaster position="bottom-right" />
</body>
</html>
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client';

import { useState, useEffect } from 'react';
import { useAuth } from './contexts/auth';
import Link from 'next/link';
import { BuildingStorefrontIcon } from '@heroicons/react/24/outline';
import { SearchProvider } from '@/app/contexts/search';
import { Search } from '@/components/Search';
import { useFeatureFlag } from '@/app/contexts/featureFlags';

interface Store {
id: string;
Expand All @@ -19,6 +21,7 @@ export default function Page() {
const [stores, setStores] = useState<Store[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const productSearchEnabled = useFeatureFlag('product_search');

useEffect(() => {
const fetchStores = async () => {
Expand Down Expand Up @@ -83,6 +86,9 @@ export default function Page() {
<p className="text-lg text-[#4A5568]">
Shop from your favorite local stores with same-day delivery
</p>
<SearchProvider>
{ productSearchEnabled && <Search />}
</SearchProvider>
</div>
</div>
</div>
Expand Down
7 changes: 1 addition & 6 deletions frontend/src/app/store/[id]/StoreContent.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use client';

import { useState, useEffect } from 'react';
import { useAuth } from '../../contexts/auth';
import { useCart } from '../../contexts/cart';
import { toast } from 'react-hot-toast';
import { FaInstagram, FaFacebook, FaXTwitter } from 'react-icons/fa6';
import { SiBluesky } from 'react-icons/si';
import Link from 'next/link';
import { ClockIcon, MapPinIcon } from '@heroicons/react/24/outline';
import { config } from '@/config';

Expand Down Expand Up @@ -40,7 +38,6 @@ export default function StoreContent({ storeId }: { storeId: string }) {

// Check if cart has items from a different store
const hasItemsFromDifferentStore = cartItems.length > 0 && cartItems[0].store !== storeId;
const currentCartStoreName = hasItemsFromDifferentStore ? store?.name : null;

useEffect(() => {
const fetchStoreAndItems = async () => {
Expand All @@ -59,13 +56,11 @@ export default function StoreContent({ storeId }: { storeId: string }) {
throw new Error('Failed to fetch store items');
}
const itemsData = await itemsResponse.json();

// Add mock image URLs to items
const itemsWithImages = itemsData.map((item: StoreItem) => ({
...item,
imageUrl: `https://picsum.photos/seed/${item.id}/400/300`
}));

setItems(itemsWithImages);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch store data');
Expand Down Expand Up @@ -222,4 +217,4 @@ export default function StoreContent({ storeId }: { storeId: string }) {
</div>
</main>
);
}
}
Loading

0 comments on commit a7be94c

Please sign in to comment.