Merge branch 'dockerfile' of github.com:jamesread/postiz-app into dockerfile
This commit is contained in:
commit
a0c90885af
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: ['20.17.0']
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
# https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching#github-actions
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
${{ github.workspace }}/.next/cache
|
||||
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
||||
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
name: "Code Quality Analysis"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
name: ESLint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: Run eslint scanning
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
strategy:
|
||||
matrix:
|
||||
service: ["backend", "frontend"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
**/package-lock.json
|
||||
|
||||
- name: Install ESLint
|
||||
run: |
|
||||
npm install eslint
|
||||
npm install @microsoft/eslint-formatter-sarif@2.1.7
|
||||
|
||||
- name: Run ESLint
|
||||
run: npx eslint apps/${{ matrix.service }}/
|
||||
--config apps/${{ matrix.service }}/.eslintrc.json
|
||||
--format @microsoft/eslint-formatter-sarif
|
||||
--output-file apps/${{ matrix.service }}/eslint-results.sarif
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: apps/${{ matrix.service }}/eslint-results.sarif
|
||||
wait-for-processing: true
|
||||
|
|
@ -7,7 +7,9 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|||
|
||||
RUN apk add --no-cache \
|
||||
bash=5.2.21-r0 \
|
||||
supervisor=4.2.5-r4
|
||||
supervisor=4.2.5-r4 \
|
||||
make \
|
||||
build-base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
|
|
@ -48,19 +48,18 @@ export class PostsController {
|
|||
@GetOrgFromRequest() org: Organization,
|
||||
@Query() query: GetPostsDto
|
||||
) {
|
||||
const [posts, comments] = await Promise.all([
|
||||
const [posts] = await Promise.all([
|
||||
this._postsService.getPosts(org.id, query),
|
||||
this._commentsService.getAllCommentsByWeekYear(
|
||||
org.id,
|
||||
query.year,
|
||||
query.week,
|
||||
query.isIsoWeek === 'true'
|
||||
),
|
||||
// this._commentsService.getAllCommentsByWeekYear(
|
||||
// org.id,
|
||||
// query.year,
|
||||
// query.week
|
||||
// ),
|
||||
]);
|
||||
|
||||
return {
|
||||
posts,
|
||||
comments,
|
||||
// comments,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,16 @@ title: Email Notifications
|
|||
description: How to send notifications to users
|
||||
---
|
||||
|
||||
At the moment we are using Resend to send email notifications to users, and might be changed the Novu later.
|
||||
Postiz uses Resend to send email notifications to users. Emails are currently
|
||||
required as part of the new-user creation process, which sends an activation
|
||||
email.
|
||||
|
||||
Register to [Resend](https://resend.com) connect your domain.
|
||||
Copy your API Key.
|
||||
Head over to .env file and add the following line.
|
||||
* Register to [Resend](https://resend.com), and connect your domain.
|
||||
* Copy your API Key from the Resend control panel.
|
||||
* Open the .env file and edit the following line.
|
||||
|
||||
```env
|
||||
RESEND_API_KEY=""
|
||||
RESEND_API_KEY="<your-api-key-here>"
|
||||
```
|
||||
|
||||
Feel free to contribute other providers to send email notifications.
|
||||
Feel free to contribute other providers to send email notifications.
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ Unlike other NX project, this project has one `.env` file that is shared between
|
|||
It makes it easier to develop and deploy the project.<br /><br />
|
||||
When deploying to websites like [Railway](https://railway.app) or [Heroku](https://heroku.com), you can use a shared environment variables for all the apps.<br /><br />
|
||||
|
||||
**At the moment it has 6 project:**
|
||||
**At the moment it has 6 projects:**
|
||||
|
||||
- **Backend** - NestJS based system
|
||||
- **Workers** - NestJS based workers connected to a Redis Queue.
|
||||
- **Cron** - NestJS scheduler to run cron jobs.
|
||||
- **Frontend** - NextJS based control panel.
|
||||
- **Docs** - Mintlify based documentation website.
|
||||
- [Frontend](#frontend) - Provides the Web user interface, talks to the Backend.
|
||||
- [Backend](#backend) - Does all the real work, provides an API for the frontend, and posts work to the redis queue.
|
||||
- [Workers](#worker) - Consumes work from the Redis Queue.
|
||||
- [Cron](#cron) - Run jobs at scheduled times.
|
||||
- [Docs](#docs) - This documentation site!
|
||||
|
||||
<img
|
||||
src="/images/arch.png"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
title: 'Quickstart'
|
||||
---
|
||||
|
||||
At the moment it is necessary to build the project locally - some dependencies
|
||||
like redis and postgres can run as docker containers, but there is no docker
|
||||
container for Postiz just yet.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To run the project you need to have multiple things:
|
||||
|
|
@ -17,25 +21,29 @@ To run the project you need to have multiple things:
|
|||
|
||||
A complete guide of how to install NodeJS can be found [here](https://nodejs.org/en/download/).
|
||||
|
||||
### PostgreSQL (or any other SQL database)
|
||||
### PostgreSQL (or any other SQL database) & Redis
|
||||
|
||||
Make sure you have PostgreSQL installed on your machine.<br />
|
||||
If you don't, you can install [Docker](https://www.docker.com/products/docker-desktop) and run:
|
||||
Make sure you have PostgreSQL installed on your machine.
|
||||
|
||||
```bash
|
||||
#### Option A) Postgres and Redis as Single containers
|
||||
|
||||
You can install [Docker](https://www.docker.com/products/docker-desktop) and run:
|
||||
|
||||
```bash Terminal
|
||||
docker run -e POSTGRES_USER=root -e POSTGRES_PASSWORD=your_password --name postgres -p 5432:5432 -d postgres
|
||||
```
|
||||
|
||||
### Redis
|
||||
|
||||
Make sure you have Redis installed on your machine.<br />
|
||||
If you don't, you can install [Docker](https://www.docker.com/products/docker-desktop) and run:
|
||||
|
||||
```bash
|
||||
docker run --name redis -p 6379:6379 -d redis
|
||||
```
|
||||
|
||||
## Installation
|
||||
#### Option B) Postgres and Redis as docker-compose
|
||||
|
||||
Download the [docker-compose.yaml file here](https://raw.githubusercontent.com/gitroomhq/postiz-app/main/docker-compose.dev.yaml),
|
||||
or grab it from the repository in the next step.
|
||||
|
||||
```bash Terminal
|
||||
docker compose -f "docker-compose.dev.yaml" up
|
||||
```
|
||||
|
||||
## Build Postiz
|
||||
|
||||
<Steps>
|
||||
<Step title="Clone the repository">
|
||||
|
|
@ -44,11 +52,11 @@ git clone https://github.com/gitroomhq/gitroom
|
|||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Copy environment variables">
|
||||
<Step title="Set environment variables">
|
||||
Copy the `.env.example` file to `.env` and fill in the values
|
||||
|
||||
```bash .env
|
||||
DATABASE_URL="postgres database URL"
|
||||
DATABASE_URL="postgres database URL i.g. postgresql://postiz-local:postiz-local-pwd@0.0.0.0:5432/postiz-db-local"
|
||||
REDIS_URL="redis database URL"
|
||||
JWT_SECRET="random string for your JWT secret, make it long"
|
||||
FRONTEND_URL="By default: http://localhost:4200"
|
||||
|
|
@ -73,21 +81,15 @@ CLOUDFLARE_BUCKET_URL="Cloudflare R2 Backet URL"
|
|||
NX_ADD_PLUGINS=false
|
||||
IS_GENERAL="true" # required for now
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Install the dependencies">
|
||||
|
||||
```bash Terminal
|
||||
npm install
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Setup postgres & redis via docker compose">
|
||||
```bash Terminal
|
||||
docker compose -f "docker-compose.dev.yaml" up
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Generate the prisma client and run the migrations">
|
||||
```bash Terminal
|
||||
npm run prisma-db-push
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
--color-custom20: #121b2c;
|
||||
--color-custom21: #506490;
|
||||
--color-custom22: #b91c1c;
|
||||
--color-custom23: #06080d;
|
||||
--color-custom23: #000000;
|
||||
--color-custom24: #eaff00;
|
||||
--color-custom25: #2e3336;
|
||||
--color-custom26: #1d9bf0;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
import interClass from '@gitroom/react/helpers/inter.font';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
import './global.scss';
|
||||
import 'react-tooltip/dist/react-tooltip.css';
|
||||
import '@copilotkit/react-ui/styles.css';
|
||||
|
||||
import LayoutContext from '@gitroom/frontend/components/layout/layout.context';
|
||||
import { ReactNode } from 'react';
|
||||
import { Chakra_Petch } from 'next/font/google';
|
||||
import { isGeneral } from '@gitroom/react/helpers/is.general';
|
||||
import PlausibleProvider from 'next-plausible';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] });
|
||||
import "@fontsource/chakra-petch";
|
||||
|
||||
export default async function AppLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
|
|
@ -24,7 +20,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
|
|||
sizes="any"
|
||||
/>
|
||||
</head>
|
||||
<body className={clsx(chakra.className, 'text-primary dark')}>
|
||||
<body className={clsx('chakra-petch', 'text-primary dark')}>
|
||||
<PlausibleProvider domain={isGeneral() ? "postiz.com" : "gitroom.com"}>
|
||||
<LayoutContext>{children}</LayoutContext>
|
||||
</PlausibleProvider>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import React, {
|
|||
import dayjs from 'dayjs';
|
||||
import { Integrations } from '@gitroom/frontend/components/launches/calendar.context';
|
||||
import clsx from 'clsx';
|
||||
import { commands } from '@uiw/react-md-editor';
|
||||
import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useModals } from '@mantine/modals';
|
||||
|
|
@ -27,16 +26,14 @@ import {
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useMoveToIntegration } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration';
|
||||
import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data';
|
||||
import { newImage } from '@gitroom/frontend/components/launches/helpers/new.image.component';
|
||||
import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';
|
||||
import { useExpend } from '@gitroom/frontend/components/launches/helpers/use.expend';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component';
|
||||
import { ProvidersOptions } from '@gitroom/frontend/components/launches/providers.options';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { postSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector';
|
||||
import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow';
|
||||
import { DatePicker } from '@gitroom/frontend/components/launches/helpers/date.picker';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
|
|
@ -56,10 +53,10 @@ export const AddEditModal: FC<{
|
|||
date: dayjs.Dayjs;
|
||||
integrations: Integrations[];
|
||||
reopenModal: () => void;
|
||||
mutate: () => void;
|
||||
}> = (props) => {
|
||||
const { date, integrations, reopenModal } = props;
|
||||
const { date, integrations, reopenModal, mutate } = props;
|
||||
const [dateState, setDateState] = useState(date);
|
||||
const { mutate } = useSWRConfig();
|
||||
|
||||
// hook to open a new modal
|
||||
const modal = useModals();
|
||||
|
|
@ -246,7 +243,7 @@ export const AddEditModal: FC<{
|
|||
await fetch(`/posts/${existingData.group}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
mutate('/posts');
|
||||
mutate();
|
||||
modal.closeAll();
|
||||
return;
|
||||
}
|
||||
|
|
@ -324,7 +321,7 @@ export const AddEditModal: FC<{
|
|||
|
||||
existingData.group = uuidv4();
|
||||
|
||||
mutate('/posts');
|
||||
mutate();
|
||||
toaster.show(
|
||||
!existingData.integration
|
||||
? 'Added successfully'
|
||||
|
|
|
|||
|
|
@ -17,16 +17,33 @@ import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
|||
import { Post, Integration } from '@prisma/client';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { isGeneral } from '@gitroom/react/helpers/is.general';
|
||||
import isoWeek from 'dayjs/plugin/isoWeek';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
|
||||
dayjs.extend(isoWeek);
|
||||
dayjs.extend(weekOfYear);
|
||||
|
||||
const CalendarContext = createContext({
|
||||
currentWeek: dayjs().week(),
|
||||
currentYear: dayjs().year(),
|
||||
currentMonth: dayjs().month(),
|
||||
comments: [] as Array<{ date: string; total: number }>,
|
||||
integrations: [] as Integrations[],
|
||||
trendings: [] as string[],
|
||||
posts: [] as Array<Post & { integration: Integration }>,
|
||||
setFilters: (filters: { currentWeek: number; currentYear: number }) => {},
|
||||
changeDate: (id: string, date: dayjs.Dayjs) => {},
|
||||
reloadCalendarView: () => {/** empty **/},
|
||||
display: 'week',
|
||||
setFilters: (filters: {
|
||||
currentWeek: number;
|
||||
currentYear: number;
|
||||
currentMonth: number;
|
||||
display: 'week' | 'month';
|
||||
}) => {
|
||||
/** empty **/
|
||||
},
|
||||
changeDate: (id: string, date: dayjs.Dayjs) => {
|
||||
/** empty **/
|
||||
},
|
||||
});
|
||||
|
||||
export interface Integrations {
|
||||
|
|
@ -40,28 +57,21 @@ export interface Integrations {
|
|||
}
|
||||
|
||||
function getWeekNumber(date: Date) {
|
||||
// Copy date so don't modify original
|
||||
const targetDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
// Set to nearest Thursday: current date + 4 - current day number
|
||||
// Make Sunday's day number 7
|
||||
targetDate.setUTCDate(targetDate.getUTCDate() + 4 - (targetDate.getUTCDay() || 7));
|
||||
// Get first day of year
|
||||
const yearStart = new Date(Date.UTC(targetDate.getUTCFullYear(), 0, 1));
|
||||
// Calculate full weeks to nearest Thursday
|
||||
return Math.ceil((((targetDate.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
||||
}
|
||||
|
||||
function isISOWeek(date: Date, weekNumber: number): boolean {
|
||||
// Copy date so don't modify original
|
||||
const targetDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
// Set to nearest Thursday: current date + 4 - current day number
|
||||
// Make Sunday's day number 7
|
||||
targetDate.setUTCDate(targetDate.getUTCDate() + 4 - (targetDate.getUTCDay() || 7));
|
||||
// Get first day of year
|
||||
const yearStart = new Date(Date.UTC(targetDate.getUTCFullYear(), 0, 1));
|
||||
// Calculate full weeks to nearest Thursday
|
||||
const isoWeekNo = Math.ceil((((targetDate.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
||||
return isoWeekNo === weekNumber;
|
||||
// Copy date so don't modify original
|
||||
const targetDate = new Date(
|
||||
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
);
|
||||
// Set to nearest Thursday: current date + 4 - current day number
|
||||
// Make Sunday's day number 7
|
||||
targetDate.setUTCDate(
|
||||
targetDate.getUTCDate() + 4 - (targetDate.getUTCDay() || 7)
|
||||
);
|
||||
// Get first day of year
|
||||
const yearStart = new Date(Date.UTC(targetDate.getUTCFullYear(), 0, 1));
|
||||
// Calculate full weeks to nearest Thursday
|
||||
return Math.ceil(
|
||||
((targetDate.getTime() - yearStart.getTime()) / 86400000 + 1) / 7
|
||||
);
|
||||
}
|
||||
|
||||
export const CalendarWeekProvider: FC<{
|
||||
|
|
@ -70,64 +80,72 @@ export const CalendarWeekProvider: FC<{
|
|||
}> = ({ children, integrations }) => {
|
||||
const fetch = useFetch();
|
||||
const [internalData, setInternalData] = useState([] as any[]);
|
||||
const [trendings, setTrendings] = useState<string[]>([]);
|
||||
const { mutate } = useSWRConfig();
|
||||
const [trendings] = useState<string[]>([]);
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (isGeneral()) {
|
||||
return [];
|
||||
}
|
||||
setTrendings(await (await fetch('/posts/predict-trending')).json());
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const display = searchParams.get('month') ? 'month' : 'week';
|
||||
const [filters, setFilters] = useState({
|
||||
currentWeek: +(searchParams.get('week') || getWeekNumber(new Date())),
|
||||
currentWeek:
|
||||
display === 'week'
|
||||
? +(searchParams.get('week') || getWeekNumber(new Date()))
|
||||
: 0,
|
||||
currentMonth:
|
||||
display === 'week' ? 0 : +(searchParams.get('month') || dayjs().month()),
|
||||
currentYear: +(searchParams.get('year') || dayjs().year()),
|
||||
display,
|
||||
});
|
||||
|
||||
const isIsoWeek = useMemo(() => {
|
||||
return isISOWeek(new Date(), filters.currentWeek);
|
||||
}, [filters]);
|
||||
|
||||
const setFiltersWrapper = useCallback(
|
||||
(filters: { currentWeek: number; currentYear: number }) => {
|
||||
setFilters(filters);
|
||||
router.replace(
|
||||
`/launches?week=${filters.currentWeek}&year=${filters.currentYear}`
|
||||
);
|
||||
setTimeout(() => {
|
||||
mutate('/posts');
|
||||
});
|
||||
},
|
||||
[filters]
|
||||
);
|
||||
|
||||
const params = useMemo(() => {
|
||||
return new URLSearchParams({
|
||||
week: filters.currentWeek.toString(),
|
||||
year: filters.currentYear.toString(),
|
||||
isIsoWeek: isIsoWeek.toString(),
|
||||
}).toString();
|
||||
return new URLSearchParams(
|
||||
filters.currentWeek
|
||||
? {
|
||||
week: filters.currentWeek.toString(),
|
||||
year: filters.currentYear.toString(),
|
||||
}
|
||||
: {
|
||||
year: filters.currentYear.toString(),
|
||||
month: (filters.currentMonth + 1).toString(),
|
||||
}
|
||||
).toString();
|
||||
}, [filters]);
|
||||
|
||||
const loadData = useCallback(
|
||||
async (url: string) => {
|
||||
const data = (await fetch(`${url}?${params}`)).json();
|
||||
async () => {
|
||||
const data = (await fetch(`/posts?${params}`)).json();
|
||||
return data;
|
||||
},
|
||||
[filters]
|
||||
[filters, params]
|
||||
);
|
||||
|
||||
const swr = useSWR(`/posts`, loadData, {
|
||||
const swr = useSWR(`/posts-${params}`, loadData, {
|
||||
refreshInterval: 3600000,
|
||||
refreshWhenOffline: false,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const setFiltersWrapper = useCallback(
|
||||
(filters: {
|
||||
currentWeek: number;
|
||||
currentYear: number;
|
||||
currentMonth: number;
|
||||
display: 'week' | 'month';
|
||||
}) => {
|
||||
setFilters(filters);
|
||||
setInternalData([]);
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
`/launches?${
|
||||
filters.currentWeek
|
||||
? `week=${filters.currentWeek}`
|
||||
: `month=${filters.currentMonth}`
|
||||
}&year=${filters.currentYear}`
|
||||
);
|
||||
},
|
||||
[filters, swr.mutate]
|
||||
);
|
||||
|
||||
const { isLoading } = swr;
|
||||
const { posts, comments } = swr?.data || { posts: [], comments: [] };
|
||||
|
||||
|
|
@ -158,6 +176,7 @@ export const CalendarWeekProvider: FC<{
|
|||
<CalendarContext.Provider
|
||||
value={{
|
||||
trendings,
|
||||
reloadCalendarView: swr.mutate,
|
||||
...filters,
|
||||
posts: isLoading ? [] : internalData,
|
||||
integrations,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
import React, {
|
||||
FC,
|
||||
Fragment,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
Integrations,
|
||||
useCalendar,
|
||||
|
|
@ -17,14 +24,16 @@ import { Integration, Post, State } from '@prisma/client';
|
|||
import { useAddProvider } from '@gitroom/frontend/components/launches/add.provider.component';
|
||||
import { CommentComponent } from '@gitroom/frontend/components/launches/comments/comment.component';
|
||||
import { useSWRConfig } from 'swr';
|
||||
import { useIntersectionObserver } from '@uidotdev/usehooks';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';
|
||||
import { PreviewPopup } from '@gitroom/frontend/components/marketplace/special.message';
|
||||
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
||||
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
|
||||
export const days = [
|
||||
'',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
|
|
@ -33,191 +42,155 @@ export const days = [
|
|||
'Saturday',
|
||||
'Sunday',
|
||||
];
|
||||
export const hours = [
|
||||
'00:00',
|
||||
'01:00',
|
||||
'02:00',
|
||||
'03:00',
|
||||
'04:00',
|
||||
'05:00',
|
||||
'06:00',
|
||||
'07:00',
|
||||
'08:00',
|
||||
'09:00',
|
||||
'10:00',
|
||||
'11:00',
|
||||
'12:00',
|
||||
'13:00',
|
||||
'14:00',
|
||||
'15:00',
|
||||
'16:00',
|
||||
'17:00',
|
||||
'18:00',
|
||||
'19:00',
|
||||
'20:00',
|
||||
'21:00',
|
||||
'22:00',
|
||||
'23:00',
|
||||
];
|
||||
export const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
|
||||
export const WeekView = () => {
|
||||
const { currentYear, currentWeek } = useCalendar();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden text-textColor flex-1">
|
||||
<div className="flex-1">
|
||||
<div className="grid grid-cols-8 bg-customColor31 gap-[1px] border-customColor31 border rounded-[10px]">
|
||||
<div className="bg-customColor20 sticky top-0 z-10 bg-gray-900"></div>
|
||||
{days.map((day, index) => (
|
||||
<div
|
||||
key={day}
|
||||
className="sticky top-0 z-10 bg-customColor20 p-2 text-center"
|
||||
>
|
||||
<div>{day}</div>
|
||||
</div>
|
||||
))}
|
||||
{hours.map((hour) => (
|
||||
<Fragment key={hour}>
|
||||
<div className="p-2 pr-4 bg-secondary text-center items-center justify-center flex">
|
||||
{hour.toString().padStart(2, '0')}:00
|
||||
</div>
|
||||
{days.map((day, indexDay) => (
|
||||
<Fragment key={`${day}-${hour}`}>
|
||||
<div className="relative bg-secondary">
|
||||
<CalendarColumn
|
||||
getDate={dayjs()
|
||||
.year(currentYear)
|
||||
.week(currentWeek)
|
||||
.day(indexDay + 1)
|
||||
.hour(hour)
|
||||
.startOf('hour')}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MonthView = () => {
|
||||
const { currentYear, currentMonth } = useCalendar();
|
||||
|
||||
const calendarDays = useMemo(() => {
|
||||
const startOfMonth = dayjs(new Date(currentYear, currentMonth, 1));
|
||||
|
||||
// Calculate the day offset for Monday (isoWeekday() returns 1 for Monday)
|
||||
const startDayOfWeek = startOfMonth.isoWeekday(); // 1 for Monday, 7 for Sunday
|
||||
const daysBeforeMonth = startDayOfWeek - 1; // Days to show from the previous month
|
||||
|
||||
// Get the start date (Monday of the first week that includes this month)
|
||||
const startDate = startOfMonth.subtract(daysBeforeMonth, 'day');
|
||||
|
||||
// Create an array to hold the calendar days (6 weeks * 7 days = 42 days max)
|
||||
const calendarDays = [];
|
||||
let currentDay = startDate;
|
||||
|
||||
for (let i = 0; i < 42; i++) {
|
||||
let label = 'current-month';
|
||||
if (currentDay.month() < currentMonth) label = 'previous-month';
|
||||
if (currentDay.month() > currentMonth) label = 'next-month';
|
||||
|
||||
calendarDays.push({
|
||||
day: currentDay,
|
||||
label,
|
||||
});
|
||||
|
||||
// Move to the next day
|
||||
currentDay = currentDay.add(1, 'day');
|
||||
}
|
||||
|
||||
return calendarDays;
|
||||
}, [currentYear, currentMonth]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden text-textColor flex-1">
|
||||
<div className="flex-1 flex">
|
||||
<div className="grid grid-cols-7 grid-rows-[40px_auto] bg-customColor31 gap-[1px] border-customColor31 border rounded-[10px] flex-1">
|
||||
{days.map((day) => (
|
||||
<div
|
||||
key={day}
|
||||
className="sticky top-0 z-10 bg-customColor20 p-2 text-center"
|
||||
>
|
||||
<div>{day}</div>
|
||||
</div>
|
||||
))}
|
||||
{calendarDays.map((date, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-secondary text-center items-center justify-center flex min-h-[100px]"
|
||||
>
|
||||
<CalendarColumn
|
||||
getDate={dayjs(date.day).endOf('day')}
|
||||
randomHour={true}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Calendar = () => {
|
||||
const { currentWeek, currentYear, comments } = useCalendar();
|
||||
|
||||
const firstDay = useMemo(() => {
|
||||
return dayjs().year(currentYear).isoWeek(currentWeek).isoWeekday(1);
|
||||
}, [currentYear, currentWeek]);
|
||||
const { display } = useCalendar();
|
||||
|
||||
return (
|
||||
<DNDProvider>
|
||||
<div className="select-none">
|
||||
<div className="grid grid-cols-8 text-center border-tableBorder border-r">
|
||||
{days.map((day, index) => (
|
||||
<div
|
||||
className="border-tableBorder gap-[4px] border-l border-b h-[36px] border-t flex items-center justify-center bg-input text-[14px] sticky top-0 z-[100]"
|
||||
key={day}
|
||||
>
|
||||
<div>{day} </div>
|
||||
<div className="text-[12px]">
|
||||
{day && `(${firstDay.add(index - 1, 'day').format('DD/MM')})`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{hours.map((hour) =>
|
||||
days.map((day, index) => (
|
||||
<>
|
||||
{index === 0 ? (
|
||||
<div
|
||||
className="border-tableBorder border-l border-b h-[216px]"
|
||||
key={day + hour}
|
||||
>
|
||||
{['00', '10', '20', '30', '40', '50'].map((num) => (
|
||||
<div
|
||||
key={day + hour + num}
|
||||
className="h-[calc(216px/6)] text-[12px] flex justify-center items-center"
|
||||
>
|
||||
{hour.split(':')[0] + ':' + num}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="group relative border-tableBorder border-l border-b h-[216px] flex flex-col overflow-hidden"
|
||||
key={day + hour}
|
||||
>
|
||||
<CommentBox
|
||||
totalComments={
|
||||
comments.find(
|
||||
(p) =>
|
||||
dayjs
|
||||
.utc(p.date)
|
||||
.local()
|
||||
.format('YYYY-MM-DD HH:mm') ===
|
||||
dayjs()
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(index + 1)
|
||||
.hour(+hour.split(':')[0] - 1)
|
||||
.minute(0)
|
||||
.format('YYYY-MM-DD HH:mm')
|
||||
)?.total || 0
|
||||
}
|
||||
date={dayjs()
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(index + 1)
|
||||
.hour(+hour.split(':')[0] - 1)
|
||||
.minute(0)}
|
||||
/>
|
||||
{['00', '10', '20', '30', '40', '50'].map((num) => (
|
||||
<CalendarColumn
|
||||
key={day + hour + num + currentWeek + currentYear}
|
||||
day={index}
|
||||
hour={hour.split(':')[0] + ':' + num}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{display === 'week' ? <WeekView /> : <MonthView />}
|
||||
</DNDProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const CalendarColumn: FC<{ day: number; hour: string }> = (props) => {
|
||||
const { day, hour } = props;
|
||||
const { currentWeek, currentYear } = useCalendar();
|
||||
|
||||
const getDate = useMemo(() => {
|
||||
const date =
|
||||
dayjs()
|
||||
.year(currentYear)
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(day)
|
||||
.format('YYYY-MM-DD') +
|
||||
'T' +
|
||||
hour +
|
||||
':00';
|
||||
return dayjs(date);
|
||||
}, [currentWeek]);
|
||||
|
||||
const isBeforeNow = useMemo(() => {
|
||||
return getDate.isBefore(dayjs());
|
||||
}, [getDate]);
|
||||
|
||||
const [ref, entry] = useIntersectionObserver({
|
||||
threshold: 0.5,
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full h-full" ref={ref}>
|
||||
{!entry?.isIntersecting ? (
|
||||
<div />
|
||||
) : (
|
||||
<CalendarColumnRender {...props} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
||||
const { day, hour } = props;
|
||||
export const CalendarColumn: FC<{
|
||||
getDate: dayjs.Dayjs;
|
||||
randomHour?: boolean;
|
||||
}> = (props) => {
|
||||
const { getDate, randomHour } = props;
|
||||
const user = useUser();
|
||||
const {
|
||||
currentWeek,
|
||||
currentYear,
|
||||
integrations,
|
||||
posts,
|
||||
trendings,
|
||||
changeDate,
|
||||
display,
|
||||
reloadCalendarView,
|
||||
} = useCalendar();
|
||||
|
||||
const toaster = useToaster();
|
||||
const modal = useModals();
|
||||
const fetch = useFetch();
|
||||
|
||||
const getDate = useMemo(() => {
|
||||
const date =
|
||||
dayjs()
|
||||
.year(currentYear)
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(day)
|
||||
.format('YYYY-MM-DD') +
|
||||
'T' +
|
||||
hour +
|
||||
':00';
|
||||
return dayjs(date);
|
||||
}, [currentWeek]);
|
||||
|
||||
const postList = useMemo(() => {
|
||||
return posts.filter((post) => {
|
||||
return dayjs
|
||||
.utc(post.publishDate)
|
||||
.local()
|
||||
.isBetween(getDate, getDate.add(59, 'minute'), 'minute', '[)');
|
||||
const pList = dayjs.utc(post.publishDate).local();
|
||||
const check =
|
||||
display === 'week'
|
||||
? pList.isSameOrAfter(getDate.startOf('hour')) &&
|
||||
pList.isBefore(getDate.endOf('hour'))
|
||||
: pList.format('DD/MM/YYYY') === getDate.format('DD/MM/YYYY');
|
||||
|
||||
return check;
|
||||
});
|
||||
}, [posts]);
|
||||
}, [posts, display, getDate]);
|
||||
|
||||
const canBeTrending = useMemo(() => {
|
||||
return !!trendings.find((trend) => {
|
||||
|
|
@ -229,7 +202,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
}, [trendings]);
|
||||
|
||||
const isBeforeNow = useMemo(() => {
|
||||
return getDate.isBefore(dayjs());
|
||||
return getDate.startOf('hour').isBefore(dayjs().startOf('hour'));
|
||||
}, [getDate]);
|
||||
|
||||
const [{ canDrop }, drop] = useDrop(() => ({
|
||||
|
|
@ -311,6 +284,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
return previewPublication(post);
|
||||
}
|
||||
const data = await (await fetch(`/posts/${post.id}`)).json();
|
||||
const publishDate = dayjs.utc(data.posts[0].publishDate).local();
|
||||
|
||||
modal.openModal({
|
||||
closeOnClickOutside: false,
|
||||
|
|
@ -323,11 +297,12 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
<ExistingDataContextProvider value={data}>
|
||||
<AddEditModal
|
||||
reopenModal={editPost(post)}
|
||||
mutate={reloadCalendarView}
|
||||
integrations={integrations
|
||||
.slice(0)
|
||||
.filter((f) => f.id === data.integration)
|
||||
.map((p) => ({ ...p, picture: data.integrationPicture }))}
|
||||
date={getDate}
|
||||
date={publishDate}
|
||||
/>
|
||||
</ExistingDataContextProvider>
|
||||
),
|
||||
|
|
@ -349,84 +324,103 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
children: (
|
||||
<AddEditModal
|
||||
integrations={integrations.slice(0).map((p) => ({ ...p }))}
|
||||
date={getDate}
|
||||
mutate={reloadCalendarView}
|
||||
date={
|
||||
randomHour ? getDate.hour(Math.floor(Math.random() * 24)) : getDate
|
||||
}
|
||||
reopenModal={() => ({})}
|
||||
/>
|
||||
),
|
||||
size: '80%',
|
||||
// title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`,
|
||||
});
|
||||
}, [integrations]);
|
||||
}, [integrations, getDate]);
|
||||
|
||||
const addProvider = useAddProvider();
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col w-full min-h-full">
|
||||
<div className="flex flex-col w-full min-h-full" ref={drop}>
|
||||
{display === 'month' && (
|
||||
<div className={clsx('pt-[5px]', isBeforeNow && 'bg-customColor23')}>
|
||||
{getDate.date()}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
{...(canBeTrending
|
||||
? {
|
||||
'data-tooltip-id': 'tooltip',
|
||||
'data-tooltip-content': 'Predicted GitHub Trending Change',
|
||||
}
|
||||
: {})}
|
||||
ref={drop}
|
||||
className={clsx(
|
||||
'flex-col flex-1 text-[12px] pointer w-full overflow-hidden justify-center overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
|
||||
isBeforeNow && 'bg-customColor23',
|
||||
canDrop && 'bg-white/80',
|
||||
canBeTrending && 'bg-customColor24'
|
||||
'relative flex flex-col flex-1',
|
||||
canDrop && 'bg-white/80'
|
||||
)}
|
||||
>
|
||||
{postList.map((post) => (
|
||||
<div
|
||||
{...(canBeTrending
|
||||
? {
|
||||
'data-tooltip-id': 'tooltip',
|
||||
'data-tooltip-content': 'Predicted GitHub Trending Change',
|
||||
}
|
||||
: {})}
|
||||
className={clsx(
|
||||
'flex-col text-[12px] pointer w-full cursor-pointer overflow-hidden overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
|
||||
isBeforeNow && 'bg-customColor23 flex-1',
|
||||
canBeTrending && 'bg-customColor24'
|
||||
)}
|
||||
>
|
||||
{postList.map((post) => (
|
||||
<div
|
||||
key={post.id}
|
||||
className={clsx(
|
||||
'text-textColor p-[2.5px] relative flex flex-col justify-center items-center'
|
||||
)}
|
||||
>
|
||||
<div className="relative w-full flex flex-col items-center p-[2.5px]">
|
||||
<CalendarItem
|
||||
isBeforeNow={isBeforeNow}
|
||||
date={getDate}
|
||||
state={post.state}
|
||||
editPost={editPost(post)}
|
||||
post={post}
|
||||
integrations={integrations}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{!isBeforeNow && (
|
||||
<div
|
||||
key={post.id}
|
||||
className={clsx(
|
||||
'text-textColor p-[2.5px] relative flex flex-col justify-center items-center'
|
||||
)}
|
||||
className="pb-[2.5px] px-[5px] flex-1 flex"
|
||||
onClick={integrations.length ? addModal : addProvider}
|
||||
>
|
||||
<div className="relative w-full flex flex-col items-center p-[2.5px]">
|
||||
<CalendarItem
|
||||
date={getDate}
|
||||
state={post.state}
|
||||
editPost={editPost(post)}
|
||||
post={post}
|
||||
integrations={integrations}
|
||||
<div
|
||||
className={clsx(
|
||||
display === 'month'
|
||||
? 'flex-1 min-h-[40px] w-full'
|
||||
: !postList.length
|
||||
? 'h-full w-full absolute left-0 top-0 p-[5px]'
|
||||
: 'min-h-[40px] w-full',
|
||||
'flex items-center justify-center cursor-pointer pb-[2.5px]'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'hover:before:content-["+"] w-full h-full text-seventh rounded-[10px] hover:border hover:border-seventh flex justify-center items-center'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
{!isBeforeNow && (
|
||||
<div className="pb-[2.5px] px-[5px]">
|
||||
<div
|
||||
className={clsx(
|
||||
!postList.length
|
||||
? 'h-full w-full absolute left-0 top-0 p-[5px]'
|
||||
: 'h-[40px]',
|
||||
'flex items-center justify-center cursor-pointer pb-[2.5px]'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
onClick={integrations.length ? addModal : addProvider}
|
||||
className={clsx(
|
||||
'hover:before:content-["+"] w-full h-full text-seventh rounded-[10px] hover:border hover:border-seventh flex justify-center items-center'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CalendarItem: FC<{
|
||||
date: dayjs.Dayjs;
|
||||
isBeforeNow: boolean;
|
||||
editPost: () => void;
|
||||
integrations: Integrations[];
|
||||
state: State;
|
||||
post: Post & { integration: Integration };
|
||||
}> = (props) => {
|
||||
const { editPost, post, date, integrations, state } = props;
|
||||
const { editPost, post, date, isBeforeNow, state } = props;
|
||||
const [{ opacity }, dragRef] = useDrag(
|
||||
() => ({
|
||||
type: 'post',
|
||||
|
|
@ -444,7 +438,7 @@ const CalendarItem: FC<{
|
|||
className={clsx(
|
||||
'gap-[5px] w-full flex h-full flex-1 rounded-[10px] border border-seventh px-[5px] p-[2.5px]',
|
||||
'relative',
|
||||
state === 'DRAFT' && '!grayscale'
|
||||
(state === 'DRAFT' || isBeforeNow) && '!grayscale'
|
||||
)}
|
||||
style={{ opacity }}
|
||||
>
|
||||
|
|
@ -458,7 +452,10 @@ const CalendarItem: FC<{
|
|||
src={`/icons/platforms/${post.integration?.providerIdentifier}.png`}
|
||||
/>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap line-clamp-3">{post.content}</div>
|
||||
<div className="whitespace-pre-wrap line-clamp-3">
|
||||
{state === 'DRAFT' ? 'Draft: ' : ''}
|
||||
{post.content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,32 +1,109 @@
|
|||
'use client';
|
||||
import { useCalendar } from '@gitroom/frontend/components/launches/calendar.context';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import {useCallback} from "react";
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const Filters = () => {
|
||||
const week = useCalendar();
|
||||
const betweenDates =
|
||||
dayjs().year(week.currentYear).isoWeek(week.currentWeek).startOf('isoWeek').format('DD/MM/YYYY') +
|
||||
' - ' +
|
||||
dayjs().year(week.currentYear).isoWeek(week.currentWeek).endOf('isoWeek').format('DD/MM/YYYY');
|
||||
week.display === 'week'
|
||||
? dayjs()
|
||||
.year(week.currentYear)
|
||||
.isoWeek(week.currentWeek)
|
||||
.startOf('isoWeek')
|
||||
.format('DD/MM/YYYY') +
|
||||
' - ' +
|
||||
dayjs()
|
||||
.year(week.currentYear)
|
||||
.isoWeek(week.currentWeek)
|
||||
.endOf('isoWeek')
|
||||
.format('DD/MM/YYYY')
|
||||
: dayjs()
|
||||
.year(week.currentYear)
|
||||
.month(week.currentMonth)
|
||||
.startOf('month')
|
||||
.format('DD/MM/YYYY') +
|
||||
' - ' +
|
||||
dayjs()
|
||||
.year(week.currentYear)
|
||||
.month(week.currentMonth)
|
||||
.endOf('month')
|
||||
.format('DD/MM/YYYY');
|
||||
|
||||
const nextWeek = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek: week.currentWeek === 52 ? 1 : week.currentWeek + 1,
|
||||
currentYear: week.currentWeek === 52 ? week.currentYear + 1 : week.currentYear,
|
||||
});
|
||||
}, [week.currentWeek, week.currentYear]);
|
||||
const setWeek = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek: dayjs().isoWeek(),
|
||||
currentYear: dayjs().year(),
|
||||
currentMonth: 0,
|
||||
display: 'week',
|
||||
});
|
||||
}, [week]);
|
||||
|
||||
const previousWeek = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek: week.currentWeek === 1 ? 52 : week.currentWeek - 1,
|
||||
currentYear: week.currentWeek === 1 ? week.currentYear - 1 : week.currentYear,
|
||||
});
|
||||
}, [week.currentWeek, week.currentYear]);
|
||||
const setMonth = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentMonth: dayjs().month(),
|
||||
currentWeek: 0,
|
||||
currentYear: dayjs().year(),
|
||||
display: 'month',
|
||||
});
|
||||
}, [week]);
|
||||
|
||||
const next = useCallback(() => {
|
||||
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,
|
||||
display: week.display as any,
|
||||
currentMonth:
|
||||
week.display === 'week'
|
||||
? 0
|
||||
: week.currentMonth === 11
|
||||
? 0
|
||||
: week.currentMonth + 1,
|
||||
});
|
||||
}, [week.display, week.currentMonth, week.currentWeek, week.currentYear]);
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<div className="h-[20px] text-textColor flex gap-[8px] items-center select-none">
|
||||
<div onClick={previousWeek}>
|
||||
<div className="text-textColor flex gap-[8px] items-center select-none">
|
||||
<div onClick={previous}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
|
|
@ -40,8 +117,12 @@ export const Filters = () => {
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>Week {week.currentWeek}</div>
|
||||
<div onClick={nextWeek}>
|
||||
<div className="w-[80px] text-center">
|
||||
{week.display === 'week'
|
||||
? `Week ${week.currentWeek}`
|
||||
: `${dayjs().month(week.currentMonth).format('MMMM')}`}
|
||||
</div>
|
||||
<div onClick={next}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
|
|
@ -55,7 +136,25 @@ export const Filters = () => {
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>{betweenDates}</div>
|
||||
<div className="flex-1">{betweenDates}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'border border-tableBorder p-[10px]',
|
||||
week.display === 'week' && 'bg-tableBorder'
|
||||
)}
|
||||
onClick={setWeek}
|
||||
>
|
||||
Week
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'border border-tableBorder p-[10px]',
|
||||
week.display === 'month' && 'bg-tableBorder'
|
||||
)}
|
||||
onClick={setMonth}
|
||||
>
|
||||
Month
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
|||
import clsx from 'clsx';
|
||||
import { useUser } from '../layout/user.context';
|
||||
import { Menu } from '@gitroom/frontend/components/launches/menu/menu';
|
||||
import { GeneratorComponent } from '@gitroom/frontend/components/launches/generator/generator';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Integration } from '@prisma/client';
|
||||
import ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';
|
||||
import { NewCalendarComponent } from '@gitroom/frontend/components/launches/new.calendar.component';
|
||||
import { Calendar } from './calendar';
|
||||
|
||||
export const LaunchesComponent = () => {
|
||||
const fetch = useFetch();
|
||||
|
|
@ -117,7 +116,7 @@ export const LaunchesComponent = () => {
|
|||
<CalendarWeekProvider integrations={sortedIntegrations}>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="flex flex-1 relative">
|
||||
<div className="outline-none absolute w-full h-full grid grid-cols-[220px_minmax(0,1fr)] gap-[30px] overflow-hidden overflow-y-auto scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary">
|
||||
<div className="outline-none w-full h-full grid grid-cols-[220px_minmax(0,1fr)] gap-[30px] scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary">
|
||||
<div className="w-[220px] bg-third p-[16px] flex flex-col gap-[24px] min-h-[100%]">
|
||||
<h2 className="text-[20px]">Channels</h2>
|
||||
<div className="gap-[16px] flex flex-col">
|
||||
|
|
@ -213,8 +212,7 @@ export const LaunchesComponent = () => {
|
|||
</div>
|
||||
<div className="flex-1 flex flex-col gap-[14px]">
|
||||
<Filters />
|
||||
<NewCalendarComponent />
|
||||
{/*<Calendar />*/}
|
||||
<Calendar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
'use client';
|
||||
import { ChevronLeft, ChevronRight, Plus } from 'lucide-react';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { Fragment } from 'react';
|
||||
import { CalendarColumn } from '@gitroom/frontend/components/launches/calendar';
|
||||
import { DNDProvider } from '@gitroom/frontend/components/launches/helpers/dnd.provider';
|
||||
|
||||
export const days = [
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
'Sunday',
|
||||
];
|
||||
export const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
|
||||
export const NewCalendarComponent = () => {
|
||||
return (
|
||||
<DNDProvider>
|
||||
<div className="flex flex-col h-screen overflow-hidden text-textColor flex-1">
|
||||
<div className="flex-1">
|
||||
<div className="grid grid-cols-8 bg-customColor31 gap-[1px] border-customColor31 border rounded-[10px]">
|
||||
<div className="bg-customColor20 sticky top-0 z-10 bg-gray-900"></div>
|
||||
{days.map((day, index) => (
|
||||
<div
|
||||
key={day}
|
||||
className="sticky top-0 z-10 bg-customColor20 p-2 text-center"
|
||||
>
|
||||
<div>{day}</div>
|
||||
</div>
|
||||
))}
|
||||
{hours.map((hour) => (
|
||||
<Fragment key={hour}>
|
||||
<div className="p-2 pr-4 bg-secondary text-center items-center justify-center flex">
|
||||
{hour.toString().padStart(2, '0')}:00
|
||||
</div>
|
||||
{days.map((day, indexDay) => (
|
||||
<Fragment key={`${day}-${hour}`}>
|
||||
<div className="relative bg-secondary">
|
||||
<CalendarColumn
|
||||
day={indexDay}
|
||||
hour={`${hour.toString().padStart(2, '0')}:00`}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DNDProvider>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,26 +1,40 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
gitroom-postgres:
|
||||
postiz-postgres:
|
||||
image: postgres:14.5
|
||||
container_name: gitroom-postgres
|
||||
container_name: postiz-postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: gitroom-local-pwd
|
||||
POSTGRES_USER: gitroom-local
|
||||
POSTGRES_DB: gitroom-db-local
|
||||
POSTGRES_PASSWORD: postiz-local-pwd
|
||||
POSTGRES_USER: postiz-local
|
||||
POSTGRES_DB: postiz-db-local
|
||||
volumes:
|
||||
- postgres-volume:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
gitroom-redis:
|
||||
networks:
|
||||
- postiz-network
|
||||
postiz-pg-admin:
|
||||
image: dpage/pgadmin4
|
||||
container_name: postiz-pg-admin
|
||||
restart: always
|
||||
ports:
|
||||
- 8081:80
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@admin.com
|
||||
PGADMIN_DEFAULT_PASSWORD: admin
|
||||
networks:
|
||||
- postiz-network
|
||||
postiz-redis:
|
||||
image: redis:7.2
|
||||
container_name: gitroom-redis
|
||||
container_name: postiz-redis
|
||||
restart: always
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
|
||||
volumes:
|
||||
postgres-volume:
|
||||
external: false
|
||||
|
||||
networks:
|
||||
postiz-network:
|
||||
external: false
|
||||
|
|
|
|||
|
|
@ -116,10 +116,9 @@ export class CommentsRepository {
|
|||
orgId: string,
|
||||
year: number,
|
||||
week: number,
|
||||
isIsoWeek: boolean
|
||||
) {
|
||||
const dateYear = dayjs().year(year);
|
||||
const date = isIsoWeek ? dateYear.isoWeek(week) : dateYear.week(week);
|
||||
const date = dateYear.isoWeek(week);
|
||||
const startDate = date.startOf('isoWeek').subtract(2, 'days').toDate();
|
||||
const endDate = date.endOf('isoWeek').add(2, 'days').toDate();
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class CommentsService {
|
|||
);
|
||||
}
|
||||
|
||||
getAllCommentsByWeekYear(orgId: string, year: number, week: number, isIsoWeek: boolean) {
|
||||
return this._commentsRepository.getAllCommentsByWeekYear(orgId, year, week, isIsoWeek);
|
||||
getAllCommentsByWeekYear(orgId: string, year: number, week: number) {
|
||||
return this._commentsRepository.getAllCommentsByWeekYear(orgId, year, week);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ export class PostsRepository {
|
|||
|
||||
getPosts(orgId: string, query: GetPostsDto) {
|
||||
const dateYear = dayjs().year(query.year);
|
||||
const date = query.isIsoWeek === 'true' ? dateYear.isoWeek(query.week) : dateYear.week(query.week);
|
||||
const date = query.week ? dateYear.isoWeek(query.week) : dateYear.month(query.month-1);
|
||||
|
||||
const startDate = date.startOf('week').subtract(2, 'days').toDate();
|
||||
const endDate = date.endOf('week').add(2, 'days').toDate();
|
||||
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();
|
||||
|
||||
return this._post.model.post.findMany({
|
||||
where: {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsIn, IsNumber, IsString, Max, Min } from 'class-validator';
|
||||
import { IsIn, IsNumber, IsString, Max, Min, ValidateIf } 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(52)
|
||||
@Min(1)
|
||||
month: number;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Max(dayjs().add(10, 'year').year())
|
||||
@Min(2022)
|
||||
year: number;
|
||||
|
||||
@IsIn(['true', 'false'])
|
||||
@IsString()
|
||||
isIsoWeek: 'true' | 'false';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"@copilotkit/react-textarea": "1.1.0",
|
||||
"@copilotkit/react-ui": "1.1.0",
|
||||
"@copilotkit/runtime": "1.1.0",
|
||||
"@fontsource/chakra-petch": "^5.0.22",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mantine/core": "^5.10.5",
|
||||
"@mantine/dates": "^5.10.5",
|
||||
|
|
@ -6242,6 +6243,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
|
||||
"integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA=="
|
||||
},
|
||||
"node_modules/@fontsource/chakra-petch": {
|
||||
"version": "5.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/chakra-petch/-/chakra-petch-5.0.22.tgz",
|
||||
"integrity": "sha512-dYhrz0As8T7H7NGeMbcwAf84xzlxzdfcXBqcgO5lWAGezud8zrrJKGHB/9To5fNQ1ZoqDVplXy3Hu+Ye7tFbvw==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@google/generative-ai": {
|
||||
"version": "0.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.11.5.tgz",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"scripts": {
|
||||
"dev": "npx nx run-many --target=serve --projects=frontend,backend,workers --parallel=4",
|
||||
"dev:stripe": "npx concurrently \"stripe listen --forward-to localhost:3000/stripe\" \"npm run dev\"",
|
||||
"build": "npx nx run-many --target=build --projects=frontend,backend,workers",
|
||||
"build": "npx nx run-many --target=build --projects=frontend,backend,workers,cron",
|
||||
"start:prod": "node dist/apps/backend/main.js",
|
||||
"start:prod:frontend": "nx run frontend:serve:production",
|
||||
"start:prod:workers": "node dist/apps/workers/main.js",
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
"@copilotkit/react-textarea": "1.1.0",
|
||||
"@copilotkit/react-ui": "1.1.0",
|
||||
"@copilotkit/runtime": "1.1.0",
|
||||
"@fontsource/chakra-petch": "^5.0.22",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mantine/core": "^5.10.5",
|
||||
"@mantine/dates": "^5.10.5",
|
||||
|
|
|
|||
Loading…
Reference in New Issue