feat: translation

This commit is contained in:
Nevo David 2025-05-31 21:44:16 +07:00
parent 92662e1624
commit ac649424c6
420 changed files with 53000 additions and 23744 deletions

View file

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities

View file

@ -21,6 +21,7 @@ As a general rule;
## Types of Contributions
Contributions can include:
- **Code improvements:** Fixing bugs or adding new features.
- **Documentation updates:** Enhancing clarity or adding missing information.
- **Feature requests:** Suggesting new capabilities or integrations.
@ -39,17 +40,15 @@ This project follows a Fork/Feature Branch/Pull Request model. If you're not fam
```bash
git checkout -b feature/your-feature-name
```
6. **Make your changes**: Implement the changes you wish to contribute.
7. **Push your changes**: Upload your changes to your fork.
4. **Make your changes**: Implement the changes you wish to contribute.
5. **Push your changes**: Upload your changes to your fork.
```bash
git push -u origin feature/your-feature-name
```
9. **Create a pull request**: Propose your changes **to the main branch**.
6. **Create a pull request**: Propose your changes **to the main branch**.
# Need Help?
Again, do check the [developer guide](https://docs.postiz.com/developer-guide). Much of what you probably need to know is in there.
If you encounter any issues, please visit our [support page](https://docs.postiz.com/support) or check the community forums. Your contributions help make Postiz better!

View file

@ -13,7 +13,6 @@
</a>
</p>
<p align="center">
<a href="https://opensource.org/licenses/Apache-2.0">
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
@ -28,7 +27,6 @@
Postiz offers everything you need to manage your social media posts,<br />build an audience, capture leads, and grow your business.
</div>
<div class="flex" align="center">
<br />
<img alt="Instagram" src="https://postiz.com/svgs/socials/Instagram.svg" width="32">
@ -77,7 +75,7 @@
## ✨ Features
| ![Image 1](https://github.com/user-attachments/assets/a27ee220-beb7-4c7e-8c1b-2c44301f82ef) | ![Image 2](https://github.com/user-attachments/assets/eb5f5f15-ed90-47fc-811c-03ccba6fa8a2) |
|--------------------------------|--------------------------------|
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| ![Image 3](https://github.com/user-attachments/assets/d51786ee-ddd8-4ef8-8138-5192e9cfe7c3) | ![Image 4](https://github.com/user-attachments/assets/91f83c89-22f6-43d6-b7aa-d2d3378289fb) |
# Intro
@ -98,9 +96,11 @@
- Resend (email notifications)
## Quick Start
To have the project up and running, please follow the [Quick Start Guide](https://docs.postiz.com/quickstart)
## Invest in the Postiz Coin :)
DMsTbeCfX1crgAse5tver98KAMarPWeP3d6U3Gmmpump
# License
@ -112,5 +112,3 @@ This repository's source code is available under the [AGPL-3.0 license](LICENSE)
<p align="center">
<a href="https://www.g2.com/products/postiz/take_survey" target="blank"><img alt="g2" src="https://github.com/user-attachments/assets/892cb74c-0b49-4589-b2f5-fbdbf7a98f66" /></a>
</p>

View file

@ -8,14 +8,14 @@ The Postiz app is committed to ensuring the security and integrity of our users'
If you discover a security vulnerability in the Postiz app, please report it to us privately via email to one of the maintainers:
* @nevo-david
* @egelhaus ([email](mailto:gelhausenno@outlook.de))
- @nevo-david
- @egelhaus ([email](mailto:gelhausenno@outlook.de))
When reporting a security vulnerability, please provide as much detail as possible, including:
* A clear description of the vulnerability
* Steps to reproduce the vulnerability
* Any relevant code or configuration files
- A clear description of the vulnerability
- Steps to reproduce the vulnerability
- Any relevant code or configuration files
## Supported Versions
@ -31,10 +31,10 @@ We will not publicly disclose security vulnerabilities until a patch or fix is a
We take security vulnerabilities seriously and will respond promptly to reports of vulnerabilities. Our response process includes:
* Investigating the report and verifying the vulnerability.
* Developing a patch or fix for the vulnerability.
* Releasing the patch or fix as soon as possible.
* Notifying users of the vulnerability and the patch or fix.
- Investigating the report and verifying the vulnerability.
- Developing a patch or fix for the vulnerability.
- Releasing the patch or fix as soon as possible.
- Notifying users of the vulnerability and the patch or fix.
## Template Attribution

View file

@ -74,7 +74,7 @@ const authenticatedController = [
TrackService,
ShortLinkService,
Nowpayments,
McpService
McpService,
],
get exports() {
return [...this.imports, ...this.providers];

View file

@ -21,7 +21,7 @@ import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integ
export class AnalyticsController {
constructor(
private _starsService: StarsService,
private _integrationService: IntegrationService,
private _integrationService: IntegrationService
) {}
@Get('/')
async getStars(@GetOrgFromRequest() org: Organization) {

View file

@ -13,9 +13,12 @@ export class CopilotController {
constructor(private _subscriptionService: SubscriptionService) {}
@Post('/chat')
chat(@Req() req: Request, @Res() res: Response) {
if (process.env.OPENAI_API_KEY === undefined || process.env.OPENAI_API_KEY === '') {
if (
process.env.OPENAI_API_KEY === undefined ||
process.env.OPENAI_API_KEY === ''
) {
Logger.warn('OpenAI API key not set, chat functionality will not work');
return
return;
}
const copilotRuntimeHandler = copilotRuntimeNestEndpoint({

View file

@ -87,10 +87,10 @@ export class IntegrationsController {
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
return {
integrations: await Promise.all(
(await this._integrationService.getIntegrationsList(org.id)).map(
async (p) => {
const findIntegration =
this._integrationManager.getSocialIntegration(
(
await this._integrationService.getIntegrationsList(org.id)
).map(async (p) => {
const findIntegration = this._integrationManager.getSocialIntegration(
p.providerIdentifier
);
return {
@ -114,8 +114,7 @@ export class IntegrationsController {
customer: p.customer,
additionalSettings: p.additionalSettings || '[]',
};
}
)
})
),
};
}

View file

@ -1,4 +1,11 @@
import { Body, Controller, HttpException, Param, Post, Sse } from '@nestjs/common';
import {
Body,
Controller,
HttpException,
Param,
Post,
Sse,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { McpService } from '@gitroom/nestjs-libraries/mcp/mcp.service';
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';

View file

@ -26,7 +26,12 @@ export class MessagesController {
@Param('groupId') groupId: string,
@Param('page') page: string
) {
return this._messagesService.getMessages(user.id, organization.id, groupId, +page);
return this._messagesService.getMessages(
user.id,
organization.id,
groupId,
+page
);
}
@Post('/:groupId')
createMessage(
@ -35,6 +40,11 @@ export class MessagesController {
@Param('groupId') groupId: string,
@Body() message: AddMessageDto
) {
return this._messagesService.createMessage(user.id, organization.id, groupId, message);
return this._messagesService.createMessage(
user.id,
organization.id,
groupId,
message
);
}
}

View file

@ -3,7 +3,7 @@ import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.req
import { Organization, User } from '@prisma/client';
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
import {ApiTags} from "@nestjs/swagger";
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Notifications')
@Controller('/notifications')

View file

@ -8,8 +8,8 @@ import {
Sections,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
import {AddTeamMemberDto} from "@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto";
import {ApiTags} from "@nestjs/swagger";
import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Settings')
@Controller('/settings')
@ -105,22 +105,31 @@ export class SettingsController {
}
@Get('/team')
@CheckPolicies([AuthorizationActions.Create, Sections.TEAM_MEMBERS], [AuthorizationActions.Create, Sections.ADMIN])
@CheckPolicies(
[AuthorizationActions.Create, Sections.TEAM_MEMBERS],
[AuthorizationActions.Create, Sections.ADMIN]
)
async getTeam(@GetOrgFromRequest() org: Organization) {
return this._organizationService.getTeam(org.id);
}
@Post('/team')
@CheckPolicies([AuthorizationActions.Create, Sections.TEAM_MEMBERS], [AuthorizationActions.Create, Sections.ADMIN])
@CheckPolicies(
[AuthorizationActions.Create, Sections.TEAM_MEMBERS],
[AuthorizationActions.Create, Sections.ADMIN]
)
async inviteTeamMember(
@GetOrgFromRequest() org: Organization,
@Body() body: AddTeamMemberDto,
@Body() body: AddTeamMemberDto
) {
return this._organizationService.inviteTeamMember(org.id, body);
}
@Delete('/team/:id')
@CheckPolicies([AuthorizationActions.Create, Sections.TEAM_MEMBERS], [AuthorizationActions.Create, Sections.ADMIN])
@CheckPolicies(
[AuthorizationActions.Create, Sections.TEAM_MEMBERS],
[AuthorizationActions.Create, Sections.ADMIN]
)
deleteTeamMember(
@GetOrgFromRequest() org: Organization,
@Param('id') id: string

View file

@ -62,8 +62,7 @@ export class UsersController {
// @ts-ignore
totalChannels: !process.env.STRIPE_PUBLISHABLE_KEY ? 10000 : organization?.subscription?.totalChannels || pricing.FREE.channel,
// @ts-ignore
tier: organization?.subscription?.subscriptionTier ||
(!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE'),
tier: organization?.subscription?.subscriptionTier || (!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE'),
// @ts-ignore
role: organization?.users[0]?.role,
// @ts-ignore

View file

@ -11,16 +11,10 @@ import { CodesService } from '@gitroom/nestjs-libraries/services/codes.service';
import { PublicIntegrationsController } from '@gitroom/backend/public-api/routes/v1/public.integrations.controller';
import { PublicAuthMiddleware } from '@gitroom/backend/services/auth/public.auth.middleware';
const authenticatedController = [
PublicIntegrationsController
];
const authenticatedController = [PublicIntegrationsController];
@Module({
imports: [
UploadModule,
],
controllers: [
...authenticatedController,
],
imports: [UploadModule],
controllers: [...authenticatedController],
providers: [
AuthService,
StripeService,

View file

@ -1,5 +1,14 @@
import {
Body, Controller, Delete, Get, HttpException, Param, Post, Query, UploadedFile, UseInterceptors
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Query,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';

View file

@ -1,6 +1,10 @@
import {SetMetadata} from "@nestjs/common";
import {AuthorizationActions, Sections} from "@gitroom/backend/services/auth/permissions/permissions.service";
import { SetMetadata } from '@nestjs/common';
import {
AuthorizationActions,
Sections,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
export const CHECK_POLICIES_KEY = 'check_policy';
export type AbilityPolicy = [AuthorizationActions, Sections];
export const CheckPolicies = (...handlers: AbilityPolicy[]) => SetMetadata(CHECK_POLICIES_KEY, handlers);
export const CheckPolicies = (...handlers: AbilityPolicy[]) =>
SetMetadata(CHECK_POLICIES_KEY, handlers);

View file

@ -1,29 +1,37 @@
import {CanActivate, ExecutionContext, Injectable} from "@nestjs/common";
import {Reflector} from "@nestjs/core";
import {AppAbility, PermissionsService} from "@gitroom/backend/services/auth/permissions/permissions.service";
import {AbilityPolicy, CHECK_POLICIES_KEY} from "@gitroom/backend/services/auth/permissions/permissions.ability";
import {Organization} from "@prisma/client";
import {SubscriptionException} from "@gitroom/backend/services/auth/permissions/subscription.exception";
import {Request} from "express";
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import {
AppAbility,
PermissionsService,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
import {
AbilityPolicy,
CHECK_POLICIES_KEY,
} from '@gitroom/backend/services/auth/permissions/permissions.ability';
import { Organization } from '@prisma/client';
import { SubscriptionException } from '@gitroom/backend/services/auth/permissions/subscription.exception';
import { Request } from 'express';
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private _reflector: Reflector,
private _authorizationService: PermissionsService,
private _authorizationService: PermissionsService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
if (request.path.indexOf('/auth') > -1 || request.path.indexOf('/stripe') > -1) {
if (
request.path.indexOf('/auth') > -1 ||
request.path.indexOf('/stripe') > -1
) {
return true;
}
const policyHandlers =
this._reflector.get<AbilityPolicy[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
context.getHandler()
) || [];
if (!policyHandlers || !policyHandlers.length) {
@ -37,14 +45,14 @@ export class PoliciesGuard implements CanActivate {
// @ts-ignore
const ability = await this._authorizationService.check(org.id, org.createdAt, org.users[0].role, policyHandlers);
const item = policyHandlers.find((handler) =>
!this.execPolicyHandler(handler, ability),
const item = policyHandlers.find(
(handler) => !this.execPolicyHandler(handler, ability)
);
if (item) {
throw new SubscriptionException({
section: item[1],
action: item[0]
action: item[0],
});
}

View file

@ -62,7 +62,7 @@ describe('PermissionsService', () => {
image_generation_count: 50,
public_api: true,
webhooks: 10,
autoPost: true // Added the missing property
autoPost: true, // Added the missing property
};
const baseIntegration = {
@ -101,7 +101,6 @@ describe('PermissionsService', () => {
describe('check()', () => {
describe('Verification Bypass (64)', () => {
it('Bypass for Empty List', async () => {
// Setup: STRIPE_PUBLISHABLE_KEY exists and requestedPermission is empty
@ -114,16 +113,18 @@ describe('PermissionsService', () => {
);
// Verification: not requested, no authorization
expect(result.cannot(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(
result.cannot(AuthorizationActions.Create, Sections.CHANNEL)
).toBe(true);
});
it('Bypass for Missing Stripe', async () => {
// Setup: STRIPE_PUBLISHABLE_KEY does not exist
process.env.STRIPE_PUBLISHABLE_KEY = undefined;
// Necessary mock to avoid undefined filter error
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false }
]);
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([{ ...baseIntegration, refreshNeeded: false }]);
// Mock of getPackageOptions (even if not used due to bypass)
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
subscription: baseSubscription,
@ -132,7 +133,7 @@ describe('PermissionsService', () => {
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Read, Sections.CHANNEL],
[AuthorizationActions.Create, Sections.AI]
[AuthorizationActions.Create, Sections.AI],
];
// Execution: call the check method
const result = await service.check(
@ -142,7 +143,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow all requested actions due to the absence of the Stripe key
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(
true
);
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(true);
});
@ -150,7 +153,7 @@ describe('PermissionsService', () => {
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Read, Sections.CHANNEL],
[AuthorizationActions.Create, Sections.AI]
[AuthorizationActions.Create, Sections.AI],
];
// Mock of getPackageOptions to force a scenario without permissions
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
@ -158,13 +161,13 @@ describe('PermissionsService', () => {
options: {
...baseOptions,
channel: 0,
ai: false
ai: false,
},
});
// Mock of getIntegrationsList for the channel scenario
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false }
]);
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([{ ...baseIntegration, refreshNeeded: false }]);
// Execution: call the check method
const result = await service.check(
'mock-org-id',
@ -173,8 +176,12 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested actions as there is no bypass
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(false);
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(false);
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(
false
);
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(
false
);
});
});
@ -187,7 +194,9 @@ describe('PermissionsService', () => {
});
// Mock of getIntegrationsList to set existing channels
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
@ -195,7 +204,7 @@ describe('PermissionsService', () => {
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.CHANNEL]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
@ -206,7 +215,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
true
);
});
it('Channel With Option Limit', async () => {
@ -216,14 +227,16 @@ describe('PermissionsService', () => {
options: { ...baseOptions, channel: 10 },
});
// Mock of getIntegrationsList to set existing channels
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
]);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.CHANNEL]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
const result = await service.check(
@ -233,7 +246,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
true
);
});
it('Channel With Subscription Limit', async () => {
@ -243,7 +258,9 @@ describe('PermissionsService', () => {
options: { ...baseOptions, channel: 3 },
});
// Mock of getIntegrationsList to set existing channels
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
@ -251,7 +268,7 @@ describe('PermissionsService', () => {
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.CHANNEL]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
const result = await service.check(
@ -261,7 +278,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
true
);
});
it('Channel Without Available Limits', async () => {
// Mock of getPackageOptions to set channel limits
@ -270,14 +289,16 @@ describe('PermissionsService', () => {
options: { ...baseOptions, channel: 3 },
});
// Mock of getIntegrationsList to set existing channels
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
]);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.CHANNEL]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
const result = await service.check(
@ -287,7 +308,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(false);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
false
);
});
it('Section Different from Channel', async () => {
// Mock of getPackageOptions to set channel limits
@ -296,14 +319,16 @@ describe('PermissionsService', () => {
options: { ...baseOptions, channel: 10 },
});
// Mock of getIntegrationsList to set existing channels
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
]);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.AI] // Requesting permission for AI instead of CHANNEL
[AuthorizationActions.Create, Sections.AI], // Requesting permission for AI instead of CHANNEL
];
// Execution: call the check method
const result = await service.check(
@ -313,7 +338,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action in CHANNEL
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(false);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
false
);
});
});
describe('Monthly Posts Permission (97/110)', () => {
@ -324,7 +351,9 @@ describe('PermissionsService', () => {
options: { ...baseOptions, posts_per_month: 100 },
});
// Mock of getSubscription
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
jest
.spyOn(mockSubscriptionService, 'getSubscription')
.mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
@ -332,7 +361,7 @@ describe('PermissionsService', () => {
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(50);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH]
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH],
];
// Execution: call the check method
const result = await service.check(
@ -342,7 +371,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(true);
expect(
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
).toBe(true);
});
it('Posts Exceed Limit', async () => {
// Mock of getPackageOptions to set post limits
@ -351,16 +382,20 @@ describe('PermissionsService', () => {
options: { ...baseOptions, posts_per_month: 100 },
});
// Mock of getSubscription
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
jest
.spyOn(mockSubscriptionService, 'getSubscription')
.mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
// Mock of countPostsFromDay to return quantity above the limit
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(150);
jest
.spyOn(mockPostsService, 'countPostsFromDay')
.mockResolvedValue(150);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH]
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH],
];
// Execution: call the check method
const result = await service.check(
@ -370,7 +405,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(false);
expect(
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
).toBe(false);
});
it('Section Different with Posts Within Limit', async () => {
// Mock of getPackageOptions to set post limits
@ -379,7 +416,9 @@ describe('PermissionsService', () => {
options: { ...baseOptions, posts_per_month: 100 },
});
// Mock of getSubscription
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
jest
.spyOn(mockSubscriptionService, 'getSubscription')
.mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
@ -387,7 +426,7 @@ describe('PermissionsService', () => {
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(50);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.AI] // Requesting permission for AI instead of POSTS_PER_MONTH
[AuthorizationActions.Create, Sections.AI], // Requesting permission for AI instead of POSTS_PER_MONTH
];
// Execution: call the check method
const result = await service.check(
@ -397,7 +436,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action in POSTS_PER_MONTH
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(false);
expect(
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
).toBe(false);
});
});
});

View file

@ -34,7 +34,7 @@ export class PermissionsService {
private _subscriptionService: SubscriptionService,
private _postsService: PostsService,
private _integrationService: IntegrationService,
private _webhooksService: WebhooksService,
private _webhooksService: WebhooksService
) {}
async getPackageOptions(orgId: string) {
const subscription =
@ -85,7 +85,7 @@ export class PermissionsService {
if (section === Sections.CHANNEL) {
const totalChannels = (
await this._integrationService.getIntegrationsList(orgId)
).filter(f => !f.refreshNeeded).length;
).filter((f) => !f.refreshNeeded).length;
if (
(options.channel && options.channel > totalChannels) ||

View file

@ -1,5 +1,7 @@
export interface ProvidersInterface {
generateLink(query?: any): Promise<string> | string;
getToken(code: string): Promise<string>;
getUser(providerToken: string): Promise<{email: string, id: string}> | false;
getUser(
providerToken: string
): Promise<{ email: string; id: string }> | false;
}

View file

@ -29,7 +29,6 @@ export class FarcasterProvider implements ProvidersInterface {
};
}
// const { client, oauth2 } = clientAndYoutube();
// client.setCredentials({ access_token: providerToken });
// const user = oauth2(client);

View file

@ -21,7 +21,9 @@ export class PublicAuthMiddleware implements NestMiddleware {
}
if (!!process.env.STRIPE_SECRET_KEY && !org.subscription) {
res.status(HttpStatus.UNAUTHORIZED).json({ msg: 'No subscription found' });
res
.status(HttpStatus.UNAUTHORIZED)
.json({ msg: 'No subscription found' });
return;
}

View file

@ -9,12 +9,7 @@ import { AgentRun } from './tasks/agent.run';
import { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module';
@Module({
imports: [
ExternalCommandModule,
DatabaseModule,
BullMqModule,
AgentModule,
],
imports: [ExternalCommandModule, DatabaseModule, BullMqModule, AgentModule],
controllers: [],
providers: [CheckStars, RefreshTokens, ConfigurationTask, AgentRun],
get exports() {

View file

@ -1,19 +1,16 @@
import { NestFactory } from '@nestjs/core';
import {CommandModule} from "./command.module";
import {CommandService} from "nestjs-command";
import { CommandModule } from './command.module';
import { CommandService } from 'nestjs-command';
async function bootstrap() {
// some comment again
const app = await NestFactory.createApplicationContext(CommandModule, {
logger: ['error']
logger: ['error'],
});
try {
await app
.select(CommandModule)
.get(CommandService)
.exec();
await app.close()
await app.select(CommandModule).get(CommandService).exec();
await app.close();
} catch (error) {
console.error(error);
await app.close();

View file

@ -10,21 +10,23 @@ export class ConfigurationTask {
})
create() {
const checker = new ConfigurationChecker();
checker.readEnvFromProcess()
checker.check()
checker.readEnvFromProcess();
checker.check();
if (checker.hasIssues()) {
for (const issue of checker.getIssues()) {
console.warn("Configuration issue:", issue)
console.warn('Configuration issue:', issue);
}
console.error("Configuration check complete, issues: ", checker.getIssuesCount())
console.error(
'Configuration check complete, issues: ',
checker.getIssuesCount()
);
} else {
console.log("Configuration check complete, no issues found.")
console.log('Configuration check complete, no issues found.');
}
console.log("Press Ctrl+C to exit.");
return true
console.log('Press Ctrl+C to exit.');
return true;
}
}

View file

@ -1,5 +1,5 @@
import { NestFactory } from '@nestjs/core';
import {CronModule} from "./cron.module";
import { CronModule } from './cron.module';
async function bootstrap() {
// some comment again

View file

@ -4,9 +4,7 @@ import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/cl
@Injectable()
export class SyncTrending {
constructor(
private _workerServiceProducer: BullMqClient
) {}
constructor(private _workerServiceProducer: BullMqClient) {}
@Cron('0 * * * *')
async syncTrending() {
this._workerServiceProducer.emit('sync_trending', {}).subscribe();

View file

@ -4,38 +4,45 @@ import type { PluginOption } from 'vite';
// plugin to remove dev icons from prod build
export function stripDevIcons(isDev: boolean) {
if (isDev) return null
if (isDev) return null;
return {
name: 'strip-dev-icons',
resolveId(source: string) {
return source === 'virtual-module' ? source : null
return source === 'virtual-module' ? source : null;
},
renderStart(outputOptions: any, inputOptions: any) {
const outDir = outputOptions.dir
fs.rm(resolve(outDir, 'dev-icon-32.png'), () => console.log(`Deleted dev-icon-32.png from prod build`))
fs.rm(resolve(outDir, 'dev-icon-128.png'), () => console.log(`Deleted dev-icon-128.png from prod build`))
}
}
const outDir = outputOptions.dir;
fs.rm(resolve(outDir, 'dev-icon-32.png'), () =>
console.log(`Deleted dev-icon-32.png from prod build`)
);
fs.rm(resolve(outDir, 'dev-icon-128.png'), () =>
console.log(`Deleted dev-icon-128.png from prod build`)
);
},
};
}
// plugin to support i18n
export function crxI18n (options: { localize: boolean, src: string }): PluginOption {
if (!options.localize) return null
export function crxI18n(options: {
localize: boolean;
src: string;
}): PluginOption {
if (!options.localize) return null;
const getJsonFiles = (dir: string): Array<string> => {
const files = fs.readdirSync(dir, {recursive: true}) as string[]
return files.filter(file => !!file && file.endsWith('.json'))
}
const entry = resolve(__dirname, options.src)
const localeFiles = getJsonFiles(entry)
const files = localeFiles.map(file => {
const files = fs.readdirSync(dir, { recursive: true }) as string[];
return files.filter((file) => !!file && file.endsWith('.json'));
};
const entry = resolve(__dirname, options.src);
const localeFiles = getJsonFiles(entry);
const files = localeFiles.map((file) => {
return {
id: '',
fileName: file,
source: fs.readFileSync(resolve(entry, file))
}
})
source: fs.readFileSync(resolve(entry, file)),
};
});
return {
name: 'crx-i18n',
enforce: 'pre',
@ -46,11 +53,11 @@ export function crxI18n (options: { localize: boolean, src: string }): PluginOpt
const refId = this.emitFile({
type: 'asset',
source: file.source,
fileName: '_locales/'+file.fileName
})
file.id = refId
})
}
}
}
fileName: '_locales/' + file.fileName,
});
file.id = refId;
});
},
},
};
}

View file

@ -8,11 +8,7 @@
},
"web_accessible_resources": [
{
"resources": [
"contentStyle.css",
"dev-icon-128.png",
"dev-icon-32.png"
],
"resources": ["contentStyle.css", "dev-icon-128.png", "dev-icon-32.png"],
"matches": []
}
]

View file

@ -11,8 +11,6 @@
"manifest.dev.json"
],
"ext": "tsx,css,html,ts,json",
"ignore": [
"src/**/*.spec.ts"
],
"ignore": ["src/**/*.spec.ts"],
"exec": "vite build --config vite.config.chrome.ts --mode development"
}

View file

@ -11,8 +11,6 @@
"manifest.dev.json"
],
"ext": "tsx,css,html,ts,json",
"ignore": [
"src/**/*.spec.ts"
],
"ignore": ["src/**/*.spec.ts"],
"exec": "vite build --config vite.config.firefox.ts --mode development"
}

View file

@ -1,32 +1,27 @@
import { fetchRequestUtil } from "@gitroom/extension/utils/request.util";
import { fetchRequestUtil } from '@gitroom/extension/utils/request.util';
const isDevelopment = process.env.NODE_ENV === "development";
const isDevelopment = process.env.NODE_ENV === 'development';
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.action === "makeHttpRequest") {
if (request.action === 'makeHttpRequest') {
fetchRequestUtil(request).then((response) => {
sendResponse(response);
});
}
if (request.action === "loadStorage") {
chrome.storage.local.get([request.key],
function (storage) {
if (request.action === 'loadStorage') {
chrome.storage.local.get([request.key], function (storage) {
sendResponse(storage[request.key]);
},
);
});
}
if (request.action === "saveStorage") {
chrome.storage.local.set(
{ [request.key]: request.value },
function () {
if (request.action === 'saveStorage') {
chrome.storage.local.set({ [request.key]: request.value }, function () {
sendResponse({ success: true });
}
);
});
}
if (request.action === "loadCookie") {
if (request.action === 'loadCookie') {
chrome.cookies.get(
{
url: import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL,
@ -34,7 +29,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
},
function (cookies) {
sendResponse(cookies?.value);
},
}
);
}

View file

@ -1,11 +1,11 @@
import { createRoot } from "react-dom/client";
import "./style.css";
import { MainContent } from "@gitroom/extension/pages/content/main.content";
const div = document.createElement("div");
div.id = "__root";
import { createRoot } from 'react-dom/client';
import './style.css';
import { MainContent } from '@gitroom/extension/pages/content/main.content';
const div = document.createElement('div');
div.id = '__root';
document.body.appendChild(div);
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Content root element");
const root = createRoot(rootContainer);
root.render(<MainContent />);

View file

@ -157,7 +157,12 @@ export const MainContentInner: FC = (props) => {
actionType={actionEl.actionType}
provider={provider}
wrap={true}
selector={stringToABC(provider.element.split(',').map(z => z.trim()).find(p => actionEl.element.matches(p)) || '')}
selector={stringToABC(
provider.element
.split(',')
.map((z) => z.trim())
.find((p) => actionEl.element.matches(p)) || ''
)}
/>,
actionEl.element
)}

View file

@ -1,10 +1,10 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import '@gitroom/extension/pages/options/index.css';
import Options from "@gitroom/extension/pages/options/Options";
import Options from '@gitroom/extension/pages/options/Options';
function init() {
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Options root element");
const root = createRoot(rootContainer);
root.render(<Options />);

View file

@ -5,7 +5,7 @@ import '@pages/panel/index.css';
import '@assets/styles/tailwind.css';
function init() {
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Panel root element");
const root = createRoot(rootContainer);
root.render(<Panel />);

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import { ProviderList } from "@gitroom/extension/providers/provider.list";
import { fetchCookie } from "@gitroom/extension/utils/load.cookie";
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { ProviderList } from '@gitroom/extension/providers/provider.list';
import { fetchCookie } from '@gitroom/extension/utils/load.cookie';
export const PopupContainerContainer: FC = () => {
const [url, setUrl] = useState<string | null>(null);
@ -11,7 +11,9 @@ export const PopupContainerContainer: FC = () => {
}, []);
if (!url) {
return <div className="text-4xl">This website is not supported by Postiz</div>;
return (
<div className="text-4xl">This website is not supported by Postiz</div>
);
}
return <PopupContainer url={url} />;
@ -54,7 +56,9 @@ export const PopupContainer: FC<{ url: string }> = (props) => {
}
if (!provider) {
return <div className="text-4xl">This website is not supported by Postiz</div>;
return (
<div className="text-4xl">This website is not supported by Postiz</div>
);
}
if (!isLoggedIn) {

View file

@ -2,10 +2,10 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import '@gitroom/extension/assets/styles/tailwind.css';
import Popup from "@gitroom/extension/pages/popup/Popup";
import Popup from '@gitroom/extension/pages/popup/Popup';
function init() {
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Popup root element");
const root = createRoot(rootContainer);
root.render(<Popup />);

View file

@ -1,12 +1,12 @@
import { ProviderInterface } from "@gitroom/extension/providers/provider.interface";
import { ProviderInterface } from '@gitroom/extension/providers/provider.interface';
export class LinkedinProvider implements ProviderInterface {
identifier = "linkedin";
baseUrl = "https://www.linkedin.com";
identifier = 'linkedin';
baseUrl = 'https://www.linkedin.com';
element = `.share-box-feed-entry__closed-share-box`;
attachTo = `[role="main"]`;
style = "light" as "light";
style = 'light' as 'light';
findIdentifier = (element: HTMLElement) => {
return element.closest('[data-urn]').getAttribute("data-urn");
return element.closest('[data-urn]').getAttribute('data-urn');
};
}

View file

@ -5,7 +5,7 @@ export class XProvider implements ProviderInterface {
baseUrl = 'https://x.com';
element = `[data-testid="primaryColumn"]:has([data-testid="toolBar"]) [data-testid="tweetTextarea_0_label"], [data-testid="SideNav_NewTweet_Button"]`;
attachTo = `#react-root`;
style = "dark" as "dark";
style = 'dark' as 'dark';
findIdentifier = (element: HTMLElement) => {
return (
Array.from(

View file

@ -1,13 +1,13 @@
export const fetchCookie = (cookieName: string) => {
return chrome.runtime.sendMessage({
action: "loadCookie",
action: 'loadCookie',
cookieName,
});
};
export const getCookie = async (
cookies: chrome.cookies.Cookie[],
cookie: string,
cookie: string
) => {
// return cookies.find((c) => c.name === cookie).value;
};

View file

@ -1,6 +1,6 @@
export const fetchStorage = (key: string) => {
return chrome.runtime.sendMessage({
action: "loadStorage",
action: 'loadStorage',
key,
});
};

View file

@ -1,12 +1,12 @@
const isDev = process.env.NODE_ENV === "development";
const isDev = process.env.NODE_ENV === 'development';
export const sendRequest = (
auth: string,
url: string,
method: "GET" | "POST",
body?: string,
method: 'GET' | 'POST',
body?: string
) => {
return chrome.runtime.sendMessage({
action: "makeHttpRequest",
action: 'makeHttpRequest',
url,
method,
body,
@ -17,16 +17,17 @@ export const sendRequest = (
export const fetchRequestUtil = async (request: any) => {
return (
await fetch(
(import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL) + request.url,
(import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL) +
request.url,
{
method: request.method || "GET",
method: request.method || 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
Authorization: request.auth,
// Add any auth headers here if needed
},
...(request.body ? { body: request.body } : {}),
},
}
)
).json();
};

View file

@ -1,6 +1,6 @@
export const saveStorage = (key: string, value: any) => {
return chrome.runtime.sendMessage({
action: "saveStorage",
action: 'saveStorage',
key,
value,
});

View file

@ -15,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"noEmit": true,
"jsx": "react-jsx",
"jsx": "react-jsx"
},
"include": [
"src",
@ -23,5 +23,5 @@
"vite.config.base.ts",
"vite.config.chrome.ts",
"vite.config.firefox.ts"
],
]
}

View file

@ -1,16 +1,16 @@
import react from "@vitejs/plugin-react";
import { resolve } from "path";
import { ManifestV3Export } from "@crxjs/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig, BuildOptions } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { stripDevIcons, crxI18n } from "./custom-vite-plugins";
import manifest from "./manifest.json";
import devManifest from "./manifest.dev.json";
import pkg from "./package.json";
import { ProviderList } from "./src/providers/provider.list";
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { ManifestV3Export } from '@crxjs/vite-plugin';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig, BuildOptions } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { stripDevIcons, crxI18n } from './custom-vite-plugins';
import manifest from './manifest.json';
import devManifest from './manifest.dev.json';
import pkg from './package.json';
import { ProviderList } from './src/providers/provider.list';
const isDev = process.env.NODE_ENV === "development";
const isDev = process.env.NODE_ENV === 'development';
// set this flag to true, if you want localization support
const localize = false;
@ -20,17 +20,15 @@ const { matches, ...rest } = manifest?.content_scripts?.[0] || {};
export const baseManifest = {
...manifest,
host_permissions: [
...ProviderList.map((p) => p.baseUrl + "/"),
...ProviderList.map((p) => p.baseUrl + '/'),
import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*',
],
permissions: [...(manifest.permissions || [])],
content_scripts: [
{
matches: ProviderList.reduce(
(all, p) => [...all, p.baseUrl + "/*"],
[
import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*',
],
(all, p) => [...all, p.baseUrl + '/*'],
[import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*']
),
...rest,
},
@ -39,9 +37,9 @@ export const baseManifest = {
...merge,
...(localize
? {
name: "__MSG_extName__",
description: "__MSG_extDescription__",
default_locale: "en",
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
}
: {}),
} as ManifestV3Export;
@ -52,13 +50,13 @@ export const baseBuildOptions: BuildOptions = {
};
export default defineConfig({
envPrefix: ["NEXT_PUBLIC_", "FRONTEND_URL"],
envPrefix: ['NEXT_PUBLIC_', 'FRONTEND_URL'],
plugins: [
tailwindcss(),
tsconfigPaths(),
react(),
stripDevIcons(isDev),
crxI18n({ localize, src: "./src/locales" }),
crxI18n({ localize, src: './src/locales' }),
],
publicDir: resolve(__dirname, "public"),
publicDir: resolve(__dirname, 'public'),
});

View file

@ -1,11 +1,11 @@
import { resolve } from "path";
import { mergeConfig, defineConfig } from "vite";
import { crx, ManifestV3Export } from "@crxjs/vite-plugin";
import baseConfig, { baseManifest, baseBuildOptions } from "./vite.config.base";
import hotReloadExtension from "hot-reload-extension-vite";
import { resolve } from 'path';
import { mergeConfig, defineConfig } from 'vite';
import { crx, ManifestV3Export } from '@crxjs/vite-plugin';
import baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base';
import hotReloadExtension from 'hot-reload-extension-vite';
const outDir = resolve(__dirname, "dist");
const isDev = process.env.NODE_ENV === "development";
const outDir = resolve(__dirname, 'dist');
const isDev = process.env.NODE_ENV === 'development';
export default mergeConfig(
baseConfig,
@ -15,11 +15,11 @@ export default mergeConfig(
manifest: {
...baseManifest,
background: {
service_worker: "src/pages/background/index.ts",
type: "module",
service_worker: 'src/pages/background/index.ts',
type: 'module',
},
} as ManifestV3Export,
browser: "chrome",
browser: 'chrome',
contentScripts: {
injectCss: true,
},
@ -28,7 +28,7 @@ export default mergeConfig(
? [
hotReloadExtension({
log: true,
backgroundPath: "src/pages/background/index.ts",
backgroundPath: 'src/pages/background/index.ts',
}),
]
: []),
@ -40,13 +40,13 @@ export default mergeConfig(
? {
rollupOptions: {
output: {
entryFileNames: "assets/[name].js",
chunkFileNames: "assets/[name].js",
assetFileNames: "assets/[name][extname]",
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name][extname]',
},
},
}
: {}),
},
}),
})
);

View file

@ -1,7 +1,7 @@
import { resolve } from 'path';
import { mergeConfig, defineConfig } from 'vite';
import { crx, ManifestV3Export } from '@crxjs/vite-plugin';
import baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base'
import baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base';
const outDir = resolve(__dirname, 'dist_firefox');
@ -13,19 +13,19 @@ export default mergeConfig(
manifest: {
...baseManifest,
background: {
scripts: [ 'src/pages/background/index.ts' ]
scripts: ['src/pages/background/index.ts'],
},
} as ManifestV3Export,
browser: 'firefox',
contentScripts: {
injectCss: true,
}
})
},
}),
],
build: {
...baseBuildOptions,
outDir
outDir,
},
publicDir: resolve(__dirname, 'public'),
})
)
);

View file

@ -40,5 +40,4 @@ const nextConfig = {
];
},
};
export default nextConfig;

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,5 @@
import { ReactNode } from 'react';
import { PreviewWrapper } from '@gitroom/frontend/components/preview/preview.wrapper';
export default async function AppLayout({ children }: { children: ReactNode }) {
return (
<div className="bg-[#000000] min-h-screen">

View file

@ -1,7 +1,5 @@
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
import Image from 'next/image';
@ -11,30 +9,32 @@ import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { VideoOrImage } from '@gitroom/react/helpers/video.or.image';
import { CopyClient } from '@gitroom/frontend/components/preview/copy.client';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
dayjs.extend(utc);
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Preview`,
description: '',
};
export default async function Auth({
params: { id },
searchParams,
}: {
params: { id: string };
searchParams?: { share?: string };
params: {
id: string;
};
searchParams?: {
share?: string;
};
}) {
const post = await (await internalFetch(`/public/posts/${id}`)).json();
const t = await getT();
if (!post.length) {
return (
<div className="text-white fixed left-0 top-0 w-full h-full flex justify-center items-center text-[20px]">
Post not found
{t('post_not_found', 'Post not found')}
</div>
);
}
return (
<div>
<div className="mx-auto w-full max-w-[1346px] py-3 text-white">
@ -91,7 +91,7 @@ export default async function Auth({
</div>
)}
<div className="flex-1">
Publication Date:{' '}
{t('publication_date', 'Publication Date:')}
{dayjs
.utc(post[0].createdAt)
.local()
@ -140,7 +140,9 @@ export default async function Auth({
<div className="flex flex-col gap-[20px]">
<div
className="text-sm whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: p.content }}
dangerouslySetInnerHTML={{
__html: p.content,
}}
/>
<div className="flex w-full gap-[10px]">
{JSON.parse(p?.image || '[]').map((p: any) => (
@ -148,7 +150,11 @@ export default async function Auth({
key={p.name}
className="flex-1 rounded-[10px] max-h-[500px] overflow-hidden"
>
<VideoOrImage isContain={true} src={p.path} autoplay={true} />
<VideoOrImage
isContain={true}
src={p.path}
autoplay={true}
/>
</div>
))}
</div>

View file

@ -1,15 +1,12 @@
export const dynamic = 'force-dynamic';
import { AnalyticsComponent } from '@gitroom/frontend/components/analytics/analytics.component';
import { Metadata } from 'next';
import { PlatformAnalytics } from '@gitroom/frontend/components/platform-analytics/platform.analytics';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Analytics`,
description: '',
};
export default async function Index() {
return (
<>

View file

@ -1,15 +1,11 @@
import { LifetimeDeal } from '@gitroom/frontend/components/billing/lifetime.deal';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Lifetime deal`,
description: '',
};
export default async function Page() {
return <LifetimeDeal />;
}

View file

@ -1,15 +1,11 @@
export const dynamic = 'force-dynamic';
import { BillingComponent } from '@gitroom/frontend/components/billing/billing.component';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Billing`,
description: '',
};
export default async function Page() {
return <BillingComponent />;
}

View file

@ -1,12 +1,17 @@
import {Metadata} from "next";
import { Metadata } from 'next';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const metadata: Metadata = {
title: 'Error',
description: '',
}
};
export default async function Page() {
const t = await getT();
return (
<div>We are experiencing some difficulty, try to refresh the page</div>
)
<div>
{t(
'we_are_experiencing_some_difficulty_try_to_refresh_the_page',
'We are experiencing some difficulty, try to refresh the page'
)}
</div>
);
}

View file

@ -1,18 +1,19 @@
import { HttpStatusCode } from 'axios';
export const dynamic = 'force-dynamic';
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
import { redirect } from 'next/navigation';
import { Redirect } from '@gitroom/frontend/components/layout/redirect';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export default async function Page({
params: { provider },
searchParams,
}: {
params: { provider: string };
params: {
provider: string;
};
searchParams: any;
}) {
const t = await getT();
if (provider === 'x') {
searchParams = {
...searchParams,
@ -21,25 +22,21 @@ export default async function Page({
refresh: searchParams.refresh || '',
};
}
if (provider === 'vk') {
searchParams = {
...searchParams,
state: searchParams.state || '',
code: searchParams.code + '&&&&' + searchParams.device_id
code: searchParams.code + '&&&&' + searchParams.device_id,
};
}
const data = await internalFetch(`/integrations/social/${provider}/connect`, {
method: 'POST',
body: JSON.stringify(searchParams),
});
if (data.status === HttpStatusCode.NotAcceptable) {
const { msg } = await data.json();
return redirect(`/launches?msg=${msg}`);
}
if (
data.status !== HttpStatusCode.Ok &&
data.status !== HttpStatusCode.Created
@ -47,20 +44,17 @@ export default async function Page({
return (
<>
<div className="mt-[50px] text-[50px]">
Could not add provider.
{t('could_not_add_provider', 'Could not add provider.')}
<br />
You are being redirected back
{t('you_are_being_redirected_back', 'You are being redirected back')}
</div>
<Redirect url="/launches" delay={3000} />
</>
);
}
const { inBetweenSteps, id } = await data.json();
if (inBetweenSteps && !searchParams.refresh) {
return redirect(`/launches?added=${provider}&continue=${id}`);
}
return redirect(`/launches?added=${provider}&msg=Channel Updated`);
}

View file

@ -1,12 +1,12 @@
import { IntegrationRedirectComponent } from '@gitroom/frontend/components/launches/integration.redirect.component';
export const dynamic = 'force-dynamic';
export default async function Page({
params: { provider },
searchParams,
}: {
params: { provider: string };
params: {
provider: string;
};
searchParams: any;
}) {
return <IntegrationRedirectComponent />;

View file

@ -1,13 +1,16 @@
import { ReactNode } from 'react';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export default async function IntegrationLayout({
children,
}: {
children: ReactNode;
}) {
const t = await getT();
return (
<div className="text-6xl text-center mt-[50px]">
Adding channel, Redirecting You{children}
{t('adding_channel_redirecting_you', 'Adding channel, Redirecting You')}
{children}
</div>
);
}

View file

@ -1,17 +1,11 @@
export const dynamic = 'force-dynamic';
import {LaunchesComponent} from "@gitroom/frontend/components/launches/launches.component";
import {Metadata} from "next";
import { LaunchesComponent } from '@gitroom/frontend/components/launches/launches.component';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz Calendar' : 'Gitroom Launches'}`,
description: '',
}
};
export default async function Index() {
return (
<LaunchesComponent />
);
return <LaunchesComponent />;
}

View file

@ -1,9 +1,8 @@
import {LayoutSettings} from "@gitroom/frontend/components/layout/layout.settings";
export default async function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutSettings>
{children}
</LayoutSettings>
);
import { LayoutSettings } from '@gitroom/frontend/components/layout/layout.settings';
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
return <LayoutSettings>{children}</LayoutSettings>;
}

View file

@ -1,9 +1,7 @@
import { Buyer } from '@gitroom/frontend/components/marketplace/buyer';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`,
description: '',
@ -11,7 +9,9 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
return <Buyer />;
}

View file

@ -1,11 +1,10 @@
import { BuyerSeller } from '@gitroom/frontend/components/marketplace/buyer.seller';
import { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<>
<BuyerSeller />
{children}
</>
)
);
}

View file

@ -1,10 +1,8 @@
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`,
description: '',
@ -12,8 +10,12 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
const currentCookie = cookies()?.get('marketplace')?.value;
return redirect(currentCookie === 'buyer' ? '/marketplace/buyer' : '/marketplace/seller');
return redirect(
currentCookie === 'buyer' ? '/marketplace/buyer' : '/marketplace/seller'
);
}

View file

@ -1,9 +1,7 @@
import { Seller } from '@gitroom/frontend/components/marketplace/seller';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`,
description: '',
@ -11,7 +9,9 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
return <Seller />;
}

View file

@ -1,15 +1,11 @@
import { Messages } from '@gitroom/frontend/components/messages/messages';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Messages`,
description: '',
};
export default async function Index() {
return <Messages />;
}

View file

@ -1,9 +1,10 @@
import { Layout } from '@gitroom/frontend/components/messages/layout';
export const dynamic = 'force-dynamic';
import { ReactNode } from 'react';
export default async function LayoutWrapper({children}: {children: ReactNode}) {
return (
<Layout renderChildren={children} />
);
export default async function LayoutWrapper({
children,
}: {
children: ReactNode;
}) {
return <Layout renderChildren={children} />;
}

View file

@ -1,20 +1,23 @@
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const dynamic = 'force-dynamic';
import {Metadata} from "next";
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Messages`,
description: '',
}
};
export default async function Index() {
const t = await getT();
return (
<div className="bg-customColor3 h-[951px] flex flex-col rounded-[4px] border border-customColor6">
<div className="bg-customColor8 h-[64px]" />
<div className="flex-1 flex justify-center items-center text-[20px]">
Select a conversation and chat away.
{t(
'select_a_conversation_and_chat_away',
'Select a conversation and chat away.'
)}
</div>
</div>
);

View file

@ -1,15 +1,11 @@
import { Plugs } from '@gitroom/frontend/components/plugs/plugs';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Plugs`,
description: '',
};
export default async function Index() {
return (
<>

View file

@ -1,10 +1,7 @@
import { SettingsPopup } from '@gitroom/frontend/components/layout/settings.component';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Settings`,
description: '',
@ -12,7 +9,9 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
return <SettingsPopup />;
}

View file

@ -2,18 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
import { createReadStream, statSync } from 'fs';
// @ts-ignore
import mime from 'mime';
async function* nodeStreamToIterator(stream: any) {
for await (const chunk of stream) {
yield chunk;
}
}
function iteratorToStream(iterator: any) {
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();
if (done) {
controller.close();
} else {
@ -22,25 +19,29 @@ function iteratorToStream(iterator: any) {
},
});
}
export const GET = (
request: NextRequest,
context: { params: { path: string[] } }
context: {
params: {
path: string[];
};
}
) => {
const filePath =
process.env.UPLOAD_DIRECTORY + '/' + context.params.path.join('/');
const response = createReadStream(filePath);
const fileStats = statSync(filePath);
const contentType = mime.getType(filePath) || 'application/octet-stream';
const iterator = nodeStreamToIterator(response);
const webStream = iteratorToStream(iterator);
return new Response(webStream, {
headers: {
'Content-Type': contentType, // Set the appropriate content-type header
'Content-Length': fileStats.size.toString(), // Set the content-length header
'Last-Modified': fileStats.mtime.toUTCString(), // Set the last-modified header
'Content-Type': contentType,
// Set the appropriate content-type header
'Content-Length': fileStats.size.toString(),
// Set the content-length header
'Last-Modified': fileStats.mtime.toUTCString(),
// Set the last-modified header
'Cache-Control': 'public, max-age=31536000, immutable', // Example cache-control header
},
});

View file

@ -1,15 +1,13 @@
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { AfterActivate } from '@gitroom/frontend/components/auth/after.activate';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} - Activate your account`,
title: `${
isGeneralServerSide() ? 'Postiz' : 'Gitroom'
} - Activate your account`,
description: '',
};
export default async function Auth() {
return <AfterActivate />;
}

View file

@ -1,17 +1,13 @@
export const dynamic = 'force-dynamic';
import {Metadata} from "next";
import { Metadata } from 'next';
import { Activate } from '@gitroom/frontend/components/auth/activate';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} - Activate your account`,
title: `${
isGeneralServerSide() ? 'Postiz' : 'Gitroom'
} - Activate your account`,
description: '',
};
export default async function Auth() {
return (
<Activate />
);
return <Activate />;
}

View file

@ -1,14 +1,15 @@
export const dynamic = 'force-dynamic';
import { ForgotReturn } from '@gitroom/frontend/components/auth/forgot-return';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Forgot Password`,
description: '',
};
export default async function Auth(params: { params: { token: string } }) {
export default async function Auth(params: {
params: {
token: string;
};
}) {
return <ForgotReturn token={params.params.token} />;
}

View file

@ -1,17 +1,11 @@
export const dynamic = 'force-dynamic';
import {Forgot} from "@gitroom/frontend/components/auth/forgot";
import {Metadata} from "next";
import { Forgot } from '@gitroom/frontend/components/auth/forgot';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Forgot Password`,
description: '',
};
export default async function Auth() {
return (
<Forgot />
);
return <Forgot />;
}

View file

@ -1,17 +1,19 @@
export const dynamic = 'force-dynamic';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const dynamic = 'force-dynamic';
import { ReactNode } from 'react';
import Image from 'next/image';
import clsx from 'clsx';
import loadDynamic from 'next/dynamic';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
const ReturnUrlComponent = loadDynamic(() => import('./return.url.component'));
export default async function AuthLayout({
children,
}: {
children: ReactNode;
}) {
const t = await getT();
return (
<div className="dark !bg-black">
<ReturnUrlComponent />
@ -27,7 +29,9 @@ export default async function AuthLayout({
alt="Logo"
/>
<div
className={clsx(!isGeneralServerSide() ? 'mt-[12px]' : 'min-w-[80px]')}
className={clsx(
!isGeneralServerSide() ? 'mt-[12px]' : 'min-w-[80px]'
)}
>
{isGeneralServerSide() ? (
<svg
@ -55,7 +59,7 @@ export default async function AuthLayout({
/>
</svg>
) : (
<div className="text-[40px]">Gitroom</div>
<div className="text-[40px]">{t('gitroom', 'Gitroom')}</div>
)}
</div>
</div>

View file

@ -1,17 +1,11 @@
export const dynamic = 'force-dynamic';
import {Login} from "@gitroom/frontend/components/auth/login";
import {Metadata} from "next";
import { Login } from '@gitroom/frontend/components/auth/login';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Login`,
description: '',
};
export default async function Auth() {
return (
<Login />
);
return <Login />;
}

View file

@ -1,18 +1,17 @@
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
export const dynamic = 'force-dynamic';
import { Register } from '@gitroom/frontend/components/auth/register';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
import Link from 'next/link';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Register`,
description: '',
};
export default async function Auth() {
const t = await getT();
if (process.env.DISABLE_REGISTRATION) {
const canRegister = (
await (await internalFetch('/auth/can-register')).json()
@ -20,13 +19,14 @@ export default async function Auth() {
if (!canRegister) {
return (
<div className="text-center">
Registration is disabled
{t('registration_is_disabled', 'Registration is disabled')}
<br />
<Link className="underline hover:font-bold" href="/auth/login">Login instead</Link>
<Link className="underline hover:font-bold" href="/auth/login">
{t('login_instead', 'Login instead')}
</Link>
</div>
);
}
}
return <Register />;
}

View file

@ -1,7 +1,7 @@
'use client';
import { useSearchParams } from 'next/navigation';
import { FC, useCallback, useEffect } from 'react';
const ReturnUrlComponent: FC = () => {
const params = useSearchParams();
const url = params.get('returnUrl');
@ -10,18 +10,15 @@ const ReturnUrlComponent: FC = () => {
localStorage.setItem('returnUrl', url!);
}
}, [url]);
return null;
}
};
export const useReturnUrl = () => {
return {
getAndClear: useCallback(() => {
const data = localStorage.getItem('returnUrl');
localStorage.removeItem('returnUrl');
return data;
}, [])
}
}
}, []),
};
};
export default ReturnUrlComponent;

View file

@ -3,7 +3,6 @@ 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';
@ -17,15 +16,15 @@ import { ToltScript } from '@gitroom/frontend/components/layout/tolt.script';
import { FacebookComponent } from '@gitroom/frontend/components/layout/facebook.component';
import { headers } from 'next/headers';
import { headerName } from '@gitroom/react/translation/i18n.config';
const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] });
const chakra = Chakra_Petch({
weight: '400',
subsets: ['latin'],
});
export default async function AppLayout({ children }: { children: ReactNode }) {
const allHeaders = headers();
const Plausible = !!process.env.STRIPE_PUBLISHABLE_KEY
? PlausibleProvider
: Fragment;
return (
<html className={interClass}>
<head>

View file

@ -3,7 +3,6 @@ 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';
@ -12,9 +11,10 @@ import clsx from 'clsx';
import { VariableContextComponent } from '@gitroom/react/helpers/variable.context';
import { Fragment } from 'react';
import UtmSaver from '@gitroom/helpers/utils/utm.saver';
const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] });
const chakra = Chakra_Petch({
weight: '400',
subsets: ['latin'],
});
export default async function AppLayout({ children }: { children: ReactNode }) {
return (
<html className={interClass}>
@ -23,6 +23,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
</head>
<body className={clsx(chakra.className, 'dark text-primary !bg-primary')}>
<VariableContextComponent
language="en"
storageProvider={
process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare'
}

View file

@ -1,7 +1,7 @@
'use client';
import { StandaloneModal } from '@gitroom/frontend/components/standalone-modal/standalone.modal';
import { usePathname } from 'next/navigation';
export default async function Modal() {
return (
<div className="text-textColor">

View file

@ -1,6 +1,5 @@
import { ReactNode } from 'react';
import { AppLayout } from '@gitroom/frontend/components/launches/layout.standalone';
export default async function AppLayoutIn({
children,
}: {

View file

@ -99,10 +99,10 @@
--color-custom17: #000;
--color-custom18: #000;
--color-custom19: #f97066;
--color-custom20: #F5F5F5;
--color-custom20: #f5f5f5;
--color-custom21: #506490;
--color-custom22: #b91c1c;
--color-custom23: #F5F5F5;
--color-custom23: #f5f5f5;
--color-custom24: #eaff00;
--color-custom25: #2e3336;
--color-custom26: #1d9bf0;
@ -110,7 +110,7 @@
--color-custom28: #b69dec;
--color-custom29: #291259;
--color-custom30: #efefef;
--color-custom31: #E0E0E0;
--color-custom31: #e0e0e0;
--color-custom32: #181818;
--color-custom33: #f2f2f2;
--color-custom34: #334155;

File diff suppressed because it is too large Load diff

View file

@ -6,14 +6,11 @@ import { StarsTableComponent } from '@gitroom/frontend/components/analytics/star
import useSWR from 'swr';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
export const AnalyticsComponent: FC = () => {
const fetch = useFetch();
const load = useCallback(async (path: string) => {
return await (await fetch(path)).json();
}, []);
const { isLoading: isLoadingAnalytics, data: analytics } = useSWR(
'/analytics',
load
@ -22,11 +19,9 @@ export const AnalyticsComponent: FC = () => {
'/analytics/trending',
load
);
if (isLoadingAnalytics || isLoadingTrending) {
return <LoadingComponent />;
}
return (
<div className="flex gap-[24px] flex-1">
<div className="flex flex-col gap-[24px] flex-1">

View file

@ -1,9 +1,9 @@
'use client';
import { FC, useEffect, useMemo, useRef } from 'react';
import DrawChart from 'chart.js/auto';
import { TotalList } from '@gitroom/frontend/components/analytics/stars.and.forks.interface';
import { chunk } from 'lodash';
function mergeDataPoints(data: TotalList[], numPoints: number): TotalList[] {
const res = chunk(data, Math.ceil(data.length / numPoints));
return res.map((row) => {
@ -13,13 +13,13 @@ function mergeDataPoints(data: TotalList[], numPoints: number): TotalList[] {
};
});
}
export const ChartSocial: FC<{ data: TotalList[] }> = (props) => {
export const ChartSocial: FC<{
data: TotalList[];
}> = (props) => {
const { data } = props;
const list = useMemo(() => {
return mergeDataPoints(data, 7);
}, [data]);
const ref = useRef<any>(null);
const chart = useRef<null | DrawChart>(null);
useEffect(() => {

View file

@ -1,4 +1,5 @@
'use client';
import { FC, useEffect, useRef } from 'react';
import DrawChart from 'chart.js/auto';
import {
@ -6,8 +7,9 @@ import {
StarsList,
} from '@gitroom/frontend/components/analytics/stars.and.forks.interface';
import dayjs from 'dayjs';
export const Chart: FC<{ list: StarsList[] | ForksList[] }> = (props) => {
export const Chart: FC<{
list: StarsList[] | ForksList[];
}> = (props) => {
const { list } = props;
const ref = useRef<any>(null);
const chart = useRef<null | DrawChart>(null);

View file

@ -2,12 +2,10 @@ export interface StarsList {
totalStars: number;
date: string;
}
export interface TotalList {
total: number;
date: string;
}
export interface ForksList {
totalForks: number;
date: string;
@ -19,7 +17,6 @@ export interface Stars {
login: string;
date: string;
}
export interface StarsAndForksInterface {
list: Array<{
login: string;

View file

@ -1,11 +1,14 @@
'use client';
import { FC } from 'react';
import { StarsAndForksInterface } from '@gitroom/frontend/components/analytics/stars.and.forks.interface';
import { Chart } from '@gitroom/frontend/components/analytics/chart';
import { UtcToLocalDateRender } from '@gitroom/react/helpers/utc.date.render';
import clsx from 'clsx';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export const StarsAndForks: FC<StarsAndForksInterface> = (props) => {
const { list } = props;
const t = useT();
return (
<>
{list.map((item) => (
@ -33,8 +36,8 @@ export const StarsAndForks: FC<StarsAndForksInterface> = (props) => {
.map((char, index) =>
index === 0 ? char.toUpperCase() : char
)
.join('')}{' '}
Stars
.join('')}
{t('stars', 'Stars')}
</div>
</div>
<div className="flex-1 relative">
@ -43,7 +46,7 @@ export const StarsAndForks: FC<StarsAndForksInterface> = (props) => {
<Chart list={item.stars} />
) : (
<div className="w-full h-full flex items-center justify-center text-3xl">
Processing stars...
{t('processing_stars', 'Processing stars...')}
</div>
)}
</div>
@ -76,8 +79,8 @@ export const StarsAndForks: FC<StarsAndForksInterface> = (props) => {
.map((char, index) =>
index === 0 ? char.toUpperCase() : char
)
.join('')}{' '}
Forks
.join('')}
{t('forks', 'Forks')}
</div>
</div>
<div className="flex-1 relative">
@ -86,7 +89,7 @@ export const StarsAndForks: FC<StarsAndForksInterface> = (props) => {
<Chart list={item.forks} />
) : (
<div className="w-full h-full flex items-center justify-center text-3xl">
Processing stars...
{t('processing_stars', 'Processing stars...')}
</div>
)}
</div>

View file

@ -16,23 +16,22 @@ import clsx from 'clsx';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import ReactLoading from 'react-loading';
import interClass from '@gitroom/react/helpers/inter.font';
export const UpDown: FC<{ name: string; param: string }> = (props) => {
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export const UpDown: FC<{
name: string;
param: string;
}> = (props) => {
const { name, param } = props;
const router = useRouter();
const searchParams = useSearchParams();
const state = useMemo(() => {
const newName = searchParams.get('key');
const newState = searchParams.get('state');
if (newName != param) {
return 'none';
}
return newState as 'asc' | 'desc';
}, [searchParams, name, param]);
const changeStateUrl = useCallback(
(newState: string) => {
const query =
@ -41,13 +40,11 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => {
},
[state, param]
);
const changeState = useCallback(() => {
changeStateUrl(
state === 'none' ? 'desc' : state === 'desc' ? 'asc' : 'none'
);
}, [state, param]);
return (
<div
className="flex gap-[5px] items-center select-none"
@ -88,8 +85,8 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => {
</div>
);
};
export const StarsTableComponent = () => {
const t = useT();
const fetch = useFetch();
const router = useRouter();
const searchParams = useSearchParams();
@ -98,7 +95,6 @@ export const StarsTableComponent = () => {
const state = searchParams.get('state');
const [loading, setLoading] = useState(false);
const [, startTransition] = useTransition();
const starsCallback = useCallback(
async (path: string) => {
startTransition(() => {
@ -108,21 +104,23 @@ export const StarsTableComponent = () => {
await fetch(path, {
body: JSON.stringify({
page,
...(key && state ? { key, state } : {}),
...(key && state
? {
key,
state,
}
: {}),
}),
method: 'POST',
})
).json();
startTransition(() => {
setLoading(false);
});
return data;
},
[page, key, state]
);
const {
isLoading: isLoadingStars,
data: stars,
@ -134,18 +132,15 @@ export const StarsTableComponent = () => {
refreshWhenHidden: false,
revalidateIfStale: false,
});
useEffect(() => {
mutate();
}, [searchParams]);
const renderMediaLink = useCallback((date: string) => {
const local = dayjs.utc(date).local();
const weekNumber = local.isoWeek();
const year = local.year();
return `/launches?week=${weekNumber}&year=${year}`;
}, []);
const changePage = useCallback(
(type: 'increase' | 'decrease') => () => {
const newPage = type === 'increase' ? page + 1 : page - 1;
@ -154,7 +149,6 @@ export const StarsTableComponent = () => {
},
[page, key, state]
);
return (
<div className="flex flex-1 flex-col gap-[15px] min-h-[426px]">
<div className="text-textColor flex gap-[8px] items-center select-none">
@ -177,7 +171,7 @@ export const StarsTableComponent = () => {
/>
</svg>
</div>
<h2 className="text-[24px]">Stars per day</h2>
<h2 className="text-[24px]">{t('stars_per_day', 'Stars per day')}</h2>
<div
onClick={changePage('increase')}
className={clsx(
@ -228,7 +222,7 @@ export const StarsTableComponent = () => {
<th>
<UpDown name="Forks" param="forks" />
</th>
<th>Media</th>
<th>{t('media', 'Media')}</th>
</tr>
</thead>
<tbody>
@ -245,7 +239,7 @@ export const StarsTableComponent = () => {
<td>{p.forks}</td>
<td>
<Link href={renderMediaLink(p.date)}>
<Button>Check Launch</Button>
<Button>{t('check_launch', 'Check Launch')}</Button>
</Link>
</td>
</tr>
@ -254,7 +248,10 @@ export const StarsTableComponent = () => {
</table>
) : (
<div className="py-[24px] px-[16px]">
Load your GitHub repository from settings to see analytics
{t(
'load_your_github_repository_from_settings_to_see_analytics',
'Load your GitHub repository from settings to see analytics'
)}
</div>
)}
</div>

View file

@ -1,15 +1,23 @@
'use client';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export function Activate() {
const t = useT();
return (
<>
<div>
<h1 className="text-3xl font-bold text-left mb-4 cursor-pointer">
Activate your account
{t('activate_your_account', 'Activate your account')}
</h1>
</div>
<div className="text-textColor">
Thank you for registering!<br />Please check your email to activate your account.
{t('thank_you_for_registering', 'Thank you for registering!')}
<br />
{t(
'please_check_your_email_to_activate_your_account',
'Please check your email to activate your account.'
)}
</div>
</>
);

View file

@ -5,12 +5,13 @@ import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export const AfterActivate = () => {
const fetch = useFetch();
const params = useParams();
const [showLoader, setShowLoader] = useState(true);
const run = useRef(false);
const t = useT();
useEffect(() => {
if (!run.current) {
@ -18,26 +19,40 @@ export const AfterActivate = () => {
loadCode();
}
}, []);
const loadCode = useCallback(async () => {
if (params.code) {
const { can } = await (
await fetch(`/auth/activate`, {
method: 'POST',
body: JSON.stringify({ code: params.code }),
body: JSON.stringify({
code: params.code,
}),
headers: {
'Content-Type': 'application/json',
},
})
).json();
if (!can) {
setShowLoader(false);
}
}
}, []);
return (
<>{showLoader ? <LoadingComponent /> : (<>This user is already activated,<br /><Link href="/auth/login" className="underline">Click here to go back to login</Link></>)}</>
<>
{showLoader ? (
<LoadingComponent />
) : (
<>
This user is already activated,
<br />
<Link href="/auth/login" className="underline">
{t(
'click_here_to_go_back_to_login',
'Click here to go back to login'
)}
</Link>
</>
)}
</>
);
};

View file

@ -1,5 +1,4 @@
'use client';
import { useForm, SubmitHandler, FormProvider } from 'react-hook-form';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import Link from 'next/link';
@ -8,21 +7,19 @@ import { Input } from '@gitroom/react/form/input';
import { useMemo, useState } from 'react';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot-return.password.dto';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
type Inputs = {
password: string;
repeatPassword: string;
token: string;
};
export function ForgotReturn({ token }: { token: string }) {
const [loading, setLoading] = useState(false);
const t = useT();
const [state, setState] = useState(false);
const resolver = useMemo(() => {
return classValidatorResolver(ForgotReturnPasswordDto);
}, []);
const form = useForm<Inputs>({
resolver,
mode: 'onChange',
@ -30,35 +27,33 @@ export function ForgotReturn({ token }: { token: string }) {
token,
},
});
const fetchData = useFetch();
const onSubmit: SubmitHandler<Inputs> = async (data) => {
setLoading(true);
const {reset} = await (await fetchData('/auth/forgot-return', {
const { reset } = await (
await fetchData('/auth/forgot-return', {
method: 'POST',
body: JSON.stringify({ ...data }),
})).json();
body: JSON.stringify({
...data,
}),
})
).json();
setState(true);
if (!reset) {
form.setError('password', {
type: 'manual',
message: 'Your password reset link has expired. Please try again.',
});
return false;
}
setLoading(false);
};
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-left mb-4 cursor-pointer">
Forgot Password
{t('forgot_password_1', 'Forgot Password')}
</h1>
</div>
{!state ? (
@ -80,13 +75,12 @@ export function ForgotReturn({ token }: { token: string }) {
<div className="text-center mt-6">
<div className="w-full flex">
<Button type="submit" className="flex-1" loading={loading}>
Change Password
{t('change_password', 'Change Password')}
</Button>
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{' '}
Go back to login
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</div>
@ -94,12 +88,14 @@ export function ForgotReturn({ token }: { token: string }) {
) : (
<>
<div className="text-left mt-6">
We successfully reset your password. You can now login with your
{t(
'we_successfully_reset_your_password_you_can_now_login_with_your',
'We successfully reset your password. You can now login with your'
)}
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{' '}
Go back to login
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</>

View file

@ -8,42 +8,39 @@ import { Input } from '@gitroom/react/form/input';
import { useMemo, useState } from 'react';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { ForgotPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot.password.dto';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
type Inputs = {
email: string;
};
export function Forgot() {
const t = useT();
const [loading, setLoading] = useState(false);
const [state, setState] = useState(false);
const resolver = useMemo(() => {
return classValidatorResolver(ForgotPasswordDto);
}, []);
const form = useForm<Inputs>({
resolver,
});
const fetchData = useFetch();
const onSubmit: SubmitHandler<Inputs> = async (data) => {
setLoading(true);
await fetchData('/auth/forgot', {
method: 'POST',
body: JSON.stringify({ ...data, provider: 'LOCAL' }),
body: JSON.stringify({
...data,
provider: 'LOCAL',
}),
});
setState(true);
setLoading(false);
};
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-left mb-4 cursor-pointer">
Forgot Password
{t('forgot_password_1', 'Forgot Password')}
</h1>
</div>
{!state ? (
@ -59,13 +56,12 @@ export function Forgot() {
<div className="text-center mt-6">
<div className="w-full flex">
<Button type="submit" className="flex-1" loading={loading}>
Send Password Reset Email
{t('send_password_reset_email', 'Send Password Reset Email')}
</Button>
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{' '}
Go back to login
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</div>
@ -73,12 +69,14 @@ export function Forgot() {
) : (
<>
<div className="text-left mt-6">
We have send you an email with a link to reset your password.
{t(
'we_have_send_you_an_email_with_a_link_to_reset_your_password',
'We have send you an email with a link to reset your password.'
)}
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{' '}
Go back to login
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</>

View file

@ -15,22 +15,21 @@ import { GoogleProvider } from '@gitroom/frontend/components/auth/providers/goog
import { useVariables } from '@gitroom/react/helpers/variable.context';
import { FarcasterProvider } from '@gitroom/frontend/components/auth/providers/farcaster.provider';
import WalletProvider from '@gitroom/frontend/components/auth/providers/wallet.provider';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
type Inputs = {
email: string;
password: string;
providerToken: '';
provider: 'LOCAL';
};
export function Login() {
const t = useT();
const [loading, setLoading] = useState(false);
const { isGeneral, neynarClientId, billingEnabled, genericOauth } =
useVariables();
const resolver = useMemo(() => {
return classValidatorResolver(LoginUserDto);
}, []);
const form = useForm<Inputs>({
resolver,
defaultValues: {
@ -38,31 +37,29 @@ export function Login() {
provider: 'LOCAL',
},
});
const fetchData = useFetch();
const onSubmit: SubmitHandler<Inputs> = async (data) => {
setLoading(true);
const login = await fetchData('/auth/login', {
method: 'POST',
body: JSON.stringify({ ...data, provider: 'LOCAL' }),
body: JSON.stringify({
...data,
provider: 'LOCAL',
}),
});
if (login.status === 400) {
form.setError('email', {
message: await login.text(),
});
setLoading(false);
}
};
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-left mb-4 cursor-pointer">
Sign In
{t('sign_in', 'Sign In')}
</h1>
</div>
{isGeneral && genericOauth ? (
@ -81,7 +78,7 @@ export function Login() {
<div
className={`absolute z-[1] ${interClass} justify-center items-center w-full left-0 top-0 flex`}
>
<div className="bg-customColor15 px-[16px]">OR</div>
<div className="bg-customColor15 px-[16px]">{t('or', 'OR')}</div>
</div>
</div>
@ -107,19 +104,18 @@ export function Login() {
className="flex-1 rounded-[4px]"
loading={loading}
>
Sign in
{t('sign_in_1', 'Sign in')}
</Button>
</div>
<p className="mt-4 text-sm">
Don{"'"}t Have An Account?{' '}
{t('don_t_have_an_account', "Don't Have An Account?")}
<Link href="/auth" className="underline cursor-pointer">
{' '}
Sign Up
{t('sign_up', 'Sign Up')}
</Link>
</p>
<p className="mt-4 text-sm text-red-600">
<Link href="/auth/forgot" className="underline cursor-pointer">
Forgot password
{t('forgot_password', 'Forgot password')}
</Link>
</p>
</div>

View file

@ -9,24 +9,19 @@ import React, {
ReactNode,
} from 'react';
import { useNeynarContext } from '@neynar/react';
export const NeynarAuthButton: FC<{
children: ReactNode;
onLogin: (code: string) => void;
}> = (props) => {
const { children, onLogin } = props;
const { client_id } = useNeynarContext();
const [showModal, setShowModal] = useState(false);
const authWindowRef = useRef<Window | null>(null);
const neynarLoginUrl = `${
process.env.NEYNAR_LOGIN_URL ?? 'https://app.neynar.com/login'
}?client_id=${client_id}`;
const authOrigin = new URL(neynarLoginUrl).origin;
const modalRef = useRef<HTMLDivElement>(null);
const handleMessage = useCallback(
async (event: MessageEvent) => {
if (
@ -40,61 +35,50 @@ export const NeynarAuthButton: FC<{
signer_uuid: event.data.signer_uuid,
...event.data.user,
};
onLogin(Buffer.from(JSON.stringify(_user)).toString('base64'));
}
},
[client_id, onLogin]
);
const handleSignIn = useCallback(() => {
const width = 600,
height = 700;
const left = window.screen.width / 2 - width / 2;
const top = window.screen.height / 2 - height / 2;
const windowFeatures = `width=${width},height=${height},top=${top},left=${left}`;
authWindowRef.current = window.open(
neynarLoginUrl,
'_blank',
windowFeatures
);
if (!authWindowRef.current) {
console.error(
'Failed to open the authentication window. Please check your pop-up blocker settings.'
);
return;
}
window.addEventListener('message', handleMessage, false);
}, [client_id, handleMessage]);
const closeModal = () => setShowModal(false);
useEffect(() => {
return () => {
window.removeEventListener('message', handleMessage); // Cleanup function to remove listener
};
}, [handleMessage]);
const handleOutsideClick = useCallback((event: any) => {
if (modalRef.current && !modalRef.current.contains(event.target)) {
closeModal();
}
}, []);
useEffect(() => {
if (showModal) {
document.addEventListener('mousedown', handleOutsideClick);
} else {
document.removeEventListener('mousedown', handleOutsideClick);
}
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, [showModal, handleOutsideClick]);
return <div onClick={handleSignIn}>{children}</div>;
};

View file

@ -1,22 +1,23 @@
'use client';
import { FC, useCallback } from 'react';
import interClass from '@gitroom/react/helpers/inter.font';
import { useVariables } from '@gitroom/react/helpers/variable.context';
import { NeynarContextProvider, Theme, useNeynarContext } from '@neynar/react';
import { NeynarAuthButton } from '@gitroom/frontend/components/auth/nayner.auth.button';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export const FarcasterProvider = () => {
const gotoLogin = useCallback(async (code: string) => {
window.location.href = `/auth?provider=FARCASTER&code=${code}`;
}, []);
return (
<ButtonCaster login={gotoLogin} />
);
return <ButtonCaster login={gotoLogin} />;
};
export const ButtonCaster: FC<{ login: (code: string) => void }> = (props) => {
export const ButtonCaster: FC<{
login: (code: string) => void;
}> = (props) => {
const { login } = props;
const { neynarClientId } = useVariables();
const t = useT();
return (
<NeynarContextProvider
settings={{
@ -48,7 +49,7 @@ export const ButtonCaster: FC<{ login: (code: string) => void }> = (props) => {
fill="white"
/>
</svg>
<div>Continue with Farcaster</div>
<div>{t('continue_with_farcaster', 'Continue with Farcaster')}</div>
</div>
</NeynarAuthButton>
</NeynarContextProvider>

View file

@ -1,14 +1,14 @@
import { useCallback } from 'react';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import interClass from '@gitroom/react/helpers/inter.font';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export const GithubProvider = () => {
const fetch = useFetch();
const t = useT();
const gotoLogin = useCallback(async () => {
const link = await (await fetch('/auth/oauth/GITHUB')).text();
window.location.href = link;
}, []);
return (
<div
onClick={gotoLogin}
@ -28,7 +28,7 @@ export const GithubProvider = () => {
/>
</svg>
</div>
<div>Sign in with GitHub</div>
<div>{t('sign_in_with_github', 'Sign in with GitHub')}</div>
</div>
);
};

View file

@ -1,14 +1,14 @@
import { useCallback } from 'react';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import interClass from '@gitroom/react/helpers/inter.font';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
export const GoogleProvider = () => {
const fetch = useFetch();
const t = useT();
const gotoLogin = useCallback(async () => {
const link = await (await fetch('/auth/oauth/GOOGLE')).text();
window.location.href = link;
}, []);
return (
<div
onClick={gotoLogin}
@ -39,7 +39,7 @@ export const GoogleProvider = () => {
/>
</svg>
</div>
<div>Continue with Google</div>
<div>{t('continue_with_google', 'Continue with Google')}</div>
</div>
);
};

Some files were not shown because too many files have changed in this diff Show more