Skip to content

Commit 2544e87

Browse files
committed
feat: delayed comments
1 parent 4836b2e commit 2544e87

File tree

12 files changed

+529
-25
lines changed

12 files changed

+529
-25
lines changed

apps/frontend/src/components/new-launch/add.edit.modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export const AddEditModalInnerInner: FC<AddEditModalProps> = (props) => {
148148
0,
149149
existingData.integration,
150150
existingData.posts.map((post) => ({
151-
delay: 0,
151+
delay: post.delay,
152152
content:
153153
post.content.indexOf('<p>') > -1
154154
? post.content
Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,42 @@
11
'use client';
22

3-
import React, { FC, useCallback } from 'react';
4-
import { DelayIcon } from '@gitroom/frontend/components/ui/icons';
3+
import React, { FC, useCallback, useEffect, useState } from 'react';
4+
import { DelayIcon, DropdownArrowIcon } from '@gitroom/frontend/components/ui/icons';
55
import clsx from 'clsx';
66
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
77
import { useShallow } from 'zustand/react/shallow';
88
import { useT } from '@gitroom/react/translation/get.transation.service.client';
9+
import { useClickOutside } from '@mantine/hooks';
10+
11+
const delayOptions = [
12+
{ value: 1, label: '1m' },
13+
{ value: 2, label: '2m' },
14+
{ value: 5, label: '5m' },
15+
{ value: 10, label: '10m' },
16+
{ value: 15, label: '15m' },
17+
{ value: 30, label: '30m' },
18+
{ value: 60, label: '1h' },
19+
{ value: 120, label: '2h' },
20+
];
921

1022
export const DelayComponent: FC<{
1123
currentIndex: number;
1224
currentDelay: number;
1325
}> = ({ currentIndex, currentDelay }) => {
1426
const t = useT();
27+
const [isOpen, setIsOpen] = useState(false);
28+
const [customValue, setCustomValue] = useState('');
29+
30+
const isCustomDelay = currentDelay > 0 && !delayOptions.some((opt) => opt.value === currentDelay);
31+
32+
useEffect(() => {
33+
if (isOpen && isCustomDelay) {
34+
setCustomValue(String(currentDelay));
35+
} else if (isOpen && !isCustomDelay) {
36+
setCustomValue('');
37+
}
38+
}, [isOpen, isCustomDelay, currentDelay]);
39+
1540
const { current, setInternalDelay, setGlobalDelay } = useLaunchStore(
1641
useShallow((state) => ({
1742
current: state.current,
@@ -20,6 +45,13 @@ export const DelayComponent: FC<{
2045
}))
2146
);
2247

48+
const ref = useClickOutside(() => {
49+
if (!isOpen) {
50+
return;
51+
}
52+
setIsOpen(false);
53+
});
54+
2355
const setDelay = useCallback(
2456
(index: number) => (minutes: number) => {
2557
if (current !== 'global') {
@@ -31,20 +63,92 @@ export const DelayComponent: FC<{
3163
[currentIndex, current]
3264
);
3365

66+
const handleSelectDelay = useCallback(
67+
(minutes: number) => {
68+
setDelay(currentIndex)(minutes);
69+
setIsOpen(false);
70+
},
71+
[currentIndex, setDelay]
72+
);
73+
74+
const getCurrentDelayLabel = () => {
75+
if (!currentDelay) return null;
76+
const option = delayOptions.find((opt) => opt.value === currentDelay);
77+
return option?.label || `${currentDelay} min`;
78+
};
79+
3480
return (
35-
<DelayIcon
36-
// move it into the modal
37-
onClick={() => setDelay(currentIndex)(100)}
38-
data-tooltip-id="tooltip"
39-
data-tooltip-content={
40-
!currentDelay
41-
? t('delay_comment', 'Delay comment')
42-
: `Comment delayed by ${currentDelay} minutes`
43-
}
44-
className={clsx(
45-
'cursor-pointer',
46-
currentDelay > 0 && 'bg-[#D82D7E] text-white rounded-full'
81+
<div ref={ref} className="relative">
82+
<div
83+
onClick={() => setIsOpen(!isOpen)}
84+
data-tooltip-id="tooltip"
85+
data-tooltip-content={
86+
!currentDelay
87+
? t('delay_comment', 'Delay comment')
88+
: `${t('delay_comment_by', 'Comment delayed by')} ${getCurrentDelayLabel()}`
89+
}
90+
className={clsx(
91+
'cursor-pointer flex items-center gap-[4px]',
92+
currentDelay > 0 && 'bg-[#D82D7E] text-white rounded-full'
93+
)}
94+
>
95+
<DelayIcon />
96+
</div>
97+
{isOpen && (
98+
<div className="z-[300] absolute end-0 top-[100%] w-[200px] bg-newBgColorInner p-[8px] menu-shadow translate-y-[10px] flex flex-col rounded-[8px]">
99+
<div className="grid grid-cols-4 gap-[4px]">
100+
{delayOptions.map((option) => (
101+
<div
102+
onClick={() => handleSelectDelay(option.value)}
103+
key={option.value}
104+
className={clsx(
105+
'h-[32px] flex items-center justify-center rounded-[4px] cursor-pointer hover:bg-newBgColor text-[13px]',
106+
currentDelay === option.value && 'bg-[#612BD3] text-white hover:bg-[#612BD3]'
107+
)}
108+
>
109+
{option.label}
110+
</div>
111+
))}
112+
</div>
113+
<div className="border-t border-newTextColor/10 mt-[8px] pt-[8px]">
114+
<div className="flex gap-[4px]">
115+
<input
116+
type="number"
117+
min="1"
118+
value={customValue}
119+
onChange={(e) => setCustomValue(e.target.value)}
120+
onClick={(e) => e.stopPropagation()}
121+
placeholder="Custom min"
122+
className={clsx(
123+
'flex-1 w-full h-[32px] px-[8px] rounded-[4px] bg-newBgColor border text-[13px] outline-none focus:border-[#612BD3]',
124+
isCustomDelay ? 'border-[#612BD3]' : 'border-newTextColor/10'
125+
)}
126+
/>
127+
<button
128+
onClick={(e) => {
129+
e.stopPropagation();
130+
const value = parseInt(customValue, 10);
131+
if (value > 0) {
132+
handleSelectDelay(value);
133+
setCustomValue('');
134+
}
135+
}}
136+
className="h-[32px] px-[10px] rounded-[4px] bg-[#612BD3] text-white text-[12px] font-[600] hover:bg-[#612BD3]/80"
137+
>
138+
Set
139+
</button>
140+
</div>
141+
</div>
142+
{currentDelay > 0 && (
143+
<button
144+
onClick={() => handleSelectDelay(0)}
145+
className="mt-[8px] h-[32px] w-full rounded-[4px] text-[13px] text-red-400 hover:bg-red-400/10"
146+
>
147+
Remove delay
148+
</button>
149+
)}
150+
</div>
47151
)}
48-
/>
152+
</div>
49153
);
50154
};

apps/frontend/src/components/new-launch/manage.modal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
315315
value: post.values.map((value: any) => ({
316316
...(value.id ? { id: value.id } : {}),
317317
content: value.content,
318+
delay: value.delay || 0,
318319
image:
319320
(value?.media || []).map(
320321
({ id, path, alt, thumbnail, thumbnailTimestamp }: any) => ({

apps/orchestrator/src/activities/post.activity.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
organizationId,
2323
postId as postIdSearchParam,
2424
} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';
25-
import { postWorkflow } from '@gitroom/orchestrator/workflows';
2625

2726
@Injectable()
2827
@Activity()
@@ -43,7 +42,7 @@ export class PostActivity {
4342
for (const post of list) {
4443
await this._temporalService.client
4544
.getRawClient()
46-
.workflow.signalWithStart('postWorkflow', {
45+
.workflow.signalWithStart('postWorkflowV101', {
4746
workflowId: `post_${post.id}`,
4847
taskQueue: 'main',
4948
signal: 'poke',

apps/orchestrator/src/workflows/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
export * from './post.workflow';
1+
export * from './post-workflows/post.workflow';
2+
export * from './post-workflows/post.workflow.v1.0.1';
23
export * from './autopost.workflow';
34
export * from './digest.email.workflow';
45
export * from './missing.post.workflow';

apps/orchestrator/src/workflows/post.workflow.ts renamed to apps/orchestrator/src/workflows/post-workflows/post.workflow.ts

File renamed without changes.

0 commit comments

Comments
 (0)