+
diff --git a/apps/frontend/src/components/launches/filters.tsx b/apps/frontend/src/components/launches/filters.tsx
index a789fea8..f399ecbd 100644
--- a/apps/frontend/src/components/launches/filters.tsx
+++ b/apps/frontend/src/components/launches/filters.tsx
@@ -7,7 +7,13 @@ import { useCallback } from 'react';
export const Filters = () => {
const week = useCalendar();
const betweenDates =
- week.display === 'week'
+ week.display === 'day'
+ ? dayjs()
+ .year(week.currentYear)
+ .isoWeek(week.currentWeek)
+ .day(week.currentDay)
+ .format('DD/MM/YYYY')
+ : week.display === 'week'
? dayjs()
.year(week.currentYear)
.isoWeek(week.currentWeek)
@@ -31,76 +37,113 @@ export const Filters = () => {
.endOf('month')
.format('DD/MM/YYYY');
- const setWeek = useCallback(() => {
+ const setDay = useCallback(() => {
week.setFilters({
+ currentDay: +dayjs().day() as 0 | 1 | 2 | 3 | 4 | 5 | 6,
currentWeek: dayjs().isoWeek(),
currentYear: dayjs().year(),
- currentMonth: 0,
+ currentMonth: dayjs().month(),
+ display: 'day',
+ });
+ }, [week]);
+
+ const setWeek = useCallback(() => {
+ week.setFilters({
+ currentDay: +dayjs().day() as 0 | 1 | 2 | 3 | 4 | 5 | 6,
+ currentWeek: dayjs().isoWeek(),
+ currentYear: dayjs().year(),
+ currentMonth: dayjs().month(),
display: 'week',
});
}, [week]);
const setMonth = useCallback(() => {
week.setFilters({
+ currentDay: +dayjs().day() as 0 | 1 | 2 | 3 | 4 | 5 | 6,
currentMonth: dayjs().month(),
- currentWeek: 0,
+ currentWeek: dayjs().isoWeek(),
currentYear: dayjs().year(),
display: 'month',
});
}, [week]);
const next = useCallback(() => {
+ const increaseDay = week.display === 'day';
+ const increaseWeek =
+ week.display === 'week' ||
+ (week.display === 'day' && week.currentDay === 6);
+ const increaseMonth =
+ week.display === 'month' || (increaseWeek && week.currentWeek === 52);
+
week.setFilters({
- currentWeek:
- week.display === 'week'
- ? week.currentWeek === 52
- ? 1
- : week.currentWeek + 1
- : 0,
- currentYear:
- week.display === 'week'
- ? week.currentWeek === 52
- ? week.currentYear + 1
- : week.currentYear
- : week.currentMonth === 11
- ? week.currentYear + 1
- : week.currentYear,
+ currentDay: (!increaseDay
+ ? 0
+ : week.currentDay === 6
+ ? 0
+ : week.currentDay + 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6,
+ currentWeek: !increaseWeek
+ ? week.currentWeek
+ : week.currentWeek === 52
+ ? 1
+ : week.currentWeek + 1,
+ currentYear: !increaseMonth
+ ? week.currentYear
+ : week.currentMonth === 11
+ ? week.currentYear + 1
+ : week.currentYear,
display: week.display as any,
- currentMonth:
- week.display === 'week'
- ? 0
- : week.currentMonth === 11
- ? 0
- : week.currentMonth + 1,
+ currentMonth: !increaseMonth
+ ? week.currentMonth
+ : week.currentMonth === 11
+ ? 0
+ : week.currentMonth + 1,
});
- }, [week.display, week.currentMonth, week.currentWeek, week.currentYear]);
+ }, [
+ week.display,
+ week.currentMonth,
+ week.currentWeek,
+ week.currentYear,
+ week.currentDay,
+ ]);
const previous = useCallback(() => {
- week.setFilters({
- currentWeek:
- week.display === 'week'
- ? week.currentWeek === 1
- ? 52
- : week.currentWeek - 1
- : 0,
- currentYear:
- week.display === 'week'
- ? week.currentWeek === 1
- ? week.currentYear - 1
- : week.currentYear
- : week.currentMonth === 0
- ? week.currentYear - 1
- : week.currentYear,
- display: week.display as any,
- currentMonth:
- week.display === 'week'
- ? 0
- : week.currentMonth === 0
- ? 11
- : week.currentMonth - 1,
- });
- }, [week.display, week.currentMonth, week.currentWeek, week.currentYear]);
+ const decreaseDay = week.display === 'day';
+ const decreaseWeek =
+ week.display === 'week' ||
+ (week.display === 'day' && week.currentDay === 0);
+ const decreaseMonth =
+ week.display === 'month' || (decreaseWeek && week.currentWeek === 1);
+ week.setFilters({
+ currentDay: (!decreaseDay
+ ? 0
+ : week.currentDay === 0
+ ? 6
+ : week.currentDay - 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6,
+ currentWeek: !decreaseWeek
+ ? week.currentWeek
+ : week.currentWeek === 1
+ ? 52
+ : week.currentWeek - 1,
+ currentYear: !decreaseMonth
+ ? week.currentYear
+ : week.currentMonth === 0
+ ? week.currentYear - 1
+ : week.currentYear,
+ display: week.display as any,
+ currentMonth: !decreaseMonth
+ ? week.currentMonth
+ : week.currentMonth === 0
+ ? 11
+ : week.currentMonth - 1,
+ });
+ }, [
+ week.display,
+ week.currentMonth,
+ week.currentWeek,
+ week.currentYear,
+ week.currentDay,
+ ]);
return (
@@ -118,7 +161,13 @@ export const Filters = () => {
- {week.display === 'week'
+ {week.display === 'day'
+ ? `${dayjs()
+ .month(week.currentMonth)
+ .week(week.currentWeek)
+ .day(week.currentDay)
+ .format('dddd')}`
+ : week.display === 'week'
? `Week ${week.currentWeek}`
: `${dayjs().month(week.currentMonth).format('MMMM')}`}
@@ -137,6 +186,15 @@ export const Filters = () => {
{betweenDates}
+
+ Day
+
{
+ const offset = dayjs.tz().utcOffset();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const newUrl = `${pathname}/continue?${searchParams.toString()}&timezone=${offset}`;
+
+ useEffect(() => {
+ router.push(newUrl);
+ }, [newUrl]);
+
+ return null;
+};
diff --git a/apps/frontend/src/components/launches/launches.component.tsx b/apps/frontend/src/components/launches/launches.component.tsx
index a9edc5d2..d0a5ef52 100644
--- a/apps/frontend/src/components/launches/launches.component.tsx
+++ b/apps/frontend/src/components/launches/launches.component.tsx
@@ -196,6 +196,7 @@ export const LaunchesComponent = () => {
{integration.name}
void,
onChange: (shouldReload: boolean) => void;
}> = (props) => {
- const { canEnable, canDisable, id, onChange } = props;
+ const { canEnable, canDisable, id, onChange, mutate } = props;
const fetch = useFetch();
+ const { integrations } = useCalendar();
const toast = useToaster();
+ usePreventWindowUnload(true);
+ const modal = useModals();
const [show, setShow] = useState(false);
const ref = useClickOutside(() => {
setShow(false);
@@ -80,6 +88,25 @@ export const Menu: FC<{
onChange(false);
}, []);
+ const editTimeTable = useCallback(() => {
+ const findIntegration = integrations.find(
+ (integration) => integration.id === id
+ );
+ modal.openModal({
+ classNames: {
+ modal: 'w-[100%] max-w-[600px] bg-transparent text-textColor',
+ },
+ size: '100%',
+ withCloseButton: false,
+ closeOnEscape: false,
+ closeOnClickOutside: false,
+ children: (
+
+ ),
+ });
+ setShow(false);
+ }, [integrations]);
+
return (
e.stopPropagation()}
className={`absolute top-[100%] left-0 p-[8px] px-[20px] bg-fifth flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder ${interClass} text-nowrap`}
>
+
{canEnable && (
({
+ value: index,
+}));
+
+const minutes = [...Array(60).keys()].map((i, index) => ({
+ value: index,
+}));
+
+export const TimeTable: FC<{
+ integration: Integrations;
+ mutate: () => void;
+}> = (props) => {
+ const {
+ integration: { time },
+ mutate,
+ } = props;
+ const [currentTimes, setCurrentTimes] = useState([...time]);
+ const [hour, setHour] = useState(0);
+ const [minute, setMinute] = useState(0);
+ const fetch = useFetch();
+ const modal = useModals();
+
+ const askClose = useCallback(async () => {
+ if (
+ !(await deleteDialog(
+ 'Are you sure you want to close the window?',
+ 'Yes, close'
+ ))
+ ) {
+ return;
+ }
+
+ modal.closeAll();
+ }, []);
+
+ useKeypress('Escape', askClose);
+
+ const removeSlot = useCallback(
+ (index: number) => async () => {
+ if (!(await deleteDialog('Are you sure you want to delete this slot?'))) {
+ return;
+ }
+ setCurrentTimes((prev) => prev.filter((_, i) => i !== index));
+ },
+ []
+ );
+
+ const addHour = useCallback(() => {
+ const calculateMinutes =
+ dayjs()
+ .utc()
+ .startOf('day')
+ .add(hour, 'hours')
+ .add(minute, 'minutes')
+ .diff(dayjs().utc().startOf('day'), 'minutes') - dayjs.tz().utcOffset();
+ setCurrentTimes((prev) => [...prev, { time: calculateMinutes }]);
+ }, [hour, minute]);
+
+ const times = useMemo(() => {
+ return sortBy(
+ currentTimes.map(({ time }) => ({
+ value: time,
+ formatted: dayjs
+ .utc()
+ .startOf('day')
+ .add(time, 'minutes')
+ .local()
+ .format('HH:mm'),
+ })),
+ (p) => p.value
+ );
+ }, [currentTimes]);
+
+ const save = useCallback(async () => {
+ await fetch(`/integrations/${props.integration.id}/time`, {
+ method: 'POST',
+ body: JSON.stringify({ time: currentTimes }),
+ });
+ mutate();
+ modal.closeAll();
+ }, [currentTimes]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
Add Time Slot
+
+
+
+ setHour(Number(e.target.value))}
+ >
+ {hours.map((hour) => (
+
+ {hour.value.toString().length === 1 ? '0' : ''}
+ {hour.value}
+
+ ))}
+
+
+
+ setMinute(Number(e.target.value))}
+ >
+ {minutes.map((minute) => (
+
+ {minute.value.toString().length === 1 ? '0' : ''}
+ {minute.value}
+
+ ))}
+
+
+
+
+
+ Add Slot
+
+
+
+
+
+ {times.map((timeSlot, index) => (
+
+ {timeSlot.formatted}
+
+ X
+
+
+ ))}
+
+
+
+ Save
+
+
+
+ );
+};
diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts
index b986538c..7778c05a 100644
--- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts
@@ -1,11 +1,11 @@
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
import { Injectable } from '@nestjs/common';
import dayjs from 'dayjs';
-import * as console from 'node:console';
import { Integration } from '@prisma/client';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import { simpleUpload } from '@gitroom/nestjs-libraries/upload/r2.uploader';
import axios from 'axios';
+import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
@Injectable()
export class IntegrationRepository {
@@ -14,10 +14,34 @@ export class IntegrationRepository {
private _posts: PrismaRepository<'post'>
) {}
+ async setTimes(org: string, id: string, times: IntegrationTimeDto) {
+ return this._integration.model.integration.update({
+ select: {
+ id: true,
+ },
+ where: {
+ id,
+ organizationId: org,
+ },
+ data: {
+ postingTimes: JSON.stringify(times.time),
+ },
+ });
+ }
+
async updateIntegration(id: string, params: Partial
) {
- if (params.picture && params.picture.indexOf(process.env.CLOUDFLARE_BUCKET_URL!) === -1) {
- const picture = await axios.get(params.picture, { responseType: 'arraybuffer' });
- params.picture = await simpleUpload(picture.data, `${makeId(10)}.png`, 'image/png');
+ if (
+ params.picture &&
+ params.picture.indexOf(process.env.CLOUDFLARE_BUCKET_URL!) === -1
+ ) {
+ const picture = await axios.get(params.picture, {
+ responseType: 'arraybuffer',
+ });
+ params.picture = await simpleUpload(
+ picture.data,
+ `${makeId(10)}.png`,
+ 'image/png'
+ );
}
return this._integration.model.integration.update({
@@ -54,8 +78,18 @@ export class IntegrationRepository {
expiresIn = 999999999,
username?: string,
isBetweenSteps = false,
- refresh?: string
+ refresh?: string,
+ timezone?: number
) {
+ const postTimes = timezone
+ ? {
+ postingTimes: JSON.stringify([
+ { time: 560 - timezone },
+ { time: 850 - timezone },
+ { time: 1140 - timezone },
+ ]),
+ }
+ : {};
return this._integration.model.integration.upsert({
where: {
organizationId_internalId: {
@@ -76,6 +110,7 @@ export class IntegrationRepository {
? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }
: {}),
internalId,
+ ...postTimes,
organizationId: org,
refreshNeeded: false,
},
@@ -212,7 +247,7 @@ export class IntegrationRepository {
where: {
organizationId: org,
integrationId: id,
- deletedAt: null
+ deletedAt: null,
},
});
}
diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts
index 5b7fc3ce..c67d7c9e 100644
--- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts
@@ -16,11 +16,11 @@ import { LinkedinPageProvider } from '@gitroom/nestjs-libraries/integrations/soc
import { simpleUpload } from '@gitroom/nestjs-libraries/upload/r2.uploader';
import axios from 'axios';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
-import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
import dayjs from 'dayjs';
import { timer } from '@gitroom/helpers/utils/timer';
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
+import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
@Injectable()
export class IntegrationService {
@@ -29,6 +29,11 @@ export class IntegrationService {
private _integrationManager: IntegrationManager,
private _notificationService: NotificationService
) {}
+
+ async setTimes(orgId: string, integrationId: string, times: IntegrationTimeDto) {
+ return this._integrationRepository.setTimes(orgId, integrationId, times);
+ }
+
async createOrUpdateIntegration(
org: string,
name: string,
@@ -41,7 +46,8 @@ export class IntegrationService {
expiresIn?: number,
username?: string,
isBetweenSteps = false,
- refresh?: string
+ refresh?: string,
+ timezone?: number
) {
const loadImage = await axios.get(picture, { responseType: 'arraybuffer' });
const uploadedPicture = await simpleUpload(
@@ -62,7 +68,8 @@ export class IntegrationService {
expiresIn,
username,
isBetweenSteps,
- refresh
+ refresh,
+ timezone
);
}
diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
index b4704b29..d213a6f4 100644
--- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
@@ -64,10 +64,31 @@ export class PostsRepository {
getPosts(orgId: string, query: GetPostsDto) {
const dateYear = dayjs().year(query.year);
- const date = query.week ? dateYear.isoWeek(query.week) : dateYear.month(query.month-1);
+ const date =
+ query.display === 'day'
+ ? dateYear.isoWeek(query.week).day(query.day)
+ : query.display === 'week'
+ ? dateYear.isoWeek(query.week)
+ : dateYear.month(query.month - 1);
- const startDate = (query.week ? date.startOf('isoWeek') : date.startOf('month')).subtract(2, 'days').toDate();
- const endDate = (query.week ? date.endOf('isoWeek') : date.endOf('month')).add(2, 'days').toDate();
+ const startDate = (
+ query.display === 'day'
+ ? date.startOf('day')
+ : query.display === 'week'
+ ? date.startOf('isoWeek')
+ : date.startOf('month')
+ )
+ .subtract(2, 'hours')
+ .toDate();
+ const endDate = (
+ query.display === 'day'
+ ? date.endOf('day')
+ : query.display === 'week'
+ ? date.endOf('isoWeek')
+ : date.endOf('month')
+ )
+ .add(2, 'hours')
+ .toDate();
return this._post.model.post.findMany({
where: {
diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma
index 079e0e74..3944ec4d 100644
--- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma
+++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma
@@ -262,6 +262,7 @@ model Integration {
orderItems OrderItems[]
inBetweenSteps Boolean @default(false)
refreshNeeded Boolean @default(false)
+ postingTimes String @default("[{\"time\":120}, {\"time\":400}, {\"time\":700}]")
@@index([updatedAt])
@@index([deletedAt])
diff --git a/libraries/nestjs-libraries/src/dtos/integrations/connect.integration.dto.ts b/libraries/nestjs-libraries/src/dtos/integrations/connect.integration.dto.ts
index 28bf4cb9..a74bfb5e 100644
--- a/libraries/nestjs-libraries/src/dtos/integrations/connect.integration.dto.ts
+++ b/libraries/nestjs-libraries/src/dtos/integrations/connect.integration.dto.ts
@@ -1,15 +1,19 @@
import { IsDefined, IsOptional, IsString } from 'class-validator';
export class ConnectIntegrationDto {
- @IsString()
- @IsDefined()
- state: string;
+ @IsString()
+ @IsDefined()
+ state: string;
- @IsString()
- @IsDefined()
- code: string;
+ @IsString()
+ @IsDefined()
+ code: string;
- @IsString()
- @IsOptional()
- refresh?: string;
-}
\ No newline at end of file
+ @IsString()
+ @IsDefined()
+ timezone: string;
+
+ @IsString()
+ @IsOptional()
+ refresh?: string;
+}
diff --git a/libraries/nestjs-libraries/src/dtos/integrations/integration.time.dto.ts b/libraries/nestjs-libraries/src/dtos/integrations/integration.time.dto.ts
new file mode 100644
index 00000000..cde4d0b6
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/integrations/integration.time.dto.ts
@@ -0,0 +1,15 @@
+import { IsArray, IsDefined, IsNumber, ValidateNested } from 'class-validator';
+import { Type } from 'class-transformer';
+
+export class IntegrationValidateTimeDto {
+ @IsDefined()
+ @IsNumber()
+ time: number;
+}
+export class IntegrationTimeDto {
+ @Type(() => IntegrationValidateTimeDto)
+ @IsArray()
+ @IsDefined()
+ @ValidateNested({each: true})
+ time: IntegrationValidateTimeDto[];
+}
\ No newline at end of file
diff --git a/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts
index 1a89560f..258fa694 100644
--- a/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts
+++ b/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts
@@ -1,16 +1,30 @@
import { Type } from 'class-transformer';
-import { IsIn, IsNumber, IsString, Max, Min, ValidateIf } from 'class-validator';
+import {
+ IsDefined,
+ IsIn,
+ IsNumber,
+ Max,
+ Min,
+} from 'class-validator';
import dayjs from 'dayjs';
export class GetPostsDto {
- @ValidateIf((o) => !o.month)
@Type(() => Number)
@IsNumber()
@Max(52)
@Min(1)
week: number;
- @ValidateIf((o) => !o.week)
+ @Type(() => Number)
+ @IsNumber()
+ @Max(6)
+ @Min(0)
+ day: number;
+
+ @IsDefined()
+ @IsIn(['day', 'week', 'month'])
+ display: 'day' | 'week' | 'month';
+
@Type(() => Number)
@IsNumber()
@Max(52)