-
Notifications
You must be signed in to change notification settings - Fork 4
fix #677: adding web socket for real-time gift generation updates #678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
app/gift-exchanges/[id]/page.tsx
Outdated
|
|
||
| {giftSuggestions?.length !== 0 && ( | ||
|
|
||
| {giftSuggestions?.length < 3 ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if a giftSuggstion fails? I would just show the gifts as they come in. If there are any failures, we'll never have 3 gifts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, I'll revert this back to how it was.
app/gift-exchanges/[id]/page.tsx
Outdated
| table: 'gift_suggestions', | ||
| filter: `gift_exchange_id=eq.${id}`, | ||
| }, | ||
| (payload: any) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No use of any.
app/gift-exchanges/[id]/page.tsx
Outdated
| filter: `gift_exchange_id=eq.${id}`, | ||
| }, | ||
| (payload: any) => { | ||
| if (payload.new.giver_id !== session.user.id) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shorthand is nice, but it comes at the cost of legibility.
| if (payload.new.giver_id !== session.user.id) return; | |
| if (payload.new.giver_id !== session.user.id) { | |
| return; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds real-time WebSocket functionality to eliminate the need for users to manually refresh the page when gift suggestions are being generated. The implementation uses Supabase's real-time subscriptions to listen for new gift suggestion inserts and automatically updates the UI.
- Implements WebSocket subscription to listen for gift suggestion INSERTs in real-time
- Adds test coverage for rendering gift suggestion cards and the waiting state
- Adds data-testid to GiftSuggestionCard for test identification
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| app/gift-exchanges/[id]/page.tsx | Added Supabase real-time subscription to listen for new gift suggestions and update state automatically |
| app/gift-exchanges/[id]/page.test.tsx | Added mock for Supabase client and two new test cases for gift suggestion rendering scenarios |
| components/GiftSuggestionCard/GiftSuggestionCard.tsx | Added data-testid attribute for testing purposes |
Comments suppressed due to low confidence (1)
app/gift-exchanges/[id]/page.tsx:159
- The
fetchGiftExchangeDatacallback has[id, session, isSignedIn]as dependencies but is called in auseEffectthat also depends on[fetchGiftExchangeData, session, id]. This creates a potential infinite loop sincefetchGiftExchangeDatawill be recreated wheneversessionoridchange, triggering the effect again. The WebSocket effect (lines 54-89) should includefetchGiftExchangeDatain its dependency array if it relies on having up-to-date gift suggestions, or addrouterandtoastto thefetchGiftExchangeDatadependencies to ensure stable function identity.
}, [id, session, isSignedIn]);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/gift-exchanges/[id]/page.tsx
Outdated
| id: String(payload.new.id), | ||
| }; | ||
|
|
||
| setGiftSuggestions((prev) => [...prev, newSuggestion]); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation could lead to duplicate gift suggestions if the WebSocket delivers the same INSERT event multiple times or if there's a race condition between the initial fetch and real-time updates. Consider checking if the suggestion already exists before adding it: setGiftSuggestions((prev) => prev.some(s => s.id === newSuggestion.id) ? prev : [...prev, newSuggestion]);
| setGiftSuggestions((prev) => [...prev, newSuggestion]); | |
| setGiftSuggestions((prev) => | |
| prev.some(s => s.id === newSuggestion.id) ? prev : [...prev, newSuggestion] | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use this suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this as well, I'll add it in.
|
|
||
| const giftSuggestionCards = screen.queryAllByTestId('gift-suggestion-card'); | ||
| expect(giftSuggestionCards.length).toBe(0); | ||
| }) |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing semicolon at the end of the test case.
| }) | |
| }); |
| const waitingForSuggestions = screen.queryByTestId('suggestions-waiting'); | ||
| expect(waitingForSuggestions).not.toBeInTheDocument(); | ||
|
|
||
| // Verifiy 3 gift suggestions are provided |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'Verifiy' to 'Verify'.
| // Verifiy 3 gift suggestions are provided | |
| // Verify 3 gift suggestions are provided |
| giftSuggestionCards.forEach(card => { | ||
| expect(card).toBeInTheDocument(); | ||
| }); | ||
| }) |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing semicolon at the end of the test case.
| }) | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jefische Is your Linter working? Your IDE should've autoformatted and added this semi colon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to get this figured out, I thought it was working. I'll make the changes manually for now.
| import { TOASTS } from '@/components/Toast/toasts.consts'; | ||
| import type { IGiftSuggestion } from '@/app/types/giftSuggestion'; | ||
|
|
||
| type GiftSuggestionsResponse = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we adding a new type into a test file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this originally as I was getting a typescript warning, but I see this actually isn't needed. I'll remove it.
app/gift-exchanges/[id]/page.tsx
Outdated
| id: String(payload.new.id), | ||
| }; | ||
|
|
||
| setGiftSuggestions((prev) => [...prev, newSuggestion]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use this suggestion
| giftSuggestionCards.forEach(card => { | ||
| expect(card).toBeInTheDocument(); | ||
| }); | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jefische Is your Linter working? Your IDE should've autoformatted and added this semi colon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (payload.new.giver_id !== session.user.id) { | ||
| return; | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] There's an extra space before the opening brace. While this doesn't affect functionality, it's inconsistent with the rest of the codebase's formatting. Consider removing the extra space for consistency.
| setGiftSuggestions((prev) => | ||
| prev.some(s => s.id === newSuggestion.id) ? prev : [...prev, newSuggestion] | ||
| ); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The duplicate check compares newSuggestion.id (which is String(payload.new.id)) against existing suggestion IDs, but the existing suggestions loaded from the API also use database IDs converted to strings. However, this check may fail to prevent duplicates if the initial fetch happens while a WebSocket INSERT event is in flight. Consider also checking if the suggestion with the same ID already exists in the initial data fetch to handle potential race conditions.
| "price": "$50", | ||
| "title": "Bamboo Cheese Board and Knife Set", | ||
| "imageUrl": null, | ||
| "matchScore": 80, | ||
| "description": "This elegant bamboo cheese board comes with a hidden slide-out drawer that holds a cheese knife set. Perfect for hosting gatherings and enjoying a selection of cheeses.", | ||
| "matchReasons": [ | ||
| "Luxurious bamboo material", | ||
| "Ideal for hosting food-related events" | ||
| ], | ||
| "id": "1451", | ||
| }, | ||
| { | ||
| "price": "$60", | ||
| "title": "Swarovski Crystal Watch", | ||
| "imageUrl": null, | ||
| "matchScore": 90, | ||
| "description": "A stunning Swarovski crystal watch that combines functionality with luxury. The intricate design and sparkling crystals make it a stylish accessory for any occasion.", | ||
| "matchReasons": [ | ||
| "Luxurious item for a luxury watch enthusiast", | ||
| "Adds a touch of style and fashion" | ||
| ], | ||
| "id": "1454", | ||
| }, | ||
| { | ||
| "price": "$55", | ||
| "title": "Artisanal Gluten-Free Baking Kit", | ||
| "imageUrl": null, | ||
| "matchScore": 85, | ||
| "description": "A complete baking kit with high-quality gluten-free ingredients and recipes for creating delicious treats at home. Perfect for someone with a gluten allergy who enjoys baking.", | ||
| "matchReasons": [ | ||
| "Caters to gluten allergy", | ||
| "Combines food & cooking with arts & crafts" | ||
| ], | ||
| "id": "1455", |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The mock gift suggestions data uses inconsistent field ordering compared to the IGiftSuggestion interface definition. Consider reordering the fields to match the interface definition (id, title, price, description, matchReasons, matchScore, imageUrl) for better readability and maintainability.
| "price": "$50", | |
| "title": "Bamboo Cheese Board and Knife Set", | |
| "imageUrl": null, | |
| "matchScore": 80, | |
| "description": "This elegant bamboo cheese board comes with a hidden slide-out drawer that holds a cheese knife set. Perfect for hosting gatherings and enjoying a selection of cheeses.", | |
| "matchReasons": [ | |
| "Luxurious bamboo material", | |
| "Ideal for hosting food-related events" | |
| ], | |
| "id": "1451", | |
| }, | |
| { | |
| "price": "$60", | |
| "title": "Swarovski Crystal Watch", | |
| "imageUrl": null, | |
| "matchScore": 90, | |
| "description": "A stunning Swarovski crystal watch that combines functionality with luxury. The intricate design and sparkling crystals make it a stylish accessory for any occasion.", | |
| "matchReasons": [ | |
| "Luxurious item for a luxury watch enthusiast", | |
| "Adds a touch of style and fashion" | |
| ], | |
| "id": "1454", | |
| }, | |
| { | |
| "price": "$55", | |
| "title": "Artisanal Gluten-Free Baking Kit", | |
| "imageUrl": null, | |
| "matchScore": 85, | |
| "description": "A complete baking kit with high-quality gluten-free ingredients and recipes for creating delicious treats at home. Perfect for someone with a gluten allergy who enjoys baking.", | |
| "matchReasons": [ | |
| "Caters to gluten allergy", | |
| "Combines food & cooking with arts & crafts" | |
| ], | |
| "id": "1455", | |
| "id": "1451", | |
| "title": "Bamboo Cheese Board and Knife Set", | |
| "price": "$50", | |
| "description": "This elegant bamboo cheese board comes with a hidden slide-out drawer that holds a cheese knife set. Perfect for hosting gatherings and enjoying a selection of cheeses.", | |
| "matchReasons": [ | |
| "Luxurious bamboo material", | |
| "Ideal for hosting food-related events" | |
| ], | |
| "matchScore": 80, | |
| "imageUrl": null, | |
| }, | |
| { | |
| "id": "1454", | |
| "title": "Swarovski Crystal Watch", | |
| "price": "$60", | |
| "description": "A stunning Swarovski crystal watch that combines functionality with luxury. The intricate design and sparkling crystals make it a stylish accessory for any occasion.", | |
| "matchReasons": [ | |
| "Luxurious item for a luxury watch enthusiast", | |
| "Adds a touch of style and fashion" | |
| ], | |
| "matchScore": 90, | |
| "imageUrl": null, | |
| }, | |
| { | |
| "id": "1455", | |
| "title": "Artisanal Gluten-Free Baking Kit", | |
| "price": "$55", | |
| "description": "A complete baking kit with high-quality gluten-free ingredients and recipes for creating delicious treats at home. Perfect for someone with a gluten allergy who enjoys baking.", | |
| "matchReasons": [ | |
| "Caters to gluten allergy", | |
| "Combines food & cooking with arts & crafts" | |
| ], | |
| "matchScore": 85, | |
| "imageUrl": null, |
Description
Before:
When initiating a gift exchange group drawing and generating gift suggestions , the
WaitingForSuggestionscomponent loads but the user has to refresh the page repeatedly to see the AI generated gift suggestions.After:
As soon as the gift suggestions are generated, they are sent back to the client real-time allowing for immediate rendering of the gifts.
Closes #677
Testing instructions
To test this an exchange group has to be created with 3 or more users to allow a drawing to be initiated. Once the drawing is initiated, the page will render the
WaitingForSuggestionscomponent if no gifts are available. Once all 3 gifts are generated in the database, the 3GiftSuggestionCardcomponents will display.Additional information
Screenshot of the
WaitingForSuggestionsandGiftSuggestionCardcomponentsPre-submission checklist
test #001: created unit test for __ component)Peer Code ReviewersandSenior+ Code Reviewersgroupsgis-code-questions