-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
feat: adds custom post repeat option #1574
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
base: main
Are you sure you want to change the base?
Changes from all commits
b2f540d
34059c4
80fb5e4
a24368f
82afa39
3c5f8be
b18f53d
7d429aa
aef2b1a
35d91b1
c20f2ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,21 @@ | ||
| 'use client'; | ||
|
|
||
| import { FC, useMemo, useState } from 'react'; | ||
| import { FC, useMemo, useRef, useState } from 'react'; | ||
| import { Select } from '@gitroom/react/form/select'; | ||
| import { useT } from '@gitroom/react/translation/get.transation.service.client'; | ||
| import { useClickOutside } from '@mantine/hooks'; | ||
| import { isUSCitizen } from '@gitroom/frontend/components/launches/helpers/isuscitizen.utils'; | ||
| import clsx from 'clsx'; | ||
| import { RepeatIcon, DropdownArrowIcon } from '@gitroom/frontend/components/ui/icons'; | ||
|
|
||
| const CUSTOM_REPEAT_VALUE = -1; | ||
|
|
||
| const UNIT_OPTIONS = [ | ||
| { value: 1, label: 'Day(s)' }, | ||
| { value: 7, label: 'Week(s)' }, | ||
| { value: 30, label: 'Month(s)' }, | ||
| ]; | ||
|
|
||
| const getList = (t: (key: string, fallback: string) => string) => [ | ||
| { | ||
| value: 1, | ||
|
|
@@ -44,11 +53,16 @@ const getList = (t: (key: string, fallback: string) => string) => [ | |
| value: 30, | ||
| label: t('month', 'Month'), | ||
| }, | ||
| { | ||
| value: CUSTOM_REPEAT_VALUE, | ||
| label: t('custom', 'Custom...') | ||
| }, | ||
| { | ||
| value: null, | ||
| label: t('cancel', 'Cancel'), | ||
| }, | ||
| ]; | ||
|
|
||
| export const RepeatComponent: FC<{ | ||
| repeat: number | null; | ||
| onChange: (newVal: number) => void; | ||
|
|
@@ -57,6 +71,10 @@ export const RepeatComponent: FC<{ | |
| const t = useT(); | ||
| const list = getList(t); | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| const [showCustom, setShowCustom] = useState<boolean>(false); | ||
| const [customAmount, setCustomAmount] = useState(1); | ||
| const [customUnit, setCustomUnit] = useState(1); // multiplier: 1=day, 7=week, 30=month | ||
| const inputRef = useRef<HTMLInputElement>(null); | ||
|
|
||
| const ref = useClickOutside(() => { | ||
| if (!isOpen) { | ||
|
|
@@ -69,9 +87,44 @@ export const RepeatComponent: FC<{ | |
| if (!repeat) { | ||
| return ''; | ||
| } | ||
| return list.find((p) => p.value === repeat)?.label; | ||
| const presetLabel = list.find((p) => p.value === repeat)?.label; | ||
| if (presetLabel) { | ||
| return presetLabel; | ||
| } | ||
| // Custom value: expressed as weeks or months for better readability when possible | ||
| if (repeat % 7 === 0) { | ||
| return `${repeat / 7} Week(s)`; | ||
| } | ||
| if (repeat % 30 === 0) { | ||
| return `${repeat / 30} Month(s)`; | ||
| } | ||
|
Comment on lines
+95
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The Suggested FixIn the Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||
| return `${repeat} Day(s)`; | ||
| }, [repeat, list]); | ||
|
|
||
| const handleItemClick = (value: number | null) => { | ||
| if (value === CUSTOM_REPEAT_VALUE) { | ||
| setShowCustom(true); | ||
| // To directly focus on input after custom panel opens for better UX | ||
| setTimeout(() => { | ||
| inputRef.current?.focus(); | ||
| }, 50); | ||
| return; | ||
| } | ||
| props.onChange(Number(value)); | ||
| setIsOpen(false); | ||
| setShowCustom(false); | ||
| } | ||
|
|
||
| const handleCustomApply = () => { | ||
| if (!customAmount || customAmount < 1) { | ||
| return; | ||
| } | ||
| const totalDays = customAmount * customUnit; | ||
|
Comment on lines
+118
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The custom repeat interval lacks upper-bound validation on both the frontend and backend, allowing for impractically long scheduling delays. Suggested FixAdd a validation check in Prompt for AI AgentAlso affects:
|
||
| props.onChange(Number(totalDays)); | ||
| setIsOpen(false); | ||
| setShowCustom(false); | ||
| } | ||
|
|
||
| return ( | ||
| <div | ||
| ref={ref} | ||
|
|
@@ -97,19 +150,83 @@ export const RepeatComponent: FC<{ | |
| </div> | ||
| </div> | ||
| {isOpen && ( | ||
| <div className="z-[300] absolute start-0 bottom-[100%] w-[240px] bg-newBgColorInner p-[12px] menu-shadow -translate-y-[10px] flex flex-col"> | ||
| {list.map((p) => ( | ||
| <div className="z-[300] absolute start-0 bottom-[100%] w-[340px] bg-newBgColorInner p-[12px] menu-shadow -translate-y-[10px] flex flex-col"> | ||
| {!showCustom && list.map((p) => ( | ||
| <div | ||
| onClick={() => { | ||
| props.onChange(Number(p.value)); | ||
| setIsOpen(false); | ||
| }} | ||
| onClick={() => handleItemClick(p.value as number | null)} | ||
| key={p.label} | ||
| className="h-[40px] py-[8px] px-[20px] -mx-[12px] hover:bg-newBgColor" | ||
| className={clsx('h-[40px] py-[8px] px-[20px] -mx-[12px] hover:bg-newBgColor', p.value === CUSTOM_REPEAT_VALUE && 'text-[#612BD3] font-[700]')} | ||
| > | ||
| {p.label} | ||
| </div> | ||
| ))} | ||
|
|
||
| {/* Custom Repeat Panel */} | ||
| {showCustom && ( | ||
| <div className="flex flex-col gap-[10px]"> | ||
| <div className="text-[14px] font-[600] mb-[2px]"> | ||
| {t('custom_repeat', 'Custom Repeat Interval')} | ||
| </div> | ||
|
|
||
| {/* Amount + Unit row */} | ||
| <div className="flex gap-[8px] items-center"> | ||
| <input | ||
| ref={inputRef} | ||
| type="number" | ||
| min={1} | ||
| max={999} | ||
| value={customAmount} | ||
| onChange={(e) => { | ||
| setCustomAmount(Number(e.target.value)); | ||
| }} | ||
| className="w-[70px] h-[36px] rounded-[6px] border bg-newBgColor text-center text-[15px] font-[600] focus:outline-none focus:border-[#612BD3]" | ||
| /> | ||
|
Comment on lines
+173
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The custom repeat amount input allows decimal values, which are sent to the backend. The backend expects an integer for Suggested FixAdd the Prompt for AI Agent |
||
| <div className="flex gap-[4px]"> | ||
| {UNIT_OPTIONS.map((u) => ( | ||
| <button | ||
| key={u.value} | ||
| type="button" | ||
| onClick={() => setCustomUnit(u.value)} | ||
| className={clsx( | ||
| 'h-[36px] px-[10px] rounded-[6px] text-[12px] font-[600] border transition-colors', | ||
| customUnit === u.value | ||
| ? 'bg-[#612BD3] border-[#612BD3] text-white' | ||
| : 'border-newTextColor/20 hover:border-[#612BD3] hover:text-[#612BD3]', | ||
| )} | ||
| > | ||
| {u.label} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Preview */} | ||
| {customAmount && ( | ||
| <div className="text-[12px] text-newTextColor/50"> | ||
| Every {customAmount} {UNIT_OPTIONS.find(u => u.value === customUnit)?.label} = {customAmount * customUnit} day(s) | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Actions */} | ||
| <div className="flex gap-[8px] mt-[4px]"> | ||
| <button | ||
| type="button" | ||
| onClick={() => setShowCustom(false)} | ||
| className="flex-1 h-[34px] rounded-[6px] border border-newTextColor/20 text-[13px] font-[600] hover:bg-newBgColor" | ||
| > | ||
| {t('back', 'Back')} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={handleCustomApply} | ||
| disabled={!customAmount || customAmount < 1} | ||
| className="flex-1 h-[34px] rounded-[6px] bg-[#612BD3] text-white text-[13px] font-[600] disabled:opacity-40 hover:bg-[#4f22a8] transition-colors" | ||
| > | ||
| {t('apply', 'Apply')} | ||
| </button> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
|
|
||
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.
Bug: When the custom repeat panel is open, clicking outside the dropdown fails to reset the
showCustomstate, causing the custom panel to appear on the next open.Severity: MEDIUM
Suggested Fix
Add
setShowCustom(false)to the callback function within theuseClickOutsidehook to ensure the component's state is fully reset when the dropdown is closed by clicking outside.Prompt for AI Agent