feat: adds custom post repeat option - #1574
Conversation
Signed-off-by: SuperGrut <72060440+SuperGrut@users.noreply.github.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
| if (repeat % 7 === 0) { | ||
| return `${repeat / 7} Week(s)`; | ||
| } | ||
| if (repeat % 30 === 0) { | ||
| return `${repeat / 30} Month(s)`; | ||
| } |
There was a problem hiding this comment.
Bug: The everyLabel function incorrectly prioritizes weeks over months. An interval like 210 days (7 months) will be displayed as '30 Week(s)' instead of '7 Month(s)'.
Severity: LOW
Suggested Fix
In the everyLabel function, reorder the conditional checks to evaluate divisibility by 30 (months) before checking for divisibility by 7 (weeks). This ensures that intervals representing whole months are displayed as months, not weeks.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: apps/frontend/src/components/launches/repeat.component.tsx#L95-L100
Potential issue: The label generation logic in the `everyLabel` function incorrectly
prioritizes weeks over months due to the order of divisibility checks. When a repeat
interval is a multiple of both 7 (for weeks) and 30 (for months), such as 210 days, the
check for divisibility by 7 is performed first. For example, if a user selects a custom
repeat of 7 months, it is stored as 210 days. The condition `210 % 7 === 0` evaluates to
true, causing the function to return '30 Week(s)' instead of the user-intended '7
Month(s)', which would have been returned by the subsequent `210 % 30 === 0` check. This
leads to a mismatch between the user's selection and the displayed label.
Did we get this right? 👍 / 👎 to inform future reviews.
|
@nevo-david requesting review |
| <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]" | ||
| /> |
There was a problem hiding this comment.
Bug: The custom repeat amount input allows decimal values, which are sent to the backend. The backend expects an integer for intervalInDays and will throw an error.
Severity: HIGH
Suggested Fix
Add the step={1} attribute to the <input type="number"> to restrict input to whole numbers. Additionally, consider adding a Math.floor() or Math.round() call in handleCustomApply before calling props.onChange to ensure only integers are propagated, providing a more robust defense.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: apps/frontend/src/components/launches/repeat.component.tsx#L173-L183
Potential issue: The `<input type="number">` for the custom repeat amount lacks a
`step={1}` attribute, allowing users to enter decimal values like "1.5". The frontend
validation (`!customAmount || customAmount < 1`) does not prevent this. The calculated
total days (e.g., `1.5 * 7 = 10.5`) is sent as the `inter` parameter to the backend. The
backend attempts to save this float value into the `intervalInDays` database column,
which is defined as an integer (`Int?`). This type mismatch will cause a Prisma/database
error, preventing the operation from completing.
| const ref = useClickOutside(() => { | ||
| if (!isOpen) { |
There was a problem hiding this comment.
Bug: When the custom repeat panel is open, clicking outside the dropdown fails to reset the showCustom state, causing the custom panel to appear on the next open.
Severity: MEDIUM
Suggested Fix
Add setShowCustom(false) to the callback function within the useClickOutside hook to ensure the component's state is fully reset when the dropdown is closed by clicking outside.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: apps/frontend/src/components/launches/repeat.component.tsx#L79-L80
Potential issue: The `useClickOutside` hook at line 79 correctly closes the dropdown by
setting `isOpen` to `false`. However, if the custom date panel is open (`showCustom` is
`true`), the hook does not reset `showCustom` to `false`. Consequently, the next time
the user opens the dropdown, they are incorrectly shown the custom panel again instead
of the initial list of preset options. This happens when a user opens the custom panel
and then decides to dismiss the entire dropdown by clicking away.
| const handleCustomApply = () => { | ||
| if (!customAmount || customAmount < 1) { | ||
| return; | ||
| } | ||
| const totalDays = customAmount * customUnit; |
There was a problem hiding this comment.
Bug: The custom repeat interval lacks upper-bound validation on both the frontend and backend, allowing for impractically long scheduling delays.
Severity: MEDIUM
Suggested Fix
Add a validation check in handleCustomApply in repeat.component.tsx to ensure customAmount does not exceed the intended maximum (e.g., 999). Additionally, implement server-side validation in the backend service or repository layer to reject values for inter that are outside the acceptable range before saving to the database.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: apps/frontend/src/components/launches/repeat.component.tsx#L118-L122
Potential issue: The custom repeat interval for post scheduling lacks upper-bound
validation. While the frontend input has an HTML `max` attribute set to 999, the
`handleCustomApply` function does not enforce this limit, allowing a user to manually
enter a much larger number. This value is passed to the backend and saved directly to
the database without any server-side validation. The scheduling workflow then uses this
large number to calculate the next post time, which can result in an impractically long
delay (e.g., years or decades), effectively breaking the repeat functionality for that
post.
Also affects:
apps/backend/src/modules/posts/posts.repository.ts:557
What kind of change does this PR introduce?
The PR introduces a Custom option(please check video demo below) which can be used by the user to add custom value for post recurrence. Currently, Postiz only has a list of preset values like - Day, 2 Day, 3 Day, 4 Day, 5 Day, 6 Day, Week, 2 Week and Month which is not enough. The users also wants to put a custom value.
Postiz.Calendar.-.3.June.2026.mp4
Why was this change needed?
Issue Link #1399
Without this capability, users must manually recreate posts or schedules, which is inefficient and increases the risk of inconsistency or missed postings.
By supporting longer and customizable repeat intervals, Postiz can provide greater flexibility, improve user productivity, and better accommodate real-world content planning needs.
Other information:
eg: Did you discuss this change with anybody before working on it (not required, but can be a good idea for bigger changes). Any plans for the future, etc?
Checklist:
Put a "X" in the boxes below to indicate you have followed the checklist;