diff --git a/client/.eslintrc.json b/client/.eslintrc.json index fc801afc8..bec4c12cb 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -28,7 +28,14 @@ "rules": { "react/display-name": 0, // I suggest you add those two rules: - "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-var-requires": "off" } diff --git a/client/package.json b/client/package.json index 371636132..6b30b9a8e 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,7 @@ "@react-oauth/google": "^0.11.1", "@storybook/addon-styling": "^1.3.6", "@tanstack/react-query": "beta", - "@teameights/types": "^1.1.17", + "@teameights/types": "^1.1.19", "@types/lodash.debounce": "^4.0.7", "@types/node": "20.4.8", "@types/react": "18.2.18", diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 61bcba71e..7b39dcc86 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -1,43 +1,24 @@ 'use client'; -import { Typography, Button } from '@/shared/ui'; -import { useState } from 'react'; -import { ActionModal } from '@/widgets/modals'; -import { userResponseFixture } from '@/shared/fixtures/user'; -import { teamFixture } from '@/shared/fixtures/team'; -import { UserInfoModal } from '@/widgets/modals/info-modal/user'; -import { TeamInfoModal } from '@/widgets/modals/info-modal/team/team'; +import { Typography } from '@/shared/ui'; +import { + generateMockTeam, + generateMockUser, + generateSystemNotification, + generateTeamInvitationNotification, +} from '@/shared/lib/mock'; +import { useEffect, useState } from 'react'; +import { IUserBase } from '@teameights/types'; export default function Home() { - const [isOpenFirstModal, setIsOpenFirstModal] = useState(false); - const [isOpenThirdModal, setIsOpenThirdModal] = useState(false); - const [openModal, setOpenModal] = useState(false); + const [user, setUser] = useState(); - const openModalNew = () => { - setOpenModal(true); - }; - const closeModalNew = () => { - setOpenModal(false); - }; + useEffect(() => { + setUser(generateMockUser()); - const openFirstModal = () => { - setIsOpenFirstModal(true); - }; - - const closeFirstModal = () => { - setIsOpenFirstModal(false); - }; - - const openThirdModal = () => { - setIsOpenThirdModal(true); - }; - - const closeThirdModal = () => { - setIsOpenThirdModal(false); - }; - - const handleJoin = () => { - console.log('Join button clicked'); - }; + console.log(generateTeamInvitationNotification()); + console.log(generateMockTeam()); + console.log(generateSystemNotification()); + }, []); return ( <> @@ -45,54 +26,11 @@ export default function Home() { We are working hard to deliver teameights on NextJS/TS soon! - Hello, {userResponseFixture.username}! + Hello, {user?.username}! Get to login - -
- - - - - -
- -
- - -
- -
- - -
); } diff --git a/client/src/shared/constant/frameworks.ts b/client/src/shared/constant/frameworks.ts new file mode 100644 index 000000000..015b980ea --- /dev/null +++ b/client/src/shared/constant/frameworks.ts @@ -0,0 +1,31 @@ +export const frameworkOptions = [ + { label: 'NodeJS', value: 'nodejs' }, + { label: 'Ruby', value: 'ruby' }, + { label: 'Angular', value: 'angular' }, + { label: 'Hadoop', value: 'hadoop' }, + { label: 'Ember', value: 'ember' }, + { label: 'Django', value: 'django' }, + { label: 'Redux', value: 'redux' }, + { label: 'React', value: 'react' }, + { label: 'Spring', value: 'spring' }, + { label: 'Spark', value: 'spark' }, + { label: 'Backbone', value: 'backbone' }, + { label: 'Figma', value: 'figma' }, + { label: 'Photoshop', value: 'photoshop' }, + { label: 'jQuery', value: 'jquery' }, + { label: 'MUI', value: 'mui' }, + { label: 'ASP.NET', value: 'aspnet' }, + { label: 'NumPy', value: 'numpy' }, + { label: 'Flutter', value: 'flutter' }, + { label: 'React N.', value: 'reactnative' }, + { label: 'Flask', value: 'flask' }, + { label: 'VueJS', value: 'vuejs' }, + { label: 'Bootstrap', value: 'bootstrap' }, + { label: 'KMM', value: 'kotlinmultiplatformmobile' }, + { label: 'GraphQL', value: 'graphql' }, + { label: 'Laravel', value: 'laravel' }, + { label: 'PyTorch', value: 'pytorch' }, + { label: 'Tensor F.', value: 'tensorflow' }, + { label: 'Express', value: 'express' }, + { label: 'Illustrator', value: 'illustrator' }, +]; diff --git a/client/src/shared/fixtures/team.ts b/client/src/shared/fixtures/team.ts deleted file mode 100644 index 51733f6d1..000000000 --- a/client/src/shared/fixtures/team.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ITeam } from '@teameights/types'; -import { userResponseFixture, generateRandomUserResponseFixture } from './user'; -import { faker } from '@faker-js/faker'; - -const users = generateRandomUserResponseFixture(7); -export const teamFixture: ITeam = { - id: 1, - name: 'Sample Team', - description: 'This is a sample team', - leader: userResponseFixture, - members: users, - country: 'Russia', - tag: 'SampleTag', - type: 'open', - wins: 10, - points: 100, - photo: { - id: faker.number.int(), - path: `https://picsum.photos/${Math.floor(Math.random() * 1001) + 3000}/${ - Math.floor(Math.random() * 1001) + 3000 - }`, - }, - createdAt: new Date('2023-10-19T12:00:00Z'), - updatedAt: new Date('2023-10-19T12:30:00Z'), - deletedAt: new Date('2023-10-19T13:00:00Z'), -}; diff --git a/client/src/shared/fixtures/user.ts b/client/src/shared/fixtures/user.ts deleted file mode 100644 index 30dc1c58f..000000000 --- a/client/src/shared/fixtures/user.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { IUserResponse } from '@teameights/types'; -import { faker } from '@faker-js/faker'; - -const getRandomNumberBetween = (min: number, max: number) => { - return Math.floor(Math.random() * (max - min + 1)) + min; -}; - -export const generateRandomUserResponseFixture = (amount: number): IUserResponse[] => { - const users = []; - faker.seed(123); // You can use a specific seed for consistent random data - for (let i = 0; i < amount; i++) { - const user: IUserResponse = { - id: faker.number.int(), - username: faker.internet.userName(), - fullName: faker.person.fullName(), - photo: { - id: faker.number.int(), - path: `https://picsum.photos/${getRandomNumberBetween(1000, 1500)}/${getRandomNumberBetween( - 1000, - 1500 - )}`, - }, - role: { id: faker.number.int(), name: 'USER' }, - status: { id: faker.number.int(), name: 'Active' }, - isLeader: faker.datatype.boolean(), - country: faker.location.country(), - dateOfBirth: faker.date.past(), - concentration: faker.lorem.word(), - description: faker.lorem.sentence(), - experience: `2 years`, - programmingLanguages: ['JS', 'C++', 'GO'], - frameworks: ['NestJS', 'NextJS'], - universities: [ - { - id: faker.number.int(), - name: faker.company.name(), - degree: faker.lorem.word(), - major: faker.lorem.word(), - admissionDate: faker.date.past(), - graduationDate: faker.date.past(), - }, - ], - jobs: [ - { - id: faker.number.int(), - title: faker.person.jobTitle(), - company: faker.company.name(), - startDate: faker.date.past(), - endDate: faker.date.past(), - }, - ], - projects: [ - { - id: faker.number.int(), - title: 'Lanselon loh', - link: faker.internet.url(), - }, - ], - links: { - id: faker.number.int(), - github: faker.internet.url(), - linkedIn: faker.internet.url(), - behance: null, - telegram: faker.internet.url(), - }, - notifications: [], - team: null, - createdAt: faker.date.past(), - updatedAt: faker.date.past(), - deletedAt: null, - }; - users.push(user); - } - - return users; -}; - -export const userResponseFixture: IUserResponse = { - id: 1, - username: 'sample_username', - fullName: 'John Doe', - photo: { - id: faker.number.int(), - path: `https://picsum.photos/${getRandomNumberBetween(1000, 1500)}/${getRandomNumberBetween( - 1000, - 1500 - )}`, - }, - role: { id: 1, name: 'Sample Role' }, // Replace with actual role data - status: { id: 1, name: 'Active' }, // Replace with actual status data - isLeader: true, - country: 'United States', - dateOfBirth: new Date('1990-01-15'), - concentration: 'Backend Developer', - description: 'Sample user description', - experience: '2 years', - frameworks: ['NodeJS', 'React', 'jQuery'], - programmingLanguages: ['JS', 'Swift', 'Dart', 'Scala', 'Ruby'], - universities: [ - { - id: 1, - name: 'Sample University', - degree: 'Bachelor of Science', - major: 'Computer Science', - admissionDate: new Date('2008-09-01'), - graduationDate: new Date('2012-05-15'), - }, - ], - jobs: [ - { - id: 1, - title: 'Software Engineer', - company: 'TechCo', - startDate: new Date('2012-06-01'), - endDate: new Date('2015-12-31'), - }, - ], - projects: [ - { - id: 1, - title: 'Project A', - link: 'https://projecta.example.com', - }, - ], - links: { - id: 1, - github: 'https://github.com/sampleuser', - linkedIn: 'https://www.linkedin.com/in/sampleuser', - behance: null, // Replace with actual Behance link if available - telegram: 'https://t.me/sampleuser', - }, - notifications: [], // Replace with actual notification data - team: null, // Replace with actual team data if the user is part of a team - createdAt: new Date('2022-01-01'), // Replace with actual creation date - updatedAt: new Date('2022-02-15'), // Replace with actual update date - deletedAt: null, -}; diff --git a/client/src/shared/lib/mock/common.ts b/client/src/shared/lib/mock/common.ts new file mode 100644 index 000000000..08e698441 --- /dev/null +++ b/client/src/shared/lib/mock/common.ts @@ -0,0 +1,33 @@ +import { faker } from '@faker-js/faker'; + +export const getRandomItemFromArray = (array: string[]): string => { + const randomIndex = faker.number.int({ min: 0, max: array.length - 1 }); + return array[randomIndex]; +}; + +export const getRandomItemFromObject = (obj: { [key: string]: T }): T => { + const keys = Object.keys(obj); + const randomKey = getRandomItemFromArray(keys); + return obj[randomKey as keyof typeof obj]; +}; + +export const getRandomNumberBetween = (min: number, max: number) => { + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +export const shuffleArray = (array: T[]): T[] => { + let currentIndex = array.length, + randomIndex; + + // While there remain elements to shuffle... + while (currentIndex !== 0) { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + // And swap it with the current element. + [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; + } + + return array; +}; diff --git a/client/src/shared/lib/mock/index.ts b/client/src/shared/lib/mock/index.ts new file mode 100644 index 000000000..1d72749fe --- /dev/null +++ b/client/src/shared/lib/mock/index.ts @@ -0,0 +1,3 @@ +export * from './team'; +export * from './user'; +export * from './notification'; diff --git a/client/src/shared/lib/mock/notification.ts b/client/src/shared/lib/mock/notification.ts new file mode 100644 index 000000000..daf41d526 --- /dev/null +++ b/client/src/shared/lib/mock/notification.ts @@ -0,0 +1,44 @@ +import { faker } from '@faker-js/faker'; +import { getRandomItemFromArray } from './common'; +import { + ISystemNotification, + ITeam, + ITeamInvitationNotification, + IUserBase, + StatusType, +} from '@teameights/types'; +import { generateMockFileEntity, generateMockUser } from './user'; +import { generateMockTeam } from './team'; + +export const generateSystemNotification = (initialUser?: IUserBase): ISystemNotification => ({ + id: faker.number.int(), + user: initialUser ? initialUser : generateMockUser(), + type: 'system', + read: faker.datatype.boolean(), + expiresAt: faker.date.future(), + createdAt: faker.date.recent(), + updatedAt: faker.date.recent(), + system_message: faker.lorem.sentence(), + deletedAt: null, +}); + +export const generateTeamInvitationNotification = ( + initialUser?: IUserBase, + initialTeam?: ITeam, + initialFromUser?: IUserBase +): ITeamInvitationNotification => ({ + id: faker.number.int(), + user: initialUser ? initialUser : generateMockUser(), + type: 'team_invite', + read: faker.datatype.boolean(), + expiresAt: faker.date.future(), + createdAt: faker.date.recent(), + updatedAt: faker.date.recent(), + team: initialTeam ? initialTeam : generateMockTeam(initialTeam), + from_user: initialFromUser ? initialFromUser : generateMockUser(), + to_user_email: faker.internet.email(), + status: getRandomItemFromArray(['pending', 'accepted', 'rejected']) as StatusType, + photo: generateMockFileEntity(), + message: faker.lorem.sentence(), + deletedAt: null, +}); diff --git a/client/src/shared/lib/mock/team.ts b/client/src/shared/lib/mock/team.ts new file mode 100644 index 000000000..cc11bd5da --- /dev/null +++ b/client/src/shared/lib/mock/team.ts @@ -0,0 +1,26 @@ +import { faker } from '@faker-js/faker'; +import { ITeam, IUserBase, TeamType } from '@teameights/types'; +import { generateMockFileEntity, generateMockUser, generateMockUsers } from './user'; +import { getRandomItemFromArray } from './common'; + +export const generateMockTeam = ( + initialLeader?: IUserBase, + initialMembers?: IUserBase[] +): ITeam => { + return { + id: faker.number.int(), + name: faker.word.words(), + description: faker.datatype.boolean() ? faker.lorem.sentence() : null, + leader: initialLeader ? initialLeader : generateMockUser(), + members: initialMembers ? initialMembers : generateMockUsers(5), + country: faker.location.country(), + tag: faker.lorem.word(), + type: getRandomItemFromArray(['invite_only', 'closed', 'open']) as TeamType, + wins: faker.number.int({ min: 0, max: 100 }), + points: faker.number.int({ min: 0, max: 1000 }), + photo: faker.datatype.boolean() ? generateMockFileEntity() : null, + createdAt: faker.date.recent(), + updatedAt: faker.date.recent(), + deletedAt: faker.datatype.boolean() ? faker.date.recent() : null, + }; +}; diff --git a/client/src/shared/lib/mock/user.ts b/client/src/shared/lib/mock/user.ts new file mode 100644 index 000000000..fecc0627e --- /dev/null +++ b/client/src/shared/lib/mock/user.ts @@ -0,0 +1,133 @@ +import { faker } from '@faker-js/faker'; +import { + ExperienceType, + IFileEntity, + IJob, + ILinks, + IProject, + IRole, + IStatus, + ITeam, + IUniversity, + IUserBase, + NotificationType, +} from '@teameights/types'; +import { getRandomItemFromArray, getRandomNumberBetween, shuffleArray } from './common'; +import { frameworkColors, languageOptions } from '@/shared/constant'; + +export const getRandomLanguages = (min: number, max: number): string[] => { + const allLanguages = Object.keys(languageOptions); + const shuffledLanguages = shuffleArray([...allLanguages]); + + const randomLength = Math.floor(Math.random() * (max - min + 1)) + min; + + return shuffledLanguages.slice(0, randomLength); +}; + +export const getRandomFrameworks = (min: number, max: number): string[] => { + const allFrameworks = Object.keys(frameworkColors); + const shuffledLanguages = shuffleArray([...allFrameworks]); + + const randomLength = Math.floor(Math.random() * (max - min + 1)) + min; + + return shuffledLanguages.slice(0, randomLength); +}; + +export const generateMockFileEntity = (): IFileEntity => { + return { + id: faker.number.int(), + path: `https://picsum.photos/${getRandomNumberBetween(1000, 1500)}/${getRandomNumberBetween( + 1000, + 1500 + )}`, + }; +}; + +export const generateMockStatus = (): IStatus => ({ + id: faker.number.int(), + name: faker.lorem.word(), +}); + +export const generateMockRole = (): IRole => ({ + id: faker.number.int(), + name: faker.lorem.word(), +}); + +export const generateMockProject = (): IProject => ({ + id: faker.number.int(), + title: faker.lorem.sentence(), + link: faker.internet.url(), +}); + +export const generateMockLinks = (): ILinks => ({ + id: faker.number.int(), + github: faker.internet.url(), + linkedIn: faker.internet.url(), + behance: faker.internet.url(), + telegram: faker.internet.url(), +}); + +export const generateMockJob = (): IJob => ({ + id: faker.number.int(), + title: faker.person.jobTitle(), + company: faker.company.name(), + startDate: faker.date.past(), + endDate: faker.datatype.boolean() ? faker.date.past() : null, +}); + +export const generateMockUniversity = (): IUniversity => ({ + id: faker.number.int(), + name: faker.company.name(), + degree: faker.person.jobType(), + major: faker.person.jobArea(), + admissionDate: faker.date.past(), + graduationDate: faker.datatype.boolean() ? faker.date.past() : null, +}); + +export const generateMockUser = ( + initialTeam?: ITeam, + initialNotifications?: NotificationType[] +): IUserBase => { + return { + id: faker.number.int(), + username: faker.internet.userName(), + fullName: faker.person.firstName(), + photo: faker.datatype.boolean() ? generateMockFileEntity() : null, + role: generateMockRole(), + status: generateMockStatus(), + isLeader: faker.datatype.boolean(), + country: faker.location.country(), + dateOfBirth: faker.date.past(), + concentration: faker.lorem.word(), + description: faker.datatype.boolean() ? faker.lorem.sentence({ min: 10, max: 280 }) : null, + experience: getRandomItemFromArray([ + 'No experience', + 'Few months', + '1 year', + '2 years', + '3 years', + '4 years', + '5+ years', + ]) as ExperienceType, + programmingLanguages: getRandomLanguages(1, 5), + frameworks: getRandomFrameworks(1, 5), + universities: Array.from({ length: faker.number.int({ min: 1, max: 3 }) }).map(() => + generateMockUniversity() + ), + jobs: Array.from({ length: faker.number.int({ min: 1, max: 3 }) }).map(() => generateMockJob()), + projects: Array.from({ length: faker.number.int({ min: 1, max: 3 }) }).map(() => + generateMockProject() + ), + links: faker.datatype.boolean() ? generateMockLinks() : null, + notifications: initialNotifications ? initialNotifications : [], + // to avoid dead lock we can't generate team here, we will need to add team + team: initialTeam ? initialTeam : null, + createdAt: faker.date.recent(), + updatedAt: faker.date.recent(), + deletedAt: faker.datatype.boolean() ? faker.date.recent() : null, + }; +}; + +export const generateMockUsers = (count: number): IUserBase[] => { + return Array.from({ length: count }).map(() => generateMockUser()); +}; diff --git a/client/src/widgets/modals/info-modal/team/desktop/desktop.stories.tsx b/client/src/widgets/modals/info-modal/team/desktop/desktop.stories.tsx index 6e69109f5..1547f1d4b 100644 --- a/client/src/widgets/modals/info-modal/team/desktop/desktop.stories.tsx +++ b/client/src/widgets/modals/info-modal/team/desktop/desktop.stories.tsx @@ -2,8 +2,7 @@ import type { Meta } from '@storybook/react'; import { TeamDesktop } from './desktop'; import { Button } from '@/shared/ui'; import { useState } from 'react'; -import { userResponseFixture } from '@/shared/fixtures/user'; -import { teamFixture } from '@/shared/fixtures/team'; +import { generateMockTeam, generateMockUser } from '@/shared/lib/mock'; const meta: Meta = { title: 'widgets/modals/info/team/desktop', @@ -20,6 +19,8 @@ const handleJoin = () => { export const InfoModalTeam_desktop = () => { const [openModal, setOpenModal] = useState(false); + const team = generateMockTeam(); + const user = generateMockUser(); const openModalNew = () => { setOpenModal(true); }; @@ -32,8 +33,8 @@ export const InfoModalTeam_desktop = () => { Open Modal Team = { title: 'widgets/modals/info/team/phone', @@ -19,6 +18,8 @@ const handleJoin = () => { }; export const InfoModalTeam_phone = () => { + const team = generateMockTeam(); + const user = generateMockUser(); const [openModal, setOpenModal] = useState(false); const openModalNew = () => { setOpenModal(true); @@ -32,8 +33,8 @@ export const InfoModalTeam_phone = () => { Open Modal Team = { title: 'widgets/modals/info/user/desktop', @@ -15,6 +15,7 @@ export default meta; export const InfoModalUser_desktop = () => { const [openModal, setOpenModal] = useState(false); + const user = generateMockUser(); const openModalNew = () => { setOpenModal(true); }; @@ -26,7 +27,7 @@ export const InfoModalUser_desktop = () => { - + ); }; diff --git a/client/src/widgets/modals/info-modal/user/phone/phone.stories.tsx b/client/src/widgets/modals/info-modal/user/phone/phone.stories.tsx index da8e1d689..ad79d75c5 100644 --- a/client/src/widgets/modals/info-modal/user/phone/phone.stories.tsx +++ b/client/src/widgets/modals/info-modal/user/phone/phone.stories.tsx @@ -2,7 +2,7 @@ import type { Meta } from '@storybook/react'; import { UserPhone } from './phone'; import { useState } from 'react'; import { Button } from '@/shared/ui'; -import { userResponseFixture } from '@/shared/fixtures/user'; +import { generateMockUser } from '@/shared/lib/mock'; const meta: Meta = { title: 'widgets/modals/info/user/phone', @@ -15,6 +15,7 @@ export default meta; export const InfoModalUser_desktop = () => { const [openModal, setOpenModal] = useState(false); + const user = generateMockUser(); const openModalNew = () => { setOpenModal(true); }; @@ -26,7 +27,7 @@ export const InfoModalUser_desktop = () => { - + ); }; diff --git a/client/yarn.lock b/client/yarn.lock index 5a3acc468..84c344909 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -5157,12 +5157,12 @@ __metadata: languageName: node linkType: hard -"@teameights/types@npm:^1.1.17": - version: 1.1.17 - resolution: "@teameights/types@npm:1.1.17" +"@teameights/types@npm:^1.1.19": + version: 1.1.19 + resolution: "@teameights/types@npm:1.1.19" dependencies: esno: ^0.17.0 - checksum: 7ba9922ee457604eaf56069ff2b74e2740efbcae4c9d2303a06eacdef531f955760a01ed6f56fedfa61dd266192594792d22e625d874367f61f08f2be2e235b0 + checksum: d4c64bc5b95d6f81ef9d012cc3a389cba85213a18e4a76a53d53fc3495f9b8c2b66dc9e7ab84d5ea6e1123446c775f8e1a6279821ca2c03fe7554e5da38e0d6d languageName: node linkType: hard @@ -7678,7 +7678,7 @@ __metadata: "@storybook/react": 7.2.1 "@storybook/testing-library": 0.2.0 "@tanstack/react-query": beta - "@teameights/types": ^1.1.17 + "@teameights/types": ^1.1.19 "@testing-library/jest-dom": ^6.1.3 "@testing-library/react": ^14.0.0 "@types/jest": ^29.5.5