Add groups#196
Conversation
- Updated package version to 2026.05.28.1200. - Introduced new routes for managing groups, including group creation, editing, and details pages. - Enhanced the Navbar to include a link for groups management. - Modified PlanTagSearchInput to conditionally hide the label. - Replaced error handling in Tags component to utilize a centralized API error message function.
- Added PlanAudioDTO and PlanAudioListResponse interfaces for audio data handling. - Implemented fetchPlanAudioList function to retrieve audio files associated with plans. - Introduced attachDayAudio function to link audio files to specific days. - Updated TaskForm to include plan title in DayAudioSection. - Integrated PlanAudioSearchInput component for searching existing audio files in DayAudioSection. - Improved user instructions for audio uploads and management in the UI.
…sk management - Added new API functions for creating multiple days and bulk deleting days. - Updated the SideBar component to support day selection mode for bulk actions. - Enhanced the DayDeleteDialog to handle bulk deletion. - Refactored usePlanMutations to integrate new API functions for creating and deleting days. - Improved user experience with feedback messages for successful and failed operations.
Confidence Score: 3/5The groups form has several correctness gaps visible to users immediately after merging: no existing avatar shown when editing, plan titles displayed as raw IDs, and uploads attempted with an empty group ID for new groups. Four distinct defects on the new groups edit path affect core workflows that users will hit on first use of the feature. src/components/routes/groups/GroupFormPage.tsx and src/lib/constant.ts Reviews (1): Last reviewed commit: "feat(task): implement bulk delete and cr..." | Re-trigger Greptile |
| export const SOCIAL_PLATFORMS = [ | ||
| { value: "facebook", label: "Facebook", icon: "Facebook" }, | ||
| { value: "SignIn", label: "SignIn", icon: "SignIn" }, | ||
| { value: "x.com", label: "X (Twitter)", icon: "Twitter" }, |
There was a problem hiding this comment.
Erroneous "SignIn" social platform entry
"SignIn" is not a social platform and appears to be an accidental addition. It will show up in the platform dropdown in GroupSocialLinksEditor as a real option for every group's social links, producing malformed data.
| export const SOCIAL_PLATFORMS = [ | |
| { value: "facebook", label: "Facebook", icon: "Facebook" }, | |
| { value: "SignIn", label: "SignIn", icon: "SignIn" }, | |
| { value: "x.com", label: "X (Twitter)", icon: "Twitter" }, | |
| export const SOCIAL_PLATFORMS = [ | |
| { value: "facebook", label: "Facebook", icon: "Facebook" }, | |
| { value: "x.com", label: "X (Twitter)", icon: "Twitter"}, |
| render={({ field }) => ( | ||
| <Pecha.FormItem className="flex items-center gap-3"> | ||
| <Pecha.FormControl> | ||
| <Pecha.Checkbox | ||
| checked={field.value} | ||
| onCheckedChange={(checked) => | ||
| field.onChange(checked === true) | ||
| } | ||
| /> | ||
| </Pecha.FormControl> | ||
| <Pecha.FormLabel className="text-sm font-bold !mt-0"> | ||
| Public group | ||
| </Pecha.FormLabel> | ||
| </Pecha.FormItem> | ||
| )} | ||
| /> | ||
| <div className="space-y-4"> | ||
| {addedLanguages.map((code) => ( | ||
| <div | ||
| key={code} | ||
| className="relative rounded-lg border border-input bg-[#FAFAFA] dark:bg-[#262626] p-4 space-y-3" | ||
| > | ||
| <button | ||
| type="button" | ||
| onClick={() => removeLanguage(code)} | ||
| className="absolute top-2 right-2 text-muted-foreground hover:text-foreground p-1" | ||
| aria-label={`Remove ${languageLabelForCode(code)}`} | ||
| > | ||
| <IoMdClose className="h-4 w-4" /> | ||
| </button> | ||
| <Pecha.FormField | ||
| control={form.control} | ||
| name={`languages.${code}.title`} | ||
| render={({ field }) => ( | ||
| <Pecha.FormItem> | ||
| <Pecha.FormLabel className="text-sm font-bold"> | ||
| {languageLabelForCode(code)} title | ||
| </Pecha.FormLabel> |
There was a problem hiding this comment.
Plan titles show as IDs due to hydration race condition
The hydration effect guards on hydratedRef.current === groupData.id to avoid running twice, but planOptions is an async query that may arrive after groupData. When that happens, the effect runs with planOptions = [], sets hydratedRef, and when planOptions finally populates (triggering the effect again), the hydratedRef guard returns early — leaving selectedPlans filled with raw IDs as titles via the titleById.get(id) ?? id fallback.
The series_ids hydration correctly avoids this by using a separate useEffect that fetches titles independently; the plan_ids side needs the same treatment.
| type="button" | ||
| onClick={() => removeLanguage(code)} | ||
| className="absolute top-2 right-2 text-muted-foreground hover:text-foreground p-1" | ||
| aria-label={`Remove ${languageLabelForCode(code)}`} | ||
| > | ||
| <IoMdClose className="h-4 w-4" /> | ||
| </button> | ||
| <Pecha.FormField |
There was a problem hiding this comment.
Existing avatar URL never loaded into preview state
During hydration, setBannerPreview(resolveGroupBannerUrl(groupData)) is called so the banner shows correctly on the edit page, but there is no equivalent call for the avatar — setAvatarPreview(...) is never populated from groupData.avatar. As a result, the "Avatar" field always shows empty when editing an existing group that already has an avatar, until the user uploads a new one.
|
|
||
| setUploading(true); | ||
| try { | ||
| const { image, key } = await uploadImageToS3(file, groupId ?? ""); |
There was a problem hiding this comment.
Image upload passes empty string as
groupId for new groups
When isNew is true, groupId is undefined, so uploadImageToS3(file, groupId ?? "") sends "" to the upload API. If the server uses this parameter for path/ownership resolution, the upload may be rejected or filed under an unowned S3 prefix. The image upload dialogs are rendered unconditionally so a user can trigger this code path before creating the group.
| const { image, key } = await uploadImageToS3(file, groupId ?? ""); | |
| if (!groupId) { | |
| toast.error("Save the group first before uploading images"); | |
| return; | |
| } | |
| const { image, key } = await uploadImageToS3(file, groupId); |
No description provided.