diff --git a/.cspell.json b/.cspell.json index 3157fbb5e..a15c231d6 100644 --- a/.cspell.json +++ b/.cspell.json @@ -368,7 +368,14 @@ "posthog", "pageviews", "pageleave", - "pageview" + "pageview", + "serverweb", + "intefaces", + "Environtment", + "asar", + "icns", + "nsis", + "WARNING️" ], "useGitignore": true, "ignorePaths": [ @@ -415,6 +422,7 @@ "apps/mobile/android/**", "apps/mobile/ios/**", "apps/desktop/i18n/**", - "apps/**/*.{svg,css,scss}" + "apps/**/*.{svg,css,scss}", + ".scripts/icon-utils/icons/**", ] } diff --git a/.scripts/electron-desktop-environment/concrete-environment-content/common-environment-content.ts b/.scripts/electron-desktop-environment/concrete-environment-content/common-environment-content.ts new file mode 100644 index 000000000..0cb2aaf28 --- /dev/null +++ b/.scripts/electron-desktop-environment/concrete-environment-content/common-environment-content.ts @@ -0,0 +1,13 @@ +import { IContentGenerator } from '../intefaces/i-content-generator'; +import { Env } from '../../env'; + + +export class CommonEnvironmentContent implements IContentGenerator { + public generate(variable: Partial): string { + return ` + I18N_FILES_URL: '${variable.I18N_FILES_URL}', + COMPANY_SITE_LINK: '${variable.COMPANY_SITE_LINK}', + COMPANY_GITHUB_LINK: '${variable.COMPANY_GITHUB_LINK}', + ` + } +} diff --git a/.scripts/electron-desktop-environment/concrete-environment-content/desktop-server-web-environment-content.ts b/.scripts/electron-desktop-environment/concrete-environment-content/desktop-server-web-environment-content.ts new file mode 100644 index 000000000..8d31126c9 --- /dev/null +++ b/.scripts/electron-desktop-environment/concrete-environment-content/desktop-server-web-environment-content.ts @@ -0,0 +1,18 @@ +import { IContentGenerator } from '../intefaces/i-content-generator'; +import { IDesktopEnvironment } from '../intefaces/i-desktop-environment'; + +export class DesktopServerWebEnvironmentContent implements IContentGenerator { + public generate(variable: Partial): string { + return ` + NAME: '${variable.DESKTOP_SERVER_WEB_APP_NAME || variable.NAME}', + DESCRIPTION: '${variable.DESKTOP_SERVER_WEB_APP_DESCRIPTION || variable.DESCRIPTION}', + APP_ID: '${variable.DESKTOP_SERVER_WEB_APP_ID || variable.APP_ID}', + REPO_NAME: '${variable.DESKTOP_SERVER_WEB_APP_REPO_NAME || variable.REPO_NAME}', + REPO_OWNER: '${variable.DESKTOP_SERVER_WEB_APP_REPO_OWNER || variable.REPO_OWNER}', + WELCOME_TITLE: '${variable.DESKTOP_SERVER_WEB_APP_WELCOME_TITLE || variable.WELCOME_TITLE}', + WELCOME_CONTENT: '${variable.DESKTOP_SERVER_WEB_APP_WELCOME_CONTENT || variable.WELCOME_CONTENT}', + PLATFORM_LOGO: '${variable.PLATFORM_LOGO}', + DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512: '${variable.DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512}', + `; + } +} diff --git a/.scripts/electron-desktop-environment/desktop-environment-manager.ts b/.scripts/electron-desktop-environment/desktop-environment-manager.ts new file mode 100644 index 000000000..7605df4e2 --- /dev/null +++ b/.scripts/electron-desktop-environment/desktop-environment-manager.ts @@ -0,0 +1,108 @@ +import { DesktopEnvirontmentContentFactory } from './desktop-environtment-content-factory'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import * as path from 'path'; +import * as fs from 'fs'; +import { env } from '../env'; +import { IDesktopEnvironment } from './intefaces/i-desktop-environment'; + +const argv: any = yargs(hideBin(process.argv)).argv; + + +export class DesktopEnvironmentManager { + private static _instance: DesktopEnvironmentManager; + private readonly desktop: string; + private readonly fileDir: string; + private readonly fileName: string; + private readonly isProd: boolean; + + private constructor() { + console.log(argv); + this.desktop = String(argv.desktop); + this.isProd = argv.environment === 'prod'; + this.fileName = 'config'; + this.fileDir = path.join('apps', this.desktop, 'src', 'configs'); + } + + + private static get instance(): DesktopEnvironmentManager { + if (!this._instance) { + this._instance = new DesktopEnvironmentManager(); + } + return this._instance; + } + + public static get environment(): any { + if ( + fs.existsSync( + path.join( + this.instance.fileDir, + this.instance.fileName.concat(`.ts`) + ) + ) + ) { + return require(path.join( + '..', + '..', + this.instance.fileDir, + this.instance.fileName + )).config; + } + return null; + } + + public static update(): void { + const environment: IDesktopEnvironment = Object.assign( + {}, + this.environment + ); + const filePath = path.join( + this.instance.fileDir, + this.instance.fileName.concat('.ts') + ); + + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + fs.writeFileSync( + filePath, + this.instance.content(environment, this.instance.isProd) + ); + return; + } + console.log(`WARNING: File ${filePath} does not exist`); + } + + public static generate(): void { + const files = ['config.ts']; + const environment: Partial = Object.assign({}, env); + + for (const file of files) { + const isProd = file === 'config.ts'; + const filePath = path.join(this.instance.fileDir, file); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + fs.writeFileSync( + filePath, + this.instance.content(environment, isProd) + ); + + console.log(`✔ Generated desktop ${isProd} environment file: ${filePath}`) + } + } + + + private content(variable: Partial, isProd: boolean): string { + return ` + export const config = { + production: ${isProd}, + ${DesktopEnvirontmentContentFactory.generate( + this.desktop, + variable + )} + }; + `; + } +} + +DesktopEnvironmentManager.update(); diff --git a/.scripts/electron-desktop-environment/desktop-environtment-content-factory.ts b/.scripts/electron-desktop-environment/desktop-environtment-content-factory.ts new file mode 100644 index 000000000..5fc55a0b0 --- /dev/null +++ b/.scripts/electron-desktop-environment/desktop-environtment-content-factory.ts @@ -0,0 +1,25 @@ + +import { IDesktopEnvironment } from './intefaces/i-desktop-environment'; +import { CommonEnvironmentContent } from './concrete-environment-content/common-environment-content'; +import { DesktopServerWebEnvironmentContent } from './concrete-environment-content/desktop-server-web-environment-content'; + +export class DesktopEnvirontmentContentFactory { + public static generate( + desktop: string, + environtment: Partial + ) { + const common = new CommonEnvironmentContent(); + switch (desktop) { + case 'server-web': + const desktopServerWeb = new DesktopServerWebEnvironmentContent(); + return ` + ${common.generate(environtment)} + ${desktopServerWeb.generate(environtment)} + `; + default: + return ` + ${common.generate(environtment)} + `; + } + } +} diff --git a/.scripts/electron-desktop-environment/intefaces/i-content-generator.ts b/.scripts/electron-desktop-environment/intefaces/i-content-generator.ts new file mode 100644 index 000000000..e84cbf2b0 --- /dev/null +++ b/.scripts/electron-desktop-environment/intefaces/i-content-generator.ts @@ -0,0 +1,5 @@ +import { IDesktopEnvironment } from './i-desktop-environment' + +export interface IContentGenerator { + generate(variable: Partial): string; +} diff --git a/.scripts/electron-desktop-environment/intefaces/i-desktop-environment.ts b/.scripts/electron-desktop-environment/intefaces/i-desktop-environment.ts new file mode 100644 index 000000000..33ccb9ac9 --- /dev/null +++ b/.scripts/electron-desktop-environment/intefaces/i-desktop-environment.ts @@ -0,0 +1,13 @@ +import { Env } from '../../env'; + +export interface IDesktopEnvironment extends Env { + NAME: string; + DESCRIPTION: string; + APP_ID: string; + REPO_NAME: string; + REPO_OWNER: string; + WELCOME_TITLE: string; + WELCOME_CONTENT: string; + PLATFORM_LOGO: string; + DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512: string; +} diff --git a/.scripts/electron-package-utils/concrete-packager/desktop-packager.ts b/.scripts/electron-package-utils/concrete-packager/desktop-packager.ts new file mode 100644 index 000000000..aae238e56 --- /dev/null +++ b/.scripts/electron-package-utils/concrete-packager/desktop-packager.ts @@ -0,0 +1,25 @@ +import { IPackage } from '../interfaces/i-package'; +import { IPackager } from '../interfaces/i-packager'; +import { IPackageBuild } from '../interfaces/i-package-build'; +import { env } from '../../env'; + +export class DesktopPackager implements IPackager { + public prepare(pkg: IPackage): IPackage { + pkg.name = env.DESKTOP_SERVER_WEB_APP_NAME || pkg.name; + pkg.productName = env.DESKTOP_SERVER_WEB_APP_DESCRIPTION || pkg.productName; + pkg.description = env.DESKTOP_SERVER_WEB_APP_DESCRIPTION || pkg.description; + pkg.homepage = env.COMPANY_SITE_LINK || pkg.homepage; + pkg.build.appId = env.DESKTOP_SERVER_WEB_APP_ID || pkg.build.appId; + pkg.build.productName = + env.DESKTOP_SERVER_WEB_APP_DESCRIPTION || pkg.build.productName; + pkg.build.linux.executableName = + env.DESKTOP_SERVER_WEB_APP_NAME || pkg.build.linux.executableName; + return pkg; + } + + public prepareBuild(pkg: IPackageBuild): IPackageBuild { + pkg.description = env.DESKTOP_SERVER_WEB_APP_DESCRIPTION || pkg.description; + pkg.name = env.DESKTOP_SERVER_WEB_APP_NAME || pkg.name; + return pkg; + } +} diff --git a/.scripts/electron-package-utils/interfaces/i-package-build.ts b/.scripts/electron-package-utils/interfaces/i-package-build.ts new file mode 100644 index 000000000..6e54741ca --- /dev/null +++ b/.scripts/electron-package-utils/interfaces/i-package-build.ts @@ -0,0 +1,10 @@ +export interface IPackageBuild { + name: string; + version: string; + description: string; + author: { + name: string; + email: string; + url: string; + } +} diff --git a/.scripts/electron-package-utils/interfaces/i-package.ts b/.scripts/electron-package-utils/interfaces/i-package.ts new file mode 100644 index 000000000..54b90d694 --- /dev/null +++ b/.scripts/electron-package-utils/interfaces/i-package.ts @@ -0,0 +1,68 @@ +export interface IPackage { + name: string; + productName: string; + version: string; + description: string; + license: string; + homepage: string; + repository: { type: string; url: string }; + bugs: { url: string }; + private: boolean; + author: { name: string; email: string; url: string }; + main: string; + workspaces: { packages: string[] }; + build: { + appId: string; + artifactName: string; + productName: string; + copyright: string; + afterSign: string; + dmg: { sign: boolean }; + asar: boolean; + npmRebuild: boolean; + asarUnpack: string[]; + directories: { buildResources: string; output: string }; + publish: {}[]; + mac: { + category: string; + icon: string; + target: any[]; + asarUnpack: string; + artifactName: string; + hardenedRuntime: boolean; + gatekeeperAssess: boolean; + entitlements: string; + entitlementsInherit: string; + extendInfo: {}; + }; + win: { + publisherName: string; + target: any[]; + icon: string; + verifyUpdateCodeSignature: boolean; + }; + linux: { + icon: string; + target: any[]; + executableName: string; + artifactName: string; + synopsis: string; + category: string; + }; + nsis: { + oneClick: boolean; + perMachine: boolean; + createDesktopShortcut: boolean; + createStartMenuShortcut: boolean; + allowToChangeInstallationDirectory: boolean; + allowElevation: boolean; + installerIcon: string; + artifactName: string; + deleteAppDataOnUninstall: boolean; + menuCategory: boolean; + }; + extraResources: (string | object[])[]; + extraFiles: string[]; + }; + dependencies: { [key: string]: string }; +} diff --git a/.scripts/electron-package-utils/interfaces/i-packager.ts b/.scripts/electron-package-utils/interfaces/i-packager.ts new file mode 100644 index 000000000..8c4b11d6a --- /dev/null +++ b/.scripts/electron-package-utils/interfaces/i-packager.ts @@ -0,0 +1,6 @@ +import { IPackage } from './i-package'; +import { IPackageBuild } from './i-package-build'; +export interface IPackager { + prepare(pkg: IPackage): IPackage; + prepareBuild(pkg: IPackageBuild): IPackageBuild; +} diff --git a/.scripts/electron-package-utils/package-util.ts b/.scripts/electron-package-utils/package-util.ts new file mode 100644 index 000000000..c7165dd79 --- /dev/null +++ b/.scripts/electron-package-utils/package-util.ts @@ -0,0 +1,79 @@ +import * as fs from 'fs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import * as path from 'path'; +import { IPackage } from './interfaces/i-package'; +import { PackagerFactory } from './packager-factory'; +import { IPackageBuild } from './interfaces/i-package-build'; + +const argv: any = yargs(hideBin(process.argv)).argv; + +export class PackageUtil { + private static _instance: PackageUtil; + private readonly filePath: string; + private readonly desktop: string; + private readonly buildFilePath: string; + + constructor() { + this.desktop = String(argv.desktop); + this.filePath = path.join('apps', this.desktop, 'package.json'); + this.buildFilePath = path.join('apps', this.desktop, 'release', 'app', 'package.json'); + } + + private static get instance(): PackageUtil { + if (!this._instance) { + this._instance = new PackageUtil(); + } + return this._instance; + } + + public static get package(): IPackage | null { + if (fs.existsSync(this.instance.filePath)) { + return JSON.parse( + fs.readFileSync(this.instance.filePath, { encoding: 'utf8' }) + ); + } + console.warn(`WARNING: File ${this.instance.filePath} doesn't exists.`); + return null; + } + + public static get packageBuild(): IPackageBuild | null { + if (fs.existsSync(this.instance.buildFilePath)) { + return JSON.parse( + fs.readFileSync(this.instance.buildFilePath, { encoding: 'utf8' }) + ) + } + return null; + } + + public static update(): void { + const pkg = this.package; + const pkgBuild = this.packageBuild; + if (pkg) { + const packager = PackagerFactory.packager(this.instance.desktop); + const packed = packager?.prepare(pkg); + + fs.writeFileSync( + this.instance.filePath, + JSON.stringify(packed, null, 4) + ); + console.warn('✔ package updated.'); + } + if (pkgBuild) { + const packager = PackagerFactory.packager(this.instance.desktop); + const packed = packager?.prepareBuild(pkgBuild); + fs.writeFileSync( + this.instance.buildFilePath, + JSON.stringify(packed, null, 4) + ) + console.warn('✔ package build updated.'); + return; + } + + + console.warn('WARNING: Package not updated.'); + } +} + +// Update package.json +PackageUtil.update(); diff --git a/.scripts/electron-package-utils/packager-factory.ts b/.scripts/electron-package-utils/packager-factory.ts new file mode 100644 index 000000000..81067f48b --- /dev/null +++ b/.scripts/electron-package-utils/packager-factory.ts @@ -0,0 +1,14 @@ +import { IPackager } from './interfaces/i-packager'; +import { DesktopPackager } from './concrete-packager/desktop-packager'; + +export class PackagerFactory { + public static packager(desktop: string): IPackager | undefined { + switch (desktop) { + case 'server-web': + return new DesktopPackager(); + default: + console.warn('WARNING: Unknown application.'); + break; + } + } +} diff --git a/.scripts/electron.env.ts b/.scripts/electron.env.ts index 028974eb4..01eee12e9 100644 --- a/.scripts/electron.env.ts +++ b/.scripts/electron.env.ts @@ -1,6 +1,2 @@ -import { argv } from 'yargs'; - -const environment = argv.environment; -const isProd = environment === 'prod'; - -console.log('Is Production: ' + isProd); +import { DesktopEnvironmentManager } from './electron-desktop-environment/desktop-environment-manager'; +DesktopEnvironmentManager.generate(); diff --git a/.scripts/env.ts b/.scripts/env.ts new file mode 100644 index 000000000..2db1013ba --- /dev/null +++ b/.scripts/env.ts @@ -0,0 +1,65 @@ +import { + cleanEnv, str, num +} from 'envalid'; + + +export type Env = Readonly<{ + PLATFORM_LOGO: string; + DESKTOP_SERVER_WEB_APP_NAME: string; + DESKTOP_SERVER_WEB_APP_DESCRIPTION: string; + DESKTOP_SERVER_WEB_APP_ID: string; + DESKTOP_SERVER_WEB_APP_REPO_NAME: string; + DESKTOP_SERVER_WEB_APP_REPO_OWNER: string; + DESKTOP_SERVER_WEB_APP_WELCOME_TITLE: string; + DESKTOP_SERVER_WEB_APP_WELCOME_CONTENT: string; + DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512: string; + DESKTOP_SERVER_WEB_APP_DEFAULT_PORT: number; + DESKTOP_SERVER_WEB_APP_DEFAULT_API_URL: string; + I18N_FILES_URL: string; + COMPANY_SITE_LINK: string; + COMPANY_GITHUB_LINK: string; +}> + + +export const env = cleanEnv(process.env, { + COMPANY_SITE_LINK: str({ + default: 'https://ever.team/' + }), + COMPANY_GITHUB_LINK: str({ + default: 'https://github.com/ever-co/ever-teams' + }), + PLATFORM_LOGO: str({ + default: 'src/resources/icons/platform-logo.png' + }), + DESKTOP_SERVER_WEB_APP_NAME: str({ + default: 'ever-teams-server-web' + }), + DESKTOP_SERVER_WEB_APP_DESCRIPTION: str({ + default: 'Ever Teams Server Web' + }), + DESKTOP_SERVER_WEB_APP_ID: str({ + default: 'com.ever.teams.serverweb' + }), + DESKTOP_SERVER_WEB_APP_REPO_NAME: str({ + default: 'ever-teams-web-server' + }), + DESKTOP_SERVER_WEB_APP_REPO_OWNER: str({ + default: 'ever-co' + }), + DESKTOP_SERVER_WEB_APP_WELCOME_TITLE: str({ + default: 'Welcome to Ever Teams Web Server' + }), + DESKTOP_SERVER_WEB_APP_WELCOME_CONTENT: str({ + default: 'Ever Teams Web Server is a web application that allows you to manage your teams and projects.' + }), + DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512: str({ + default: 'assets/icons/desktop_logo_512x512.png' + }), + DESKTOP_SERVER_WEB_APP_DEFAULT_PORT: num({ + default: 3333 + }), + DESKTOP_SERVER_WEB_APP_DEFAULT_API_URL: str({ + default: 'http://localhost:3000' + }), + I18N_FILES_URL: str({ default: '' }), +}); diff --git a/.scripts/icon-utils/concrete-download-strategy/download-asset-strategy.ts b/.scripts/icon-utils/concrete-download-strategy/download-asset-strategy.ts new file mode 100644 index 000000000..b521913b0 --- /dev/null +++ b/.scripts/icon-utils/concrete-download-strategy/download-asset-strategy.ts @@ -0,0 +1,29 @@ +import { IDownloadStrategy } from '../interfaces/i-download-strategy'; +import * as fs from 'fs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import * as path from 'path'; + +const argv: any = yargs(hideBin(process.argv)).argv; + +export class DownloadAssetStrategy implements IDownloadStrategy { + private readonly desktop = String(argv.desktop); + + private checkImageExistence(imagePath: string): boolean { + try { + fs.accessSync(imagePath); + return true; + } catch (error) { + return false; + } + } + + public download(assetPath: string): Promise | string | undefined | null { + const filePath = path.join('apps', this.desktop, 'src', assetPath); + if (this.checkImageExistence(filePath)) { + return filePath; + } + console.warn(`WARNING️: This asset ${filePath} do not exist.`); + return null; + } +} diff --git a/.scripts/icon-utils/concrete-download-strategy/download-https-strategy.ts b/.scripts/icon-utils/concrete-download-strategy/download-https-strategy.ts new file mode 100644 index 000000000..56654c3d9 --- /dev/null +++ b/.scripts/icon-utils/concrete-download-strategy/download-https-strategy.ts @@ -0,0 +1,23 @@ +import { IDownloadStrategy } from '../interfaces/i-download-strategy'; +import fetch from 'node-fetch'; +import * as path from 'path'; +import * as fs from 'fs'; + +export class DownloadHttpsStrategy implements IDownloadStrategy { + public async download( + imageUrl: string, + destination: string + ): Promise { + if (!fs.existsSync(destination)) { + fs.mkdirSync(destination); + console.log('✔ directory created!'); + } + const response = await fetch(imageUrl); + const buffer = await response.buffer(); + const fileName = path.basename(imageUrl); + const filePath = path.join(destination, fileName); + fs.writeFileSync(filePath, buffer); + console.log(`✔ image ${fileName} downloaded successfully.`); + return filePath; + } +} diff --git a/.scripts/icon-utils/concrete-generators/desktop-default-icon-generator.ts b/.scripts/icon-utils/concrete-generators/desktop-default-icon-generator.ts new file mode 100644 index 000000000..c4cab42f5 --- /dev/null +++ b/.scripts/icon-utils/concrete-generators/desktop-default-icon-generator.ts @@ -0,0 +1,46 @@ +import { IIconGeneratorBase } from '../interfaces/i-icon-generator-base'; +import * as fs from 'fs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import { env } from '../../env'; +import * as path from 'path'; +import { DesktopEnvironmentManager } from '../../electron-desktop-environment/desktop-environment-manager'; + +const argv: any = yargs(hideBin(process.argv)).argv; +export class DesktopDefaultIconGenerator implements IIconGeneratorBase { + private readonly desktop: string; + private readonly destination: string; + private readonly source: string; + + constructor() { + this.desktop = String(argv.desktop); + this.destination = path.join('apps', this.desktop, 'assets'); + this.source = path.join('.scripts', 'icon-utils', 'icons'); + } + + public async generate(): Promise { + await new Promise((resolve, reject) => + fs.cp( + this.source, + this.destination, + { recursive: true }, + (error) => { + if (error) { + console.error( + 'ERROR: An error occurred while generating the files:', + error + ); + reject(error); + return; + } + DesktopEnvironmentManager.environment.DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512 = + env.DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512; + DesktopEnvironmentManager.environment.PLATFORM_LOGO = + env.PLATFORM_LOGO; + console.log('✔ default icons generated successfully!'); + resolve(true); + } + ) + ); + } +} diff --git a/.scripts/icon-utils/concrete-generators/desktop-icon-generator.ts b/.scripts/icon-utils/concrete-generators/desktop-icon-generator.ts new file mode 100644 index 000000000..373a155bd --- /dev/null +++ b/.scripts/icon-utils/concrete-generators/desktop-icon-generator.ts @@ -0,0 +1,191 @@ +import * as Jimp from 'jimp'; +import * as fs from 'fs'; +import * as path from 'path'; +import { IconGenerator } from '../interfaces/icon-generator'; +import { IDesktopIconGenerator } from '../interfaces/i-desktop-icon-generator'; +import { IconFactory } from '../icon-factory'; +import { env } from '../../env'; +import * as PngIco from 'png-to-ico'; +import { DesktopEnvironmentManager } from '../../electron-desktop-environment/desktop-environment-manager'; + +export class DesktopIconGenerator + extends IconGenerator + implements IDesktopIconGenerator { + constructor() { + super(); + this.imageUrl = env.DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512; + this.destination = path.join('apps', this.desktop, 'assets'); + } + generateMenuIcon(originalImage: Jimp): Promise { + throw new Error('Method not implemented.'); + } + + private async updateLogoPath(): Promise { + const source = path.join(this.destination, 'icon.png'); + const destination = path.join( + 'apps', + this.desktop, + 'assets', + 'icons', + 'desktop_logo_512x512.png' + ); + await new Promise((resolve, reject) => + fs.copyFile(source, destination, (error) => { + if (error) { + console.error( + 'ERROR: An error occurred while generating the files:', + error + ); + reject(error); + } else { + DesktopEnvironmentManager.environment.DESKTOP_SERVER_WEB_APP_DESKTOP_APP_LOGO_512X512 = + './assets/icons/desktop_logo_512x512.png'; + console.log( + '✔ desktop logo 512x512 icons generated successfully.' + ); + resolve(true); + } + }) + ); + } + + public async generateLinuxIcons(originalImage: Jimp): Promise { + const linuxIconSizes = [512, 256, 128, 64, 32, 16]; + const linuxDestination = path.join(this.destination, 'linux'); + if (!fs.existsSync(linuxDestination)) { + fs.mkdirSync(linuxDestination); + console.log('✔ linux icons directory created.'); + } + for (const iconSize of linuxIconSizes) { + const linuxIconFilePath = path.join( + linuxDestination, + `${iconSize}x${iconSize}.png` + ); + await new Promise((resolve) => + originalImage + .clone() + .resize(iconSize, iconSize) + .write(linuxIconFilePath, () => { + console.log( + `✔ linux ${iconSize}x${iconSize}.png icon generated.` + ); + resolve(true); + }) + ); + } + } + + public async generateMacIcon(originalImage: Jimp): Promise { + const macIconFilePath = path.join(this.destination, 'icon.icns'); + await new Promise((resolve) => + originalImage.clone().write(macIconFilePath, () => { + console.log('✔ macOS icon.icns generated.'); + resolve(true); + }) + ); + } + + public async generateWindowsIcon(originalImage: Jimp): Promise { + const ICON_SIZE = 256; + const png = `icon_${ICON_SIZE}x${ICON_SIZE}.png`; + const ico = 'icon.ico'; + const windowsTempIconFilePath = path.join(this.destination, png); + const windowsIconFilePath = path.join(this.destination, ico); + await originalImage + .clone() + .resize(ICON_SIZE, Jimp.AUTO, Jimp.RESIZE_NEAREST_NEIGHBOR) + .writeAsync(windowsTempIconFilePath); + const buffer = await PngIco([windowsTempIconFilePath]); + fs.writeFileSync(windowsIconFilePath, buffer); + await this.remove(windowsTempIconFilePath, true); + console.log(`✔ window ${ico} generated.`); + } + + public async generateTrayIcon(originalImage: Jimp): Promise { + const REF_SIZE = 16; + const scales = [1, 1.25, 1.33, 1.4, 1.5, 1.8, 2, 2.5, 3, 4, 5]; + const pngFilePath = path.join( + 'apps', + this.desktop, + 'assets', + 'icons' + ); + for (const scale of scales) { + const size = REF_SIZE * scale; + const icon = + scale === scales[0] ? 'icon.png' : `icon@${scale}x.png`; + await new Promise((resolve) => + originalImage + .clone() + .resize(size, size) + .write(path.join(pngFilePath, icon), () => { + console.log( + `✔ tray icon ${icon} generated successfully.` + ); + resolve(true); + }) + ); + } + } + + public async resizeAndConvert(filePath: string): Promise { + const image = await Jimp.read(filePath); + const pngFilePath = path.join(this.destination, 'icon.png'); + await new Promise((resolve) => + image + .clone() + .resize(512, 512) + .write(pngFilePath, () => { + console.log( + '✔ image converted to PNG format successfully.' + ); + resolve(true); + }) + ); + + try { + await this.generateLinuxIcons(image); + } catch (error) { + console.error('linux', error); + throw error; + } + + try { + await this.generateWindowsIcon(image); + } catch (error) { + console.error('mac', error); + throw error; + + } + try { + await this.generateMacIcon(image); + } catch (error) { + console.error('mac', error); + throw error; + } + + try { + await this.generateTrayIcon(image); + } catch (error) { + console.error('tray', error); + throw error; + } + + } + + public async generate(): Promise { + try { + const filePath = await this.downloadImage(); + console.log('file path', filePath); + if (filePath) { + await this.resizeAndConvert(filePath); + await this.remove(filePath); + } else { + await IconFactory.generateDefaultIcons(); + } + await this.updateLogoPath(); + } catch (error) { + console.error('error generate', error); + } + } +} diff --git a/.scripts/icon-utils/concrete-generators/no-internet-logo-generator.ts b/.scripts/icon-utils/concrete-generators/no-internet-logo-generator.ts new file mode 100644 index 000000000..a1d042f5c --- /dev/null +++ b/.scripts/icon-utils/concrete-generators/no-internet-logo-generator.ts @@ -0,0 +1,51 @@ +import { IconGenerator } from '../interfaces/icon-generator'; +import { IIconGenerator } from '../interfaces/i-icon-generator'; +import * as path from 'path'; +import * as fs from 'fs'; +import { env } from '../../env'; +import { DesktopEnvironmentManager } from '../../electron-desktop-environment/desktop-environment-manager'; + +export class NoInternetLogoGenerator + extends IconGenerator + implements IIconGenerator +{ + constructor() { + super(); + this.imageUrl = env.NO_INTERNET_LOGO; + this.destination = path.join( + 'apps', + this.desktop, + 'src', + 'assets', + 'images', + 'logos' + ); + } + + public async resizeAndConvert(filePath: string): Promise { + const extName = path.extname(this.imageUrl); + const noInternetLogoFilePath = path.join( + this.destination, + `no_internet_logo${extName}` + ); + await new Promise((resolve, reject) => { + fs.copyFile(filePath, noInternetLogoFilePath, async (err) => { + if (err) { + console.error( + 'ERROR: An error occurred while generating the files:', + err + ); + reject(err); + return; + } + // load image from assets + DesktopEnvironmentManager.environment.NO_INTERNET_LOGO = `./assets/images/logos/no_internet_logo${extName}`; + + // remove downloaded file + await this.remove(filePath); + console.log(`✔ ${extName} copied successfully.`); + resolve(true); + }); + }); + } +} diff --git a/.scripts/icon-utils/concrete-generators/platform-logo-generator.ts b/.scripts/icon-utils/concrete-generators/platform-logo-generator.ts new file mode 100644 index 000000000..82737b000 --- /dev/null +++ b/.scripts/icon-utils/concrete-generators/platform-logo-generator.ts @@ -0,0 +1,49 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { IconGenerator } from '../interfaces/icon-generator'; +import { IIconGenerator } from '../interfaces/i-icon-generator'; +import { env } from '../../env'; +import { DesktopEnvironmentManager } from '../../electron-desktop-environment/desktop-environment-manager'; + +export class PlatformLogoGenerator + extends IconGenerator + implements IIconGenerator { + constructor() { + super(); + this.imageUrl = env.PLATFORM_LOGO; + this.destination = path.join( + 'apps', + this.desktop, + 'src', + 'resources', + 'icons' + ); + } + + public async resizeAndConvert(filePath: string): Promise { + const extName = path.extname(this.imageUrl); + console.log('✔ Resizing and converting platform logo...', extName); + const platformLogoFilePath = path.join( + this.destination, + `platform-logo${extName}` + ); + await new Promise((resolve, reject) => + fs.copyFile(filePath, platformLogoFilePath, async (err) => { + if (err) { + console.error( + 'An error occurred while generating the files:', + err + ); + reject(err); + return; + } + // load image from assets + DesktopEnvironmentManager.environment.PLATFORM_LOGO = `../../../resources/icons/platform-logo${extName}`; + // remove downloaded file + await this.remove(filePath); + console.log(`✔ ${extName} copied successfully.`); + resolve(true); + }) + ); + } +} diff --git a/.scripts/icon-utils/context/download-context.ts b/.scripts/icon-utils/context/download-context.ts new file mode 100644 index 000000000..e39412a46 --- /dev/null +++ b/.scripts/icon-utils/context/download-context.ts @@ -0,0 +1,16 @@ +import { IDownloadStrategy } from '../interfaces/i-download-strategy'; + +export class DownloadContext { + private _strategy: IDownloadStrategy; + constructor(strategy?: IDownloadStrategy) { + this.strategy = strategy; + } + + public get strategy(): IDownloadStrategy { + return this._strategy; + } + + public set strategy(value: IDownloadStrategy) { + this._strategy = value; + } +} diff --git a/.scripts/icon-utils/icon-factory.ts b/.scripts/icon-utils/icon-factory.ts new file mode 100644 index 000000000..cf49786fa --- /dev/null +++ b/.scripts/icon-utils/icon-factory.ts @@ -0,0 +1,41 @@ +import * as dotenv from 'dotenv'; +import { PlatformLogoGenerator } from './concrete-generators/platform-logo-generator'; +import { DesktopIconGenerator } from './concrete-generators/desktop-icon-generator'; +import { DesktopDefaultIconGenerator } from './concrete-generators/desktop-default-icon-generator'; +import { NoInternetLogoGenerator } from './concrete-generators/no-internet-logo-generator'; +import { DesktopEnvironmentManager } from '../electron-desktop-environment/desktop-environment-manager'; + +dotenv.config(); + +export class IconFactory { + public static async generateDefaultIcons(): Promise { + const defaultIcon = new DesktopDefaultIconGenerator(); + await defaultIcon.generate(); + } + + public static async generatePlatformLogo(): Promise { + const platformLogo = new PlatformLogoGenerator(); + await platformLogo.generate(); + } + + public static async generateDesktopIcons(): Promise { + const desktopIcon = new DesktopIconGenerator(); + await desktopIcon.generate(); + } + + public static async generateNoInternetLogo(): Promise { + const noInternetLogo = new NoInternetLogoGenerator(); + await noInternetLogo.generate(); + } +} + +(async () => { + //Generate platform logo from URL + await IconFactory.generatePlatformLogo(); + //Generate platform logo from URL + await IconFactory.generateNoInternetLogo(); + //Generate desktop icons from URL + await IconFactory.generateDesktopIcons(); + // Update environment file + DesktopEnvironmentManager.update(); +})(); diff --git a/.scripts/icon-utils/icons/icon.icns b/.scripts/icon-utils/icons/icon.icns new file mode 100644 index 000000000..d7bd9522e Binary files /dev/null and b/.scripts/icon-utils/icons/icon.icns differ diff --git a/.scripts/icon-utils/icons/icon.ico b/.scripts/icon-utils/icons/icon.ico new file mode 100644 index 000000000..948320fcc Binary files /dev/null and b/.scripts/icon-utils/icons/icon.ico differ diff --git a/.scripts/icon-utils/icons/icon.png b/.scripts/icon-utils/icons/icon.png new file mode 100644 index 000000000..5796ce0ae Binary files /dev/null and b/.scripts/icon-utils/icons/icon.png differ diff --git a/.scripts/icon-utils/icons/linux/128x128.png b/.scripts/icon-utils/icons/linux/128x128.png new file mode 100644 index 000000000..4fdfea08f Binary files /dev/null and b/.scripts/icon-utils/icons/linux/128x128.png differ diff --git a/.scripts/icon-utils/icons/linux/16x16.png b/.scripts/icon-utils/icons/linux/16x16.png new file mode 100644 index 000000000..94a604a2f Binary files /dev/null and b/.scripts/icon-utils/icons/linux/16x16.png differ diff --git a/.scripts/icon-utils/icons/linux/256x256.png b/.scripts/icon-utils/icons/linux/256x256.png new file mode 100644 index 000000000..8109b808e Binary files /dev/null and b/.scripts/icon-utils/icons/linux/256x256.png differ diff --git a/.scripts/icon-utils/icons/linux/32x32.png b/.scripts/icon-utils/icons/linux/32x32.png new file mode 100644 index 000000000..d55d53b02 Binary files /dev/null and b/.scripts/icon-utils/icons/linux/32x32.png differ diff --git a/.scripts/icon-utils/icons/linux/512x512.png b/.scripts/icon-utils/icons/linux/512x512.png new file mode 100644 index 000000000..5796ce0ae Binary files /dev/null and b/.scripts/icon-utils/icons/linux/512x512.png differ diff --git a/.scripts/icon-utils/icons/linux/64x64.png b/.scripts/icon-utils/icons/linux/64x64.png new file mode 100644 index 000000000..37bf8972c Binary files /dev/null and b/.scripts/icon-utils/icons/linux/64x64.png differ diff --git a/.scripts/icon-utils/interfaces/i-desktop-icon-generator.ts b/.scripts/icon-utils/interfaces/i-desktop-icon-generator.ts new file mode 100644 index 000000000..43c5476d1 --- /dev/null +++ b/.scripts/icon-utils/interfaces/i-desktop-icon-generator.ts @@ -0,0 +1,14 @@ +import * as Jimp from 'jimp'; +import { IIconGenerator } from './i-icon-generator'; + +export interface IDesktopIconGenerator extends IIconGenerator { + generateLinuxIcons(originalImage: Jimp): Promise; + + generateWindowsIcon(originalImage: Jimp): Promise; + + generateMacIcon(originalImage: Jimp): Promise; + + generateTrayIcon(originalImage: Jimp): Promise; + + generateMenuIcon(originalImage: Jimp): Promise; +} diff --git a/.scripts/icon-utils/interfaces/i-download-strategy.ts b/.scripts/icon-utils/interfaces/i-download-strategy.ts new file mode 100644 index 000000000..b0597c957 --- /dev/null +++ b/.scripts/icon-utils/interfaces/i-download-strategy.ts @@ -0,0 +1,3 @@ +export interface IDownloadStrategy { + download(imageUrl: string, destination?: string): Promise | string | undefined | null; +} diff --git a/.scripts/icon-utils/interfaces/i-icon-generator-base.ts b/.scripts/icon-utils/interfaces/i-icon-generator-base.ts new file mode 100644 index 000000000..578fa9151 --- /dev/null +++ b/.scripts/icon-utils/interfaces/i-icon-generator-base.ts @@ -0,0 +1,3 @@ +export interface IIconGeneratorBase { + generate(): Promise; +} diff --git a/.scripts/icon-utils/interfaces/i-icon-generator.ts b/.scripts/icon-utils/interfaces/i-icon-generator.ts new file mode 100644 index 000000000..c26ab90c4 --- /dev/null +++ b/.scripts/icon-utils/interfaces/i-icon-generator.ts @@ -0,0 +1,9 @@ +import { IIconGeneratorBase } from './i-icon-generator-base'; + +export interface IIconGenerator extends IIconGeneratorBase { + checkUrlValidity(urlString: string): boolean; + + downloadImage(): Promise; + + resizeAndConvert(filePath: string): Promise; +} diff --git a/.scripts/icon-utils/interfaces/icon-generator.ts b/.scripts/icon-utils/interfaces/icon-generator.ts new file mode 100644 index 000000000..6e00a5234 --- /dev/null +++ b/.scripts/icon-utils/interfaces/icon-generator.ts @@ -0,0 +1,92 @@ +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import * as fs from 'fs'; +import { DownloadContext } from '../context/download-context'; +import { DownloadAssetStrategy } from '../concrete-download-strategy/download-asset-strategy'; +import { DownloadHttpsStrategy } from '../concrete-download-strategy/download-https-strategy'; + +const argv: any = yargs(hideBin(process.argv)).argv; + +export abstract class IconGenerator { + private downloadContext: DownloadContext; + protected destination: string; + protected imageUrl: string; + protected desktop: string | null; + + protected constructor() { + this.desktop = String(argv.desktop); + this.downloadContext = new DownloadContext(); + } + + protected async remove(filePath: string, force = false): Promise { + if (this.downloadContext.strategy instanceof DownloadAssetStrategy) { + if (!force) { + return; + } + } + await new Promise((resolve, reject) => + fs.rm(filePath, (error) => { + if (error) { + console.error('ERROR: An error occurred while removing the file:', error); + reject(error); + } + resolve(true); + }) + ); + } + + public checkUrlValidity(urlString: string): boolean { + try { + if (!urlString) { + console.warn('WARNING: Path is invalid, its cannot be null.'); + return false; + } + + const securedProtocol = urlString.indexOf('https://'); + const localProtocol = urlString.indexOf('assets'); + + if (localProtocol > -1 && localProtocol === Number(0)) { + console.log('✔ url is valid!'); + this.downloadContext.strategy = new DownloadAssetStrategy(); + return true; + } + + if (securedProtocol > -1 && securedProtocol === Number(0)) { + const url = new URL(urlString); + console.log('✔ url is valid!'); + this.downloadContext.strategy = new DownloadHttpsStrategy(); + return !!url; + } + console.warn('WARNING: Only secured https url is allowed'); + return false; + } catch (error) { + console.error(error); + return false; + } + } + + public async downloadImage(): Promise { + if (!this.desktop) { + console.warn('WARNING: A desktop application variant must be selected, process exit.'); + process.exit(0); + } + if (!this.checkUrlValidity(this.imageUrl)) { + return null; + } + + return this.downloadContext.strategy.download(this.imageUrl, this.destination); + } + + public async generate(): Promise { + try { + const filePath = await this.downloadImage(); + if (filePath) { + await this.resizeAndConvert(filePath); + } + } catch (error) { + console.warn(error); + } + } + + public abstract resizeAndConvert(filePath: string): Promise; +} diff --git a/apps/server-web/assets/icon.ico b/apps/server-web/assets/icon.ico index 346ae4440..948320fcc 100644 Binary files a/apps/server-web/assets/icon.ico and b/apps/server-web/assets/icon.ico differ diff --git a/apps/server-web/assets/icons/1024x1024.png b/apps/server-web/assets/icons/1024x1024.png deleted file mode 100755 index 5940b65a3..000000000 Binary files a/apps/server-web/assets/icons/1024x1024.png and /dev/null differ diff --git a/apps/server-web/assets/icons/128x128.png b/apps/server-web/assets/icons/128x128.png deleted file mode 100755 index 14e578d24..000000000 Binary files a/apps/server-web/assets/icons/128x128.png and /dev/null differ diff --git a/apps/server-web/assets/icons/16x16.png b/apps/server-web/assets/icons/16x16.png deleted file mode 100755 index 260a46cb0..000000000 Binary files a/apps/server-web/assets/icons/16x16.png and /dev/null differ diff --git a/apps/server-web/assets/icons/24x24.png b/apps/server-web/assets/icons/24x24.png deleted file mode 100755 index 56172416c..000000000 Binary files a/apps/server-web/assets/icons/24x24.png and /dev/null differ diff --git a/apps/server-web/assets/icons/256x256.png b/apps/server-web/assets/icons/256x256.png deleted file mode 100755 index 755a6e51d..000000000 Binary files a/apps/server-web/assets/icons/256x256.png and /dev/null differ diff --git a/apps/server-web/assets/icons/32x32.png b/apps/server-web/assets/icons/32x32.png deleted file mode 100755 index 63423dfec..000000000 Binary files a/apps/server-web/assets/icons/32x32.png and /dev/null differ diff --git a/apps/server-web/assets/icons/48x48.png b/apps/server-web/assets/icons/48x48.png deleted file mode 100755 index 74d87a0cf..000000000 Binary files a/apps/server-web/assets/icons/48x48.png and /dev/null differ diff --git a/apps/server-web/assets/icons/512x512.png b/apps/server-web/assets/icons/512x512.png deleted file mode 100755 index 313cd499d..000000000 Binary files a/apps/server-web/assets/icons/512x512.png and /dev/null differ diff --git a/apps/server-web/assets/icons/64x64.png b/apps/server-web/assets/icons/64x64.png deleted file mode 100755 index 6de0ec0e0..000000000 Binary files a/apps/server-web/assets/icons/64x64.png and /dev/null differ diff --git a/apps/server-web/assets/icons/96x96.png b/apps/server-web/assets/icons/96x96.png deleted file mode 100755 index 8255ab58c..000000000 Binary files a/apps/server-web/assets/icons/96x96.png and /dev/null differ diff --git a/apps/server-web/assets/icons/desktop_logo_512x512.png b/apps/server-web/assets/icons/desktop_logo_512x512.png new file mode 100644 index 000000000..5796ce0ae Binary files /dev/null and b/apps/server-web/assets/icons/desktop_logo_512x512.png differ diff --git a/apps/server-web/assets/icons/gauzy/icon@1.25x.png b/apps/server-web/assets/icons/gauzy/icon@1.25x.png deleted file mode 100644 index 46ea6d876..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@1.25x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@1.33x.png b/apps/server-web/assets/icons/gauzy/icon@1.33x.png deleted file mode 100644 index bc73df996..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@1.33x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@1.4x.png b/apps/server-web/assets/icons/gauzy/icon@1.4x.png deleted file mode 100644 index 19ee320f0..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@1.4x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@1.5x.png b/apps/server-web/assets/icons/gauzy/icon@1.5x.png deleted file mode 100644 index 5faa32626..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@1.5x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@1.8x.png b/apps/server-web/assets/icons/gauzy/icon@1.8x.png deleted file mode 100644 index 0f97c50d5..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@1.8x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@2.5x.png b/apps/server-web/assets/icons/gauzy/icon@2.5x.png deleted file mode 100644 index aeb2dadc3..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@2.5x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@2x.png b/apps/server-web/assets/icons/gauzy/icon@2x.png deleted file mode 100644 index 9d27eebae..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@2x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@3x.png b/apps/server-web/assets/icons/gauzy/icon@3x.png deleted file mode 100644 index 999c00be1..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@3x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@4x.png b/apps/server-web/assets/icons/gauzy/icon@4x.png deleted file mode 100644 index ff6c4fdec..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@4x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon@5x.png b/apps/server-web/assets/icons/gauzy/icon@5x.png deleted file mode 100644 index da278ab3f..000000000 Binary files a/apps/server-web/assets/icons/gauzy/icon@5x.png and /dev/null differ diff --git a/apps/server-web/assets/icons/gauzy/icon.png b/apps/server-web/assets/icons/icon.png similarity index 100% rename from apps/server-web/assets/icons/gauzy/icon.png rename to apps/server-web/assets/icons/icon.png diff --git a/apps/server-web/assets/linux/128x128.png b/apps/server-web/assets/linux/128x128.png index 55a3fc4da..4fdfea08f 100644 Binary files a/apps/server-web/assets/linux/128x128.png and b/apps/server-web/assets/linux/128x128.png differ diff --git a/apps/server-web/assets/linux/16x16.png b/apps/server-web/assets/linux/16x16.png index faa960171..94a604a2f 100644 Binary files a/apps/server-web/assets/linux/16x16.png and b/apps/server-web/assets/linux/16x16.png differ diff --git a/apps/server-web/assets/linux/256x256.png b/apps/server-web/assets/linux/256x256.png index d449f44a8..8109b808e 100644 Binary files a/apps/server-web/assets/linux/256x256.png and b/apps/server-web/assets/linux/256x256.png differ diff --git a/apps/server-web/assets/linux/32x32.png b/apps/server-web/assets/linux/32x32.png index 9d27eebae..d55d53b02 100644 Binary files a/apps/server-web/assets/linux/32x32.png and b/apps/server-web/assets/linux/32x32.png differ diff --git a/apps/server-web/assets/linux/512x512.png b/apps/server-web/assets/linux/512x512.png index d7bd9522e..5796ce0ae 100644 Binary files a/apps/server-web/assets/linux/512x512.png and b/apps/server-web/assets/linux/512x512.png differ diff --git a/apps/server-web/assets/linux/64x64.png b/apps/server-web/assets/linux/64x64.png index ff6c4fdec..37bf8972c 100644 Binary files a/apps/server-web/assets/linux/64x64.png and b/apps/server-web/assets/linux/64x64.png differ diff --git a/apps/server-web/src/main/main.ts b/apps/server-web/src/main/main.ts index c0249ea0a..636b57d51 100644 --- a/apps/server-web/src/main/main.ts +++ b/apps/server-web/src/main/main.ts @@ -14,12 +14,16 @@ import { WebServer } from './helpers/interfaces'; import { replaceConfig } from './helpers'; import Log from 'electron-log'; import MenuBuilder from './menu'; +import { config } from '../configs/config'; console.log = Log.log; Object.assign(console, Log.functions); +app.name = config.DESCRIPTION; + + const eventEmitter = new EventEmitter(); @@ -90,8 +94,8 @@ i18nextMainBackend.on('initialized', () => { let trayMenuItems: any = []; const RESOURCES_PATH = app.isPackaged - ? path.join(process.resourcesPath, 'assets/icons/gauzy') - : path.join(__dirname, '../../assets/icons/gauzy'); + ? path.join(process.resourcesPath, 'assets') + : path.join(__dirname, '../../assets'); const getAssetPath = (...paths: string[]): string => { return path.join(RESOURCES_PATH, ...paths); @@ -111,7 +115,7 @@ const resourceDir = { }; const resourcesFiles = { webServer: 'standalone/apps/web/server.js', - iconTray: 'icons/tray/icon.png' + iconTray: 'icons/icon.png' } const devServerPath = path.join(__dirname, resourceDir.webServer, resourcesFiles.webServer); @@ -151,10 +155,11 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW') => { } const defaultOptionWindow = { + title: app.name, show: false, width: 1024, height: 728, - icon: getAssetPath('icon.png'), + icon: getAssetPath('icons/icon.png'), maximizable: false, resizable: false, webPreferences: { @@ -249,7 +254,7 @@ const onInitApplication = () => { LocalStore.setDefaultServerConfig(); // check and set default config createIntervalAutoUpdate() trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); - tray = _initTray(trayMenuItems, getAssetPath('icon.png')); + tray = _initTray(trayMenuItems, getAssetPath('icons/icon.png')); i18nextMainBackend.on('languageChanged', (lng) => { if (i18nextMainBackend.isInitialized) { trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); diff --git a/apps/server-web/src/main/menu.ts b/apps/server-web/src/main/menu.ts index 7e788eef2..be4348ceb 100644 --- a/apps/server-web/src/main/menu.ts +++ b/apps/server-web/src/main/menu.ts @@ -5,6 +5,7 @@ import { BrowserWindow, MenuItemConstructorOptions, } from 'electron'; +import { config } from '../configs/config'; interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { selector?: string; @@ -54,10 +55,10 @@ export default class MenuBuilder { buildDarwinTemplate(): MenuItemConstructorOptions[] { const subMenuAbout: DarwinMenuItemConstructorOptions = { - label: app.getName(), + label: config.DESCRIPTION || app.getName(), submenu: [ { - label: `About ${app.getName()}`, + label: `About ${config.DESCRIPTION || app.getName()}`, selector: 'orderFrontStandardAboutPanel:', }, { type: 'separator' }, @@ -148,7 +149,7 @@ export default class MenuBuilder { const subMenuView = process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' + process.env.DEBUG_PROD === 'true' ? subMenuViewDev : subMenuViewProd; @@ -173,23 +174,23 @@ export default class MenuBuilder { label: '&View', submenu: process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' + process.env.DEBUG_PROD === 'true' ? [ - { - label: '&Reload', - accelerator: 'Ctrl+R', - click: () => { - this.mainWindow.webContents.reload(); - }, + { + label: '&Reload', + accelerator: 'Ctrl+R', + click: () => { + this.mainWindow.webContents.reload(); }, - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, + }, + { + label: 'Toggle &Developer Tools', + accelerator: 'Alt+Ctrl+I', + click: () => { + this.mainWindow.webContents.toggleDevTools(); }, - ] + }, + ] : [ { label: 'Toggle &Developer Tools', @@ -198,7 +199,7 @@ export default class MenuBuilder { this.mainWindow.webContents.toggleDevTools(); }, }, - ], + ], }, { label: 'Help', @@ -206,23 +207,17 @@ export default class MenuBuilder { { label: 'Learn More', click() { - shell.openExternal('https://ever.team/'); + shell.openExternal(config.COMPANY_SITE_LINK); }, }, { label: 'Documentation', click() { shell.openExternal( - 'https://github.com/ever-co/ever-teams/blob/develop/README.md', + config.COMPANY_GITHUB_LINK ); }, }, - { - label: 'Search Issues', - click() { - shell.openExternal('https://github.com/ever-co/ever-teams/issues'); - }, - }, ], }, ]; diff --git a/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx b/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx index d25ff9e8a..239325f44 100644 --- a/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx +++ b/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx @@ -1,53 +1,6 @@ +import Logo from '../../../resources/icons/platform-logo.png'; export const EverTeamsLogo = () => { return ( - + EverTeams Logo ); }; diff --git a/apps/server-web/src/renderer/index.ejs b/apps/server-web/src/renderer/index.ejs index b2a5d5b40..b52ffacd9 100644 --- a/apps/server-web/src/renderer/index.ejs +++ b/apps/server-web/src/renderer/index.ejs @@ -1,14 +1,14 @@ - - - - Ever Teams - - -
- + + + + + + + + +
+ + diff --git a/apps/server-web/src/resources/icons/platform-logo.png b/apps/server-web/src/resources/icons/platform-logo.png new file mode 100644 index 000000000..de6094fcb Binary files /dev/null and b/apps/server-web/src/resources/icons/platform-logo.png differ diff --git a/apps/server-web/src/resources/icons/tray/icon.png b/apps/server-web/src/resources/icons/tray/icon.png deleted file mode 100644 index faa960171..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@1.25x.png b/apps/server-web/src/resources/icons/tray/icon@1.25x.png deleted file mode 100644 index 46ea6d876..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@1.25x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@1.33x.png b/apps/server-web/src/resources/icons/tray/icon@1.33x.png deleted file mode 100644 index bc73df996..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@1.33x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@1.4x.png b/apps/server-web/src/resources/icons/tray/icon@1.4x.png deleted file mode 100644 index 19ee320f0..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@1.4x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@1.5x.png b/apps/server-web/src/resources/icons/tray/icon@1.5x.png deleted file mode 100644 index 5faa32626..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@1.5x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@1.8x.png b/apps/server-web/src/resources/icons/tray/icon@1.8x.png deleted file mode 100644 index 0f97c50d5..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@1.8x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@2.5x.png b/apps/server-web/src/resources/icons/tray/icon@2.5x.png deleted file mode 100644 index aeb2dadc3..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@2.5x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@2x.png b/apps/server-web/src/resources/icons/tray/icon@2x.png deleted file mode 100644 index 9d27eebae..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@2x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@3x.png b/apps/server-web/src/resources/icons/tray/icon@3x.png deleted file mode 100644 index 999c00be1..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@3x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@4x.png b/apps/server-web/src/resources/icons/tray/icon@4x.png deleted file mode 100644 index ff6c4fdec..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@4x.png and /dev/null differ diff --git a/apps/server-web/src/resources/icons/tray/icon@5x.png b/apps/server-web/src/resources/icons/tray/icon@5x.png deleted file mode 100644 index da278ab3f..000000000 Binary files a/apps/server-web/src/resources/icons/tray/icon@5x.png and /dev/null differ diff --git a/apps/web/.env b/apps/web/.env index 947339c52..41c6a1aa3 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -85,15 +85,20 @@ NEXT_PUBLIC_DISABLE_AUTO_REFRESH=false # App Defaults APP_NAME="Ever Teams" +APP_SLOGAN_TEXT="Real-Time Clarity, Real-Time Reality™." +APP_LINK="https://ever.team" APP_SIGNATURE="Ever Teams" APP_LOGO_URL="https://app.ever.team/assets/ever-teams.png" +# Company +COMPANY_NAME="Ever Co. LTD" +COMPANY_LINK="https://ever.co" +TERMS_LINK="https://ever.team/tos" +PRIVACY_POLICY_LINK="https://ever.team/privacy" -#Footer links -NEXT_PUBLIC_EVER_TEAMS_LINK="https://ever.team" -NEXT_PUBLIC_EVER_PLATFORM_LINK="https://ever.co" -NEXT_PUBLIC_EVER_LEGAL_TERM="https://demo.gauzy.co/#/pages/legal/terms" -NEXT_PUBLIC_EVER_LEGAL_PRIVACY="https://demo.gauzy.co/#/pages/legal/privacy" +#Main picture +MAIN_PICTURE='/assets/cover/auth-bg-cover.png' +MAIN_PICTURE_DARK='/assets/cover/auth-bg-cover-dark.png' # Cookies NEXT_PUBLIC_COOKIE_DOMAINS=ever.team diff --git a/apps/web/.env.sample b/apps/web/.env.sample index 8074779ff..7a6cedce2 100644 --- a/apps/web/.env.sample +++ b/apps/web/.env.sample @@ -46,15 +46,21 @@ SMTP_USERNAME= SMTP_PASSWORD= # App Defaults -APP_NAME='Ever Teams' -APP_SIGNATURE='Ever Teams' -APP_LOGO_URL='https://app.ever.team/assets/ever-teams.png' - -#Footer links -NEXT_PUBLIC_EVER_TEAMS_LINK="https://ever.team" -NEXT_PUBLIC_EVER_PLATFORM_LINK="https://ever.co" -NEXT_PUBLIC_EVER_LEGAL_TERM="https://demo.gauzy.co/#/pages/legal/terms" -NEXT_PUBLIC_EVER_LEGAL_PRIVACY="https://demo.gauzy.co/#/pages/legal/privacy" +APP_NAME="Ever Teams" +APP_SLOGAN_TEXT="Real-Time Clarity, Real-Time Reality™." +APP_LINK="https://ever.team" +APP_SIGNATURE="Ever Teams" +APP_LOGO_URL="https://app.ever.team/assets/ever-teams.png" + +# Company +COMPANY_NAME="Ever Co. LTD" +COMPANY_LINK="https://ever.co" +TERMS_LINK="https://ever.team/tos" +PRIVACY_POLICY_LINK="https://ever.team/privacy" + +#Main picture +MAIN_PICTURE='/assets/cover/auth-bg-cover.png' +MAIN_PICTURE_DARK='/assets/cover/auth-bg-cover-dark.png' # Cookies NEXT_PUBLIC_COOKIE_DOMAINS=ever.team diff --git a/apps/web/app/[locale]/auth/password/component.tsx b/apps/web/app/[locale]/auth/password/component.tsx index dec387ab3..ad22c6d7c 100644 --- a/apps/web/app/[locale]/auth/password/component.tsx +++ b/apps/web/app/[locale]/auth/password/component.tsx @@ -123,6 +123,7 @@ function WorkSpaceScreen({ form, className }: { form: TAuthenticationPassword } [form.workspaces] ); + useEffect(() => { if (form.workspaces.length === 1 && !hasMultipleTeams) { setTimeout(() => { @@ -149,6 +150,7 @@ function WorkSpaceScreen({ form, className }: { form: TAuthenticationPassword } ); setSelectedTeam(lastSelectedTeamId); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [form.workspaces]); useEffect(() => { diff --git a/apps/web/app/[locale]/calendar/component.tsx b/apps/web/app/[locale]/calendar/component.tsx index 6656ec634..f000fa1d8 100644 --- a/apps/web/app/[locale]/calendar/component.tsx +++ b/apps/web/app/[locale]/calendar/component.tsx @@ -70,10 +70,16 @@ export function HeadTimeSheet({ timesheet, isOpen, openModal, closeModal }: { ti return (
- + { + closeModal ? ( + + ) : + <> + } +
{timesheet === 'TimeSheet' && (
diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index c7dff39c2..4b2aab1d7 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -62,7 +62,7 @@ const Kanban = () => { { title: activeTeam?.name || '', href: '/' }, { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` } ], - [activeTeam?.name, currentLocale] + [activeTeam?.name, currentLocale, t] ); const activeTeamMembers = activeTeam?.members ? activeTeam.members : []; diff --git a/apps/web/app/[locale]/meet/livekit/component.tsx b/apps/web/app/[locale]/meet/livekit/component.tsx index f362623f0..ad382fc81 100644 --- a/apps/web/app/[locale]/meet/livekit/component.tsx +++ b/apps/web/app/[locale]/meet/livekit/component.tsx @@ -41,7 +41,7 @@ function LiveKitPage() {
{token && roomName &&
- + {member ? : <>}
{user?.email} diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts index 9efedc8d0..82adf0d6b 100644 --- a/apps/web/app/constants.ts +++ b/apps/web/app/constants.ts @@ -77,6 +77,16 @@ export const APP_NAME = process.env.APP_NAME || 'Ever Teams'; export const APP_SIGNATURE = process.env.APP_SIGNATURE || 'Ever Teams'; export const APP_LOGO_URL = process.env.APP_LOGO_URL || 'https://app.ever.team/assets/ever-teams.png'; export const APP_LINK = process.env.APP_LINK || 'https://ever.team/'; +export const APP_SLOGAN_TEXT = process.env.APP_SLOGAN_TEXT || 'Real-Time Clarity, Real-Time Reality™.'; + +export const COMPANY_NAME = process.env.COMPANY_NAME || 'Ever Co. LTD'; +export const COMPANY_LINK = process.env.COMPANY_LINK || 'https://ever.co'; + +export const TERMS_LINK = process.env.TERMS_LINK || 'https://ever.team/tos'; +export const PRIVACY_POLICY_LINK = process.env.PRIVACY_POLICY_LINK || 'https://ever.team/privacy'; + +export const MAIN_PICTURE = process.env.MAIN_PICTURE || '/assets/cover/auth-bg-cover.png'; +export const MAIN_PICTURE_DARK = process.env.MAIN_PICTURE_DARK || '/assets/cover/auth-bg-cover-dark.png'; export const CHARACTER_LIMIT_TO_SHOW = 20; @@ -275,7 +285,7 @@ export const TASKS_ESTIMATE_HOURS_MODAL_DATE = 'tasks-estimate-hours-modal-date' export const DAILY_PLAN_ESTIMATE_HOURS_MODAL_DATE = 'daily-plan-estimate-hours-modal'; export const DEFAULT_PLANNED_TASK_ID = 'default-planned-task-id'; -// OAuth providers keys +// OAuth provider's keys export const APPLE_CLIENT_ID = process.env.APPLE_CLIENT_ID; export const APPLE_CLIENT_SECRET = process.env.APPLE_CLIENT_SECRET; diff --git a/apps/web/app/helpers/daily-plan-estimated.ts b/apps/web/app/helpers/daily-plan-estimated.ts index 1c18fb348..6d81d429d 100644 --- a/apps/web/app/helpers/daily-plan-estimated.ts +++ b/apps/web/app/helpers/daily-plan-estimated.ts @@ -20,9 +20,10 @@ export const dailyPlanCompareEstimated = (plans: IDailyPlan[]): IDailyPlanCompar }; } - const workTimePlanned = convertHourToSeconds(plan.workTimePlanned!); + const workTimePlanned = plan.workTimePlanned ? convertHourToSeconds(plan.workTimePlanned) : 0; const times = plan.tasks?.map((task) => task.estimate).filter((time): time is number => typeof time === 'number') ?? []; - const estimated = plan.tasks?.map((task) => task.estimate! > 0); + const estimated = plan.tasks?.map((task) => (task.estimate ?? 0) > 0); + let estimatedTime = 0; if (times.length > 0) { diff --git a/apps/web/app/helpers/drag-and-drop.ts b/apps/web/app/helpers/drag-and-drop.ts index c1d3af554..639766aca 100644 --- a/apps/web/app/helpers/drag-and-drop.ts +++ b/apps/web/app/helpers/drag-and-drop.ts @@ -1,48 +1,52 @@ -import { IDailyPlan, ITeamTask } from "@app/interfaces"; -import { DropResult } from "react-beautiful-dnd"; +import { IDailyPlan, ITeamTask } from '@app/interfaces'; +import { DropResult } from 'react-beautiful-dnd'; -export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setPlans: React.Dispatch>) => { - const { source, destination } = results; +export const handleDragAndDrop = ( + results: DropResult, + plans: IDailyPlan[], + setPlans: React.Dispatch> +) => { + const { source, destination } = results; - if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; + if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; - const newPlans = [...plans]; + const newPlans = [...plans]; - const planSourceIndex = newPlans.findIndex(plan => plan.id === source.droppableId); - const planDestinationIndex = newPlans.findIndex(plan => plan.id === destination.droppableId); + const planSourceIndex = newPlans.findIndex((plan) => plan.id === source.droppableId); + const planDestinationIndex = newPlans.findIndex((plan) => plan.id === destination.droppableId); - const newSourceTasks = [...newPlans[planSourceIndex].tasks!]; - const newDestinationTasks = source.droppableId !== destination.droppableId - ? [...newPlans[planDestinationIndex].tasks!] - : newSourceTasks; + const newSourceTasks = [...(newPlans[planSourceIndex].tasks ?? [])]; + const newDestinationTasks = + source.droppableId !== destination.droppableId + ? [...(newPlans[planDestinationIndex].tasks ?? [])] + : newSourceTasks; - const [deletedTask] = newSourceTasks.splice(source.index, 1); - newDestinationTasks.splice(destination.index, 0, deletedTask); + const [deletedTask] = newSourceTasks.splice(source.index, 1); + newDestinationTasks.splice(destination.index, 0, deletedTask); - newPlans[planSourceIndex] = { - ...newPlans[planSourceIndex], - tasks: newSourceTasks, - }; - newPlans[planDestinationIndex] = { - ...newPlans[planDestinationIndex], - tasks: newDestinationTasks, - }; - setPlans(newPlans); + newPlans[planSourceIndex] = { + ...newPlans[planSourceIndex], + tasks: newSourceTasks + }; + newPlans[planDestinationIndex] = { + ...newPlans[planDestinationIndex], + tasks: newDestinationTasks + }; + setPlans(newPlans); }; - export const handleDragAndDropDailyOutstandingAll = ( - results: DropResult, - tasks: ITeamTask[], - setTasks: React.Dispatch> + results: DropResult, + tasks: ITeamTask[], + setTasks: React.Dispatch> ) => { - const { source, destination } = results; + const { source, destination } = results; - if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; + if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; - const newTasks = [...tasks]; - const [movedTask] = newTasks.splice(source.index, 1); - newTasks.splice(destination.index, 0, movedTask); + const newTasks = [...tasks]; + const [movedTask] = newTasks.splice(source.index, 1); + newTasks.splice(destination.index, 0, movedTask); - setTasks(newTasks); + setTasks(newTasks); }; diff --git a/apps/web/app/helpers/plan-day-badge.ts b/apps/web/app/helpers/plan-day-badge.ts index 1bd23d610..409e350e7 100644 --- a/apps/web/app/helpers/plan-day-badge.ts +++ b/apps/web/app/helpers/plan-day-badge.ts @@ -29,26 +29,25 @@ export const planBadgeContent = ( } }; - -export const planBadgeContPast = ( - dailyPlan: IDailyPlan[], - taskId: ITeamTask['id'] -): string | null => { +export const planBadgeContPast = (dailyPlan: IDailyPlan[], taskId: ITeamTask['id']): string | null => { const today = new Date().toISOString().split('T')[0]; - const dailyPlanDataPast = dailyPlan.filter(plan => new Date(plan.date) < new Date(today)); - const allTasks = dailyPlanDataPast.flatMap(plan => plan.tasks); - const taskCount: { [key: string]: number } = allTasks?.reduce((acc, task) => { - if (task && task.id) { acc[task.id] = (acc[task.id] || 0) + 1; } - return acc; - }, {} as { [key: string]: number }); - - const dailyPlanPast = allTasks?.filter(task => task && taskCount[task.id] === 1); + const dailyPlanDataPast = dailyPlan.filter((plan) => new Date(plan.date) < new Date(today)); + const allTasks = dailyPlanDataPast.flatMap((plan) => plan.tasks); + const taskCount: { [key: string]: number } = allTasks?.reduce( + (acc, task) => { + if (task && task.id) { + acc[task.id] = (acc[task.id] || 0) + 1; + } + return acc; + }, + {} as { [key: string]: number } + ); + + const dailyPlanPast = allTasks?.filter((task) => task && taskCount[task.id] === 1); const filterDailyPlan = dailyPlanPast.filter((plan) => plan?.id === taskId); if (filterDailyPlan.length > 0) { return 'Planned'; } else { return null; } - - -} +}; diff --git a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts index 724f309e1..9f2ecb175 100644 --- a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts +++ b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts @@ -14,6 +14,7 @@ import { usePathname, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useQuery } from '../useQuery'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; type AuthCodeRef = { focus: () => void; @@ -24,6 +25,7 @@ export function useAuthenticationPasscode() { const router = useRouter(); const pathname = usePathname(); const query = useSearchParams(); + const t = useTranslations(); const queryTeamId = query?.get('teamId'); @@ -72,83 +74,104 @@ export function useAuthenticationPasscode() { setFormValues((prevState) => ({ ...prevState, [name]: value })); }; + const signInToWorkspaceRequest = useCallback( + (params: { + email: string; + token: string; + selectedTeam: string; + code?: string; + defaultTeamId?: string; + lastTeamId?: string; + }) => { + signInWorkspaceQueryCall(params) + .then(() => { + setAuthenticated(true); + router.push('/'); + }) + .catch((err: AxiosError) => { + if (err.response?.status === 400) { + setErrors((err.response?.data as any)?.errors || {}); + } + + inputCodeRef.current?.clear(); + }); + }, + [signInWorkspaceQueryCall, router] + ); + /** * Verify auth request */ - const verifySignInEmailConfirmRequest = async ({ - email, - code, - lastTeamId - }: { - email: string; - code: string; - lastTeamId?: string; - }) => { - signInEmailConfirmQueryCall(email, code) - .then((res) => { - if ('team' in res.data) { - router.replace('/'); - return; - } - - const checkError: { - message: string; - } = res.data as any; - - const isError = checkError.message === 'Unauthorized'; - - if (isError) { - setErrors({ - code: 'Invalid code. Please try again.' - }); - } else { - setErrors({}); - } - - const data = res.data as ISigninEmailConfirmResponse; - if (!data.workspaces) { - return; - } - - if (data && Array.isArray(data.workspaces) && data.workspaces.length > 0) { - setWorkspaces(data.workspaces); - setDefaultTeamId(data.defaultTeamId); - - setScreen('workspace'); - } - - // If user tries to login from public Team Page as an Already a Member - // Redirect to the current team automatically - if (pathname === '/team/[teamId]/[profileLink]' && data.workspaces.length) { - if (queryTeamId) { - const currentWorkspace = data.workspaces.find((workspace) => - workspace.current_teams.map((item) => item.team_id).includes(queryTeamId as string) - ); - - signInToWorkspaceRequest({ - email: email, - code: code, - token: currentWorkspace?.token as string, - selectedTeam: queryTeamId as string, - lastTeamId + const verifySignInEmailConfirmRequest = useCallback( + async ({ email, code, lastTeamId }: { email: string; code: string; lastTeamId?: string }) => { + signInEmailConfirmQueryCall(email, code) + .then((res) => { + if ('team' in res.data) { + router.replace('/'); + return; + } + + const checkError: { + message: string; + } = res.data as any; + + const isError = checkError.message === 'Unauthorized'; + + if (isError) { + setErrors({ + code: t('pages.auth.INVALID_CODE_TRY_AGAIN') }); + } else { + setErrors({}); } - } - - // if (res.data?.status !== 200 && res.data?.status !== 201) { - // setErrors({ code: t('pages.auth.INVALID_INVITE_CODE_MESSAGE') }); - // } - }) - .catch((err: AxiosError<{ errors: Record }, any> | { errors: Record }) => { - if (isAxiosError(err)) { - if (err.response?.status === 400) { - setErrors(err.response.data?.errors || {}); + + const data = res.data as ISigninEmailConfirmResponse; + if (!data.workspaces) { + return; + } + + if (data && Array.isArray(data.workspaces) && data.workspaces.length > 0) { + setWorkspaces(data.workspaces); + setDefaultTeamId(data.defaultTeamId); + + setScreen('workspace'); + } + + // If user tries to login from public Team Page as an Already a Member + // Redirect to the current team automatically + if (pathname === '/team/[teamId]/[profileLink]' && data.workspaces.length) { + if (queryTeamId) { + const currentWorkspace = data.workspaces.find((workspace) => + workspace.current_teams.map((item) => item.team_id).includes(queryTeamId as string) + ); + + signInToWorkspaceRequest({ + email: email, + code: code, + token: currentWorkspace?.token as string, + selectedTeam: queryTeamId as string, + lastTeamId + }); + } } - } else { - setErrors(err.errors || {}); - } - }); - }; + + // if (res.data?.status !== 200 && res.data?.status !== 201) { + // setErrors({ code: t('pages.auth.INVALID_INVITE_CODE_MESSAGE') }); + // } + }) + .catch((err: AxiosError<{ errors: Record }, any> | { errors: Record }) => { + if (isAxiosError(err)) { + if (err.response?.status === 400) { + setErrors(err.response.data?.errors || {}); + } + } else { + setErrors(err.errors || {}); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [signInEmailConfirmQueryCall, t, signInToWorkspaceRequest, router, pathname, queryTeamId] + ); const verifyPasscodeRequest = useCallback( ({ email, code }: { email: string; code: string }) => { @@ -175,28 +198,6 @@ export function useAuthenticationPasscode() { [queryCall] ); - const signInToWorkspaceRequest = (params: { - email: string; - token: string; - selectedTeam: string; - code?: string; - defaultTeamId?: string; - lastTeamId?: string; - }) => { - signInWorkspaceQueryCall(params) - .then(() => { - setAuthenticated(true); - router.push('/'); - }) - .catch((err: AxiosError) => { - if (err.response?.status === 400) { - setErrors((err.response?.data as any)?.errors || {}); - } - - inputCodeRef.current?.clear(); - }); - }; - const handleCodeSubmit = (e: React.FormEvent) => { e.preventDefault(); setErrors({}); diff --git a/apps/web/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts index c8a561547..a8be0524c 100644 --- a/apps/web/app/hooks/features/useDailyPlan.ts +++ b/apps/web/app/hooks/features/useDailyPlan.ts @@ -1,7 +1,7 @@ 'use client'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useQuery } from '../useQuery'; import { activeTeamState, @@ -269,77 +269,87 @@ export function useDailyPlan() { ] ); - const ascSortedPlans = - profileDailyPlans.items && - [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); - const futurePlans = ascSortedPlans?.filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization - return planDate.getTime() >= today.getTime(); - }); - - const descSortedPlans = - profileDailyPlans.items && - [...profileDailyPlans.items].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - const pastPlans = descSortedPlans?.filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(0, 0, 0, 0); // Set today time to exclude timestamps in comparization - return planDate.getTime() < today.getTime(); - }); - - const todayPlan = - profileDailyPlans.items && - [...profileDailyPlans.items].filter((plan) => + const ascSortedPlans = useMemo(() => { + return [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + }, [profileDailyPlans]); + + const futurePlans = useMemo(() => { + return ascSortedPlans?.filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization + return planDate.getTime() >= today.getTime(); + }); + }, [ascSortedPlans]); + + const descSortedPlans = useMemo(() => { + return [...profileDailyPlans.items].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + }, [profileDailyPlans]); + + const pastPlans = useMemo(() => { + return descSortedPlans?.filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(0, 0, 0, 0); // Set today time to exclude timestamps in comparization + return planDate.getTime() < today.getTime(); + }); + }, [descSortedPlans]); + + const todayPlan = useMemo(() => { + return [...profileDailyPlans.items].filter((plan) => plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0]) ); + }, [profileDailyPlans]); - const todayTasks = todayPlan - .map((plan) => { - return plan.tasks ? plan.tasks : []; - }) - .flat(); - - const futureTasks = - futurePlans && - futurePlans + const todayTasks = useMemo(() => { + return todayPlan .map((plan) => { return plan.tasks ? plan.tasks : []; }) .flat(); + }, [todayPlan]); - const outstandingPlans = - profileDailyPlans.items && - [...profileDailyPlans.items] - // Exclude today plans - .filter((plan) => !plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0])) - - // Exclude future plans - .filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization - return planDate.getTime() <= today.getTime(); + const futureTasks = useMemo(() => { + return futurePlans + .map((plan) => { + return plan.tasks ? plan.tasks : []; }) - .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) - .map((plan) => ({ - ...plan, - // Include only no completed tasks - tasks: plan.tasks?.filter((task) => task.status !== 'completed') - })) - .map((plan) => ({ - ...plan, - // Include only tasks that are not added yet to the today plan or future plans - tasks: plan.tasks?.filter( - (_task) => ![...todayTasks, ...futureTasks].find((task) => task.id === _task.id) - ) - })) - .filter((plan) => plan.tasks?.length && plan.tasks.length > 0); - - const sortedPlans = - profileDailyPlans.items && - [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + .flat(); + }, [futurePlans]); + + const outstandingPlans = useMemo(() => { + return ( + [...profileDailyPlans.items] + // Exclude today plans + .filter((plan) => !plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0])) + + // Exclude future plans + .filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization + return planDate.getTime() <= today.getTime(); + }) + .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) + .map((plan) => ({ + ...plan, + // Include only no completed tasks + tasks: plan.tasks?.filter((task) => task.status !== 'completed') + })) + .map((plan) => ({ + ...plan, + // Include only tasks that are not added yet to the today plan or future plans + tasks: plan.tasks?.filter( + (_task) => ![...todayTasks, ...futureTasks].find((task) => task.id === _task.id) + ) + })) + .filter((plan) => plan.tasks?.length && plan.tasks.length > 0) + ); + }, [profileDailyPlans, todayTasks, futureTasks]); + + const sortedPlans = useMemo(() => { + return [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + }, [profileDailyPlans]); useEffect(() => { if (firstLoad) { diff --git a/apps/web/app/hooks/features/useEmployee.ts b/apps/web/app/hooks/features/useEmployee.ts index 4ca1d9037..5cf037242 100644 --- a/apps/web/app/hooks/features/useEmployee.ts +++ b/apps/web/app/hooks/features/useEmployee.ts @@ -55,7 +55,7 @@ export const useEmployeeUpdate = () => { .catch((error) => { console.log(error); }); - }, []); + }, [employeeUpdateQuery]); return { updateEmployee, isLoading }; }; diff --git a/apps/web/app/hooks/features/useGetTasksStatsData.ts b/apps/web/app/hooks/features/useGetTasksStatsData.ts index 37e8548d9..f4d8a67ff 100644 --- a/apps/web/app/hooks/features/useGetTasksStatsData.ts +++ b/apps/web/app/hooks/features/useGetTasksStatsData.ts @@ -32,7 +32,7 @@ export function useGetTasksStatsData(employeeId: string | undefined, triggerWith if (entry?.isIntersecting && supported) { loadTaskStats(); } - }, [employeeId, triggerWithIObserver, entry]); + }, [employeeId, triggerWithIObserver, entry, getTasksStatsData]); return IObserverRef; } diff --git a/apps/web/app/hooks/features/useOrganizationTeamManagers.ts b/apps/web/app/hooks/features/useOrganizationTeamManagers.ts index edaab309e..b379bd566 100644 --- a/apps/web/app/hooks/features/useOrganizationTeamManagers.ts +++ b/apps/web/app/hooks/features/useOrganizationTeamManagers.ts @@ -2,18 +2,21 @@ import { useRecoilValue } from 'recoil'; import { useAuthenticateUser } from './useAuthenticateUser'; import { useOrganizationTeams } from './useOrganizationTeams'; import { filterValue } from '@app/stores/all-teams'; +import { useMemo } from 'react'; export function useOrganizationAndTeamManagers() { const { user } = useAuthenticateUser(); const { teams } = useOrganizationTeams(); const { value: filtered } = useRecoilValue(filterValue); - const userManagedTeams = teams.filter((team) => - team.members.some((member) => member.employee?.user?.id === user?.id && member.role?.name === 'MANAGER') - ); + const userManagedTeams = useMemo(() => { + return teams.filter((team) => + team.members.some((member) => member.employee?.user?.id === user?.id && member.role?.name === 'MANAGER') + ); + }, [teams, user]); - const filteredTeams = - filtered === 'all' + const filteredTeams = useMemo(() => { + return filtered === 'all' ? userManagedTeams : filtered === 'pause' ? userManagedTeams.map((team) => ({ @@ -36,6 +39,7 @@ export function useOrganizationAndTeamManagers() { members: team.members.filter((member) => member.employee.acceptDate) })) : userManagedTeams; + }, [filtered, userManagedTeams]); return { userManagedTeams, diff --git a/apps/web/app/hooks/features/usePagination.ts b/apps/web/app/hooks/features/usePagination.ts index 3b4630b7b..f1a19a96a 100644 --- a/apps/web/app/hooks/features/usePagination.ts +++ b/apps/web/app/hooks/features/usePagination.ts @@ -48,7 +48,7 @@ export function useScrollPagination({ setPage(1); setSlicedItems(items.slice(0, defaultItemsPerPage)); } - }, [enabled, items]); + }, [enabled, items, defaultItemsPerPage]); useEffect(() => { const container = $scrollableElement.current; @@ -68,6 +68,8 @@ export function useScrollPagination({ return () => { container.removeEventListener('scroll', handleScroll); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [$scrollableElement.current, enabled]); useEffect(() => { diff --git a/apps/web/app/hooks/features/useTaskInput.ts b/apps/web/app/hooks/features/useTaskInput.ts index 241ca8f18..bb204f8f6 100644 --- a/apps/web/app/hooks/features/useTaskInput.ts +++ b/apps/web/app/hooks/features/useTaskInput.ts @@ -170,13 +170,17 @@ export function useTaskInput({ [updateTask, userRef] ); - const closedTaskCount = filteredTasks2.filter((f_task) => { - return f_task.status === 'closed'; - }).length; - - const openTaskCount = filteredTasks2.filter((f_task) => { - return f_task.status !== 'closed'; - }).length; + const closedTaskCount = useMemo(() => { + return filteredTasks2.filter((f_task) => { + return f_task.status === 'closed'; + }).length; + }, [filteredTasks2]); + + const openTaskCount = useMemo(() => { + return filteredTasks2.filter((f_task) => { + return f_task.status !== 'closed'; + }).length; + }, [filteredTasks2]); useEffect(() => { setTaskIssue(''); diff --git a/apps/web/app/hooks/features/useTeamTasks.ts b/apps/web/app/hooks/features/useTeamTasks.ts index a60f7d6c4..f317fb5fd 100644 --- a/apps/web/app/hooks/features/useTeamTasks.ts +++ b/apps/web/app/hooks/features/useTeamTasks.ts @@ -115,7 +115,7 @@ export function useTeamTasks() { return res; }); }, - [getTasksByIdQueryCall, setDetailedTask] + [getTasksByIdQueryCall, setDetailedTask, tasksRef] ); const getTasksByEmployeeId = useCallback( @@ -285,8 +285,8 @@ export function useTeamTasks() { // TODO: Make it dynamic when we add Dropdown in Navbar ...(activeTeam?.projects && activeTeam?.projects.length > 0 ? { - projectId: activeTeam.projects[0].id - } + projectId: activeTeam.projects[0].id + } : {}), ...(description ? { description: `

${description}

` } : {}), ...(members ? { members } : {}), @@ -451,7 +451,17 @@ export function useTeamTasks() { } } }, - [setActiveTeamTask, setActiveUserTaskCookieCb, updateOrganizationTeamEmployeeActiveTask, activeTeam, authUser] + [ + setActiveTeamTask, + setActiveUserTaskCookieCb, + updateOrganizationTeamEmployeeActiveTask, + activeTeam, + authUser, + $memberActiveTaskId, + $user, + tasksRef, + updateTask + ] ); const deleteEmployeeFromTasks = useCallback( diff --git a/apps/web/app/hooks/features/useTimeLogs.ts b/apps/web/app/hooks/features/useTimeLogs.ts index bd18fa220..49e9b1f96 100644 --- a/apps/web/app/hooks/features/useTimeLogs.ts +++ b/apps/web/app/hooks/features/useTimeLogs.ts @@ -43,7 +43,6 @@ export function useTimeLogs() { profile.member?.employeeId, queryTimerLogsDailyReport, setTimerLogsDailyReport, - user?.employee.id, user?.employee.organizationId, user?.tenantId, ] diff --git a/apps/web/app/hooks/features/useTimer.ts b/apps/web/app/hooks/features/useTimer.ts index d94e77b27..a54cce2e3 100644 --- a/apps/web/app/hooks/features/useTimer.ts +++ b/apps/web/app/hooks/features/useTimer.ts @@ -104,7 +104,7 @@ function useLocalTimeCounter(timerStatus: ITimerStatus | null, activeTeamTask: I // THis is form constant update of the progress line timerSecondsRef.current = useMemo(() => { - // if (!firstLoad) return 0; + if (!firstLoad) return 0; if (seconds > timerSecondsRef.current) { return seconds; } diff --git a/apps/web/app/hooks/features/useUserProfilePage.ts b/apps/web/app/hooks/features/useUserProfilePage.ts index 90303976a..74d5cf3fe 100644 --- a/apps/web/app/hooks/features/useUserProfilePage.ts +++ b/apps/web/app/hooks/features/useUserProfilePage.ts @@ -23,11 +23,13 @@ export function useUserProfilePage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [params, userMemberId]); - const members = activeTeam?.members || []; + const members = useMemo(() => activeTeam?.members || [], [activeTeam]); - const matchUser = members.find((m) => { - return m.employee.userId === memberId; - }); + const matchUser = useMemo(() => { + return members.find((m) => { + return m.employee.userId === memberId; + }); + }, [members, memberId]); const isAuthUser = auth?.employee?.userId === memberId; diff --git a/apps/web/app/hooks/useDateRange.ts b/apps/web/app/hooks/useDateRange.ts index 4c8cfd08e..146737a74 100644 --- a/apps/web/app/hooks/useDateRange.ts +++ b/apps/web/app/hooks/useDateRange.ts @@ -1,18 +1,24 @@ -import { dateRangeAllPlanState, dateRangeFuturePlanState, dateRangePastPlanState, getFirstAndLastDateState } from "@app/stores"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { + dateRangeAllPlanState, + dateRangeFuturePlanState, + dateRangePastPlanState, + getFirstAndLastDateState +} from '@app/stores'; +import { useRecoilState, useRecoilValue } from 'recoil'; export const useDateRange = (tab: string | any) => { - const itemsDate = useRecoilValue(getFirstAndLastDateState); - const [dateFuture, setDateFuture] = useRecoilState(dateRangeFuturePlanState); - const [dateAllPlan, setDateAllPlan] = useRecoilState(dateRangeAllPlanState); - const [datePastPlan, setDatePastPlan] = useRecoilState(dateRangePastPlanState); - switch (tab) { - case 'Future Tasks': - return { date: dateFuture, setDate: setDateFuture, data: itemsDate }; - case 'Past Tasks': - return { date: datePastPlan, setDate: setDatePastPlan, data: itemsDate }; - case 'All Tasks': - default: - return { date: dateAllPlan, setDate: setDateAllPlan, data: itemsDate }; - } -} + const itemsDate = useRecoilValue(getFirstAndLastDateState); + const [dateFuture, setDateFuture] = useRecoilState(dateRangeFuturePlanState); + const [dateAllPlan, setDateAllPlan] = useRecoilState(dateRangeAllPlanState); + const [datePastPlan, setDatePastPlan] = useRecoilState(dateRangePastPlanState); + + switch (tab) { + case 'Future Tasks': + return { date: dateFuture, setDate: setDateFuture, data: itemsDate }; + case 'Past Tasks': + return { date: datePastPlan, setDate: setDatePastPlan, data: itemsDate }; + case 'All Tasks': + default: + return { date: dateAllPlan, setDate: setDateAllPlan, data: itemsDate }; + } +}; diff --git a/apps/web/app/stores/daily-plan.ts b/apps/web/app/stores/daily-plan.ts index af57cd9da..95f5c0e59 100644 --- a/apps/web/app/stores/daily-plan.ts +++ b/apps/web/app/stores/daily-plan.ts @@ -3,7 +3,6 @@ import { IDailyPlan, PaginationResponse } from '@app/interfaces'; import { DateRange } from 'react-day-picker'; import { isTestDateRange } from '@app/helpers'; - export const dailyPlanListState = atom>({ key: 'dailyPlanListState', default: { items: [], total: 0 } @@ -47,70 +46,81 @@ export const activeDailyPlanState = selector({ return dailyPlans.items.find((plan) => plan.id === activeId) || dailyPlans.items[0] || null; } }); -const createDailyPlanCountFilterAtom = (key: string | any) => atom( - { + +const createDailyPlanCountFilterAtom = (key: string | any) => + atom({ key, default: 0 - } -) + }); -const createDailyPlanAtom = (key: string | any) => atom({ - key, - default: [], -}); +const createDailyPlanAtom = (key: string | any) => + atom({ + key, + default: [] + }); -const createDateRangeAtom = (key: string | any) => atom({ - key, - default: { from: undefined, to: undefined } -}); +const createDateRangeAtom = (key: string | any) => + atom({ + key, + default: { from: undefined, to: undefined } + }); export const dataDailyPlanState = createDailyPlanAtom('originalPlanState'); -const getFirstAndLastDateSelector = (key: string | any) => selector({ - key, - get: ({ get }) => { - const itemsData = get(dataDailyPlanState); - if (!itemsData?.length) return { from: null, to: null }; - const sortedData = itemsData?.slice().sort((a, b) => new Date(a.date)?.getTime() - new Date(b?.date).getTime()); - return { from: new Date(sortedData[0]?.date), to: new Date(sortedData[sortedData.length - 1]?.date) }; - } -}); -export const dateRangeDailyPlanState = createDateRangeAtom('dateRangeDailyPlanState') - -const createFilteredDailyPlanDataSelector = (key: string | any, dateRangeState: RecoilState, originalDataState: RecoilState) => selector({ - key, - get: ({ get }) => { - const dateRange = get(dateRangeState); - const data = get(originalDataState); - if (!dateRange || !data.length) return data; - const { from, to } = dateRange; - if (!from && !to) { - return data +const getFirstAndLastDateSelector = (key: string | any) => + selector({ + key, + get: ({ get }) => { + const itemsData = get(dataDailyPlanState); + if (!itemsData?.length) return { from: null, to: null }; + const sortedData = itemsData + ?.slice() + .sort((a, b) => new Date(a.date)?.getTime() - new Date(b?.date).getTime()); + return { from: new Date(sortedData[0]?.date), to: new Date(sortedData[sortedData.length - 1]?.date) }; } - return data.filter((plan) => { - const itemDate = new Date(plan.date); - return isTestDateRange(itemDate, from, to); - }); - }, -}); + }); +export const dateRangeDailyPlanState = createDateRangeAtom('dateRangeDailyPlanState'); + +const createFilteredDailyPlanDataSelector = ( + key: string | any, + dateRangeState: RecoilState, + originalDataState: RecoilState +) => + selector({ + key, + get: ({ get }) => { + const dateRange = get(dateRangeState); + const data = get(originalDataState); + if (!dateRange || !data.length) return data; + const { from, to } = dateRange; + if (!from && !to) { + return data; + } + return data.filter((plan) => { + const itemDate = new Date(plan.date); + return isTestDateRange(itemDate, from, to); + }); + } + }); export const dataDailyPlanAllFilterState = createDailyPlanAtom('dataDailyPlanAllFilterState'); export const dateRangeAllPlanState = createDateRangeAtom('dateRangeAllPlanState'); -export const setCreateFilteredDailyPlanDataSelector = () => selector({ - key: 'dataDailyPlanAllFilter', - get: ({ get }) => { - const dateRange = get(dateRangeAllPlanState); - const data = get(dataDailyPlanAllFilterState); - if (!dateRange || !data.length) return data; - const { from, to } = dateRange; - if (!from && !to) { - return data +export const setCreateFilteredDailyPlanDataSelector = () => + selector({ + key: 'dataDailyPlanAllFilter', + get: ({ get }) => { + const dateRange = get(dateRangeAllPlanState); + const data = get(dataDailyPlanAllFilterState); + if (!dateRange || !data.length) return data; + const { from, to } = dateRange; + if (!from && !to) { + return data; + } + return data.filter((plan) => { + const itemDate = new Date(plan.date); + return isTestDateRange(itemDate, from, to); + }); } - return data.filter((plan) => { - const itemDate = new Date(plan.date); - return isTestDateRange(itemDate, from, to); - }); - }, -}); + }); export const dataDailyPlanCountFilterState = createDailyPlanCountFilterAtom('dataDailyPlanCountFilterState'); export const dateRangePastPlanState = createDateRangeAtom('dateRangePastPlanState'); diff --git a/apps/web/app/stores/employee.ts b/apps/web/app/stores/employee.ts index ecd867c97..7fbeca90b 100644 --- a/apps/web/app/stores/employee.ts +++ b/apps/web/app/stores/employee.ts @@ -14,5 +14,5 @@ export const workingEmployeesEmailState = atom({ export const employeeUpdateState = atom({ key: 'employeeUpdateState', - default: null!, + default: undefined, }) diff --git a/apps/web/components/layout/Meta.tsx b/apps/web/components/layout/Meta.tsx index c61bb0d65..5f3ecccfd 100644 --- a/apps/web/components/layout/Meta.tsx +++ b/apps/web/components/layout/Meta.tsx @@ -1,7 +1,7 @@ import Head from 'next/head'; import { MetaProps } from '../../app/interfaces/hooks'; -const Meta = ({ title, keywords, description }: MetaProps) => { +const Meta = ({ title = 'Gauzy Teams', keywords = '', description = '' }: Partial) => { return ( @@ -11,10 +11,4 @@ const Meta = ({ title, keywords, description }: MetaProps) => { ); }; -Meta.defaultProps = { - title: 'Gauzy Teams', - keywords: '', - description: '' -}; - export default Meta; diff --git a/apps/web/components/layout/footer/footer.tsx b/apps/web/components/layout/footer/footer.tsx index ad7a71a13..b5562ca49 100644 --- a/apps/web/components/layout/footer/footer.tsx +++ b/apps/web/components/layout/footer/footer.tsx @@ -1,3 +1,4 @@ +import { APP_LINK, APP_NAME, COMPANY_LINK, COMPANY_NAME, PRIVACY_POLICY_LINK, TERMS_LINK } from '@app/constants'; import ToggleThemeContainer from '../toggleThemeBtns'; import { useTranslations } from 'next-intl'; @@ -8,22 +9,12 @@ const Footer = () => {
{t('layout.footer.COPY_RIGHT1', { date: new Date().getFullYear() })} - - {t('layout.footer.COPY_RIGHT4')} + + {APP_NAME}
by
- - {t('layout.footer.COMPANY_NAME')} + + {COMPANY_NAME} {' '}
@@ -37,16 +28,11 @@ const Footer = () => {
- + {t('layout.footer.TERMS')} void }) => { const [items, setItems] = useState(arr); const [saveLoader, setSaveLoader] = useState(false); const [saveCheck, setSaveCheck] = useState(false); const organizationId = getOrganizationIdCookie(); - const [_, setState] = useRecoilState(taskStatusListState); + const setState = useSetRecoilState(taskStatusListState); + const t = useTranslations(); const { reOrderQueryCall } = useTaskStatus(); const onDragEnd = (result: DropResult) => { diff --git a/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx b/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx index ffdff3dc9..338fe0edf 100644 --- a/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx +++ b/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx @@ -1,6 +1,6 @@ -import { useModal, useSyncRef, useTeamTasks } from '@app/hooks'; +import { useModal, useTeamTasks } from '@app/hooks'; import { ITaskVersionCreate, ITeamTask } from '@app/interfaces'; -import { detailedTaskState, taskVersionListState } from '@app/stores'; +import { detailedTaskState } from '@app/stores'; import { PlusIcon } from '@heroicons/react/20/solid'; import { Button, Card, Modal, Tooltip } from 'lib/components'; import { @@ -27,8 +27,6 @@ type StatusType = 'version' | 'epic' | 'status' | 'label' | 'size' | 'priority'; const TaskSecondaryInfo = () => { const task = useRecoilValue(detailedTaskState); - const taskVersion = useRecoilValue(taskVersionListState); - const $taskVersion = useSyncRef(taskVersion); const { updateTask } = useTeamTasks(); const { handleStatusUpdate } = useTeamTasks(); @@ -52,7 +50,7 @@ const TaskSecondaryInfo = () => { (version: ITaskVersionCreate) => { handleStatusUpdate(version.value || version.name, 'version', task?.taskStatusId, task); }, - [$taskVersion, task, handleStatusUpdate] + [task, handleStatusUpdate] ); const onTaskSelect = useCallback( diff --git a/apps/web/components/shared/invite/invite-modal.tsx b/apps/web/components/shared/invite/invite-modal.tsx index 21dc0c7b6..ec74bd0a7 100644 --- a/apps/web/components/shared/invite/invite-modal.tsx +++ b/apps/web/components/shared/invite/invite-modal.tsx @@ -54,7 +54,7 @@ const InviteModal = ({ isOpen, Fragment, closeModal }: IInviteProps) => { } inviteUser(formData.email, formData.name) - .then((data) => { + .then(() => { setFormData(initalValues); closeModal(); toast({ diff --git a/apps/web/components/ui/inputs/input.tsx b/apps/web/components/ui/inputs/input.tsx index b7428d03e..803496863 100644 --- a/apps/web/components/ui/inputs/input.tsx +++ b/apps/web/components/ui/inputs/input.tsx @@ -3,7 +3,7 @@ import { IInputProps } from '@app/interfaces/hooks'; const Input = ({ label, name, - type, + type = 'text', placeholder, required, onChange, @@ -40,8 +40,4 @@ const Input = ({ ); }; -Input.defaultProps = { - type: 'text' -}; - export default Input; diff --git a/apps/web/lib/components/custom-select/multi-select.tsx b/apps/web/lib/components/custom-select/multi-select.tsx index 6b87b8084..a88cb2e65 100644 --- a/apps/web/lib/components/custom-select/multi-select.tsx +++ b/apps/web/lib/components/custom-select/multi-select.tsx @@ -71,7 +71,7 @@ export function MultiSelect({ if (triggerRef.current) { setPopoverWidth(triggerRef.current.offsetWidth); } - }, [triggerRef.current]); + }, []); return (
diff --git a/apps/web/lib/components/inputs/auth-code-input.tsx b/apps/web/lib/components/inputs/auth-code-input.tsx index 7a8a2b4e2..1df14a9a2 100644 --- a/apps/web/lib/components/inputs/auth-code-input.tsx +++ b/apps/web/lib/components/inputs/auth-code-input.tsx @@ -4,6 +4,7 @@ import { clsxm } from '@app/utils'; import React, { MutableRefObject, forwardRef, useState, useEffect, useImperativeHandle, useRef } from 'react'; import { InputField } from './input'; import { useTranslations } from 'next-intl'; +import { useCallbackRef } from '@app/hooks'; const allowedCharactersValues = ['alpha', 'numeric', 'alphanumeric'] as const; @@ -95,6 +96,8 @@ export const AuthCodeInputField = forwardRef( } const [canSubmit, setCanSubmit] = useState(false); const reference = useRef([]); + const $submitCode = useCallbackRef(submitCode); + const inputsRef = inputReference || reference; const inputProps = propsMap[allowedCharacters]; const validDefaultValue = @@ -126,15 +129,8 @@ export const AuthCodeInputField = forwardRef( }, [autoFocus, inputsRef]); useEffect(() => { - if (autoComplete && autoComplete.length > 0) { - handleAutoComplete(autoComplete); - setCanSubmit(true); - } - }, [autoComplete, canSubmit]); - - useEffect(() => { - canSubmit && submitCode && submitCode(); - }, []); + canSubmit && $submitCode.current?.(); + }, [canSubmit, $submitCode]); const sendResult = () => { const res = inputsRef.current.map((input) => input.value).join(''); @@ -225,6 +221,14 @@ export const AuthCodeInputField = forwardRef( sendResult(); }; + useEffect(() => { + if (autoComplete && autoComplete.length > 0) { + handleAutoComplete(autoComplete); + setCanSubmit(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [autoComplete, canSubmit]); + const hintColor = { success: '#4BB543', error: '#FF9494', diff --git a/apps/web/lib/components/lazy-render.tsx b/apps/web/lib/components/lazy-render.tsx index e11e30427..2261578c0 100644 --- a/apps/web/lib/components/lazy-render.tsx +++ b/apps/web/lib/components/lazy-render.tsx @@ -53,7 +53,7 @@ export function LazyRender({ items, children, itemsPerPage = 1 return () => { window.cancelIdleCallback(cancelableIdlCallback); }; - }, [page, items, itemsRef]); + }, [page, items, itemsRef, itemsPerPage]); return ( <> diff --git a/apps/web/lib/components/sidebar-accordian.tsx b/apps/web/lib/components/sidebar-accordian.tsx index 7d63cb34e..d71f0f60f 100644 --- a/apps/web/lib/components/sidebar-accordian.tsx +++ b/apps/web/lib/components/sidebar-accordian.tsx @@ -28,8 +28,9 @@ export const SidebarAccordian = ({ children, title, className, wrapperClassName, {children && ( diff --git a/apps/web/lib/components/svgs/app-logo.tsx b/apps/web/lib/components/svgs/app-logo.tsx index c1e7181bf..a65d4f12c 100644 --- a/apps/web/lib/components/svgs/app-logo.tsx +++ b/apps/web/lib/components/svgs/app-logo.tsx @@ -1,70 +1,75 @@ +import { APP_LINK, APP_LOGO_URL } from '@app/constants'; import { IClassName } from '@app/interfaces'; import { clsxm } from '@app/utils'; import Link from 'next/link'; export function AppLogo({ className, dash }: IClassName & { dash?: boolean }) { return ( - - - + + {APP_LOGO_URL ? ( + EverTeams Logo + ) : ( + - - - - - - - - - - - + + + + + + + + + + + + + - - + + )} ); } diff --git a/apps/web/lib/components/svgs/ever-teams-logo.tsx b/apps/web/lib/components/svgs/ever-teams-logo.tsx index 7f094a6cc..eba1473df 100644 --- a/apps/web/lib/components/svgs/ever-teams-logo.tsx +++ b/apps/web/lib/components/svgs/ever-teams-logo.tsx @@ -1,3 +1,4 @@ +import { APP_LINK, APP_LOGO_URL } from '@app/constants'; import { IClassName } from '@app/interfaces'; import { clsxm } from '@app/utils'; import Link from 'next/link'; @@ -9,56 +10,75 @@ type Props = IClassName<{ export function EverTeamsLogo({ className, dash, color = 'auto' }: Props) { return ( - - + ) : ( + + + )} ); } diff --git a/apps/web/lib/components/time-picker/index.tsx b/apps/web/lib/components/time-picker/index.tsx index 210c1281d..17daf3663 100644 --- a/apps/web/lib/components/time-picker/index.tsx +++ b/apps/web/lib/components/time-picker/index.tsx @@ -31,7 +31,7 @@ export function TimePicker({ onChange, defaultValue }: IPopoverTimePicker) { const handleTimeChange = (newTime: any) => { setTime(newTime); - onChange!(newTime) + onChange && onChange(newTime); }; return ( diff --git a/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx index 60f7c6c99..92acf67a9 100644 --- a/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx +++ b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx @@ -51,10 +51,10 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa const t = useTranslations(); const { updateDailyPlan, myDailyPlans } = useDailyPlan(); - const { startTimer } = useTimerView(); + const { startTimer, timerStatus } = useTimerView(); const { activeTeam, activeTeamTask, setActiveTask } = useTeamTasks(); const [showSearchInput, setShowSearchInput] = useState(false); - const [workTimePlanned, setWorkTimePlanned] = useState(plan.workTimePlanned); + const [workTimePlanned, setWorkTimePlanned] = useState(plan.workTimePlanned); const currentDate = useMemo(() => new Date().toISOString().split('T')[0], []); const requirePlan = useMemo(() => activeTeam?.requirePlanToTrack, [activeTeam?.requirePlanToTrack]); const tasksEstimationTimes = useMemo(() => estimatedTotalTime(plan.tasks).timesEstimated / 3600, [plan.tasks]); @@ -65,6 +65,7 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa () => plan.tasks?.some((task) => task.id == activeTeamTask?.id), [activeTeamTask?.id, plan.tasks] ); + const [isWorkingTimeInputFocused, setWorkingTimeInputFocused] = useState(false); const canStartWorking = useMemo(() => { const isTodayPlan = @@ -222,6 +223,27 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa setWorkTimePlanned(plan.workTimePlanned); }, [plan]); + const StartWorkingButton = ( + + ); + const content = (
@@ -249,12 +271,24 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa placeholder={t('timer.todayPlanSettings.WORK_TIME_PLANNED_PLACEHOLDER')} className="h-full" wrapperClassName=" h-full" - onChange={(e) => setWorkTimePlanned(parseFloat(e.target.value))} + onChange={(e) => { + !isNaN(parseInt(e.target.value)) + ? setWorkTimePlanned(parseInt(e.target.value)) + : setWorkTimePlanned(0); + }} required noWrapper min={0} - value={workTimePlanned} - defaultValue={plan.workTimePlanned ?? 0} + value={ + !isNaN(workTimePlanned) && workTimePlanned.toString() !== '0' + ? workTimePlanned.toString().replace(/^0+/, '') + : isWorkingTimeInputFocused + ? '' + : 0 + } + onFocus={() => setWorkingTimeInputFocused(true)} + onBlur={() => setWorkingTimeInputFocused(false)} + defaultValue={plan.workTimePlanned ? parseInt(plan.workTimePlanned.toString()) : 0} /> - + {timerStatus?.running ? ( + + {StartWorkingButton} + + ) : ( +
{StartWorkingButton}
+ )}
diff --git a/apps/web/lib/features/daily-plan/all-plans-modal.tsx b/apps/web/lib/features/daily-plan/all-plans-modal.tsx index 55e648054..b12cf295f 100644 --- a/apps/web/lib/features/daily-plan/all-plans-modal.tsx +++ b/apps/web/lib/features/daily-plan/all-plans-modal.tsx @@ -31,7 +31,7 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) const [showCalendar, setShowCalendar] = useState(false); const [showCustomPlan, setShowCustomPlan] = useState(false); const [customDate, setCustomDate] = useState(); - const { futurePlans, myDailyPlans } = useDailyPlan(); + const { myDailyPlans, pastPlans } = useDailyPlan(); // Utility function for checking if two dates are the same const isSameDate = useCallback( @@ -87,6 +87,26 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) return undefined; } }, [selectedTab, todayPlan, tomorrowPlan, selectedFuturePlan]); + + // Set the related tab for today and tomorrow dates + const handleCalendarSelect = useCallback(() => { + if (customDate) { + if ( + new Date(customDate).toLocaleDateString('en') === new Date(moment().toDate()).toLocaleDateString('en') + ) { + setSelectedTab('Today'); + } else if ( + new Date(customDate).toLocaleDateString('en') === + new Date(moment().add(1, 'days').toDate()).toLocaleDateString('en') + ) { + setSelectedTab('Tomorrow'); + } else { + setShowCalendar(false); + setShowCustomPlan(true); + } + } + }, [customDate]); + return ( @@ -140,9 +160,10 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)

Select a date to be able to see a plan

@@ -162,10 +183,7 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) variant="default" type="submit" className={clsxm('py-3 px-5 rounded-md font-light text-md dark:text-white')} - onClick={() => { - setShowCalendar(false); - setShowCustomPlan(true); - }} + onClick={handleCalendarSelect} > Select @@ -200,31 +218,32 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) */ interface ICalendarProps { - setSelectedFuturePlan: Dispatch>; - selectedFuturePlan: Date | undefined; - futurePlans: IDailyPlan[]; + setSelectedPlan: Dispatch>; + selectedPlan: Date | undefined; + plans: IDailyPlan[]; + pastPlans: IDailyPlan[]; } /** - * The component tha handles the selection of a future plan + * The component that handles the selection of a plan * * @param {Object} props - The props object * @param {Dispatch>} props.setSelectedFuturePlan - A function that set the selected plan * @param {IDailyPlan} props.selectedFuturePlan - The selected plan + * @param {IDailyPlan[]} props.plans - Available plans + * @param {IDailyPlan[]} props.pastPlans - Past plans + * + * @returns {JSX.Element} The Calendar component. */ const FuturePlansCalendar = memo(function FuturePlansCalendar(props: ICalendarProps) { - const { futurePlans, selectedFuturePlan, setSelectedFuturePlan } = props; + const { selectedPlan, setSelectedPlan, plans, pastPlans } = props; - const sortedFuturePlans = useMemo( + const sortedPlans = useMemo( () => - futurePlans - .filter( - (plan) => - new Date(plan.date).toLocaleDateString('en') !== - new Date(moment().add(1, 'days').toDate()).toLocaleDateString('en') - ) - .sort((plan1, plan2) => (new Date(plan1.date).getTime() < new Date(plan2.date).getTime() ? 1 : -1)), - [futurePlans] + [...plans].sort((plan1, plan2) => + new Date(plan1.date).getTime() < new Date(plan2.date).getTime() ? 1 : -1 + ), + [plans] ); /** @@ -236,46 +255,43 @@ const FuturePlansCalendar = memo(function FuturePlansCalendar(props: ICalendarPr */ const isDateUnplanned = useCallback( (dateToCheck: Date) => { - return !futurePlans - // Start from the day after tomorrow (Tomorrow has a tab) - .filter( - (plan) => - new Date(plan.date).toLocaleDateString('en') !== - new Date(moment().add(1, 'days').toDate()).toLocaleDateString('en') - ) + return !plans .map((plan) => new Date(plan.date)) .some( (date) => new Date(date).toLocaleDateString('en') == new Date(dateToCheck).toLocaleDateString('en') ); }, - [futurePlans] + [plans] ); return ( { if (date) { - setSelectedFuturePlan(date); + setSelectedPlan(date); } }} initialFocus disabled={isDateUnplanned} modifiers={{ - booked: sortedFuturePlans?.map((plan) => new Date(plan.date)) + booked: sortedPlans?.map((plan) => new Date(plan.date)), + pastDay: pastPlans?.map((plan) => new Date(plan.date)) }} modifiersClassNames={{ booked: clsxm( 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-primary after:rounded-full' ), - selected: clsxm('bg-primary text-white !rounded-full') + selected: clsxm('bg-primary after:hidden text-white !rounded-full'), + + pastDay: clsxm( + 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-yellow-600 after:rounded-full' + ) }} - fromMonth={new Date(sortedFuturePlans?.[0]?.date ?? Date.now())} - toMonth={new Date(sortedFuturePlans?.[sortedFuturePlans?.length - 1]?.date ?? Date.now())} - fromYear={new Date(sortedFuturePlans?.[0]?.date ?? Date.now())?.getFullYear()} - toYear={new Date(sortedFuturePlans?.[sortedFuturePlans?.length - 1]?.date ?? Date.now())?.getFullYear()} + fromYear={new Date(sortedPlans?.[0]?.date ?? Date.now())?.getFullYear()} + toYear={new Date(sortedPlans?.[sortedPlans?.length - 1]?.date ?? Date.now())?.getFullYear() + 5} /> ); }); diff --git a/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx b/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx index 7d4f56c7c..e762df8cf 100644 --- a/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx +++ b/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx @@ -38,7 +38,7 @@ export function DailyPlanCompareEstimatedModal({ const hour = dh.toString()?.padStart(2, '0'); const minute = dm.toString()?.padStart(2, '0'); const [times, setTimes] = useState({ - hours: (workTimePlanned! / 3600).toString(), + hours: workTimePlanned ? (workTimePlanned / 3600).toString() : '--', meridiem: 'PM', minute: '--' }); diff --git a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx index 4f7b6bbc1..2fa91a4ce 100644 --- a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx +++ b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx @@ -24,30 +24,37 @@ export function ConfirmStatusChange({ closeModal, isOpen, newStatus, oldStatus } const oldStatusClass = getStatusClasses(oldStatus); return ( - -
- Time entry will be changed from - + <> + { + closeModal ? ( + +
+ Time entry will be changed from + - - -
-
+ + +
+
+ ) + : + <> + } + ) } - const StatusTransition = ({ previousStatus, currentStatus, currentStatusClass, previousStatusClass }: { previousStatus: string; currentStatus: string; currentStatusClass: string; previousStatusClass: string }) => (
{previousStatus} diff --git a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx index 53029d319..d5ff98662 100644 --- a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx +++ b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx @@ -191,7 +191,7 @@ export const CardItemsMember = ({ imageUrl, name, time }: { imageUrl?: string, n return (
- +
{name}
@@ -208,7 +208,7 @@ export const CardItemsProjects = ({ logo, title, totalHours }: { logo?: string, return (
- logos + logos
{title} {totalHours} diff --git a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx index e6c533f9d..f18ae4c5e 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -417,6 +417,7 @@ const TaskDetails = ({ description, name }: { description: string; name: string {name} +
{description}
); }; diff --git a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx index faf7c9506..490d14ce3 100644 --- a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx +++ b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx @@ -29,8 +29,8 @@ import { Item, ManageOrMemberComponent, getNestedValue } from './manage-member-c */ interface IAddManualTimeModalProps { isOpen: boolean; - params: "AddManuelTime" | "AddTime"; - timeSheetStatus?: "ManagerTimesheet" | "TeamMemberTimesheet", + params: 'AddManuelTime' | 'AddTime'; + timeSheetStatus?: 'ManagerTimesheet' | 'TeamMemberTimesheet'; closeModal: () => void; } @@ -115,12 +115,13 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { const timeString = [ hours > 0 ? `${String(hours).padStart(2, '0')}h` : '0h', minutes > 0 ? `${String(minutes).padStart(2, '0')}m` : '' - ].filter(Boolean).join(' '); + ] + .filter(Boolean) + .join(' '); setTimeDifference(timeString); }, [endTime, startTime]); - useEffect(() => { calculateTimeDifference(); }, [calculateTimeDifference, endTime, startTime]); @@ -142,16 +143,15 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { } }, [addManualTimeLoading, closeModal, timeLog]); - const memberItemsLists = { - 'Project': activeTeam?.projects, - 'Employee': activeTeam?.members, - 'Task': tasks, + Project: activeTeam?.projects, + Employee: activeTeam?.members, + Task: tasks }; const selectedValues = { - 'Teams': null, - 'Members': null, - "Task": null + Teams: null, + Members: null, + Task: null }; const fields = [ { @@ -162,15 +162,18 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { displayKey: 'name', element: 'Project' }, - ...(timeSheetStatus === 'ManagerTimesheet' ? - [{ - label: t('manualTime.EMPLOYEE'), - placeholder: 'Select an employee', - isRequired: true, - valueKey: 'id', - displayKey: 'employee.fullName', - element: 'Employee' - }] : []), + ...(timeSheetStatus === 'ManagerTimesheet' + ? [ + { + label: t('manualTime.EMPLOYEE'), + placeholder: 'Select an employee', + isRequired: true, + valueKey: 'id', + displayKey: 'employee.fullName', + element: 'Employee' + } + ] + : []), { label: t('manualTime.TASK'), placeholder: 'Select a Task', @@ -181,9 +184,6 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { } ]; - - - const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => { console.log(values); }; @@ -196,29 +196,33 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) {
} @@ -249,7 +253,8 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) {
- +
- +
{timeDifference}
+ {/*
+ + { + activeTeam ? + setTeam(team)} + itemId={(team) => (team ? team.id : '')} + itemToString={(team) => (team ? team.name : '')} + triggerClassName="border-gray-300 dark:border-slate-600" + /> + : + <> + } +
*/} + + {params === 'AddManuelTime' ? ( + <> + getNestedValue(item, displayKey) || ''} + itemToValue={(item, valueKey) => getNestedValue(item, valueKey) || ''} + /> +
+ +