diff --git a/.cspell.json b/.cspell.json index 16d3b2e4a..cd7d666ff 100644 --- a/.cspell.json +++ b/.cspell.json @@ -5,6 +5,7 @@ "words": [ "barcodes", "cacheable", + "camelcase", "cloudinary", "clsxm", "dummyimage", @@ -13,6 +14,9 @@ "exposdk", "headlessui", "heroicons", + "JITSU", + "kanban", + "passcode", "plasmo", "RECAPTCHA", "svgs", @@ -53,7 +57,13 @@ "docker-compose.demo.yml", "docker-compose.yml", "wait", + "signin", + "Chatwoot", + "CHATWOOT", "apps/web/lib/i18n/*.ts", + "apps/web/public/locales/**", "apps/mobile/app/i18n/*.ts", + "apps/mobile/android/**", + "apps/mobile/ios/**" ] } diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml new file mode 100644 index 000000000..9888f0b7f --- /dev/null +++ b/.github/workflows/docker-build-publish.yml @@ -0,0 +1,80 @@ +name: Build and Publish Docker Images Prod + +on: + push: + branches: [main] + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + ever-teams-webapp: + runs-on: buildjet-8vcpu-ubuntu-2204 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + file: ./Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-teams-webapp:latest + everco/ever-teams-webapp:latest + registry.digitalocean.com/ever/ever-teams-webapp:latest + ${{ secrets.CW_DOCKER_REGISTRY }}/ever-co/ever-teams-webapp:latest + cache-from: type=registry,ref=everco/ever-teams-webapp:latest + cache-to: type=inline + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-teams-webapp:latest + + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-teams-webapp:latest + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-teams-webapp:latest + + - name: Login to CW Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ secrets.CW_DOCKER_REGISTRY }} + username: ${{ secrets.CW_DOCKER_USER }} + password: ${{ secrets.CW_DOCKER_USER_PASSWORD }} + + - name: Push to CW Registry + run: | + docker push ${{ secrets.CW_DOCKER_REGISTRY }}/ever-co/ever-teams-webapp:latest diff --git a/.github/workflows/mobile-prod.yml b/.github/workflows/mobile-prod.yml index 0cb11392b..2826598e5 100644 --- a/.github/workflows/mobile-prod.yml +++ b/.github/workflows/mobile-prod.yml @@ -50,7 +50,7 @@ jobs: yarn build:mobile - name: Build on EAS - run: cd apps/mobile && eas build --platform android --non-interactive + run: cd apps/mobile && eas build --platform all --non-interactive - name: Publish update run: cd apps/mobile && eas update --auto @@ -58,7 +58,7 @@ jobs: - name: Decode Google Credentials run: | DECODED_GOOGLE_CREDENTIALS=$(echo '${{ secrets.GOOGLE_CREDENTIALS }}' | base64 --decode) - echo "DECODED_GOOGLE_CREDENTIALS=$DECODED_GOOGLE_CREDENTIALS" >> $GITHUB_ENV + echo "DECODED_GOOGLE_CREDENTIALS=$DECODED_GOOGLE_CREDENTIALS" >> $GITHUB_ENV echo "::add-mask::$DECODED_GOOGLE_CREDENTIALS" ESCAPED_GOOGLE_CREDENTIALS=$(echo "$DECODED_GOOGLE_CREDENTIALS" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') ESCAPED_GOOGLE_CREDENTIALS=$(echo $ESCAPED_GOOGLE_CREDENTIALS | sed 's/\\n/\\\\n/g') @@ -79,4 +79,7 @@ jobs: project_id: "ever-teams-399720" - name: Upload to Play Store Console - run: cd apps/mobile && eas submit -p android --latest --key ${{ secrets.GOOGLE_CREDENTIALS }} + run: cd apps/mobile && eas submit --platform android --latest --non-interactive + + - name: Publish to App Store + run: EXPO_APPLE_APP_SPECIFIC_PASSWORD=${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }} eas submit --platform ios --latest --non-interactive diff --git a/.gitignore b/.gitignore index 425c699ed..4f744ed77 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ Thumbs.db /apps/**/.cache /packages/**/.cache /packages/**/dist + +# Local Netlify folder +.netlify diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..87ec8842b --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +18.18.2 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..87ec8842b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.18.2 diff --git a/Dockerfile b/Dockerfile index 016acfb61..0520af00c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,19 @@ ARG NODE_VERSION=18.17.1 FROM node:${NODE_VERSION}-slim as base - # Next.js app lives here WORKDIR /app -# Set production environment -ENV NEXT_SHARP_PATH=/app/node_modules/sharp +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NEXT_BUILD_OUTPUT_TYPE=standalone +ENV NEXT_SHARP_PATH=/temp/node_modules/sharp + +RUN npm i -g npm@latest +# Install sharp, NextJS image optimization +RUN mkdir /temp && cd /temp && \ + npm i sharp + +RUN npm cache clean --force # Throw-away build stage to reduce size of final image @@ -33,7 +40,7 @@ RUN cd apps/web && \ # Copy application code COPY --link . . -ENV NODE_ENV="production" +ENV NODE_ENV=production # Build application RUN yarn run build:web @@ -42,16 +49,22 @@ RUN yarn run build:web RUN cd apps/web && \ yarn install --prod --ignore-scripts +RUN yarn cache clean + # Final stage for app image FROM base -ENV NODE_ENV="production" +ENV NODE_ENV=production # Copy built application -COPY --from=build /app /app +COPY --from=build /app/apps/web/.next/standalone ./ +COPY --from=build /app/apps/web/.next/static ./apps/web/.next/static +COPY --from=build /app/apps/web/public ./apps/web/public # Start the server by default, this can be overwritten at runtime EXPOSE 3000 -CMD [ "npm", "run", "start:web" ] +ENV PORT=3000 + +CMD [ "node", "./apps/web/server.js" ] diff --git a/README.md b/README.md index 92f57d0d3..1c3e3ddf8 100644 --- a/README.md +++ b/README.md @@ -132,17 +132,21 @@ WIP [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fever-co%2Fever-teams&project-name=ever-teams&repository-name=ever-teams&output-directory=.next&build-command=yarn%20build&install-command=yarn%20install%20--frozen-lockfile&root-directory=apps%2Fweb) +### Render + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/ever-co/ever-teams) + ### Railway -Note: WIP +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/7_OfzR?referralCode=40jeja) -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/EverTeams) +### Fly -### Render +[![Deploy on Railway](https://ever.team/fly.png)](https://github.com/ever-co/ever-teams/wiki/Deploy-to-Fly) -Note: WIP +### Netlify -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/ever-co/ever-teams) +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/ever-co/ever-teams) ## 📄 Content @@ -166,8 +170,8 @@ Note: WIP ## 🔐 Security -Ever Teams Platform follows good security practices, but 100% security cannot be guaranteed in any software! -Ever Teams Platform is provided AS IS without any warranty. Use at your own risk! +**Ever Teams Platform** follows good security practices, but 100% security cannot be guaranteed in any software! +**Ever Teams Platform** is provided AS IS without any warranty. Use at your own risk! See more details in the [LICENSE.md](LICENSE.md). In a production setup, all client-side to server-side (backend, APIs) communications should be encrypted using HTTPS/WSS/SSL (REST APIs, GraphQL endpoint, Socket.io WebSockets, etc.). @@ -241,7 +245,7 @@ You can also view a full list of our [contributors tracked by Github](https://gi [![Circle CI](https://circleci.com/gh/ever-co/ever-teams.svg?style=svg)](https://circleci.com/gh/ever-co/ever-teams) [![codecov](https://codecov.io/gh/ever-co/ever-teams/branch/master/graph/badge.svg)](https://codecov.io/gh/ever-co/ever-teams) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/8c46f9eb9df64aa9859dea4d572059ac)](https://www.codacy.com/gh/ever-co/ever-teams/dashboard?utm_source=github.com&utm_medium=referral&utm_content=ever-co/ever-teams&utm_campaign=Badge_Grade) -[![DeepScan grade](https://deepscan.io/api/teams/3293/projects/16703/branches/363423/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=3293&pid=16703&bid=363423) +[![DeepScan grade](https://deepscan.io/api/teams/3293/projects/25855/branches/814579/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=3293&pid=25855&bid=814579) [![Known Vulnerabilities](https://snyk.io/test/github/ever-co/ever-teams/badge.svg)](https://snyk.io/test/github/ever-co/ever-teams) [![Total alerts](https://img.shields.io/lgtm/alerts/g/ever-co/ever-teams.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ever-co/ever-teams/alerts/) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ever-co/ever-teams.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ever-co/ever-teams/context:javascript) diff --git a/apps/extensions/assets/logo/gauzy-teams-dark.png b/apps/extensions/assets/logo/ever-teams-dark.png similarity index 100% rename from apps/extensions/assets/logo/gauzy-teams-dark.png rename to apps/extensions/assets/logo/ever-teams-dark.png diff --git a/apps/extensions/components/popup/Header.tsx b/apps/extensions/components/popup/Header.tsx index 7f96ad2da..3c79004f6 100644 --- a/apps/extensions/components/popup/Header.tsx +++ b/apps/extensions/components/popup/Header.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import logoImg from 'data-base64:~assets/logo/gauzy-teams-dark.png'; +import logoImg from 'data-base64:~assets/logo/ever-teams-dark.png'; import React, { FC, useState } from 'react'; import AppDropdown from '~components/shared/AppDropdown'; diff --git a/apps/extensions/package.json b/apps/extensions/package.json index 4c47ea6d5..b8a8b0c15 100644 --- a/apps/extensions/package.json +++ b/apps/extensions/package.json @@ -1,38 +1,42 @@ { - "name": "@ever-teams/extensions", - "displayName": "Ever Teams", - "version": "0.1.0", - "description": "Ever Teams Browser Extensions", - "license": "UNLICENSED", - "author": "Ever Co. LTD", - "scripts": { - "dev": "yarn plasmo dev", - "start": "yarn dev", - "build": "yarn plasmo build" - }, - "dependencies": { - "@tailwindcss/forms": "^0.5.3", - "@tailwindcss/typography": "^0.5.8", - "classnames": "^2.3.2", - "plasmo": "^0.59.1", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-timer-hook": "^3.0.5", - "tailwindcss": "^3.2.4" - }, - "devDependencies": { - "@plasmohq/prettier-plugin-sort-imports": "^3.6.0", - "@types/chrome": "^0.0.203", - "@types/node": "18.8.3", - "@types/react": "18.0.21", - "@types/react-dom": "18.0.6", - "postcss": "^8.4.19", - "prettier": "^2.8.0", - "typescript": "4.8.4" - }, - "manifest": { - "host_permissions": [ - "https://*/*" - ] - } + "name": "@ever-teams/extensions", + "displayName": "Ever Teams", + "version": "0.1.0", + "description": "Ever Teams Browser Extensions", + "license": "UNLICENSED", + "author": "Ever Co. LTD", + "scripts": { + "dev": "yarn plasmo dev", + "start": "yarn dev", + "build": "yarn plasmo build" + }, + "dependencies": { + "@tailwindcss/forms": "^0.5.3", + "@tailwindcss/typography": "^0.5.8", + "classnames": "^2.3.2", + "plasmo": "^0.59.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-timer-hook": "^3.0.5", + "tailwindcss": "^3.2.4" + }, + "devDependencies": { + "@plasmohq/prettier-plugin-sort-imports": "^3.6.0", + "@types/chrome": "^0.0.203", + "@types/node": "18.8.3", + "@types/react": "18.0.21", + "@types/react-dom": "18.0.6", + "postcss": "^8.4.19", + "prettier": "^2.8.0", + "typescript": "4.8.4" + }, + "manifest": { + "host_permissions": [ + "https://*/*" + ] + }, + "engines": { + "node": ">=16.0.0", + "yarn": ">=1.13.0" + } } diff --git a/apps/mobile/.env b/apps/mobile/.env index 442a2a6c9..f3df1a804 100644 --- a/apps/mobile/.env +++ b/apps/mobile/.env @@ -2,8 +2,7 @@ # We are using react-native-dotenv (.env) GAUZY_API_URL=https://api.gauzy.co -INVITE_CALLBACK_URL=https://app.ever.team/passcode - +INVITE_CALLBACK_URL=https://app.ever.team/auth/passcode EXPO_PUBLIC_SENTRY_DSN= SENTRY_ORG=ever-co SENTRY_PROJECT=ever-teams-mobile diff --git a/apps/mobile/.env.template b/apps/mobile/.env.template index 6ecf045ff..f3df1a804 100644 --- a/apps/mobile/.env.template +++ b/apps/mobile/.env.template @@ -1,9 +1,8 @@ # NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) # We are using react-native-dotenv (.env) -GAUZY_API_URL= -INVITE_CALLBACK_URL=https://app.ever.team/passcode - +GAUZY_API_URL=https://api.gauzy.co +INVITE_CALLBACK_URL=https://app.ever.team/auth/passcode EXPO_PUBLIC_SENTRY_DSN= -SENTRY_ORG= -SENTRY_PROJECT= +SENTRY_ORG=ever-co +SENTRY_PROJECT=ever-teams-mobile diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index f5e56efee..907eb40da 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -118,7 +118,7 @@ android { applicationId 'ever.team' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 + versionCode 2 versionName "0.1.0" } diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml index eaaa246de..80f5e741c 100644 --- a/apps/mobile/android/app/src/main/AndroidManifest.xml +++ b/apps/mobile/android/app/src/main/AndroidManifest.xml @@ -15,10 +15,10 @@ - + - + @@ -33,4 +33,4 @@ - + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/values/strings.xml b/apps/mobile/android/app/src/main/res/values/strings.xml index be1d463f8..54ccbc9f2 100644 --- a/apps/mobile/android/app/src/main/res/values/strings.xml +++ b/apps/mobile/android/app/src/main/res/values/strings.xml @@ -2,5 +2,5 @@ Ever Teams cover false - 1.0.0 - + exposdk:48.0.0 + \ No newline at end of file diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 81e750d65..fb8bbd820 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -3,10 +3,10 @@ "displayName": "Ever Teams", "expo": { "name": "Ever Teams", - "slug": "ever-gauzy-teams-mobile-expo", + "slug": "ever-teams-mobile", "version": "0.1.0", "orientation": "portrait", - "icon": "./assets/images/gauzy-teams-logo.png", + "icon": "./assets/images/ever-teams-logo.png", "splash": { "image": "./assets/images/splash-ever-teams.png", "resizeMode": "cover", @@ -15,7 +15,7 @@ "owner": "everco", "updates": { "fallbackToCacheTimeout": 0, - "url": "https://u.expo.dev/9f38f768-16f6-4c47-b354-9745ebda1335" + "url": "https://u.expo.dev/2ff924e4-7a91-4b23-9db9-7453a8063bb0" }, "jsEngine": "hermes", "assetBundlePatterns": ["**/*"], @@ -51,7 +51,7 @@ "ios": { "icon": "./assets/images/app-icon-ios-ever-teams.png", "supportsTablet": true, - "bundleIdentifier": "ever.team", + "bundleIdentifier": "co.ever.teams", "splash": { "image": "./assets/images/splash-ever-teams.png", "tabletImage": "./assets/images/splash-logo-ever-teams-ios-tablet.png", @@ -74,7 +74,7 @@ }, "extra": { "eas": { - "projectId": "9f38f768-16f6-4c47-b354-9745ebda1335" + "projectId": "2ff924e4-7a91-4b23-9db9-7453a8063bb0" } }, "hooks": { diff --git a/apps/mobile/app/components/Accordion.tsx b/apps/mobile/app/components/Accordion.tsx new file mode 100644 index 000000000..34ca4a382 --- /dev/null +++ b/apps/mobile/app/components/Accordion.tsx @@ -0,0 +1,60 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import { View, Text, StyleSheet, TouchableOpacity } from "react-native" +import React, { useState } from "react" +import { Feather } from "@expo/vector-icons" +import { useAppTheme } from "../theme" + +const Accordion = ({ children, title }) => { + const [expanded, setExpanded] = useState(true) + const { colors } = useAppTheme() + + function toggleItem() { + setExpanded(!expanded) + } + + const body = {children} + return ( + + + {title} + + + {expanded && ( + + + + )} + {expanded && body} + + ) +} + +export default Accordion + +const styles = StyleSheet.create({ + accordContainer: { + borderRadius: 8, + width: "100%", + }, + accordHeader: { + alignItems: "center", + flexDirection: "row", + justifyContent: "space-between", + padding: 12, + }, + accordTitle: { + fontSize: 20, + fontWeight: "600", + }, +}) diff --git a/apps/mobile/app/components/IssuesModal.tsx b/apps/mobile/app/components/IssuesModal.tsx index 40e11202b..b1d015c4c 100644 --- a/apps/mobile/app/components/IssuesModal.tsx +++ b/apps/mobile/app/components/IssuesModal.tsx @@ -1,5 +1,6 @@ /* eslint-disable react-native/no-color-literals */ -import React, { FC, useState } from 'react'; +/* eslint-disable react-native/no-inline-styles */ +import React, { FC, useState } from "react" import { Text, View, @@ -9,56 +10,89 @@ import { Modal, ViewStyle, TouchableWithoutFeedback, - TouchableOpacity -} from 'react-native'; -import { useTaskIssue } from '../services/hooks/features/useTaskIssue'; -import { ITeamTask } from '../services/interfaces/ITask'; -import { SvgUri } from 'react-native-svg'; -import { IIssueType } from '../services/interfaces/ITaskIssue'; -import { useTeamTasks } from '../services/hooks/features/useTeamTasks'; + TouchableOpacity, +} from "react-native" +import { useTaskIssue } from "../services/hooks/features/useTaskIssue" +import { ITeamTask } from "../services/interfaces/ITask" +import { SvgUri } from "react-native-svg" +import { IIssueType } from "../services/interfaces/ITaskIssue" +import { useTeamTasks } from "../services/hooks/features/useTeamTasks" +import { useAppTheme } from "../theme" +import { BlurView } from "expo-blur" interface IssuesModalProps { - task: ITeamTask; - readonly?: boolean; + task: ITeamTask + readonly?: boolean + nameIncluded?: boolean + smallFont?: boolean } -const IssuesModal: FC = ({ task, readonly = false }) => { - const { allTaskIssues } = useTaskIssue(); - const [isModalOpen, setIsModalOpen] = useState(false); - const { updateTask } = useTeamTasks(); +const IssuesModal: FC = ({ task, readonly = false, nameIncluded, smallFont }) => { + const { allTaskIssues } = useTaskIssue() + const [isModalOpen, setIsModalOpen] = useState(false) + const { updateTask } = useTeamTasks() + const { colors } = useAppTheme() const currentIssue = task?.issueType ? allTaskIssues.find((issue) => issue.name === task?.issueType) - : allTaskIssues.find((issue) => issue.name === 'Task'); + : allTaskIssues.find((issue) => issue.name === "Task") const onChangeIssue = async (text) => { if (task) { const taskEdit = { ...task, - issueType: text - }; + issueType: text, + } - await updateTask(taskEdit, task.id); + await updateTask(taskEdit, task.id) } - }; + } - const iconDimension: number = currentIssue?.name === 'Bug' ? 15 : currentIssue?.name === 'Story' ? 14 : 13; + const iconDimension: number = + currentIssue?.name === "Bug" ? 15 : currentIssue?.name === "Story" ? 14 : 13 return ( - <> + { - if (currentIssue.name !== 'Epic' && !readonly) { - setIsModalOpen(true); + if (currentIssue.name !== "Epic" && !readonly) { + setIsModalOpen(true) } }} > - + + + {nameIncluded && ( + + {currentIssue?.name} + + )} setIsModalOpen(false)}> - + ( @@ -72,59 +106,70 @@ const IssuesModal: FC = ({ task, readonly = false }) => { /> - - ); -}; + + ) +} -export default IssuesModal; +export default IssuesModal const ModalPopUp = ({ visible, children, onDismiss }) => { - const [showModal, setShowModal] = React.useState(visible); - const scaleValue = React.useRef(new Animated.Value(0)).current; + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current React.useEffect(() => { - toggleModal(); - }, [visible]); + toggleModal() + }, [visible]) const toggleModal = () => { if (visible) { - setShowModal(true); + setShowModal(true) Animated.spring(scaleValue, { toValue: 1, - useNativeDriver: true - }).start(); + useNativeDriver: true, + }).start() } else { - setTimeout(() => setShowModal(false), 200); + setTimeout(() => setShowModal(false), 200) Animated.timing(scaleValue, { toValue: 0, duration: 300, - useNativeDriver: true - }).start(); + useNativeDriver: true, + }).start() } - }; + } return ( + onDismiss()}> - {children} + + {children} + - ); -}; + ) +} interface IItem { - issue: IIssueType; - onChangeIssue: (text: string) => void; - closeModal: () => void; - readonly?: boolean; + issue: IIssueType + onChangeIssue: (text: string) => void + closeModal: () => void + readonly?: boolean } const Item = ({ issue, onChangeIssue, closeModal, readonly = false }: IItem) => { return ( { - onChangeIssue(issue.name); - closeModal(); + onChangeIssue(issue.name) + closeModal() }} activeOpacity={readonly ? 1 : 0.2} > @@ -132,48 +177,45 @@ const Item = ({ issue, onChangeIssue, closeModal, readonly = false }: IItem) => style={[ styles.issueContainer, { - backgroundColor: issue.color - } + backgroundColor: issue.color, + }, ]} > {issue.name} - ); -}; + ) +} const $modalBackGround: ViewStyle = { flex: 1, - backgroundColor: '#000000AA', - justifyContent: 'center' -}; + justifyContent: "center", +} const styles = StyleSheet.create({ issueContainer: { - alignItems: 'center', + alignItems: "center", borderRadius: 10, - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', + display: "flex", + flexDirection: "row", + justifyContent: "flex-start", marginVertical: 7, paddingHorizontal: 25, - paddingVertical: 20 + paddingVertical: 20, }, - issueText: { color: '#FFFF', fontSize: 22, marginHorizontal: 8 }, + issueText: { color: "#FFFF", fontSize: 22, marginHorizontal: 8 }, modalContainer: { - alignSelf: 'center', - backgroundColor: '#fff', + alignSelf: "center", + backgroundColor: "#fff", borderRadius: 20, - height: 'auto', + height: "auto", padding: 22, - width: '40%' + width: 170, }, wrapButton: { - alignItems: 'center', + alignItems: "center", borderRadius: 3, - height: 20, - justifyContent: 'center', - width: 20 - } -}); + justifyContent: "center", + }, +}) diff --git a/apps/mobile/app/components/StatusType.tsx b/apps/mobile/app/components/StatusType.tsx index 86002049d..f42ce589e 100644 --- a/apps/mobile/app/components/StatusType.tsx +++ b/apps/mobile/app/components/StatusType.tsx @@ -1,65 +1,80 @@ -import React, { useMemo } from 'react'; -import { View } from 'react-native'; -import { SvgUri } from 'react-native-svg'; -import { useTaskLabels } from '../services/hooks/features/useTaskLabels'; -import { useTaskPriority } from '../services/hooks/features/useTaskPriority'; -import { useTaskSizes } from '../services/hooks/features/useTaskSizes'; -import { useTaskStatus } from '../services/hooks/features/useTaskStatus'; -import { ITaskStatusItem } from '../services/interfaces/ITaskStatus'; +import React, { useMemo } from "react" +import { View } from "react-native" +import { SvgUri } from "react-native-svg" +import { useTaskLabels } from "../services/hooks/features/useTaskLabels" +import { useTaskPriority } from "../services/hooks/features/useTaskPriority" +import { useTaskSizes } from "../services/hooks/features/useTaskSizes" +import { useTaskStatus } from "../services/hooks/features/useTaskStatus" +import { ITaskStatusItem } from "../services/interfaces/ITaskStatus" +import { useTaskVersion } from "../services/hooks/features/useTaskVersion" export type TStatusItem = { - bgColor?: string; - icon?: React.ReactNode | undefined; - name?: string; - value?: string; - bordered?: boolean; -}; + bgColor?: string + icon?: React.ReactNode | undefined + name?: string + value?: string + bordered?: boolean +} export type TStatus = { - [k in T]: TStatusItem; -}; + [k in T]: TStatusItem +} -export function useMapToTaskStatusValues(data: T[], bordered = false): TStatus { +export function useMapToTaskStatusValues( + data: T[], + bordered = false, +): TStatus { return useMemo(() => { return data.reduce((acc, item) => { const value: TStatus[string] = { - name: item.name?.split('-').join(' '), + name: item.name?.split("-").join(" "), value: item.value || item.name, bgColor: item.color, bordered, - icon: {item.fullIconUrl && } - }; + icon: ( + + {item.fullIconUrl && ( + + )} + + ), + } if (value.name) { - acc[value.name] = value; + acc[value.name] = value } else if (value.value) { - acc[value.value] = value; + acc[value.value] = value } - return acc; - }, {} as TStatus); - }, [data, bordered]); + return acc + }, {} as TStatus) + }, [data, bordered]) } // ==================== Task Status ======================================== export function useTaskStatusValue() { - const { allStatuses } = useTaskStatus(); - return useMapToTaskStatusValues(allStatuses); + const { allStatuses } = useTaskStatus() + return useMapToTaskStatusValues(allStatuses) } // =================== Task Size ============================================== export function useTaskSizeValue() { - const { allTaskSizes } = useTaskSizes(); - return useMapToTaskStatusValues(allTaskSizes); + const { allTaskSizes } = useTaskSizes() + return useMapToTaskStatusValues(allTaskSizes) } // =================== Task Label ============================================== export function useTaskLabelValue() { - const { allTaskLabels } = useTaskLabels(); - return useMapToTaskStatusValues(allTaskLabels); + const { allTaskLabels } = useTaskLabels() + return useMapToTaskStatusValues(allTaskLabels) } // =================== Task Priority ============================================== export function useTaskPriorityValue() { - const { allTaskPriorities } = useTaskPriority(); - return useMapToTaskStatusValues(allTaskPriorities); + const { allTaskPriorities } = useTaskPriority() + return useMapToTaskStatusValues(allTaskPriorities) +} +// =================== Task Priority ============================================== +export function useTaskVersionValue() { + const { taskVersionList } = useTaskVersion() + return useMapToTaskStatusValues(taskVersionList) } diff --git a/apps/mobile/app/components/Task/DetailsBlock/blocks/TaskMainInfo.tsx b/apps/mobile/app/components/Task/DetailsBlock/blocks/TaskMainInfo.tsx new file mode 100644 index 000000000..2261c9811 --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/blocks/TaskMainInfo.tsx @@ -0,0 +1,350 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import { View, Text, StyleSheet } from "react-native" +import React from "react" +import { useStores } from "../../../../models" +import TaskRow from "../components/TaskRow" +import { SvgXml } from "react-native-svg" +import { + calendarIcon, + categoryIcon, + clipboardIcon, + peopleIconSmall, + profileIcon, +} from "../../../svgs/icons" +import ProfileInfo from "../components/ProfileInfo" +import ManageAssignees from "../components/ManageAssignees" +import { useOrganizationTeam } from "../../../../services/hooks/useOrganization" +import CalendarModal from "../components/CalendarModal" +import { useTeamTasks } from "../../../../services/hooks/features/useTeamTasks" +import moment from "moment-timezone" +import TaskStatus from "../../../TaskStatus" +import TaskSize from "../../../TaskSize" +import TaskPriority from "../../../TaskPriority" +import TaskLabels from "../../../TaskLabels" +import TaskVersion from "../../../TaskVersion" +import { useAppTheme } from "../../../../theme" +import { ITeamTask } from "../../../../services/interfaces/ITask" +import { TouchableOpacity } from "react-native-gesture-handler" +import { useNavigation } from "@react-navigation/native" +import { SettingScreenNavigationProp } from "../../../../navigators/AuthenticatedNavigator" +import TaskEpic from "../../../TaskEpic" +import IssuesModal from "../../../IssuesModal" +import { observer } from "mobx-react-lite" +import { translate } from "../../../../i18n" + +const TaskMainInfo = observer(() => { + const { + TaskStore: { detailedTask: task }, + } = useStores() + + const { currentTeam } = useOrganizationTeam() + const { updateTask } = useTeamTasks() + + const { colors } = useAppTheme() + + return ( + + {/* Issue type */} + + + + {translate("taskDetailsScreen.typeIssue")} + + + } + > + + + {/* Creator */} + + + + {translate("taskDetailsScreen.creator")} + + + } + > + + + {/* Assignees */} + + + + {translate("taskDetailsScreen.assignees")} + + + } + > + {task?.members?.map((member, index) => ( + + ))} + + {/* Manage Assignees */} + + + + {/* Manage Start Date */} + + + + {translate("taskDetailsScreen.startDate")} + + + } + > + updateTask({ ...task, startDate: date }, task?.id)} + selectedDate={task?.startDate} + /> + + + {/* Manage Due Date */} + + + {translate("taskDetailsScreen.dueDate")} + + + } + > + updateTask({ ...task, dueDate: date }, task?.id)} + selectedDate={task?.dueDate} + isDueDate={true} + /> + + + {/* Days Remaining */} + {task?.startDate && task?.dueDate && ( + + + {translate("taskDetailsScreen.daysRemaining")} + + + } + > + + {moment(task?.dueDate).diff(moment(), "days") < 0 + ? 0 + : moment(task?.dueDate).diff(moment(), "days")} + + + )} + + {/* horizontal separator */} + + + {/* Version */} + + + {translate("taskDetailsScreen.version")} + + + } + > + + + + {/* Epic */} + {task && task.issueType === "Story" && ( + + + {translate("taskDetailsScreen.epic")} + + + } + > + + + )} + {task && } + + {/* Status */} + + + {translate("taskDetailsScreen.status")} + + + } + > + + + + {/* Labels */} + + + {translate("taskDetailsScreen.labels")} + + + } + > + + + + {/* Size */} + + {translate("taskDetailsScreen.size")} + + } + > + + + + {/* Priority */} + + + {translate("taskDetailsScreen.priority")} + + + } + > + + + + ) +}) + +export default TaskMainInfo + +const EpicParent: React.FC<{ task: ITeamTask }> = ({ task }) => { + const { colors } = useAppTheme() + + const navigation = useNavigation>() + + const navigateToEpic = () => { + navigation.navigate("TaskScreen", { taskId: task?.rootEpic?.id }) + } + + if (task?.issueType === "Story") { + return <> + } + return (!task?.issueType || task?.issueType === "Task" || task?.issueType === "Bug") && + task?.rootEpic ? ( + + {translate("taskDetailsScreen.epic")} + + } + > + + + + + {`#${task?.rootEpic?.number} ${task?.rootEpic?.title}`} + + + ) : ( + <> + ) +} + +const styles = StyleSheet.create({ + epicParentButton: { alignItems: "center", flexDirection: "row", gap: 4, width: "60%" }, + epicParentIconWrapper: { + backgroundColor: "#8154BA", + borderRadius: 4, + marginVertical: 5, + padding: 4, + }, + horizontalSeparator: { + borderBottomColor: "#F2F2F2", + borderBottomWidth: 1, + marginVertical: 10, + width: "100%", + }, + labelComponent: { + alignItems: "center", + flexDirection: "row", + gap: 7, + }, + labelText: { + color: "#A5A2B2", + fontSize: 12, + }, +}) diff --git a/apps/mobile/app/components/Task/DetailsBlock/blocks/TaskPublicity.tsx b/apps/mobile/app/components/Task/DetailsBlock/blocks/TaskPublicity.tsx new file mode 100644 index 000000000..98ee9060d --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/blocks/TaskPublicity.tsx @@ -0,0 +1,86 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import { View, Text, StyleSheet } from "react-native" +import React, { useCallback, useEffect, useState } from "react" +import { useStores } from "../../../../models" +import { translate } from "../../../../i18n" +import { SvgXml } from "react-native-svg" +import { globeDarkTheme, globeLightTheme, lockDarkTheme, lockLightTheme } from "../../../svgs/icons" +import { useAppTheme } from "../../../../theme" +import { TouchableOpacity } from "react-native-gesture-handler" +import { debounce } from "lodash" +import { useTeamTasks } from "../../../../services/hooks/features/useTeamTasks" + +const TaskPublicity = () => { + const { + TaskStore: { detailedTask: task }, + } = useStores() + const { updatePublicity } = useTeamTasks() + + const { dark, colors } = useAppTheme() + const [isTaskPublic, setIsTaskPublic] = useState(task?.public) + + const handlePublicity = useCallback( + (value: boolean) => { + setIsTaskPublic(value) + const debounceUpdatePublicity = debounce((value) => { + updatePublicity(value, task, true) + }, 500) + debounceUpdatePublicity(value) + }, + [task, updatePublicity], + ) + + useEffect(() => { + setIsTaskPublic(task?.public) + }, [task?.public]) + + return ( + + {isTaskPublic ? ( + + + + + {translate("taskDetailsScreen.taskPublic")} + + + handlePublicity(false)}> + + {translate("taskDetailsScreen.makePrivate")} + + + + ) : ( + + + + + {translate("taskDetailsScreen.taskPrivate")} + + + handlePublicity(true)}> + + {translate("taskDetailsScreen.makePublic")} + + + + )} + + ) +} + +export default TaskPublicity + +const styles = StyleSheet.create({ + taskPrivacyWrapper: { + alignItems: "center", + flexDirection: "row", + gap: 8, + }, + wrapper: { + alignItems: "center", + flexDirection: "row", + justifyContent: "space-between", + }, +}) diff --git a/apps/mobile/app/components/Task/DetailsBlock/components/CalendarModal.tsx b/apps/mobile/app/components/Task/DetailsBlock/components/CalendarModal.tsx new file mode 100644 index 000000000..974f46be6 --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/components/CalendarModal.tsx @@ -0,0 +1,155 @@ +/* eslint-disable react-native/no-inline-styles */ +import { + View, + Text, + Animated, + Modal, + TouchableWithoutFeedback, + TouchableOpacity, + ViewStyle, + StyleSheet, +} from "react-native" +import React, { useEffect, useState } from "react" +import { useAppTheme } from "../../../../theme" +import { Calendar } from "react-native-calendars" +import moment from "moment-timezone" +import { SvgXml } from "react-native-svg" +import { trashIconSmall } from "../../../svgs/icons" +import { BlurView } from "expo-blur" +import { translate } from "../../../../i18n" + +interface ICalendarModal { + selectedDate: string + isDueDate?: boolean + updateTask: (date: string) => void +} + +const CalendarModal: React.FC = ({ selectedDate, isDueDate, updateTask }) => { + const { colors } = useAppTheme() + const [modalVisible, setModalVisible] = useState(false) + const [selected, setSelected] = useState("") + + useEffect(() => { + selectedDate && setSelected(moment(selectedDate).format("YYYY-MM-DD")) + }, [selectedDate]) + + const formatted = moment(selected).format("DD MMM YYYY") + + return ( + + + setModalVisible(true)}> + + {selected + ? formatted + : isDueDate + ? translate("taskDetailsScreen.setDueDate") + : translate("taskDetailsScreen.setStartDate")} + + + + {selected && selectedDate && ( + { + setSelected("") + updateTask(null) + }} + > + + + )} + + + setModalVisible(false)}> + + { + setSelected(day.dateString) + updateTask(moment(day.dateString).toISOString()) + }} + markedDates={{ + [selected]: { + selected: true, + disableTouchEvent: true, + selectedColor: colors.secondary, + dotColor: "orange", + }, + }} + theme={{ + todayTextColor: colors.secondary, + arrowColor: colors.secondary, + }} + /> + + + + ) +} + +export default CalendarModal + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + return ( + + + + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + buttonWrapper: { + alignItems: "center", + flexDirection: "row", + height: 14, + justifyContent: "space-between", + width: 110, + }, + container: { + alignSelf: "center", + borderRadius: 20, + padding: 20, + width: "90%", + }, +}) diff --git a/apps/mobile/app/components/Task/DetailsBlock/components/ManageAssignees.tsx b/apps/mobile/app/components/Task/DetailsBlock/components/ManageAssignees.tsx new file mode 100644 index 000000000..d93fc4fc2 --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/components/ManageAssignees.tsx @@ -0,0 +1,233 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable camelcase */ +import { + View, + Text, + Modal, + TouchableWithoutFeedback, + Animated, + ViewStyle, + StyleSheet, + TouchableOpacity, + Dimensions, +} from "react-native" +import React, { useEffect, useMemo, useState } from "react" +import { useAppTheme } from "../../../../theme" +import { OT_Member } from "../../../../services/interfaces/IOrganizationTeam" +import ProfileInfo from "./ProfileInfo" +import { SvgXml } from "react-native-svg" +import { trashIconLarge } from "../../../svgs/icons" +import { ITeamTask } from "../../../../services/interfaces/ITask" +import { useTeamMemberCard } from "../../../../services/hooks/features/useTeamMemberCard" +import { ScrollView } from "react-native-gesture-handler" +import { BlurView } from "expo-blur" +import { translate } from "../../../../i18n" + +interface IManageAssignees { + memberList: OT_Member[] + task: ITeamTask +} + +const ManageAssignees: React.FC = ({ memberList, task }) => { + const [modalVisible, setModalVisible] = useState(false) + const [member, setMember] = useState() + const [memberToRemove, setMemberToRemove] = useState(false) + const [memberToAdd, setMemberToAdd] = useState(false) + + const { colors } = useAppTheme() + const memberInfo = useTeamMemberCard(member) + const { height } = Dimensions.get("window") + + const assignedToTaskMembers = useMemo( + () => + memberList?.filter((member) => + member.employee + ? task?.members.map((item) => item.userId).includes(member.employee?.userId) + : false, + ), + [memberList, task?.members], + ) + + const unassignedMembers = useMemo( + () => + memberList?.filter((member) => + member.employee + ? !task?.members.map((item) => item.userId).includes(member.employee.userId) + : false, + ), + [memberList, task?.members], + ) + + useEffect(() => { + if (task && member && memberToRemove) { + memberInfo + .unassignTask(task) + .then(() => { + setMember(undefined) + setMemberToRemove(false) + }) + .catch(() => { + setMember(undefined) + setMemberToRemove(false) + }) + } else if (task && member && memberToAdd) { + memberInfo + .assignTask(task) + .then(() => { + setMember(undefined) + setMemberToAdd(false) + }) + .catch(() => { + setMember(undefined) + setMemberToAdd(false) + }) + } + }, [task, member, memberInfo, memberToAdd, memberToRemove]) + + return ( + + setModalVisible(true)} style={styles.button}> + + {translate("taskDetailsScreen.manageAssignees")} + + + + setModalVisible(false)}> + + + {assignedToTaskMembers?.map((member, index) => ( + { + setMember(member) + setMemberToRemove(true) + setModalVisible(false) + }} + key={index} + style={styles.memberWrapper} + > + + + + + + + + ))} + {unassignedMembers?.map((member, index) => ( + { + setMember(member) + setMemberToAdd(true) + setModalVisible(false) + }} + key={index} + style={styles.memberWrapper} + > + + + + + ))} + + + + + ) +} + +export default ManageAssignees + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + return ( + + + + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + button: { + alignItems: "center", + borderColor: "#E5E7EB", + borderRadius: 100, + borderWidth: 1, + height: 24, + justifyContent: "center", + marginVertical: 10, + paddingHorizontal: 8, + paddingVertical: 4, + width: 140, + }, + container: { + alignSelf: "center", + borderRadius: 20, + padding: 20, + width: "70%", + }, + memberWrapper: { + alignItems: "center", + flexDirection: "row", + justifyContent: "space-between", + marginBottom: 10, + }, +}) diff --git a/apps/mobile/app/components/Task/DetailsBlock/components/ProfileInfo.tsx b/apps/mobile/app/components/Task/DetailsBlock/components/ProfileInfo.tsx new file mode 100644 index 000000000..a19c22bf2 --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/components/ProfileInfo.tsx @@ -0,0 +1,84 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import { Text, StyleSheet, TouchableOpacity } from "react-native" +import React from "react" +import { Avatar } from "react-native-paper" +import { imgTitleProfileAvatar } from "../../../../helpers/img-title-profile-avatar" +import { typography, useAppTheme } from "../../../../theme" +import { limitTextCharaters } from "../../../../helpers/sub-text" +import { useNavigation } from "@react-navigation/native" +import { + DrawerNavigationProp, + SettingScreenNavigationProp, +} from "../../../../navigators/AuthenticatedNavigator" + +interface IProfileInfo { + names: string + profilePicSrc: string + userId?: string + largerProfileInfo?: boolean +} + +const ProfileInfo: React.FC = ({ + profilePicSrc, + names, + userId, + largerProfileInfo, +}) => { + const { colors } = useAppTheme() + + const alternateNavigation = useNavigation>() + const navigation = useNavigation>() + + const navigateToProfile = () => { + alternateNavigation.navigate("AuthenticatedTab") + setTimeout(() => { + navigation.navigate("Profile", { userId, activeTab: "worked" }) + }, 50) + } + return ( + + {profilePicSrc ? ( + + ) : ( + + )} + + + {limitTextCharaters({ text: names.trim(), numChars: 18 })} + + + ) +} + +export default ProfileInfo + +const styles = StyleSheet.create({ + container: { + alignItems: "center", + flexDirection: "row", + gap: 7, + }, + prefix: { + color: "#FFFFFF", + fontFamily: typography.fonts.PlusJakartaSans.light, + }, + profileImage: { + borderRadius: 100, + }, +}) diff --git a/apps/mobile/app/components/Task/DetailsBlock/components/TaskRow.tsx b/apps/mobile/app/components/Task/DetailsBlock/components/TaskRow.tsx new file mode 100644 index 000000000..f2576fa44 --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/components/TaskRow.tsx @@ -0,0 +1,34 @@ +/* eslint-disable react-native/no-inline-styles */ +import { View, StyleSheet } from "react-native" +import React from "react" + +interface ITaskRow { + labelComponent: React.ReactNode + children: React.ReactNode + alignItems?: boolean +} + +const TaskRow = ({ labelComponent, children, alignItems }: ITaskRow) => { + return ( + + {labelComponent} + {children} + + ) +} + +export default TaskRow + +const styles = StyleSheet.create({ + childrenContainer: { + gap: 7, + width: "56%", + }, + container: { + flexDirection: "row", + justifyContent: "space-between", + }, + labelContainer: { + width: "40%", + }, +}) diff --git a/apps/mobile/app/components/Task/DetailsBlock/index.tsx b/apps/mobile/app/components/Task/DetailsBlock/index.tsx new file mode 100644 index 000000000..a4b209f0a --- /dev/null +++ b/apps/mobile/app/components/Task/DetailsBlock/index.tsx @@ -0,0 +1,16 @@ +import React from "react" +import Accordion from "../../Accordion" +import TaskPublicity from "./blocks/TaskPublicity" +import TaskMainInfo from "./blocks/TaskMainInfo" +import { translate } from "../../../i18n" + +const DetailsBlock = () => { + return ( + + + + + ) +} + +export default DetailsBlock diff --git a/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx b/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx new file mode 100644 index 000000000..c3ffcc69d --- /dev/null +++ b/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx @@ -0,0 +1,247 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import { + View, + Text, + Animated, + Modal, + TouchableWithoutFeedback, + ViewStyle, + StyleSheet, + ActivityIndicator, + Pressable, + TextInput, +} from "react-native" +import React, { useCallback, useEffect, useRef, useState } from "react" +import ComboBox from "../../../screens/Authenticated/TimerScreen/components/ComboBox" +import { translate } from "../../../i18n" +import IssuesModal from "../../IssuesModal" +import { useStores } from "../../../models" +import { useTaskInput } from "../../../services/hooks/features/useTaskInput" +import { typography, useAppTheme } from "../../../theme" +import { Feather } from "@expo/vector-icons" +import { ITeamTask } from "../../../services/interfaces/ITask" +import { BlurView } from "expo-blur" + +interface ICreateParentTaskModal { + visible: boolean + onDismiss: () => void + task: ITeamTask +} + +const CreateParentTaskModal: React.FC = ({ visible, onDismiss, task }) => { + const { colors } = useAppTheme() + + const taskInput = useTaskInput() + const { + setEditMode, + setQuery, + activeTask, + editMode, + // isModalOpen, + hasCreateForm, + handleTaskCreation, + createLoading, + } = taskInput + + const [combxShow, setCombxShow] = useState(true) + const inputRef = useRef(null) + const { + TimerStore: { localTimerStatus }, + } = useStores() + + const closeCombox = useCallback(() => { + setCombxShow(false) + }, [setCombxShow]) + + useEffect(() => { + setEditMode(true) + setCombxShow(true) + }, [editMode, combxShow]) + + useEffect(() => { + if (!editMode) { + inputRef.current?.blur() + } + }, [editMode]) + + return ( + + + + + + + {!editMode && activeTask ? `#${activeTask.taskNumber} ` : ""} + + setEditMode(true)} + // onBlur={() => setEditMode(false)} + onChangeText={(newText) => setQuery(newText)} + /> + {hasCreateForm && editMode && !createLoading ? ( + { + handleTaskCreation() + // setEditMode(false) + }} + > + + + ) : null} + + {createLoading ? ( + + ) : null} + + {combxShow && ( + + )} + + + ) +} + +export default CreateParentTaskModal + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + const modalRef = useRef(null) + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + + const handlePressOutside = (event) => { + const { locationX, locationY } = event.nativeEvent + + if (modalRef.current) { + modalRef.current.measureInWindow((x, y, width, height) => { + if ( + locationX < x || + locationX > x + width || + locationY < y || + locationY > y + height + ) { + onDismiss() + } + }) + } + } + return ( + + + + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + container: { + alignSelf: "center", + borderRadius: 20, + padding: 20, + width: "90%", + }, + loading: { + right: 10, + }, + taskNumberStyle: { + color: "#7B8089", + fontFamily: typography.primary.semiBold, + fontSize: 14, + marginLeft: 5, + }, + textInput: { + backgroundColor: "#fff", + borderRadius: 10, + color: "rgba(40, 32, 72, 0.4)", + fontFamily: typography.fonts.PlusJakartaSans.semiBold, + fontSize: 12, + height: 43, + paddingHorizontal: 6, + paddingVertical: 13, + width: "80%", + }, + wrapInput: { + backgroundColor: "#fff", + borderColor: "rgba(0, 0, 0, 0.1)", + borderRadius: 10, + borderWidth: 1, + height: 45, + paddingHorizontal: 16, + paddingVertical: 2, + width: "100%", + }, +}) diff --git a/apps/mobile/app/components/Task/TitleBlock/index.tsx b/apps/mobile/app/components/Task/TitleBlock/index.tsx index e78de6f1b..f073a4d3c 100644 --- a/apps/mobile/app/components/Task/TitleBlock/index.tsx +++ b/apps/mobile/app/components/Task/TitleBlock/index.tsx @@ -1,17 +1,24 @@ /* eslint-disable react-native/no-inline-styles */ /* eslint-disable react-native/no-color-literals */ -import { View, TextInput, StyleSheet, TouchableOpacity } from "react-native" +import { View, TextInput, StyleSheet, TouchableOpacity, Text, Dimensions } from "react-native" import React, { SetStateAction, useCallback, useEffect, useState } from "react" import { useStores } from "../../../models" -import { useAppTheme } from "../../../theme" +import { typography, useAppTheme } from "../../../theme" import { SvgXml } from "react-native-svg" import * as Clipboard from "expo-clipboard" import { closeIconLight, copyIcon, editIcon, tickIconLight } from "../../svgs/icons" import { useTeamTasks } from "../../../services/hooks/features/useTeamTasks" import { showMessage } from "react-native-flash-message" import { translate } from "../../../i18n" +import IssuesModal from "../../IssuesModal" +import { ITeamTask } from "../../../services/interfaces/ITask" +import { limitTextCharaters } from "../../../helpers/sub-text" +import CreateParentTaskModal from "./CreateParentTaskModal" +import { observer } from "mobx-react-lite" +import { useNavigation } from "@react-navigation/native" +import { SettingScreenNavigationProp } from "../../../navigators/AuthenticatedNavigator" -const TaskTitleBlock = () => { +const TaskTitleBlock = observer(() => { const { TaskStore: { detailedTask: task }, } = useStores() @@ -52,7 +59,7 @@ const TaskTitleBlock = () => { } return ( - + { { color: colors.primary, borderColor: edit ? (dark ? "#464242" : "#e5e7eb") : "transparent", + fontFamily: typography.fonts.PlusJakartaSans.semiBold, }, ]} onChangeText={(text) => setTitle(text)} @@ -75,9 +83,37 @@ const TaskTitleBlock = () => { saveTitle={() => saveTitle(title)} /> + + + #{task?.number} + + + + {task?.issueType !== "Epic" && ( + + )} + + {(!task?.issueType || task?.issueType === "Task" || task?.issueType === "Bug") && + task?.rootEpic && + task?.parentId !== task?.rootEpic.id && ( + + )} + + + + + ) -} +}) export default TaskTitleBlock @@ -131,6 +167,112 @@ const TitleIcons: React.FC = ({ dark, edit, setEdit, copyTitle, sav ) } +const ParentTaskBadge: React.FC<{ task: ITeamTask }> = observer(({ task }) => { + const navigation = useNavigation>() + + const { width } = Dimensions.get("window") + + const navigateToParent = (): void => { + navigation.navigate("TaskScreen", { taskId: task?.parentId || task?.parent.id }) + } + return task?.parentId && task?.parent ? ( + + + + #{task?.parent?.taskNumber || task?.parent.number} + + {` - ${limitTextCharaters({ + text: task?.parent?.title, + numChars: width < 391 ? 8 : width <= 410 ? 12 : 18, + })}`} + + + ) : ( + <> + ) +}) + +const ParentTaskInput: React.FC<{ task: ITeamTask }> = observer(({ task }) => { + const [modalVisible, setModalVisible] = useState(false) + return task && task?.issueType !== "Epic" ? ( + setModalVisible(true)} + > + + {task?.parentId + ? translate("taskDetailsScreen.changeParent") + : "+ " + translate("taskDetailsScreen.addParent")} + + + setModalVisible(false)} + task={task} + /> + + ) : ( + <> + ) +}) + const styles = StyleSheet.create({ copyButton: { alignItems: "center", @@ -152,6 +294,15 @@ const styles = StyleSheet.create({ borderWidth: 1, padding: 3, }, + taskNumber: { + alignItems: "center", + backgroundColor: "#D6D6D6", + borderRadius: 3, + height: 24, + justifyContent: "center", + paddingHorizontal: 8, + paddingVertical: 2, + }, textInput: { borderRadius: 5, borderWidth: 1, diff --git a/apps/mobile/app/components/TaskEpic.tsx b/apps/mobile/app/components/TaskEpic.tsx new file mode 100644 index 000000000..5ee6b7df9 --- /dev/null +++ b/apps/mobile/app/components/TaskEpic.tsx @@ -0,0 +1,164 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import { StyleSheet, Text, View, ViewStyle } from "react-native" +import React, { useCallback, useMemo, useState } from "react" +import { TouchableOpacity } from "react-native-gesture-handler" +import { ITeamTask } from "../services/interfaces/ITask" +import { typography, useAppTheme } from "../theme" +import { useTeamTasks } from "../services/hooks/features/useTeamTasks" +import { cloneDeep } from "lodash" +import { SvgXml } from "react-native-svg" +import { categoryIcon } from "./svgs/icons" +import TaskEpicPopup from "./TaskEpicPopup" +import { Entypo } from "@expo/vector-icons" +import { translate } from "../i18n" + +interface ITaskEpic { + task: ITeamTask + containerStyle?: ViewStyle +} + +export type formattedEpic = { + [key: string]: { + icon: React.ReactNode + id: string + name: string + value: string + } +} + +const TaskEpic: React.FC = ({ containerStyle, task }) => { + const { colors, dark } = useAppTheme() + const { updateTask } = useTeamTasks() + + const [openModal, setOpenModal] = useState(false) + + const { teamTasks } = useTeamTasks() + + const epicsList = useMemo(() => { + const temp: formattedEpic = {} + teamTasks.forEach((task) => { + if (task.issueType === "Epic") { + temp[`#${task.taskNumber} ${task.title}`] = { + id: task.id, + name: `#${task.taskNumber} ${task.title}`, + value: task.id, + icon: ( + + + + ), + } + } + }) + return temp + }, [teamTasks]) + + const onTaskSelect = useCallback( + async (parentTask: ITeamTask | undefined) => { + if (!parentTask) return + const childTask = cloneDeep(task) + + await updateTask( + { + ...childTask, + parentId: parentTask.id ? parentTask.id : null, + parent: parentTask.id ? parentTask : null, + }, + task?.id, + ) + }, + [task, updateTask], + ) + return ( + <> + setOpenModal(false)} + onTaskSelect={onTaskSelect} + epicsList={epicsList} + currentEpic={task?.parent?.id} + teamTasks={teamTasks} + /> + + setOpenModal(true)}> + + {task?.parent ? ( + <> + + + + {`#${task?.parent?.number} ${task?.parent?.title}`} + + ) : ( + <> + + + {translate("taskDetailsScreen.epic")} + + + )} + + + + ) +} + +export default TaskEpic + +const styles = StyleSheet.create({ + container: { + alignItems: "center", + borderColor: "rgba(0,0,0,0.16)", + borderRadius: 10, + flexDirection: "row", + gap: 3, + justifyContent: "space-between", + minHeight: 30, + minWidth: 100, + paddingHorizontal: 8, + }, + epicParentIconWrapper: { + backgroundColor: "#8154BA", + borderRadius: 4, + marginVertical: 5, + padding: 4, + }, + text: { + fontFamily: typography.fonts.PlusJakartaSans.semiBold, + fontSize: 10, + textTransform: "capitalize", + width: "90%", + }, +}) diff --git a/apps/mobile/app/components/TaskEpicPopup.tsx b/apps/mobile/app/components/TaskEpicPopup.tsx new file mode 100644 index 000000000..ab85fca88 --- /dev/null +++ b/apps/mobile/app/components/TaskEpicPopup.tsx @@ -0,0 +1,191 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import React from "react" +import { BlurView } from "expo-blur" +import { + Animated, + Modal, + TouchableWithoutFeedback, + View, + Text, + ViewStyle, + StyleSheet, + FlatList, + TouchableOpacity, +} from "react-native" +import { Feather, AntDesign } from "@expo/vector-icons" +import { useAppTheme } from "../theme" +import { ITeamTask } from "../services/interfaces/ITask" +import { formattedEpic } from "./TaskEpic" + +interface ITaskEpicPopup { + visible: boolean + onDismiss: () => unknown + onTaskSelect: (parentTask: ITeamTask | undefined) => Promise + epicsList: formattedEpic + currentEpic: string + teamTasks: ITeamTask[] +} + +const TaskEpicPopup: React.FC = ({ + visible, + onDismiss, + onTaskSelect, + epicsList, + currentEpic, + teamTasks, +}) => { + const { colors } = useAppTheme() + + const allEpics = Object.values(epicsList) + + return ( + + + ( + + )} + legacyImplementation={true} + showsVerticalScrollIndicator={true} + keyExtractor={(_, index) => index.toString()} + /> + + + ) +} + +export default TaskEpicPopup + +interface ItemProps { + currentEpicId: string + onTaskSelect: (parentTask: ITeamTask | undefined) => Promise + epic: formattedEpic[keyof formattedEpic] + teamTasks: ITeamTask[] + onDismiss() +} +const Item: React.FC = ({ currentEpicId, epic, onTaskSelect, teamTasks, onDismiss }) => { + const { colors } = useAppTheme() + const selected = epic.id === currentEpicId + + const epicTask = teamTasks.find((task) => task.id === epic.id) + + return ( + { + onTaskSelect(epicTask) + onDismiss() + }} + > + + + {epic.icon} + {epic.name} + + + {!selected ? ( + + ) : ( + + )} + + + + ) +} + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + return ( + + + onDismiss()}> + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + colorFrame: { + alignItems: "center", + borderRadius: 10, + flexDirection: "row", + gap: 5, + height: 44, + paddingLeft: 16, + width: 180, + }, + container: { + alignSelf: "center", + backgroundColor: "#fff", + borderRadius: 20, + maxHeight: 396, + paddingHorizontal: 6, + paddingVertical: 16, + width: "80%", + }, + wrapperItem: { + alignItems: "center", + borderColor: "rgba(0,0,0,0.13)", + borderRadius: 10, + borderWidth: 1, + flexDirection: "row", + justifyContent: "space-between", + marginBottom: 10, + padding: 2, + paddingRight: 18, + width: "100%", + }, +}) diff --git a/apps/mobile/app/components/TaskLabels.tsx b/apps/mobile/app/components/TaskLabels.tsx index ac18409ed..374d05265 100644 --- a/apps/mobile/app/components/TaskLabels.tsx +++ b/apps/mobile/app/components/TaskLabels.tsx @@ -1,274 +1,364 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ -import React, { FC, useEffect, useRef, useState } from 'react'; -import { TouchableOpacity, View, Text, StyleSheet, ViewStyle, FlatList } from 'react-native'; -import { AntDesign, Entypo } from '@expo/vector-icons'; -import { observer } from 'mobx-react-lite'; -import { ITeamTask } from '../services/interfaces/ITask'; -import { useTeamTasks } from '../services/hooks/features/useTeamTasks'; -import { useAppTheme, typography } from '../theme'; -import TaskLabelPopup from './TaskLabelPopup'; -import { ITaskLabelItem } from '../services/interfaces/ITaskLabel'; -import { translate } from '../i18n'; -import { limitTextCharaters } from '../helpers/sub-text'; -import { SvgUri } from 'react-native-svg'; -import { isEqual } from 'lodash'; +import React, { FC, useEffect, useRef, useState } from "react" +import { + TouchableOpacity, + View, + Text, + StyleSheet, + ViewStyle, + FlatList, + Dimensions, +} from "react-native" +import { AntDesign, Entypo } from "@expo/vector-icons" +import { observer } from "mobx-react-lite" +import { ITeamTask } from "../services/interfaces/ITask" +import { useTeamTasks } from "../services/hooks/features/useTeamTasks" +import { useAppTheme, typography } from "../theme" +import TaskLabelPopup from "./TaskLabelPopup" +import { ITaskLabelItem } from "../services/interfaces/ITaskLabel" +import { translate } from "../i18n" +import { limitTextCharaters } from "../helpers/sub-text" +import { SvgUri } from "react-native-svg" +import { isEqual } from "lodash" interface TaskLabelProps { - task?: ITeamTask; - containerStyle?: ViewStyle; - labels?: string; - setLabels?: (label: ITaskLabelItem[]) => unknown; - newTaskLabels?: ITaskLabelItem[] | undefined; + task?: ITeamTask + containerStyle?: ViewStyle + labels?: string + setLabels?: (label: ITaskLabelItem[]) => unknown + newTaskLabels?: ITaskLabelItem[] | undefined + taskScreenButton?: boolean + noBorders?: boolean } interface IndividualTaskLabel { - color: string; - createdAt: string; - description: string | null; - fullIconUrl: string; - icon: string; - id: string; - isSystem: boolean; - name: string; - organizationId: string; - organizationTeamId: string; - tenantId: string; - updatedAt: string; + color: string + createdAt: string + description: string | null + fullIconUrl: string + icon: string + id: string + isSystem: boolean + name: string + organizationId: string + organizationTeamId: string + tenantId: string + updatedAt: string } -const TaskLabels: FC = observer(({ task, setLabels, newTaskLabels }) => { - const { colors, dark } = useAppTheme(); - const { updateTask } = useTeamTasks(); - const [openModal, setOpenModal] = useState(false); - const flatListRef = useRef(null); - const [labelIndex, setLabelIndex] = useState(0); - const [tempLabels, setTempLabels] = useState(task?.tags || newTaskLabels || []); - const [arrayChanged, setArrayChanged] = useState(false); +const TaskLabels: FC = observer( + ({ task, setLabels, newTaskLabels, taskScreenButton, noBorders, containerStyle }) => { + const { colors, dark } = useAppTheme() + const { updateTask } = useTeamTasks() + const [openModal, setOpenModal] = useState(false) + const flatListRef = useRef(null) + const [labelIndex, setLabelIndex] = useState(0) + const [tempLabels, setTempLabels] = useState( + task?.tags || newTaskLabels || [], + ) + const [arrayChanged, setArrayChanged] = useState(false) - const freshOpenModal = () => { - setOpenModal(true); - setTempLabels(task?.tags || newTaskLabels || []); - arraysHaveSameValues(tempLabels, task?.tags || newTaskLabels || []); - }; + const freshOpenModal = () => { + setOpenModal(true) + setTempLabels(task?.tags || newTaskLabels || []) + arraysHaveSameValues(tempLabels, task?.tags || newTaskLabels || []) + } - const saveLabels = async () => { - if (task) { - const taskEdit = { - ...task, - tags: tempLabels - }; - await updateTask(taskEdit, task.id); - } else { - setLabels(tempLabels); + const saveLabels = async () => { + if (task) { + const taskEdit = { + ...task, + tags: tempLabels, + } + await updateTask(taskEdit, task.id) + } else { + setLabels(tempLabels) + } + setOpenModal(false) } - setOpenModal(false); - }; - const addOrRemoveLabelsInTempArray = (tag: ITaskLabelItem): void => { - const exist = tempLabels.find((label) => label.id === tag.id); - if (exist) { - setTempLabels(tempLabels.filter((label) => label.id !== tag.id)); - } else { - setTempLabels([...tempLabels, tag]); + const addOrRemoveLabelsInTempArray = (tag: ITaskLabelItem): void => { + const exist = tempLabels.find((label) => label.id === tag.id) + if (exist) { + setTempLabels(tempLabels.filter((label) => label.id !== tag.id)) + } else { + setTempLabels([...tempLabels, tag]) + } } - }; - const arraysHaveSameValues = (array1: ITaskLabelItem[] | [], array2: ITaskLabelItem[] | []): void => { - const sortedArray1 = array1.slice().sort((a, b) => a.id.localeCompare(b.id)); - const sortedArray2 = array2.slice().sort((a, b) => a.id.localeCompare(b.id)); + const arraysHaveSameValues = ( + array1: ITaskLabelItem[] | [], + array2: ITaskLabelItem[] | [], + ): void => { + const sortedArray1 = array1.slice().sort((a, b) => a.id.localeCompare(b.id)) + const sortedArray2 = array2.slice().sort((a, b) => a.id.localeCompare(b.id)) - const areArraysEqual = isEqual(sortedArray1, sortedArray2); + const areArraysEqual = isEqual(sortedArray1, sortedArray2) - setArrayChanged(!areArraysEqual); - }; + setArrayChanged(!areArraysEqual) + } - useEffect(() => { - arraysHaveSameValues(tempLabels, task?.tags || newTaskLabels || []); - }, [tempLabels]); + useEffect(() => { + arraysHaveSameValues(tempLabels, task?.tags || newTaskLabels || []) + }, [tempLabels]) - const scrollToIndexWithDelay = (index: number) => { - flatListRef.current?.scrollToIndex({ - animated: true, - index: index < 0 ? 0 : index, - viewPosition: 0 - }); - }; + const scrollToIndexWithDelay = (index: number) => { + flatListRef.current?.scrollToIndex({ + animated: true, + index: index < 0 ? 0 : index, + viewPosition: 0, + }) + } - const onNextPressed = () => { - if (labelIndex !== task?.tags?.length - 2) { - scrollToIndexWithDelay(labelIndex + 1); + const onNextPressed = () => { + if (labelIndex !== task?.tags?.length - 2) { + scrollToIndexWithDelay(labelIndex + 1) + } } - }; - const onPrevPressed = () => { - if (labelIndex > 0) { - const newIndex = labelIndex - 2; - scrollToIndexWithDelay(newIndex); + const onPrevPressed = () => { + if (labelIndex > 0) { + const newIndex = labelIndex - 2 + scrollToIndexWithDelay(newIndex) + } } - }; - const handleScrollEnd = (event: any) => { - const offsetX = event.nativeEvent.contentOffset.x; - const currentIndex = Math.round(offsetX / 100); // Assuming 100 is the item width - setLabelIndex(currentIndex); - }; + const handleScrollEnd = (event: any) => { + const offsetX = event.nativeEvent.contentOffset.x + const currentIndex = Math.round(offsetX / 100) // Assuming 100 is the item width + setLabelIndex(currentIndex) + } - return ( - <> - setOpenModal(false)} - canCreateLabel={true} - /> - {(task?.tags !== undefined || newTaskLabels !== undefined) && - (task?.tags?.length > 0 || newTaskLabels?.length > 0) ? ( - - - ) : ( - - - - - - - {translate('settingScreen.labelScreen.labels')} - - + + + + + {translate("settingScreen.labelScreen.labels")} + + - - - - )} - - ); -}); + + + + )} + + ) + }, +) interface ILabel { - item: IndividualTaskLabel | null; - freshOpenModal: () => void; + item: IndividualTaskLabel | null + freshOpenModal: () => void + taskScreenButton?: boolean + noBorders?: boolean } -const Label: FC = ({ item, freshOpenModal }) => { - const { colors } = useAppTheme(); +const Label: FC = ({ item, freshOpenModal, taskScreenButton, noBorders }) => { + const { colors } = useAppTheme() + + const { width } = Dimensions.get("screen") + return ( - {limitTextCharaters({ text: item?.name, numChars: 12 })} + {limitTextCharaters({ + text: item?.name, + numChars: taskScreenButton && width > 420 ? 15 : 12, + })} - ); -}; + ) +} const styles = StyleSheet.create({ container: { - alignItems: 'center', + alignItems: "center", borderRadius: 10, borderWidth: 1, - flexDirection: 'row', + flexDirection: "row", height: 32, - justifyContent: 'space-between', + justifyContent: "space-between", marginVertical: 20, paddingHorizontal: 12, paddingVertical: 7, - width: 160 + width: 160, }, scrollButtons: { - alignItems: 'center', - backgroundColor: '#fff', + alignItems: "center", + backgroundColor: "#fff", borderRadius: 20, bottom: 23, elevation: 10, height: 27, - justifyContent: 'center', + justifyContent: "center", padding: 5, - position: 'absolute', - shadowColor: 'rgba(0,0,0,0.16)', + position: "absolute", + shadowColor: "rgba(0,0,0,0.16)", shadowOffset: { width: 0, height: 5 }, shadowOpacity: 1, shadowRadius: 15, - width: 28 + width: 28, }, text: { fontFamily: typography.fonts.PlusJakartaSans.semiBold, - fontSize: 10 + fontSize: 10, }, wrapStatus: { - alignItems: 'center', - flexDirection: 'row', - width: '70%' - } -}); + alignItems: "center", + flexDirection: "row", + width: "70%", + }, +}) -export default TaskLabels; +export default TaskLabels diff --git a/apps/mobile/app/components/TaskStatus.tsx b/apps/mobile/app/components/TaskStatus.tsx index 2f248f960..e28f4f9c9 100644 --- a/apps/mobile/app/components/TaskStatus.tsx +++ b/apps/mobile/app/components/TaskStatus.tsx @@ -1,115 +1,131 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ -import React, { FC, useState } from 'react'; -import { TouchableOpacity, View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'; -import { AntDesign, Feather } from '@expo/vector-icons'; -import { ITaskStatus, ITeamTask } from '../services/interfaces/ITask'; -import { observer } from 'mobx-react-lite'; -import { useTeamTasks } from '../services/hooks/features/useTeamTasks'; -import TaskStatusPopup from './TaskStatusPopup'; -import { typography, useAppTheme } from '../theme'; -import { translate } from '../i18n'; -import { useTaskStatusValue } from './StatusType'; -import { limitTextCharaters } from '../helpers/sub-text'; +import React, { FC, useState } from "react" +import { TouchableOpacity, View, Text, StyleSheet, ViewStyle, TextStyle } from "react-native" +import { AntDesign, Feather } from "@expo/vector-icons" +import { ITaskStatus, ITeamTask } from "../services/interfaces/ITask" +import { observer } from "mobx-react-lite" +import { useTeamTasks } from "../services/hooks/features/useTeamTasks" +import TaskStatusPopup from "./TaskStatusPopup" +import { typography, useAppTheme } from "../theme" +import { translate } from "../i18n" +import { useTaskStatusValue } from "./StatusType" +import { limitTextCharaters } from "../helpers/sub-text" interface TaskStatusProps { - task?: ITeamTask; - containerStyle?: ViewStyle; - statusTextSyle?: TextStyle; - iconsOnly?: boolean; - status?: string; - setStatus?: (status: string) => unknown; + task?: ITeamTask + containerStyle?: ViewStyle + statusTextSyle?: TextStyle + iconsOnly?: boolean + status?: string + setStatus?: (status: string) => unknown } -const TaskStatus: FC = observer(({ task, containerStyle, status, setStatus, iconsOnly }) => { - const { colors, dark } = useAppTheme(); - const { updateTask } = useTeamTasks(); - const [openModal, setOpenModal] = useState(false); +const TaskStatus: FC = observer( + ({ task, containerStyle, status, setStatus, iconsOnly }) => { + const { colors, dark } = useAppTheme() + const { updateTask } = useTeamTasks() + const [openModal, setOpenModal] = useState(false) - const allStatuses = useTaskStatusValue(); + const allStatuses = useTaskStatusValue() - const statusValue = (task?.status?.split('-').join(' ') || (status && status.split('-').join(' ')))?.toLowerCase(); - const statusItem = - allStatuses && Object.values(allStatuses).find((item) => item?.name.toLowerCase() === statusValue); + const statusValue = ( + task?.status?.split("-").join(" ") || + (status && status.split("-").join(" ")) + )?.toLowerCase() + const statusItem = + allStatuses && + Object.values(allStatuses).find((item) => item?.name.toLowerCase() === statusValue) - const onChangeStatus = async (text) => { - if (task) { - const value: ITaskStatus = text; - const taskEdit = { - ...task, - status: value - }; + const onChangeStatus = async (text) => { + if (task) { + const value: ITaskStatus = text + const taskEdit = { + ...task, + status: value, + } - await updateTask(taskEdit, task.id); - } else { - setStatus(text); + await updateTask(taskEdit, task.id) + } else { + setStatus(text) + } } - }; - return ( - <> - onChangeStatus(e)} - onDismiss={() => setOpenModal(false)} - /> - setOpenModal(true)}> - - {statusItem ? ( - - {statusItem.icon} - {iconsOnly ? null : ( - - {limitTextCharaters({ text: statusItem?.name, numChars: 11 })} - - )} - - ) : ( - - {iconsOnly ? ( - - ) : ( - translate('settingScreen.statusScreen.statuses') - )} - - )} - - - - - ); -}); + return ( + <> + onChangeStatus(e)} + onDismiss={() => setOpenModal(false)} + /> + setOpenModal(true)}> + + {statusItem ? ( + + {statusItem.icon} + {iconsOnly ? null : ( + + {limitTextCharaters({ + text: statusItem?.name, + numChars: 11, + })} + + )} + + ) : ( + + {iconsOnly ? ( + + ) : ( + translate("settingScreen.statusScreen.statuses") + )} + + )} + + + + + ) + }, +) const styles = StyleSheet.create({ container: { - alignItems: 'center', + alignItems: "center", borderRadius: 10, - flexDirection: 'row', - justifyContent: 'space-between', + flexDirection: "row", + justifyContent: "space-between", minHeight: 30, - paddingHorizontal: 8 + paddingHorizontal: 8, }, text: { fontFamily: typography.fonts.PlusJakartaSans.semiBold, fontSize: 10, - textTransform: 'capitalize' + textTransform: "capitalize", }, wrapStatus: { - alignItems: 'center', - flexDirection: 'row' - } -}); + alignItems: "center", + flexDirection: "row", + }, +}) -export default TaskStatus; +export default TaskStatus diff --git a/apps/mobile/app/components/TaskVersion.tsx b/apps/mobile/app/components/TaskVersion.tsx new file mode 100644 index 000000000..5ffbe7398 --- /dev/null +++ b/apps/mobile/app/components/TaskVersion.tsx @@ -0,0 +1,124 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import React, { FC, useState } from "react" +import { TouchableOpacity, View, Text, StyleSheet, ViewStyle } from "react-native" +import { AntDesign, Entypo } from "@expo/vector-icons" +import { observer } from "mobx-react-lite" +import { ITeamTask } from "../services/interfaces/ITask" +import { useTeamTasks } from "../services/hooks/features/useTeamTasks" +import { typography, useAppTheme } from "../theme" +import { translate } from "../i18n" +import { useTaskVersionValue } from "./StatusType" +import { limitTextCharaters } from "../helpers/sub-text" +import TaskVersionPopup from "./TaskVersionPopup" + +interface TaskVersionProps { + task?: ITeamTask + containerStyle?: ViewStyle + version?: string + setPriority?: (priority: string) => unknown +} + +const TaskVersion: FC = observer( + ({ task, containerStyle, version, setPriority }) => { + const { colors } = useAppTheme() + const { updateTask } = useTeamTasks() + const [openModal, setOpenModal] = useState(false) + + const allTaskVersions = useTaskVersionValue() + + const versionValue = (task?.version || (version && version))?.toLowerCase() + + const currentVersion = + allTaskVersions && + Object.values(allTaskVersions).find((item) => item.value.toLowerCase() === versionValue) + + const onChangeVersion = async (text) => { + if (task) { + const taskEdit = { + ...task, + version: text, + } + + await updateTask(taskEdit, task.id) + } else { + setPriority(text) + } + } + + return ( + <> + onChangeVersion(e.value)} + onDismiss={() => setOpenModal(false)} + /> + setOpenModal(true)}> + + {(task?.version || version) && currentVersion ? ( + + {currentVersion.icon} + + {limitTextCharaters({ + text: currentVersion.name, + numChars: 15, + })} + + + ) : ( + + + + {translate("taskDetailsScreen.version")} + + + )} + + + + + ) + }, +) + +const styles = StyleSheet.create({ + container: { + alignItems: "center", + borderColor: "rgba(0,0,0,0.16)", + borderRadius: 10, + borderWidth: 1, + flexDirection: "row", + justifyContent: "space-between", + minHeight: 30, + minWidth: 100, + paddingHorizontal: 8, + }, + text: { + fontFamily: typography.fonts.PlusJakartaSans.semiBold, + fontSize: 10, + textTransform: "capitalize", + }, + wrapStatus: { + alignItems: "center", + flexDirection: "row", + width: "70%", + }, +}) + +export default TaskVersion diff --git a/apps/mobile/app/components/TaskVersionPopup.tsx b/apps/mobile/app/components/TaskVersionPopup.tsx new file mode 100644 index 000000000..0c5c1349c --- /dev/null +++ b/apps/mobile/app/components/TaskVersionPopup.tsx @@ -0,0 +1,180 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import React, { FC } from "react" +import { + View, + ViewStyle, + Modal, + Animated, + StyleSheet, + Text, + FlatList, + TouchableOpacity, + TouchableWithoutFeedback, +} from "react-native" +import { Feather, AntDesign } from "@expo/vector-icons" +import { spacing, useAppTheme } from "../theme" +import { ITaskPriorityItem } from "../services/interfaces/ITaskPriority" +// import { translate } from "../i18n" +import { BlurView } from "expo-blur" +import { useTaskVersion } from "../services/hooks/features/useTaskVersion" +import { BadgedTaskVersion } from "./VersionIcon" +import { ITaskVersionItemList } from "../services/interfaces/ITaskVersion" + +export interface Props { + visible: boolean + onDismiss: () => unknown + versionName: string + setSelectedVersion: (status: ITaskVersionItemList) => unknown +} + +const TaskVersionPopup: FC = function TaskPriorityPopup({ + visible, + onDismiss, + setSelectedVersion, + versionName, +}) { + const { taskVersionList } = useTaskVersion() + const { colors } = useAppTheme() + const onVersionSelected = (size: ITaskPriorityItem) => { + setSelectedVersion(size) + onDismiss() + } + + return ( + + + Versions + ( + + )} + legacyImplementation={true} + showsVerticalScrollIndicator={true} + keyExtractor={(_, index) => index.toString()} + /> + + + ) +} + +export default TaskVersionPopup + +interface ItemProps { + currentVersionName: string + version: ITaskPriorityItem + onVersionSelected: (size: ITaskPriorityItem) => unknown +} +const Item: FC = ({ currentVersionName, version, onVersionSelected }) => { + const { colors } = useAppTheme() + const selected = version.value === currentVersionName + + return ( + onVersionSelected(version)}> + + + + + + {!selected ? ( + + ) : ( + + )} + + + + ) +} + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + return ( + + + onDismiss()}> + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + colorFrame: { + borderRadius: 10, + height: 44, + justifyContent: "center", + paddingLeft: 16, + width: 180, + }, + container: { + alignSelf: "center", + backgroundColor: "#fff", + borderRadius: 20, + maxHeight: 396, + paddingHorizontal: 6, + paddingVertical: 16, + width: "90%", + }, + title: { + fontSize: spacing.medium - 2, + marginBottom: 16, + marginHorizontal: 10, + }, + wrapperItem: { + alignItems: "center", + borderColor: "rgba(0,0,0,0.13)", + borderRadius: 10, + borderWidth: 1, + flexDirection: "row", + justifyContent: "space-between", + marginBottom: 10, + padding: 6, + paddingRight: 18, + width: "100%", + }, +}) diff --git a/apps/mobile/app/components/VersionIcon.tsx b/apps/mobile/app/components/VersionIcon.tsx new file mode 100644 index 000000000..61219a604 --- /dev/null +++ b/apps/mobile/app/components/VersionIcon.tsx @@ -0,0 +1,42 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import React, { useMemo } from "react" +import { View, Text } from "react-native" +import { SvgUri } from "react-native-svg" +import { typography } from "../theme" +import { observer } from "mobx-react-lite" +import { limitTextCharaters } from "../helpers/sub-text" +import { useTaskVersion } from "../services/hooks/features/useTaskVersion" + +export const BadgedTaskVersion = observer( + ({ version, TextSize, iconSize }: { version: string; TextSize: number; iconSize: number }) => { + const { taskVersionList } = useTaskVersion() + + const currentSize = useMemo( + () => taskVersionList.find((s) => s.name === version), + [version, taskVersionList], + ) + + return ( + + + + {limitTextCharaters({ text: currentSize?.name, numChars: 15 })} + + + ) + }, +) diff --git a/apps/mobile/app/components/svgs/icons.tsx b/apps/mobile/app/components/svgs/icons.tsx index 48dcd2d17..b84a03ceb 100644 --- a/apps/mobile/app/components/svgs/icons.tsx +++ b/apps/mobile/app/components/svgs/icons.tsx @@ -591,3 +591,233 @@ export const copyIcon = ` ` +// Task Details + +export const globeLightTheme = ` + + + + + + ` + +export const globeDarkTheme = ` + + + + + + ` + +export const lockLightTheme = ` + + + + ` + +export const lockDarkTheme = ` + + + + ` + +export const clipboardIcon = ` + + + + +` + +export const profileIcon = ` + + + +` +export const peopleIconSmall = ` + + + + + + + +` +export const settingsIconSmall = ` + + + +` + +export const trashIconLarge = ` + + + + + +` + +export const trashIconSmall = ` + + + + + +` + +export const calendarIcon = ` + + + + + + + + +` +export const categoryIcon = ` + + + + + +` diff --git a/apps/mobile/app/helpers/sub-text.tsx b/apps/mobile/app/helpers/sub-text.ts similarity index 100% rename from apps/mobile/app/helpers/sub-text.tsx rename to apps/mobile/app/helpers/sub-text.ts diff --git a/apps/mobile/app/i18n/ar.ts b/apps/mobile/app/i18n/ar.ts index 9f7b943f9..fed879a1a 100644 --- a/apps/mobile/app/i18n/ar.ts +++ b/apps/mobile/app/i18n/ar.ts @@ -95,6 +95,30 @@ const ar: Translations = { characterLimitErrorTitle: "لم نتمكن من تحديث عنوان المهمة.", characterLimitErrorDescription: "لا يمكن أن يتجاوز عنوان المهمة 255 حرفًا.", copyTitle: "تم نسخ العنوان.", + changeParent: "تغيير الوالدين", + addParent: "أضف أحد الوالدين", + taskScreen: "شاشة المهام", + details: "تفاصيل", + taskPublic: "هذه المهمة عامة", + makePrivate: "جعل خاص", + taskPrivate: "هذه المهمة خاصة", + makePublic: "جعل العامة", + typeIssue: "نوع المشكلة", + creator: "المُنشئ", + assignees: "المُنفذون", + startDate: "تاريخ البدء", + dueDate: "تاريخ الاستحقاق", + daysRemaining: "الأيام المتبقية", + version: "الإصدار", + epic: "ملحمة", + status: "الحالة", + labels: "التسميات", + size: "الحجم", + priority: "الأولوية", + manageAssignees: "إدارة المكلفين", + setDueDate: "تحديد تاريخ الاستحقاق", + setStartDate: "تحديد تاريخ البدء", + items: "أغراض", }, tasksScreen: { name: "مهام", diff --git a/apps/mobile/app/i18n/bg.ts b/apps/mobile/app/i18n/bg.ts index 9f1b1f4b3..988ea86c3 100644 --- a/apps/mobile/app/i18n/bg.ts +++ b/apps/mobile/app/i18n/bg.ts @@ -91,6 +91,30 @@ const bg = { characterLimitErrorTitle: "We couldn't update Task Title.", characterLimitErrorDescription: "Task title can't exceed 255 characters.", copyTitle: "Title Copied.", + changeParent: "Change Parent", + addParent: "Add Parent", + taskScreen: "Task Screen", + details: "Details", + taskPublic: "This task is Public", + makePrivate: "Make a Private", + taskPrivate: "This task is Private", + makePublic: "Make a Public", + typeIssue: "Type of Issue", + creator: "Creator", + assignees: "Assignees", + startDate: "Start Date", + dueDate: "Due Date", + daysRemaining: "Days Remaining", + version: "Version", + epic: "Epic", + status: "Status", + labels: "Labels", + size: "Size", + priority: "Priority", + manageAssignees: "Manage Assignees", + setDueDate: "Set Due Date", + setStartDate: "Set Start Date", + items: "Items", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/en.ts b/apps/mobile/app/i18n/en.ts index e9a2414c8..ddd202873 100644 --- a/apps/mobile/app/i18n/en.ts +++ b/apps/mobile/app/i18n/en.ts @@ -92,6 +92,30 @@ const en = { characterLimitErrorTitle: "We couldn't update Task Title.", characterLimitErrorDescription: "Task title can't exceed 255 characters.", copyTitle: "Title Copied.", + changeParent: "Change Parent", + addParent: "Add Parent", + taskScreen: "Task Screen", + details: "Details", + taskPublic: "This task is Public", + makePrivate: "Make a Private", + taskPrivate: "This task is Private", + makePublic: "Make a Public", + typeIssue: "Type of Issue", + creator: "Creator", + assignees: "Assignees", + startDate: "Start Date", + dueDate: "Due Date", + daysRemaining: "Days Remaining", + version: "Version", + epic: "Epic", + status: "Status", + labels: "Labels", + size: "Size", + priority: "Priority", + manageAssignees: "Manage Assignees", + setDueDate: "Set Due Date", + setStartDate: "Set Start Date", + items: "Items", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/es.ts b/apps/mobile/app/i18n/es.ts index 3e0bbcde9..2ca3a04b7 100644 --- a/apps/mobile/app/i18n/es.ts +++ b/apps/mobile/app/i18n/es.ts @@ -91,6 +91,30 @@ const es = { characterLimitErrorTitle: "We couldn't update Task Title.", characterLimitErrorDescription: "Task title can't exceed 255 characters.", copyTitle: "Title Copied.", + changeParent: "Change Parent", + addParent: "Add Parent", + taskScreen: "Task Screen", + details: "Details", + taskPublic: "This task is Public", + makePrivate: "Make a Private", + taskPrivate: "This task is Private", + makePublic: "Make a Public", + typeIssue: "Type of Issue", + creator: "Creator", + assignees: "Assignees", + startDate: "Start Date", + dueDate: "Due Date", + daysRemaining: "Days Remaining", + version: "Version", + epic: "Epic", + status: "Status", + labels: "Labels", + size: "Size", + priority: "Priority", + manageAssignees: "Manage Assignees", + setDueDate: "Set Due Date", + setStartDate: "Set Start Date", + items: "Items", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/fr.ts b/apps/mobile/app/i18n/fr.ts index d419eff47..3bc127ac4 100644 --- a/apps/mobile/app/i18n/fr.ts +++ b/apps/mobile/app/i18n/fr.ts @@ -94,6 +94,30 @@ const fr = { characterLimitErrorTitle: "Nous n'avons pas pu mettre à jour le titre de la tâche.", characterLimitErrorDescription: "Le titre de la tâche ne peut pas dépasser 255 caractères.", copyTitle: "Titre copié.", + changeParent: "Changer de parent", + addParent: "Ajouter un parent", + taskScreen: "Écran des tâches", + details: "Détails", + taskPublic: "Cette tâche est publique", + makePrivate: "Créer un privé", + taskPrivate: "Cette tâche est privée", + makePublic: "Faire un public", + typeIssue: "Type d'Issue", + creator: "Créateur", + assignees: "Bénéficiaires", + startDate: "Date de début", + dueDate: "Date d'échéance", + daysRemaining: "Jours restants", + version: "Version", + epic: "Épique", + status: "Statut", + labels: "Étiquettes", + size: "Taille", + priority: "Priorité", + manageAssignees: "Gérer les destinataires", + setDueDate: "Définir la date d'échéance", + setStartDate: "Définir la date de début", + items: "Articles", }, tasksScreen: { name: "Tâches", diff --git a/apps/mobile/app/i18n/he.ts b/apps/mobile/app/i18n/he.ts index 3e189e982..206e8effb 100644 --- a/apps/mobile/app/i18n/he.ts +++ b/apps/mobile/app/i18n/he.ts @@ -91,6 +91,30 @@ const he = { characterLimitErrorTitle: "We couldn't update Task Title.", characterLimitErrorDescription: "Task title can't exceed 255 characters.", copyTitle: "Title Copied.", + changeParent: "Change Parent", + addParent: "Add Parent", + taskScreen: "Task Screen", + details: "Details", + taskPublic: "This task is Public", + makePrivate: "Make a Private", + taskPrivate: "This task is Private", + makePublic: "Make a Public", + typeIssue: "Type of Issue", + creator: "Creator", + assignees: "Assignees", + startDate: "Start Date", + dueDate: "Due Date", + daysRemaining: "Days Remaining", + version: "Version", + epic: "Epic", + status: "Status", + labels: "Labels", + size: "Size", + priority: "Priority", + manageAssignees: "Manage Assignees", + setDueDate: "Set Due Date", + setStartDate: "Set Start Date", + items: "Items", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/ko.ts b/apps/mobile/app/i18n/ko.ts index a5d2d4ac0..25318694b 100644 --- a/apps/mobile/app/i18n/ko.ts +++ b/apps/mobile/app/i18n/ko.ts @@ -94,6 +94,30 @@ const ko: Translations = { characterLimitErrorTitle: "작업 제목을 업데이트할 수 없습니다.", characterLimitErrorDescription: "작업 제목은 255자를 초과할 수 없습니다.", copyTitle: "제목이 복사되었습니다.", + changeParent: "상위 변경", + addParent: "상위 추가", + taskScreen: "작업 화면", + details: "세부", + taskPublic: "이 작업은 공개입니다.", + makePrivate: "비공개로 설정", + taskPrivate: "이 작업은 비공개입니다.", + makePublic: "공개로 설정", + typeIssue: "이슈 유형", + creator: "생성자", + assignees: "담당자", + startDate: "시작일", + dueDate: "마감일", + daysRemaining: "남은 일수", + version: "버전", + epic: "에픽", + status: "상태", + labels: "라벨", + size: "크기", + priority: "우선 순위", + manageAssignees: "담당자 관리", + setDueDate: "마감일 설정", + setStartDate: "시작일 설정", + items: "품목", }, tasksScreen: { name: "작업", diff --git a/apps/mobile/app/i18n/ru.ts b/apps/mobile/app/i18n/ru.ts index 92502220a..9b807d67f 100644 --- a/apps/mobile/app/i18n/ru.ts +++ b/apps/mobile/app/i18n/ru.ts @@ -91,6 +91,30 @@ const ru = { characterLimitErrorTitle: "We couldn't update Task Title.", characterLimitErrorDescription: "Task title can't exceed 255 characters.", copyTitle: "Title Copied.", + changeParent: "Change Parent", + addParent: "Add Parent", + taskScreen: "Task Screen", + details: "Details", + taskPublic: "This task is Public", + makePrivate: "Make a Private", + taskPrivate: "This task is Private", + makePublic: "Make a Public", + typeIssue: "Type of Issue", + creator: "Creator", + assignees: "Assignees", + startDate: "Start Date", + dueDate: "Due Date", + daysRemaining: "Days Remaining", + version: "Version", + epic: "Epic", + status: "Status", + labels: "Labels", + size: "Size", + priority: "Priority", + manageAssignees: "Manage Assignees", + setDueDate: "Set Due Date", + setStartDate: "Set Start Date", + items: "Items", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/navigators/AuthenticatedNavigator.tsx b/apps/mobile/app/navigators/AuthenticatedNavigator.tsx index 30f420187..6143ced2d 100644 --- a/apps/mobile/app/navigators/AuthenticatedNavigator.tsx +++ b/apps/mobile/app/navigators/AuthenticatedNavigator.tsx @@ -86,6 +86,12 @@ export type SettingScreenNavigationProp > +export type DrawerNavigationProp = + CompositeNavigationProp< + BottomTabNavigationProp, + StackNavigationProp + > + export type SettingScreenRouteProp = RouteProp< AuthenticatedTabParamList, T diff --git a/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ListCardItem.tsx b/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ListCardItem.tsx index 26210ec92..ba18e8032 100644 --- a/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ListCardItem.tsx +++ b/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ListCardItem.tsx @@ -101,7 +101,7 @@ export const ListItemContent: React.FC = observer((props) => { - + diff --git a/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ProfileHeader.tsx b/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ProfileHeader.tsx index 79c118923..a32feaa3c 100644 --- a/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ProfileHeader.tsx +++ b/apps/mobile/app/screens/Authenticated/ProfileScreen/components/ProfileHeader.tsx @@ -1,50 +1,130 @@ /* eslint-disable react-native/no-inline-styles */ /* eslint-disable react-native/no-color-literals */ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import { typography, useAppTheme } from '../../../../theme'; +import React from "react" +import { View, StyleSheet } from "react-native" +import { typography, useAppTheme } from "../../../../theme" // COMPONENTS -import { Text } from '../../../../components'; -import { IUser } from '../../../../services/interfaces/IUserData'; -import ProfileImage from '../../../../components/ProfileImage'; -import { observer } from 'mobx-react-lite'; +import { Text } from "../../../../components" +import { IUser } from "../../../../services/interfaces/IUserData" +import ProfileImage from "../../../../components/ProfileImage" +import { observer } from "mobx-react-lite" +import { useStores } from "../../../../models" +import { Avatar } from "react-native-paper" +import { limitTextCharaters } from "../../../../helpers/sub-text" +import { imgTitle } from "../../../../helpers/img-title" const ProfileHeader = observer((member: IUser) => { - const { colors, dark } = useAppTheme(); + const { colors, dark } = useAppTheme() return ( - - - - {member?.name} - {member?.email} + + + + + {member?.name} + {member?.email} + + + + + + ) +}) + +const UserTeam = observer(() => { + const { + teamStore: { activeTeam }, + } = useStores() + + return ( + + {activeTeam?.image?.thumbUrl || activeTeam?.logo || activeTeam?.image?.fullUrl ? ( + + ) : ( + + )} + + {`${limitTextCharaters({ + text: activeTeam?.name, + numChars: 16, + })} `} - ); -}); + ) +}) const styles = StyleSheet.create({ + activeTeamContainer: { + alignItems: "center", + backgroundColor: "#F5F5F5", + borderRadius: 60, + flexDirection: "row", + gap: 3, + paddingHorizontal: 4, + paddingVertical: 0, + }, + activeTeamTxt: { + fontFamily: typography.fonts.PlusJakartaSans.semiBold, + fontSize: 8, + fontWeight: "600", + + // left: 12, + }, container: { - backgroundColor: '#fff', - flexDirection: 'row', + backgroundColor: "#fff", + flexDirection: "row", + justifyContent: "space-between", paddingBottom: 24, paddingHorizontal: 20, - paddingTop: 14 + paddingTop: 14, }, containerInfo: { - justifyContent: 'center', - marginLeft: 10 + justifyContent: "center", + marginLeft: 10, }, email: { - color: '#7E7991', + color: "#7E7991", fontFamily: typography.secondary.medium, - fontSize: 12 + fontSize: 12, }, name: { - color: '#282048', + color: "#282048", fontFamily: typography.primary.semiBold, - fontSize: 18 - } -}); + fontSize: 18, + }, + prefix: { + fontFamily: typography.fonts.PlusJakartaSans.semiBold, + fontSize: 8, + fontWeight: "600", + }, + teamImage: { + backgroundColor: "#C1E0EA", + }, +}) -export default ProfileHeader; +export default ProfileHeader diff --git a/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx b/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx index db3592e6c..efb7b6331 100644 --- a/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx +++ b/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx @@ -1,14 +1,14 @@ /* eslint-disable react-native/no-inline-styles */ -import { View, Text, ViewStyle, TouchableOpacity, StyleSheet } from "react-native" +import { View, Text, ViewStyle, TouchableOpacity, StyleSheet, ScrollView } from "react-native" import React, { FC, useEffect } from "react" import { AuthenticatedDrawerScreenProps } from "../../../navigators/AuthenticatedNavigator" import { Screen } from "../../../components" -import Animated from "react-native-reanimated" import { typography, useAppTheme } from "../../../theme" import { AntDesign } from "@expo/vector-icons" import { useTeamTasks } from "../../../services/hooks/features/useTeamTasks" import TaskTitleBlock from "../../../components/Task/TitleBlock" -// import { translate } from "../../../i18n" +import DetailsBlock from "../../../components/Task/DetailsBlock" +import { translate } from "../../../i18n" export const AuthenticatedTaskScreen: FC> = ( _props, @@ -16,35 +16,45 @@ export const AuthenticatedTaskScreen: FC { if (route.params.taskId) { getTaskById(taskId) } - }, [getTaskById, route, task]) + }, [getTaskById, route, task, route.params.taskId]) return ( - + navigation.navigate("AuthenticatedTab")}> - Task Screen + + {translate("taskDetailsScreen.taskScreen")} + - - + + + + + - + ) } @@ -73,6 +83,13 @@ const styles = StyleSheet.create({ flexDirection: "row", width: "100%", }, + screenContentWrapper: { + alignItems: "center", + flex: 4, + gap: 12, + paddingBottom: 20, + width: "100%", + }, title: { alignSelf: "center", diff --git a/apps/mobile/app/screens/Authenticated/TeamScreen/components/ListCardItem.tsx b/apps/mobile/app/screens/Authenticated/TeamScreen/components/ListCardItem.tsx index d9b1bee76..cd4313667 100644 --- a/apps/mobile/app/screens/Authenticated/TeamScreen/components/ListCardItem.tsx +++ b/apps/mobile/app/screens/Authenticated/TeamScreen/components/ListCardItem.tsx @@ -153,7 +153,7 @@ const ListCardItem: React.FC = observer((props) => { taskEdition.setEstimateEditMode(false) props.setOpenMenuIndex(null) isTaskScreen - ? navigation.navigate("TaskScreen", { taskId: memberInfo?.memberTask.id }) + ? navigation.navigate("TaskScreen", { taskId: memberInfo?.memberTask?.id }) : navigation.navigate("Profile", { userId: memberInfo.memberUser.id, activeTab: "worked", diff --git a/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx b/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx index 51ad21485..ab670ff95 100644 --- a/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx +++ b/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx @@ -1,26 +1,37 @@ /* eslint-disable camelcase */ /* eslint-disable react-native/no-inline-styles */ /* eslint-disable react-native/no-color-literals */ -import React, { FC, useState } from 'react'; -import { Text } from 'react-native-paper'; -import { View, StyleSheet, TouchableWithoutFeedback, Pressable, FlatList } from 'react-native'; -import { Ionicons } from '@expo/vector-icons'; -import TaskDisplayBox from './TaskDisplayBox'; -import { observer } from 'mobx-react-lite'; -import { typography, useAppTheme } from '../../../../theme'; -import { translate } from '../../../../i18n'; -import { RTuseTaskInput } from '../../../../services/hooks/features/useTaskInput'; -import IndividualTask from './IndividualTask'; +import React, { FC, useState } from "react" +import { Text } from "react-native-paper" +import { View, StyleSheet, TouchableWithoutFeedback, Pressable, FlatList } from "react-native" +import { Ionicons } from "@expo/vector-icons" +import TaskDisplayBox from "./TaskDisplayBox" +import { observer } from "mobx-react-lite" +import { typography, useAppTheme } from "../../../../theme" +import { translate } from "../../../../i18n" +import { RTuseTaskInput } from "../../../../services/hooks/features/useTaskInput" +import IndividualTask from "./IndividualTask" +import { ITeamTask } from "../../../../services/interfaces/ITask" export interface Props { - tasksHandler: RTuseTaskInput; - closeCombo: () => unknown; - setEditMode: (val: boolean) => void; + tasksHandler: RTuseTaskInput + closeCombo: () => unknown + setEditMode: (val: boolean) => void + parentTasksFilter?: boolean + childTask?: ITeamTask + onDismiss?: () => void } -const ComboBox: FC = observer(function ComboBox({ tasksHandler, closeCombo, setEditMode }) { - const { colors } = useAppTheme(); - const [isScrolling, setIsScrolling] = useState(false); +const ComboBox: FC = observer(function ComboBox({ + tasksHandler, + closeCombo, + setEditMode, + parentTasksFilter, + childTask, + onDismiss, +}) { + const { colors } = useAppTheme() + const [isScrolling, setIsScrolling] = useState(false) return ( @@ -29,27 +40,27 @@ const ComboBox: FC = observer(function ComboBox({ tasksHandler, closeComb onPress={() => tasksHandler.handleTaskCreation()} style={[ styles.createTaskBtn, - { backgroundColor: colors.background, borderColor: colors.secondary } + { backgroundColor: colors.background, borderColor: colors.secondary }, ]} > - {translate('myWorkScreen.tabCreateTask')} + {translate("myWorkScreen.tabCreateTask")} - tasksHandler.setFilter('open')}> + tasksHandler.setFilter("open")}> - tasksHandler.setFilter('closed')}> + tasksHandler.setFilter("closed")}> @@ -58,9 +69,16 @@ const ComboBox: FC = observer(function ComboBox({ tasksHandler, closeComb onScrollBeginDrag={() => setIsScrolling(true)} onScrollEndDrag={() => setIsScrolling(false)} showsVerticalScrollIndicator={false} - data={tasksHandler.filteredTasks} + data={ + parentTasksFilter + ? tasksHandler.filteredEpicTasks + : tasksHandler.filteredTasks + } renderItem={({ item, index }) => ( tasksHandler.handleReopenTask(item)} task={item} @@ -74,40 +92,40 @@ const ComboBox: FC = observer(function ComboBox({ tasksHandler, closeComb - ); -}); + ) +}) const styles = StyleSheet.create({ createTaskBtn: { - alignItems: 'center', + alignItems: "center", borderRadius: 10, borderWidth: 1.5, - flexDirection: 'row', + flexDirection: "row", height: 33, - justifyContent: 'center', + justifyContent: "center", paddingLeft: 24, paddingRight: 16, - width: '100%' + width: "100%", }, createTaskTxt: { - color: '#3826A6', + color: "#3826A6", fontFamily: typography.fonts.PlusJakartaSans.semiBold, fontSize: 10, - lineHeight: 12.6 + lineHeight: 12.6, }, filterSection: { - flexDirection: 'row', - justifyContent: 'space-between', + flexDirection: "row", + justifyContent: "space-between", marginTop: 26, paddingBottom: 16, - width: 232 + width: 232, }, mainContainer: { marginTop: 16, - width: '100%', - zIndex: 5 - } -}); + width: "100%", + zIndex: 5, + }, +}) -export default ComboBox; +export default ComboBox diff --git a/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx b/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx index 9acd7f564..6d9639bbb 100644 --- a/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx +++ b/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx @@ -1,72 +1,114 @@ /* eslint-disable react-native/no-inline-styles */ /* eslint-disable react-native/no-color-literals */ -import React, { FC, useMemo, useState } from 'react'; -import { View, StyleSheet, Text, ImageStyle, TouchableOpacity } from 'react-native'; -import { Entypo, EvilIcons } from '@expo/vector-icons'; -import { GLOBAL_STYLE as GS } from '../../../../../assets/ts/styles'; -import { colors, spacing, typography, useAppTheme } from '../../../../theme'; -import DeletePopUp from './DeletePopUp'; -import { ITeamTask } from '../../../../services/interfaces/ITask'; -import { observer } from 'mobx-react-lite'; -import TaskStatus from '../../../../components/TaskStatus'; -import { useTeamTasks } from '../../../../services/hooks/features/useTeamTasks'; -import IssuesModal from '../../../../components/IssuesModal'; -import { limitTextCharaters } from '../../../../helpers/sub-text'; -import { Avatar } from 'react-native-paper'; -import { imgTitleProfileAvatar } from '../../../../helpers/img-title-profile-avatar'; +import React, { FC, useCallback, useMemo, useState } from "react" +import { View, StyleSheet, Text, ImageStyle, TouchableOpacity } from "react-native" +import { Entypo, EvilIcons } from "@expo/vector-icons" +import { GLOBAL_STYLE as GS } from "../../../../../assets/ts/styles" +import { colors, spacing, typography, useAppTheme } from "../../../../theme" +import DeletePopUp from "./DeletePopUp" +import { ITeamTask } from "../../../../services/interfaces/ITask" +import { observer } from "mobx-react-lite" +import TaskStatus from "../../../../components/TaskStatus" +import { useTeamTasks } from "../../../../services/hooks/features/useTeamTasks" +import IssuesModal from "../../../../components/IssuesModal" +import { limitTextCharaters } from "../../../../helpers/sub-text" +import { Avatar } from "react-native-paper" +import { imgTitleProfileAvatar } from "../../../../helpers/img-title-profile-avatar" +import { cloneDeep } from "lodash" export interface Props { - task: ITeamTask; - handleActiveTask: (value: ITeamTask) => unknown; - closeCombo?: () => unknown; - onReopenTask: () => unknown; - setEditMode: (val: boolean) => void; - isScrolling: boolean; + task: ITeamTask + handleActiveTask: (value: ITeamTask) => unknown + closeCombo?: () => unknown + onReopenTask: () => unknown + setEditMode: (val: boolean) => void + isScrolling: boolean + parentTasksFilter: boolean + childTask?: ITeamTask + onDismiss?: () => void } const IndividualTask: FC = observer( - ({ task, handleActiveTask, closeCombo, onReopenTask, setEditMode, isScrolling }) => { - const { colors } = useAppTheme(); - const [showDel, setShowDel] = useState(false); - const { updateTask } = useTeamTasks(); + ({ + task, + handleActiveTask, + closeCombo, + onReopenTask, + setEditMode, + isScrolling, + parentTasksFilter, + childTask, + onDismiss, + }) => { + const { colors } = useAppTheme() + const [showDel, setShowDel] = useState(false) + const { updateTask } = useTeamTasks() const onCloseTask = async () => { await updateTask( { ...task, - status: 'closed' + status: "closed", }, - task.id - ); - closeCombo(); - }; + task.id, + ) + closeCombo() + } - // Display two fist users profile image - const assigneeImg1 = useMemo(() => task?.members[0]?.user?.imageUrl, [task]); - const assigneeImg2 = useMemo(() => task?.members[1]?.user?.imageUrl, [task]); + const setParent = useCallback( + async ( + parentTask: ITeamTask, + newTask: ITeamTask, + closeModal: () => void, + ): Promise => { + if (parentTask.issueType !== "Epic") return + + const childTask = cloneDeep(newTask) + + await updateTask( + { + ...childTask, + parentId: parentTask.id, + parent: parentTask, + }, + childTask.id, + ) + + closeModal() + }, + [task], + ) + + // Display two first users profile image + const assigneeImg1 = useMemo(() => task?.members[0]?.user?.imageUrl, [task]) + const assigneeImg2 = useMemo(() => task?.members[1]?.user?.imageUrl, [task]) return ( { - closeCombo(); - setEditMode(false); + closeCombo() + setEditMode(false) }} > { - !isScrolling && handleActiveTask(task); + !parentTasksFilter + ? !isScrolling && handleActiveTask(task) + : !isScrolling && setParent(task, childTask, onDismiss) }} // added it here because doesn't work when assigned to the parent style={{ - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - width: '60%' + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + width: "60%", }} > - {`#${task.taskNumber}`} + {`#${task.taskNumber}`} @@ -75,136 +117,159 @@ const IndividualTask: FC = observer( { - !isScrolling && handleActiveTask(task); + !parentTasksFilter + ? !isScrolling && handleActiveTask(task) + : !isScrolling && setParent(task, childTask, onDismiss) }} // added it here because doesn't work when assigned to the parent style={{ - flexDirection: 'row', - width: '40%', - alignItems: 'center', + flexDirection: "row", + width: "40%", + alignItems: "center", zIndex: 1000, - justifyContent: 'space-between' + justifyContent: "space-between", }} > - + - + {assigneeImg1 ? ( - + ) : task.members[0] ? ( ) : null} {assigneeImg2 ? ( - + ) : task.members[1] ? ( ) : null} - {task.status === 'closed' ? ( - onReopenTask()} /> + {task.status === "closed" ? ( + onReopenTask()} + /> ) : ( setShowDel(true)}> )} {showDel && ( - + )} - ); - } -); + ) + }, +) const styles = StyleSheet.create({ container: { - alignItems: 'center', - borderTopColor: 'rgba(0, 0, 0, 0.06)', + alignItems: "center", + borderTopColor: "rgba(0, 0, 0, 0.06)", borderTopWidth: 1, - flexDirection: 'row', - justifyContent: 'space-between', + flexDirection: "row", + justifyContent: "space-between", paddingVertical: 12, - width: '100%', - zIndex: 1000 + width: "100%", + zIndex: 1000, }, prefix: { - color: '#FFFFFF', + color: "#FFFFFF", fontFamily: typography.fonts.PlusJakartaSans.light, - fontSize: 20 + fontSize: 20, }, statusContainer: { - alignItems: 'center', - backgroundColor: '#ECE8FC', - borderColor: 'transparent', + alignItems: "center", + backgroundColor: "#ECE8FC", + borderColor: "transparent", height: 27, marginRight: 6, paddingHorizontal: 7, width: 50, - zIndex: 1000 + zIndex: 1000, }, statusDisplay: { - flexDirection: 'row' + flexDirection: "row", }, taskTitle: { - color: '#282048', + color: "#282048", fontFamily: typography.fonts.PlusJakartaSans.semiBold, fontSize: 10, - width: '67%' + width: "67%", }, wrapBugIcon: { - alignItems: 'center', - backgroundColor: '#C24A4A', + alignItems: "center", + backgroundColor: "#C24A4A", borderRadius: 3, height: 20, - justifyContent: 'center', + justifyContent: "center", marginRight: 3, - width: 20 + width: 20, }, wrapTaskNumber: { - alignItems: 'center', - flexDirection: 'row' - } -}); + alignItems: "center", + flexDirection: "row", + }, +}) const $usersProfile: ImageStyle = { ...GS.roundedFull, backgroundColor: colors.background, width: spacing.extraLarge - spacing.tiny, height: spacing.extraLarge - spacing.tiny, - borderColor: '#fff', - borderWidth: 2 -}; + borderColor: "#fff", + borderWidth: 2, +} const $usersProfile2: ImageStyle = { ...GS.roundedFull, backgroundColor: colors.background, width: spacing.extraLarge - spacing.tiny, height: spacing.extraLarge - spacing.tiny, - borderColor: '#fff', + borderColor: "#fff", borderWidth: 2, - position: 'absolute', - left: -15 -}; + position: "absolute", + left: -15, +} -export default IndividualTask; +export default IndividualTask diff --git a/apps/mobile/app/screens/Authenticated/TimerScreen/components/TimerTaskSection.tsx b/apps/mobile/app/screens/Authenticated/TimerScreen/components/TimerTaskSection.tsx index 9fee67bb1..7c7614c4b 100644 --- a/apps/mobile/app/screens/Authenticated/TimerScreen/components/TimerTaskSection.tsx +++ b/apps/mobile/app/screens/Authenticated/TimerScreen/components/TimerTaskSection.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from "react" import { View, StyleSheet, @@ -9,29 +9,29 @@ import { ViewStyle, TouchableWithoutFeedback, Dimensions, - Pressable -} from 'react-native'; -import { ActivityIndicator } from 'react-native-paper'; -import { GLOBAL_STYLE as GS } from '../../../../../assets/ts/styles'; -import ComboBox from './ComboBox'; -import EstimateTime from './EstimateTime'; -import { Feather } from '@expo/vector-icons'; -import { observer } from 'mobx-react-lite'; -import TaskPriorities from '../../../../components/TaskPriority'; + Pressable, +} from "react-native" +import { ActivityIndicator } from "react-native-paper" +import { GLOBAL_STYLE as GS } from "../../../../../assets/ts/styles" +import ComboBox from "./ComboBox" +import EstimateTime from "./EstimateTime" +import { Feather } from "@expo/vector-icons" +import { observer } from "mobx-react-lite" +import TaskPriorities from "../../../../components/TaskPriority" // import TaskLabel from "../../../../components/TaskLabel" -import { typography, useAppTheme } from '../../../../theme'; -import { translate } from '../../../../i18n'; -import TaskStatus from '../../../../components/TaskStatus'; -import TimerCard from '../../../../components/TimerCard'; -import TaskSize from '../../../../components/TaskSize'; -import { RTuseTaskInput } from '../../../../services/hooks/features/useTaskInput'; -import TaskLabels from '../../../../components/TaskLabels'; -import IssuesModal from '../../../../components/IssuesModal'; -import { useStores } from '../../../../models'; +import { typography, useAppTheme } from "../../../../theme" +import { translate } from "../../../../i18n" +import TaskStatus from "../../../../components/TaskStatus" +import TimerCard from "../../../../components/TimerCard" +import TaskSize from "../../../../components/TaskSize" +import { RTuseTaskInput } from "../../../../services/hooks/features/useTaskInput" +import TaskLabels from "../../../../components/TaskLabels" +import IssuesModal from "../../../../components/IssuesModal" +import { useStores } from "../../../../models" const TimerTaskSection = observer( ({ taskInput, outsideClick }: { taskInput: RTuseTaskInput; outsideClick: () => unknown }) => { - const { colors } = useAppTheme(); + const { colors } = useAppTheme() const { setEditMode, setQuery, @@ -40,32 +40,32 @@ const TimerTaskSection = observer( isModalOpen, hasCreateForm, handleTaskCreation, - createLoading - } = taskInput; + createLoading, + } = taskInput - const [combxShow, setCombxShow] = useState(false); - const inputRef = useRef(null); + const [combxShow, setCombxShow] = useState(false) + const inputRef = useRef(null) const { - TimerStore: { localTimerStatus } - } = useStores(); + TimerStore: { localTimerStatus }, + } = useStores() const closeCombox = useCallback(() => { - setCombxShow(false); - }, [setCombxShow]); + setCombxShow(false) + }, [setCombxShow]) useEffect(() => { if (isModalOpen || editMode) { - setCombxShow(true); + setCombxShow(true) } else { - setCombxShow(false); + setCombxShow(false) } - }, [isModalOpen, editMode]); + }, [isModalOpen, editMode]) useEffect(() => { if (!editMode) { - inputRef.current.blur(); + inputRef.current.blur() } - }, [editMode]); + }, [editMode]) return ( outsideClick()}> @@ -74,17 +74,17 @@ const TimerTaskSection = observer( style={[ styles.wrapInput, { - flexDirection: 'row', - alignItems: 'center', + flexDirection: "row", + alignItems: "center", borderColor: colors.border, - backgroundColor: colors.background - } + backgroundColor: colors.background, + }, ]} > - {!editMode && activeTask ? `#${activeTask.taskNumber} ` : ''} + {!editMode && activeTask ? `#${activeTask.taskNumber} ` : ""} { - handleTaskCreation(); - setEditMode(false); + handleTaskCreation() + setEditMode(false) }} > ) : null} - {createLoading ? : null} + {createLoading ? ( + + ) : null} {combxShow ? ( - + closeCombox()} + setEditMode={setEditMode} + tasksHandler={taskInput} + /> ) : ( - {translate('myWorkScreen.estimateLabel')} :{' '} + {translate("myWorkScreen.estimateLabel")} :{" "} @@ -158,23 +164,23 @@ const TimerTaskSection = observer( task={activeTask} containerStyle={{ ...styles.sizeContainer, - borderColor: colors.border + borderColor: colors.border, }} /> @@ -182,7 +188,7 @@ const TimerTaskSection = observer( task={activeTask} containerStyle={{ ...styles.sizeContainer, - borderColor: colors.border + borderColor: colors.border, }} /> @@ -190,9 +196,9 @@ const TimerTaskSection = observer( task={activeTask} containerStyle={{ ...styles.sizeContainer, - width: '100%', + width: "100%", borderColor: colors.border, - marginVertical: 20 + marginVertical: 20, }} /> @@ -200,12 +206,12 @@ const TimerTaskSection = observer( )} - ); - } -); -export default TimerTaskSection; + ) + }, +) +export default TimerTaskSection -const width = Dimensions.get('window').width; +const width = Dimensions.get("window").width const $timerSection: ViewStyle = { marginTop: 20, @@ -214,103 +220,103 @@ const $timerSection: ViewStyle = { borderRadius: 16, ...GS.noBorder, borderWidth: 1, - shadowColor: '#000', + shadowColor: "#000", shadowOffset: { width: 0, - height: 14 + height: 14, }, shadowOpacity: 0.1, shadowRadius: 44, - elevation: 8 -}; + elevation: 8, +} const styles = StyleSheet.create({ container: {}, dashed: { - borderBottomColor: '#fff', - borderBottomWidth: 10 + borderBottomColor: "#fff", + borderBottomWidth: 10, }, estimate: { - alignSelf: 'flex-end', - color: '#9490A0', + alignSelf: "flex-end", + color: "#9490A0", fontSize: 12, - fontWeight: '600', - marginBottom: 10 + fontWeight: "600", + marginBottom: 10, }, horizontal: { - flexDirection: 'row', - justifyContent: 'center', - marginBottom: 20 + flexDirection: "row", + justifyContent: "center", + marginBottom: 20, }, horizontalInput: { - alignItems: 'flex-end', - flexDirection: 'row' + alignItems: "flex-end", + flexDirection: "row", }, loading: { - right: 10 + right: 10, }, mainContainer: { - backgroundColor: '#fff', + backgroundColor: "#fff", borderRadius: 25, paddingTop: 30, padding: 20, ...GS.noBorder, borderWidth: 1, elevation: 5, - shadowColor: '#1B005D0D', + shadowColor: "#1B005D0D", shadowOffset: { width: 10, height: 10.5 }, shadowOpacity: 1, - shadowRadius: 15 + shadowRadius: 15, }, sizeContainer: { - alignItems: 'center', - borderColor: 'rgba(255, 255, 255, 0.13)', + alignItems: "center", + borderColor: "rgba(255, 255, 255, 0.13)", borderWidth: 1, height: 32, paddingHorizontal: 9, - width: width / 2.7 + width: width / 2.7, }, taskNumberStyle: { - color: '#7B8089', + color: "#7B8089", fontFamily: typography.primary.semiBold, fontSize: 14, - marginLeft: 5 + marginLeft: 5, }, textInput: { - backgroundColor: '#fff', + backgroundColor: "#fff", borderRadius: 10, - color: 'rgba(40, 32, 72, 0.4)', + color: "rgba(40, 32, 72, 0.4)", fontFamily: typography.fonts.PlusJakartaSans.semiBold, fontSize: 12, height: 43, paddingHorizontal: 6, paddingVertical: 13, - width: '80%' + width: "80%", }, textInputOne: { - height: 30 + height: 30, }, working: { - color: '#9490A0', - fontWeight: '600', - marginBottom: 10 + color: "#9490A0", + fontWeight: "600", + marginBottom: 10, }, wrapBugIcon: { - alignItems: 'center', - backgroundColor: '#C24A4A', + alignItems: "center", + backgroundColor: "#C24A4A", borderRadius: 3, height: 20, - justifyContent: 'center', - width: 20 + justifyContent: "center", + width: 20, }, wrapInput: { - backgroundColor: '#fff', - borderColor: 'rgba(0, 0, 0, 0.1)', + backgroundColor: "#fff", + borderColor: "rgba(0, 0, 0, 0.1)", borderRadius: 10, borderWidth: 1, height: 45, paddingHorizontal: 16, paddingVertical: 2, - width: '100%' - } -}); + width: "100%", + }, +}) diff --git a/apps/mobile/app/services/client/queries/task/task-version.ts b/apps/mobile/app/services/client/queries/task/task-version.ts new file mode 100644 index 000000000..0dcc31537 --- /dev/null +++ b/apps/mobile/app/services/client/queries/task/task-version.ts @@ -0,0 +1,27 @@ +import { useQuery } from "react-query" +import { getTaskVersionListRequest } from "../../requests/task-version" + +interface IGetTaskVersionsParams { + authToken: string + tenantId: string + organizationId: string + activeTeamId: string +} +const fetchAllVersions = async (params: IGetTaskVersionsParams) => { + const { organizationId, tenantId, activeTeamId, authToken } = params + const { data } = await getTaskVersionListRequest( + { + tenantId, + organizationId, + activeTeamId, + }, + authToken, + ) + return data +} + +const useFetchAllVersions = (IGetTaskVersionsParams) => + useQuery(["versions", IGetTaskVersionsParams], () => fetchAllVersions(IGetTaskVersionsParams), { + refetchInterval: 62000, + }) +export default useFetchAllVersions diff --git a/apps/mobile/app/services/client/requests/auth.ts b/apps/mobile/app/services/client/requests/auth.ts index 915946b47..a2709aaf3 100644 --- a/apps/mobile/app/services/client/requests/auth.ts +++ b/apps/mobile/app/services/client/requests/auth.ts @@ -4,136 +4,140 @@ import { IRegisterDataRequest, ISuccessResponse, ISignInResponse, - ILoginResponse -} from '../../interfaces/IAuthentication'; -import { IUser } from '../../interfaces/IUserData'; -import { serverFetch } from '../fetch'; + ILoginResponse, +} from "../../interfaces/IAuthentication" +import { IUser } from "../../interfaces/IUserData" +import { serverFetch } from "../fetch" const registerDefaultValue = { - appName: 'Ever Teams', - appSignature: 'Ever Team', - appLogo: 'https://app.ever.team/assets/gauzy-team.png', - appLink: 'https://ever.team/', - appEmailConfirmationUrl: 'https://app.gauzy.co/#/auth/confirm-email' -}; + appName: "Ever Teams", + appSignature: "Ever Team", + appLogo: "https://app.ever.team/assets/ever-teams.png", + appLink: "https://ever.team/", + appEmailConfirmationUrl: "https://app.gauzy.co/#/auth/confirm-email", +} export const registerUserRequest = (data: IRegisterDataRequest) => { const body = { ...data, - ...registerDefaultValue - }; + ...registerDefaultValue, + } return serverFetch({ - path: '/auth/register', - method: 'POST', - body - }); -}; + path: "/auth/register", + method: "POST", + body, + }) +} export const loginUserRequest = (email: string, password: string) => { return serverFetch({ - path: '/auth/login', - method: 'POST', + path: "/auth/login", + method: "POST", body: { email, - password - } - }); -}; + password, + }, + }) +} export const whetherUserAuthenticatedRequest = (bearer_token: string) => { return serverFetch({ - path: '/user/authenticated', - method: 'GET', - bearer_token - }); -}; + path: "/user/authenticated", + method: "GET", + bearer_token, + }) +} type IUEmployeeParam = { - bearer_token: string; - relations?: string[]; -}; + bearer_token: string + relations?: string[] +} export const currentAuthenticatedUserRequest = ({ bearer_token, - relations = ['employee', 'role', 'tenant'] + relations = ["employee", "role", "tenant"], }: IUEmployeeParam) => { - const params = {} as { [x: string]: string }; + const params = {} as { [x: string]: string } relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); + params[`relations[${i}]`] = rl + }) - const query = new URLSearchParams(params); + const query = new URLSearchParams(params) return serverFetch({ path: `/user/me?${query.toString()}`, - method: 'GET', - bearer_token - }); -}; + method: "GET", + bearer_token, + }) +} export const refreshTokenRequest = (refresh_token: string) => { return serverFetch<{ token: string }>({ - path: '/auth/refresh-token', - method: 'POST', + path: "/auth/refresh-token", + method: "POST", body: { - refresh_token - } - }); -}; + refresh_token, + }, + }) +} // auth/signin.email export function sendAuthCodeRequest(email: string) { - return serverFetch<{ status: number; message: string | 'ok' }>({ - path: '/auth/signin.email', - method: 'POST', - body: { email } - }); + return serverFetch<{ status: number; message: string | "ok" }>({ + path: "/auth/signin.email", + method: "POST", + body: { email }, + }) } // auth/signin.email/confirm Gives response with tenantId's export function verifyAuthCodeRequest(email: string, code: string) { return serverFetch({ - path: '/auth/signin.email/confirm?includeTeams=true', - method: 'POST', - body: { email, code } - }); + path: "/auth/signin.email/confirm?includeTeams=true", + method: "POST", + body: { email, code }, + }) } // auth/signin.workspace Need the email and the token from auth/signin.email/confirm export const signInWorkspaceRequest = (email: string, token: string) => { return serverFetch({ - path: '/auth/signin.workspace', - method: 'POST', - body: { email, token } - }); -}; + path: "/auth/signin.workspace", + method: "POST", + body: { email, token }, + }) +} export const verifyUserEmailByCodeRequest = (data: { - bearer_token: string; - code: string; - email: string; - tenantId: string; + bearer_token: string + code: string + email: string + tenantId: string }) => { - const { code, email, bearer_token, tenantId } = data; + const { code, email, bearer_token, tenantId } = data return serverFetch({ - path: '/auth/email/verify/code', - method: 'POST', + path: "/auth/email/verify/code", + method: "POST", body: { code, email, tenantId }, tenantId: data.tenantId, - bearer_token - }); -}; + bearer_token, + }) +} -export const resentVerifyUserLinkRequest = (data: { bearer_token: string; email: string; tenantId: string }) => { - const { email, bearer_token, tenantId } = data; +export const resentVerifyUserLinkRequest = (data: { + bearer_token: string + email: string + tenantId: string +}) => { + const { email, bearer_token, tenantId } = data return serverFetch({ - path: '/auth/email/verify/resend-link', - method: 'POST', + path: "/auth/email/verify/resend-link", + method: "POST", body: { email, tenantId }, tenantId: data.tenantId, - bearer_token - }); -}; + bearer_token, + }) +} diff --git a/apps/mobile/app/services/client/requests/task-version.ts b/apps/mobile/app/services/client/requests/task-version.ts new file mode 100644 index 000000000..4c300c850 --- /dev/null +++ b/apps/mobile/app/services/client/requests/task-version.ts @@ -0,0 +1,73 @@ +/* eslint-disable camelcase */ +import { ITaskVersionCreate, ITaskVersionItemList } from "../../interfaces/ITaskVersion" +import { serverFetch } from "../fetch" + +export function createVersionRequest( + datas: ITaskVersionCreate, + bearer_token: string, + tenantId?: any, +) { + return serverFetch({ + path: "/task-versions", + method: "POST", + body: datas, + bearer_token, + tenantId, + }) +} + +export function editTaskVersionRequest({ + id, + datas, + bearer_token, + tenantId, +}: { + id: string | any + datas: ITaskVersionCreate + bearer_token: string + tenantId?: any +}) { + return serverFetch({ + path: `/task-versions/${id}`, + method: "PUT", + body: datas, + bearer_token, + tenantId, + }) +} + +export function deleteTaskVersionRequest({ + id, + bearer_token, + tenantId, +}: { + id: string | any + bearer_token: string | any + tenantId?: any +}) { + return serverFetch({ + path: `/task-versions/${id}`, + method: "DELETE", + bearer_token, + tenantId, + }) +} + +export function getTaskVersionListRequest( + { + organizationId, + tenantId, + activeTeamId, + }: { + tenantId: string + organizationId: string + activeTeamId: string | null + }, + bearer_token: string, +) { + return serverFetch({ + path: `/task-versions?tenantId=${tenantId}&organizationId=${organizationId}&organizationTeamId=${activeTeamId}`, + method: "GET", + bearer_token, + }) +} diff --git a/apps/mobile/app/services/client/requests/tasks.ts b/apps/mobile/app/services/client/requests/tasks.ts index 06a0df130..33a837e4b 100644 --- a/apps/mobile/app/services/client/requests/tasks.ts +++ b/apps/mobile/app/services/client/requests/tasks.ts @@ -1,5 +1,5 @@ /* eslint-disable camelcase */ -import { DeleteReponse, PaginationResponse, CreateReponse } from "../../interfaces/IDataResponse" +import { DeleteResponse, PaginationResponse, CreateResponse } from "../../interfaces/IDataResponse" import { ICreateTask, ITeamTask } from "../../interfaces/ITask" import { serverFetch } from "../fetch" @@ -75,7 +75,7 @@ export function getTaskByIdRequest({ const query = new URLSearchParams(obj) - return serverFetch>({ + return serverFetch>({ path: `/tasks/${taskId}?${query.toString()}`, method: "GET", bearer_token, @@ -92,7 +92,7 @@ export function deleteTaskRequest({ taskId: string bearer_token: string }) { - return serverFetch({ + return serverFetch({ path: `/tasks/${taskId}?tenantId=${tenantId}`, method: "DELETE", bearer_token, @@ -138,7 +138,7 @@ export function deleteEmployeeFromTasksRequest({ organizationTeamId: string bearer_token: string }) { - return serverFetch({ + return serverFetch({ path: `/tasks/employee/${employeeId}?organizationTeamId=${organizationTeamId}`, method: "DELETE", bearer_token, diff --git a/apps/mobile/app/services/hooks/features/useTaskInput.ts b/apps/mobile/app/services/hooks/features/useTaskInput.ts index c95f5d73c..b5bf0f727 100644 --- a/apps/mobile/app/services/hooks/features/useTaskInput.ts +++ b/apps/mobile/app/services/hooks/features/useTaskInput.ts @@ -1,23 +1,23 @@ /* eslint-disable camelcase */ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { ITeamTask } from '../../interfaces/ITask'; -import { useTeamTasks } from './useTeamTasks'; -import useAuthenticateUser from './useAuthentificateUser'; -import { useSyncRef } from '../useSyncRef'; -import { Nullable } from '../../interfaces/hooks'; -import { useModal } from '../useModal'; -import { useStores } from '../../../models'; - -export const h_filter = (status: string, filters: 'closed' | 'open') => { +import { useCallback, useEffect, useMemo, useRef, useState } from "react" +import { ITeamTask } from "../../interfaces/ITask" +import { useTeamTasks } from "./useTeamTasks" +import useAuthenticateUser from "./useAuthentificateUser" +import { useSyncRef } from "../useSyncRef" +import { Nullable } from "../../interfaces/hooks" +import { useModal } from "../useModal" +import { useStores } from "../../../models" + +export const h_filter = (status: string, filters: "closed" | "open") => { switch (filters) { - case 'open': - return status !== 'closed'; - case 'closed': - return status === 'closed'; + case "open": + return status !== "closed" + case "closed": + return status === "closed" default: - return true; + return true } -}; +} /** * It returns a bunch of variables and functions that are used to manage the task input @@ -28,132 +28,159 @@ export const h_filter = (status: string, filters: 'closed' | 'open') => { export function useTaskInput({ task, initEditMode, - tasks: customTasks + tasks: customTasks, }: { - tasks?: ITeamTask[]; - task?: Nullable; - initEditMode?: boolean; + tasks?: ITeamTask[] + task?: Nullable + initEditMode?: boolean } = {}) { const { - teamStore: { activeTeam } - } = useStores(); - const { isOpen: isModalOpen, openModal, closeModal } = useModal(); - const [closeableTask, setCloseableTaskTask] = useState(null); + teamStore: { activeTeam }, + } = useStores() + const { isOpen: isModalOpen, openModal, closeModal } = useModal() + const [closeableTask, setCloseableTaskTask] = useState(null) - const { teamTasks, createNewTask, activeTask, createLoading, tasksFetching, updateTask, setActiveTeamTask } = - useTeamTasks(); + const { + teamTasks, + createNewTask, + activeTask, + createLoading, + tasksFetching, + updateTask, + setActiveTeamTask, + } = useTeamTasks() - const { user } = useAuthenticateUser(); - const userRef = useSyncRef(user); + const { user } = useAuthenticateUser() + const userRef = useSyncRef(user) - const taskIssue = useRef(null); + const taskIssue = useRef(null) - const tasks = customTasks || (teamTasks as ITeamTask[]); + const tasks = customTasks || (teamTasks as ITeamTask[]) /** * If task has null value then consider it as value 😄 */ - const inputTask = task !== undefined ? task : activeTask; + const inputTask = task !== undefined ? task : activeTask - const [filter, setFilter] = useState<'closed' | 'open'>('open'); - const [editMode, setEditMode] = useState(initEditMode || false); + const [filter, setFilter] = useState<"closed" | "open">("open") + const [editMode, setEditMode] = useState(initEditMode || false) const handleOpenModal = useCallback( (concernedTask: ITeamTask) => { - setCloseableTaskTask(concernedTask); - openModal(); + setCloseableTaskTask(concernedTask) + openModal() }, - [setCloseableTaskTask] - ); + [setCloseableTaskTask], + ) const handleReopenTask = useCallback( async (concernedTask: ITeamTask) => { return updateTask( { ...concernedTask, - status: 'open' + status: "open", }, - concernedTask.id - ); + concernedTask.id, + ) }, - [updateTask] - ); + [updateTask], + ) - const [query, setQuery] = useState(''); + const [query, setQuery] = useState("") const filteredTasks = useMemo(() => { - return query.trim() === '' + return query.trim() === "" ? tasks.filter((task) => h_filter(task.status, filter)) : tasks.filter( (task) => task.title .trim() .toLowerCase() - .replace(/\s+/g, '') - .startsWith(query.toLowerCase().replace(/\s+/g, '')) && h_filter(task.status, filter) - ); - }, [query, filter, editMode, activeTeam]); + .replace(/\s+/g, "") + .startsWith(query.toLowerCase().replace(/\s+/g, "")) && + h_filter(task.status, filter), + ) + }, [query, filter, editMode, activeTeam]) const filteredTasks2 = useMemo(() => { - return query.trim() === '' + return query.trim() === "" ? tasks : tasks.filter((task) => { return task.title .trim() .toLowerCase() - .replace(/\s+/g, '') - .startsWith(query.toLowerCase().replace(/\s+/g, '')); - }); - }, [query]); + .replace(/\s+/g, "") + .startsWith(query.toLowerCase().replace(/\s+/g, "")) + }) + }, [query]) + + const filteredEpicTasks = useMemo(() => { + return query.trim() === "" + ? tasks.filter((task) => task.issueType === "Epic") + : tasks.filter((task) => { + return ( + task.title + .trim() + .toLowerCase() + .replace(/\s+/g, "") + .startsWith(query.toLowerCase().replace(/\s+/g, "")) && + task.issueType === "Epic" + ) + }) + }, [query]) - const hasCreateForm = filteredTasks2.length === 0 && query !== ''; + const hasCreateForm = filteredTasks2.length === 0 && query !== "" const handleTaskCreation = ({ autoAssignTaskAuth = true, - assignToUsers = [] + assignToUsers = [], }: { - autoActiveTask?: boolean; - autoAssignTaskAuth?: boolean; + autoActiveTask?: boolean + autoAssignTaskAuth?: boolean assignToUsers?: { - id: string; - }[]; + id: string + }[] } = {}) => { - if (query.trim().length < 2 || inputTask?.title === query.trim() || !userRef.current?.isEmailVerified) - return null; - - setEditMode(false); + if ( + query.trim().length < 2 || + inputTask?.title === query.trim() || + !userRef.current?.isEmailVerified + ) + return null + + setEditMode(false) return createNewTask( { taskName: query.trim(), - issueType: taskIssue.current || undefined + issueType: taskIssue.current || undefined, }, - !autoAssignTaskAuth ? assignToUsers : undefined - ); - }; + !autoAssignTaskAuth ? assignToUsers : undefined, + ) + } const updateTaskTitleHandler = useCallback((itask: ITeamTask, title: string) => { - if (!userRef.current?.isEmailVerified) return null; + if (!userRef.current?.isEmailVerified) return null return updateTask( { ...itask, - title + title, }, - itask.id - ); - }, []); + itask.id, + ) + }, []) const closedTaskCount = filteredTasks2.filter((f_task) => { - return f_task.status === 'closed'; - }).length; + return f_task.status === "closed" + }).length const openTaskCount = filteredTasks2.filter((f_task) => { - return f_task.status !== 'closed'; - }).length; + return f_task.status !== "closed" + }).length useEffect(() => { - taskIssue.current = null; - }, [hasCreateForm]); + taskIssue.current = null + }, [hasCreateForm]) return { closedTaskCount, @@ -161,6 +188,7 @@ export function useTaskInput({ hasCreateForm, handleTaskCreation, filteredTasks, + filteredEpicTasks, handleReopenTask, handleOpenModal, activeTask, @@ -179,8 +207,8 @@ export function useTaskInput({ taskIssue, user, userRef, - setActiveTeamTask - }; + setActiveTeamTask, + } } -export type RTuseTaskInput = ReturnType; +export type RTuseTaskInput = ReturnType diff --git a/apps/mobile/app/services/hooks/features/useTaskVersion.ts b/apps/mobile/app/services/hooks/features/useTaskVersion.ts new file mode 100644 index 000000000..95e82a49d --- /dev/null +++ b/apps/mobile/app/services/hooks/features/useTaskVersion.ts @@ -0,0 +1,70 @@ +import { useQueryClient } from "react-query" +import { + createVersionRequest, + deleteTaskVersionRequest, + editTaskVersionRequest, +} from "../../client/requests/task-version" +import { ITaskVersionCreate, ITaskVersionItemList } from "../../interfaces/ITaskVersion" +import { useStores } from "../../../models" +import { useCallback, useEffect, useState } from "react" +import useFetchAllVersions from "../../client/queries/task/task-version" + +export function useTaskVersion() { + const queryClient = useQueryClient() + const { + authenticationStore: { authToken, tenantId, organizationId }, + teamStore: { activeTeamId }, + } = useStores() + + const [taskVersionList, setTaskVersionList] = useState([]) + + const { + data: versions, + isLoading, + isSuccess, + isRefetching, + } = useFetchAllVersions({ tenantId, organizationId, activeTeamId, authToken }) + + const createTaskVersion = useCallback( + async (data: ITaskVersionCreate) => { + await createVersionRequest(data, authToken, tenantId) + queryClient.invalidateQueries("versions") + }, + [authToken, tenantId, queryClient], + ) + + const deleteTaskVersion = useCallback( + async (id: string) => { + await deleteTaskVersionRequest({ bearer_token: authToken, tenantId, id }) + + queryClient.invalidateQueries("versions") + }, + [authToken, tenantId, queryClient], + ) + + const updateTaskVersion = useCallback( + async ({ id, data }) => { + await editTaskVersionRequest({ id, datas: data, bearer_token: authToken, tenantId }) + queryClient.invalidateQueries("versions") + }, + [authToken, tenantId, queryClient], + ) + + useEffect(() => { + if (isSuccess) { + if (versions) { + // @ts-ignore + setTaskVersionList(versions?.items || []) + } + } + }, [isLoading, isRefetching]) + + return { + createTaskVersion, + deleteTaskVersion, + updateTaskVersion, + taskVersionList, + versions, + isLoading, + } +} diff --git a/apps/mobile/app/services/hooks/features/useTeamTasks.ts b/apps/mobile/app/services/hooks/features/useTeamTasks.ts index 47c90e3b5..75bceca4d 100644 --- a/apps/mobile/app/services/hooks/features/useTeamTasks.ts +++ b/apps/mobile/app/services/hooks/features/useTeamTasks.ts @@ -243,6 +243,26 @@ export function useTeamTasks() { [], ) + const updatePublicity = useCallback( + (publicity: boolean, task?: ITeamTask | null, loader?: boolean) => { + if (task && publicity !== task.public) { + loader && setTasksFetching(true) + return updateTask( + { + ...task, + public: publicity, + }, + task.id, + ).then((res) => { + setTasksFetching(false) + return res + }) + } + return Promise.resolve() + }, + [setTasksFetching], + ) + /** * Change active task */ @@ -308,6 +328,7 @@ export function useTeamTasks() { detailedTask, deleteTask, updateTask, + updatePublicity, setActiveTeamTask, updateDescription, updateTitle, diff --git a/apps/mobile/app/services/interfaces/IDataResponse.ts b/apps/mobile/app/services/interfaces/IDataResponse.ts index 67b7743ca..28f74a142 100644 --- a/apps/mobile/app/services/interfaces/IDataResponse.ts +++ b/apps/mobile/app/services/interfaces/IDataResponse.ts @@ -16,12 +16,12 @@ export type PaginationResponse = { total: number } -export type CreateReponse = { +export type CreateResponse = { data: T response: any } -export type DeleteReponse = { +export type DeleteResponse = { raw: string[] affected: number } diff --git a/apps/mobile/app/services/interfaces/ITask.ts b/apps/mobile/app/services/interfaces/ITask.ts index 3e27d07f5..b6c13fc88 100644 --- a/apps/mobile/app/services/interfaces/ITask.ts +++ b/apps/mobile/app/services/interfaces/ITask.ts @@ -1,142 +1,163 @@ -import { IEmployee } from './IEmployee'; -import { IOrganizationTeamList } from './IOrganizationTeam'; -import { ITaskLabelItem } from './ITaskLabel'; +import { IEmployee } from "./IEmployee" +import { IOrganizationTeamList } from "./IOrganizationTeam" +import { ITaskLabelItem } from "./ITaskLabel" export type ITeamTask = { - id: string; - createdAt: string; - updatedAt: string; - tenantId: string; - organizationId: string; - number: number; - prefix: string; - title: string; - description: string; - estimate: null | number; - totalWorkedTime?: number; - estimateDays?: number; - estimateHours?: number; - estimateMinutes?: number; - dueDate: string; - projectId: string; - creatorId: string; - members: IEmployee[]; - selectedTeam?: IOrganizationTeamList; - tags: ITaskLabelItem[]; - teams: SelectedTeam[]; - creator: Creator; - taskNumber: string; - label?: string; -} & ITaskStatusStack; + id: string + createdAt: string + updatedAt: string + tenantId: string + organizationId: string + number: number + prefix: string + title: string + description: string + estimate: null | number + totalWorkedTime?: number + estimateDays?: number + estimateHours?: number + estimateMinutes?: number + startDate?: string + dueDate: string + projectId: string + public: boolean + resolvedAt?: string + creatorId: string + members: IEmployee[] + selectedTeam?: IOrganizationTeamList + tags: ITaskLabelItem[] + teams: SelectedTeam[] + linkedIssues?: LinkedTaskIssue[] + children?: Omit[] + creator: Creator + taskNumber: string + label?: string + parentId?: string + parent?: ITeamTask + rootEpic?: ITeamTask | null +} & ITaskStatusStack type SelectedTeam = Pick< IOrganizationTeamList, - 'id' | 'createdAt' | 'name' | 'organizationId' | 'tenantId' | 'updatedAt' | 'prefix' ->; + "id" | "createdAt" | "name" | "organizationId" | "tenantId" | "updatedAt" | "prefix" +> export interface Tag { - id: string; - createdAt: string; - updatedAt: string; - tenantId: string; - organizationId: string; - name: string; - description: string; - color: string; - isSystem: boolean; + id: string + createdAt: string + updatedAt: string + tenantId: string + organizationId: string + name: string + description: string + color: string + isSystem: boolean } interface Creator { - id: string; - createdAt: string; - updatedAt: string; - tenantId: string; - thirdPartyId: any; - firstName: string; - lastName: string; - email: string; - username: any; - hash: string; - refreshToken: any; - imageUrl: string; - preferredLanguage: string; - preferredComponentLayout: string; - isActive: boolean; - roleId: string; - name: string; - employeeId: any; + id: string + createdAt: string + updatedAt: string + tenantId: string + thirdPartyId: any + firstName: string + lastName: string + email: string + username: any + hash: string + refreshToken: any + imageUrl: string + preferredLanguage: string + preferredComponentLayout: string + isActive: boolean + roleId: string + name: string + employeeId: any } -export type ITaskPriority = 'Highest' | 'High' | 'Medium' | 'Low' | 'Lowest'; +export type LinkedTaskIssue = { + id: string + createdAt: string + updatedAt: string + tenantId: string + organizationId: string + action: number + taskFromId: string + taskToId: string + taskTo: Omit + taskFrom: Omit +} + +export type ITaskPriority = "Highest" | "High" | "Medium" | "Low" | "Lowest" -export type IVersionProperty = 'Version 1' | 'Version 2'; +export type IVersionProperty = "Version 1" | "Version 2" -export type IEpicProperty = string; +export type IEpicProperty = string -export type ITaskSize = 'X-Large' | 'Large' | 'Medium' | 'Small' | 'Tiny'; +export type ITaskSize = "X-Large" | "Large" | "Medium" | "Small" | "Tiny" -export type ITaskLabel = 'UI/UX' | 'Mobile' | 'WEB' | 'Tablet'; +export type ITaskLabel = "UI/UX" | "Mobile" | "WEB" | "Tablet" export type ITaskStatus = - | 'Blocked' - | 'Ready' - | 'Backlog' - | 'Todo' - | 'In Progress' - | 'Completed' - | 'Closed' - | 'In Review'; + | "Blocked" + | "Ready" + | "Backlog" + | "Todo" + | "In Progress" + | "Completed" + | "Closed" + | "In Review" -export type ITaskIssue = 'Bug' | 'Task' | 'Story' | 'Epic'; +export type ITaskIssue = "Bug" | "Task" | "Story" | "Epic" export type ITaskStatusField = - | 'status' - | 'size' - | 'priority' - | 'label' - | 'issue' - | 'version' - | 'epic' - | 'project' - | 'team' - | 'tags'; + | "status" + | "size" + | "priority" + | "label" + | "issue" + | "version" + | "epic" + | "project" + | "team" + | "tags" export type ITaskStatusStack = { - status: string; - size: string; - label: string; - priority: string; - issueType: string; - version: string; - epic: string; - project: string; // TODO: these types are not strings, but rather objects for team and project. To reimplement - team: string; // TODO: these types are not strings, but rather objects for team and project. To reimplement - tags: any; // TODO: these types are not strings, but rather array of objects for tags. To reimplement -}; + status: string + size: string + label: string + priority: string + issueType: string + version: string + epic: string + project: string // TODO: these types are not strings, but rather objects for team and project. To reimplement + team: string // TODO: these types are not strings, but rather objects for team and project. To reimplement + tags: any // TODO: these types are not strings, but rather array of objects for tags. To reimplement +} export interface ICreateTask { - title: string; - status: string; - priority?: string; - size?: string; - issueType?: string; - members?: { id: string; [x: string]: any }[]; - estimateDays?: number; - estimateHours?: string; - estimateMinutes?: string; - dueDate?: string; - description: string; - tags: ITaskLabelItem[]; - teams: { id: string }[]; - estimate: number; - organizationId: string; - tenantId: string; + title: string + status: string + priority?: string + size?: string + issueType?: string + members?: { id: string; [x: string]: any }[] + estimateDays?: number + estimateHours?: string + estimateMinutes?: string + dueDate?: string + description: string + tags: ITaskLabelItem[] + teams: { id: string }[] + estimate: number + organizationId: string + tenantId: string } export interface IParamsStatistic { - taskId: string; - bearer_token: string; - organizationId: string; - tenantId: string; - activeTask: boolean; + taskId: string + bearer_token: string + organizationId: string + tenantId: string + activeTask: boolean } diff --git a/apps/mobile/app/services/interfaces/ITaskVersion.ts b/apps/mobile/app/services/interfaces/ITaskVersion.ts new file mode 100644 index 000000000..62653e8a2 --- /dev/null +++ b/apps/mobile/app/services/interfaces/ITaskVersion.ts @@ -0,0 +1,28 @@ +export interface ITaskVersionItemList { + id: string + createdAt: string + updatedAt: string + tenantId: string + organizationId: string + name?: string + value?: string + description?: string + icon?: string + fullIconUrl?: string + color?: string + is_system?: boolean + isSystem?: boolean + projectId?: string +} + +export interface ITaskVersionCreate { + name: string + description?: string + icon?: string + color?: string + projectId?: string + organizationId?: string + tenantId?: string | undefined | null + organizationTeamId?: string | undefined | null + value?: string +} diff --git a/apps/mobile/app/services/interfaces/interfaces/IDataResponse.ts b/apps/mobile/app/services/interfaces/interfaces/IDataResponse.ts index c9e7162f6..baa3753f7 100644 --- a/apps/mobile/app/services/interfaces/interfaces/IDataResponse.ts +++ b/apps/mobile/app/services/interfaces/interfaces/IDataResponse.ts @@ -1,22 +1,22 @@ interface IResponseMetadata { - status: ResStatusEnum; - message: string; - error: Error | null; + status: ResStatusEnum + message: string + error: Error | null } -export type IDataResponse = IResponseMetadata & T; +export type IDataResponse = IResponseMetadata & T export enum ResStatusEnum { - error = 'error', - success = 'success' + error = "error", + success = "success", } export type PaginationResponse = { - items: T[]; - total: number; -}; + items: T[] + total: number +} -export type DeleteReponse = { - raw: string[]; - affected: number; -}; +export type DeleteResponse = { + raw: string[] + affected: number +} diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json index 0b0484cec..b7a2130fc 100644 --- a/apps/mobile/eas.json +++ b/apps/mobile/eas.json @@ -20,5 +20,14 @@ "production": { "channel": "production" } + }, + "submit":{ + "production":{ + "android":{ + "track":"internal", + "releaseStatus":"draft", + "changesNotSentForReview":false + } + } } } diff --git a/apps/mobile/ios/GauzyTeams.xcodeproj/project.pbxproj b/apps/mobile/ios/GauzyTeams.xcodeproj/project.pbxproj index d1cfad613..404c4a98a 100644 --- a/apps/mobile/ios/GauzyTeams.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/GauzyTeams.xcodeproj/project.pbxproj @@ -373,7 +373,7 @@ "-lc++", ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; - PRODUCT_BUNDLE_IDENTIFIER = ever.team; + PRODUCT_BUNDLE_IDENTIFIER = co.ever.teams; PRODUCT_NAME = GauzyTeams; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -400,7 +400,7 @@ "-lc++", ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; - PRODUCT_BUNDLE_IDENTIFIER = ever.team; + PRODUCT_BUNDLE_IDENTIFIER = co.ever.teams; PRODUCT_NAME = GauzyTeams; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/apps/mobile/ios/GauzyTeams/Info.plist b/apps/mobile/ios/GauzyTeams/Info.plist index 8fb9370d1..9d5b6bbbd 100644 --- a/apps/mobile/ios/GauzyTeams/Info.plist +++ b/apps/mobile/ios/GauzyTeams/Info.plist @@ -1,84 +1,84 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Ever Teams - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 0.1.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLSchemes - - ever.team - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSExceptionDomains - - localhost - - NSExceptionAllowsInsecureHTTPLoads - - - - - NSCameraUsageDescription - This app uses the camera to scan barcodes on event tickets. - NSMicrophoneUsageDescription - Allow $(PRODUCT_NAME) to access your microphone - NSPhotoLibraryAddUsageDescription - Allow $(PRODUCT_NAME) to save photos. - NSPhotoLibraryUsageDescription - Allow $(PRODUCT_NAME) to access your photos. - UILaunchStoryboardName - SplashScreen - UIRequiredDeviceCapabilities - - armv7 - - UIRequiresFullScreen - - UIStatusBarStyle - UIStatusBarStyleDefault - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIUserInterfaceStyle - Light - UIViewControllerBasedStatusBarAppearance - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Ever Teams + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 0.1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + co.ever.teams + + + + CFBundleVersion + 3 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSCameraUsageDescription + This app uses the camera to scan barcodes on event tickets. + NSMicrophoneUsageDescription + Allow $(PRODUCT_NAME) to access your microphone + NSPhotoLibraryAddUsageDescription + Allow $(PRODUCT_NAME) to save photos. + NSPhotoLibraryUsageDescription + Allow $(PRODUCT_NAME) to access your photos. + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + + diff --git a/apps/mobile/ios/GauzyTeams/Supporting/Expo.plist b/apps/mobile/ios/GauzyTeams/Supporting/Expo.plist index e27e06cb5..1b1bfc440 100644 --- a/apps/mobile/ios/GauzyTeams/Supporting/Expo.plist +++ b/apps/mobile/ios/GauzyTeams/Supporting/Expo.plist @@ -9,8 +9,8 @@ EXUpdatesLaunchWaitMs 0 EXUpdatesRuntimeVersion - 1.0.0 + exposdk:48.0.0 EXUpdatesURL - https://u.expo.dev/9f38f768-16f6-4c47-b354-9745ebda1335 + https://u.expo.dev/2ff924e4-7a91-4b23-9db9-7453a8063bb0 \ No newline at end of file diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 6f9a66c29..73c4e1c48 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -25,6 +25,8 @@ PODS: - ExpoModulesCore - ExpoBlur (12.2.2): - ExpoModulesCore + - ExpoClipboard (4.1.2): + - ExpoModulesCore - ExpoDevice (5.2.1): - ExpoModulesCore - ExpoImagePicker (14.1.1): @@ -445,10 +447,14 @@ PODS: - RNScreens (3.20.0): - React-Core - React-RCTImage + - RNSentry (4.15.2): + - React-Core + - Sentry/HybridSDK (= 7.31.5) - RNSVG (13.4.0): - React-Core - RNVectorIcons (9.2.0): - React-Core + - Sentry/HybridSDK (7.31.5) - Yoga (1.14.0) DEPENDENCIES: @@ -465,6 +471,7 @@ DEPENDENCIES: - EXMediaLibrary (from `../node_modules/expo-media-library/ios`) - Expo (from `../node_modules/expo`) - ExpoBlur (from `../node_modules/expo-blur/ios`) + - ExpoClipboard (from `../node_modules/expo-clipboard/ios`) - ExpoDevice (from `../node_modules/expo-device/ios`) - ExpoImagePicker (from `../node_modules/expo-image-picker/ios`) - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) @@ -515,6 +522,7 @@ DEPENDENCIES: - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) + - "RNSentry (from `../node_modules/@sentry/react-native`)" - RNSVG (from `../node_modules/react-native-svg`) - RNVectorIcons (from `../node_modules/react-native-vector-icons`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -524,6 +532,7 @@ SPEC REPOS: - ASN1Decoder - fmt - libevent + - Sentry EXTERNAL SOURCES: boost: @@ -552,6 +561,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo" ExpoBlur: :path: "../node_modules/expo-blur/ios" + ExpoClipboard: + :path: "../node_modules/expo-clipboard/ios" ExpoDevice: :path: "../node_modules/expo-device/ios" ExpoImagePicker: @@ -648,6 +659,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-reanimated" RNScreens: :path: "../node_modules/react-native-screens" + RNSentry: + :path: "../node_modules/@sentry/react-native" RNSVG: :path: "../node_modules/react-native-svg" RNVectorIcons: @@ -670,6 +683,7 @@ SPEC CHECKSUMS: EXMediaLibrary: 587cd8aad27a6fc8d7c38b950bc75bc1845a7480 Expo: 863488a600a4565698a79577117c70b170054d08 ExpoBlur: fac3c6318fdf409dd5740afd3313b2bd52a35bac + ExpoClipboard: 9b87df0ad145e3aa841614fab6aea54b7a604850 ExpoDevice: e0bebf68f978b3d353377ce42e73c20c0a070215 ExpoImagePicker: 270dea232b3a072d981dd564e2cafc63a864edb1 ExpoKeepAwake: 69f5f627670d62318410392d03e0b5db0f85759a @@ -720,8 +734,10 @@ SPEC CHECKSUMS: RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 RNReanimated: cc5e3aa479cb9170bcccf8204291a6950a3be128 RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f + RNSentry: 4ea90ea061624364637e8077d965bf2e50478066 RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2 RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 + Sentry: 4c9babff9034785067c896fd580b1f7de44da020 Yoga: 065f0b74dba4832d6e328238de46eb72c5de9556 PODFILE CHECKSUM: 3b35c72b749b61fbf59479bf07c13fa9d63dbdc8 diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 325e1326d..38ff3efcf 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -82,6 +82,7 @@ "react-native": "0.71.8", "react-native-animatable": "^1.3.3", "react-native-bootsplash": "4.5.3", + "react-native-calendars": "^1.1302.0", "react-native-circular-progress": "^1.3.8", "react-native-dotenv": "^3.4.8", "react-native-dropdown-picker": "^5.4.6", @@ -270,5 +271,9 @@ "react/prop-types": 0, "space-before-function-paren": 0 } + }, + "engines": { + "node": ">=16.0.0", + "yarn": ">=1.13.0" } } diff --git a/apps/mobile/yarn.lock b/apps/mobile/yarn.lock index 7bd0935e4..833bae03c 100644 --- a/apps/mobile/yarn.lock +++ b/apps/mobile/yarn.lock @@ -7761,7 +7761,7 @@ hoek@6.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -9554,7 +9554,7 @@ lodash-es@^4.2.1: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -lodash.debounce@^4.0.8: +lodash.debounce@4.0.8, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== @@ -9778,7 +9778,7 @@ memfs@^3.4.3: dependencies: fs-monkey "^1.0.3" -memoize-one@^5.0.0: +memoize-one@^5.0.0, memoize-one@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== @@ -12146,7 +12146,7 @@ prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@*, prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@*, prop-types@15.8.1, prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -12459,6 +12459,21 @@ react-native-bootsplash@4.5.3: picocolors "^1.0.0" sharp "^0.31.3" +react-native-calendars@^1.1302.0: + version "1.1302.0" + resolved "https://registry.yarnpkg.com/react-native-calendars/-/react-native-calendars-1.1302.0.tgz#1b81074d08a9aa5aadcd2fb546d08517d4974952" + integrity sha512-QZdkFYVKafxjc/oHmbmzyEhMkF0sWl+1hYd9FbKQFcf/c3D0K+sfG81A40C1YsOR8nxb1nq2OpsNT81CGd1L4Q== + dependencies: + hoist-non-react-statics "^3.3.1" + lodash "^4.17.15" + memoize-one "^5.2.1" + prop-types "^15.5.10" + react-native-swipe-gestures "^1.0.5" + recyclerlistview "^4.0.0" + xdate "^0.8.0" + optionalDependencies: + moment "^2.29.4" + react-native-circular-progress@^1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/react-native-circular-progress/-/react-native-circular-progress-1.3.8.tgz#84713b42286e4778aaaeecd7910953bb9de018ce" @@ -12583,6 +12598,11 @@ react-native-svg@13.4.0: css-select "^5.1.0" css-tree "^1.1.3" +react-native-swipe-gestures@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz#a172cb0f3e7478ccd681fd36b8bfbcdd098bde7c" + integrity sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw== + react-native-tab-view@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.5.2.tgz#2789b8af6148b16835869566bf13dc3b0e6c1b46" @@ -12840,6 +12860,15 @@ recast@^0.20.4: source-map "~0.6.1" tslib "^2.0.1" +recyclerlistview@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-4.2.0.tgz#a140149aaa470c9787a1426452651934240d69ef" + integrity sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A== + dependencies: + lodash.debounce "4.0.8" + prop-types "15.8.1" + ts-object-utils "0.0.5" + redux-logger@^2.7.4: version "2.10.2" resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-2.10.2.tgz#3c5a5f0a6f32577c1deadf6655f257f82c6c3937" @@ -14694,6 +14723,11 @@ ts-jest@^29.1.0: semver "^7.5.3" yargs-parser "^21.0.1" +ts-object-utils@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/ts-object-utils/-/ts-object-utils-0.0.5.tgz#95361cdecd7e52167cfc5e634c76345e90a26077" + integrity sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA== + tsconfig-paths@^3.14.1: version "3.14.2" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" @@ -15470,6 +15504,11 @@ xcode@3.0.1, xcode@^3.0.0, xcode@^3.0.1: simple-plist "^1.1.0" uuid "^7.0.3" +xdate@^0.8.0: + version "0.8.2" + resolved "https://registry.yarnpkg.com/xdate/-/xdate-0.8.2.tgz#d7b033c00485d02695baf0044f4eacda3fc961a3" + integrity sha512-sNBlLfOC8S3V0vLDEUianQOXcTsc9j4lfeKU/klHe0RjHAYn0CXsSttumTot8dzalboV8gZbH38B+WcCIBjhFQ== + xdl@^51.5.0: version "51.6.4" resolved "https://registry.yarnpkg.com/xdl/-/xdl-51.6.4.tgz#32abaf71be10426f81a98a43b0def2f39ad4590e" diff --git a/apps/web/.env b/apps/web/.env index 84b813c92..9bbae6394 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -2,7 +2,6 @@ RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false NEXT_PUBLIC_GAUZY_API_SERVER_URL=https://api.gauzy.co GAUZY_API_SERVER_URL=https://api.gauzy.co/api -INVITE_CALLBACK_URL=https://app.ever.team/passcode NEXT_PUBLIC_GA_MEASUREMENT_ID=SOME_MEASUREMENT_ID # CAPTCHA Settings @@ -29,7 +28,7 @@ NEXT_PUBLIC_DISABLE_AUTO_REFRESH=false # App Defaults APP_NAME="Ever Teams" APP_SIGNATURE="Ever Teams" -APP_LOGO_URL="https://app.ever.team/assets/gauzy-team.png" +APP_LOGO_URL="https://app.ever.team/assets/ever-teams.png" # Cookies NEXT_PUBLIC_COOKIE_DOMAINS=ever.team @@ -41,7 +40,7 @@ NEXT_PUBLIC_BOARD_FIREBASE_CONFIG= # Meet NEXT_PUBLIC_MEET_DOMAIN="meet.ever.team" -# Private Variables (Meet) +# Meet Private Vars MEET_JWT_APP_ID=ever_teams MEET_JWT_APP_SECRET= diff --git a/apps/web/.env.sample b/apps/web/.env.sample index 12cba1897..64988b851 100644 --- a/apps/web/.env.sample +++ b/apps/web/.env.sample @@ -2,12 +2,11 @@ RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false NEXT_PUBLIC_GAUZY_API_SERVER_URL=https://api.gauzy.co GAUZY_API_SERVER_URL=https://api.gauzy.co/api -INVITE_CALLBACK_URL=https://app.ever.team/passcode -NEXT_PUBLIC_GA_MEASUREMENT_ID=SOME_MEASUREMENT_ID +NEXT_PUBLIC_GA_MEASUREMENT_ID= # CAPTCHA Settings -NEXT_PUBLIC_CAPTCHA_SITE_KEY=SOME_CAPTCHA_SITE_KEY -CAPTCHA_SECRET_KEY=SOME_CAPTCHA_SECRET_KEY +NEXT_PUBLIC_CAPTCHA_SITE_KEY= +CAPTCHA_SECRET_KEY= # Invite Callback URL INVITE_CALLBACK_URL=https://app.ever.team/auth/passcode @@ -26,7 +25,7 @@ SMTP_PASSWORD= # App Defaults APP_NAME='Ever Teams' APP_SIGNATURE='Ever Teams' -APP_LOGO_URL='https://app.ever.team/assets/gauzy-team.png' +APP_LOGO_URL='https://app.ever.team/assets/ever-teams.png' # Cookies NEXT_PUBLIC_COOKIE_DOMAINS=ever.team diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts index 181e81743..66a737880 100644 --- a/apps/web/app/constants.ts +++ b/apps/web/app/constants.ts @@ -36,6 +36,7 @@ export const INVITE_CALLBACK_URL = process.env.INVITE_CALLBACK_URL; export const INVITE_CALLBACK_PATH = '/auth/passcode'; export const VERIFY_EMAIL_CALLBACK_URL = process.env.VERIFY_EMAIL_CALLBACK_URL; export const VERIFY_EMAIL_CALLBACK_PATH = '/verify-email'; +export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID; export const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS || 'noreply@ever.team'; export const SMTP_HOST = process.env.SMTP_HOST || ''; @@ -47,7 +48,7 @@ export const DISABLE_AUTO_REFRESH = process.env.NEXT_PUBLIC_DISABLE_AUTO_REFRESH 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/gauzy-team.png'; +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 CHARACTER_LIMIT_TO_SHOW = 20; diff --git a/apps/web/app/hooks/features/useTaskStatus.ts b/apps/web/app/hooks/features/useTaskStatus.ts index 2e0239d9a..a11495330 100644 --- a/apps/web/app/hooks/features/useTaskStatus.ts +++ b/apps/web/app/hooks/features/useTaskStatus.ts @@ -1,7 +1,7 @@ import { ITaskStatusCreate } from '@app/interfaces'; import { createTaskStatusAPI, - getTaskstatusList, + getTaskStatusList, deleteTaskStatusAPI, editTaskStatusAPI } from '@app/services/client/api'; @@ -17,7 +17,7 @@ export function useTaskStatus() { const [user] = useRecoilState(userState); const activeTeamId = useRecoilValue(activeTeamIdState); - const { loading, queryCall } = useQuery(getTaskstatusList); + const { loading, queryCall } = useQuery(getTaskStatusList); const { loading: createTaskStatusLoading, queryCall: createQueryCall } = useQuery(createTaskStatusAPI); const { loading: deleteTaskStatusLoading, queryCall: deleteQueryCall } = useQuery(deleteTaskStatusAPI); const { loading: editTaskStatusLoading, queryCall: editQueryCall } = useQuery(editTaskStatusAPI); diff --git a/apps/web/app/hooks/features/useTaskVersion.ts b/apps/web/app/hooks/features/useTaskVersion.ts index 0616243df..5e5c1958c 100644 --- a/apps/web/app/hooks/features/useTaskVersion.ts +++ b/apps/web/app/hooks/features/useTaskVersion.ts @@ -2,7 +2,7 @@ import { ITaskVersionCreate } from '@app/interfaces'; import { createTaskVersionAPI, - getTaskversionList, + getTaskVersionList, deleteTaskVersionAPI, editTaskVersionAPI } from '@app/services/client/api'; @@ -19,7 +19,7 @@ export function useTaskVersion(onVersionCreated?: (version: ITaskVersionCreate) const [user] = useRecoilState(userState); const activeTeamId = useRecoilValue(activeTeamIdState); - const { loading, queryCall } = useQuery(getTaskversionList); + const { loading, queryCall } = useQuery(getTaskVersionList); const { loading: createTaskVersionLoading, queryCall: createQueryCall } = useQuery(createTaskVersionAPI); const { loading: deleteTaskVersionLoading, queryCall: deleteQueryCall } = useQuery(deleteTaskVersionAPI); const { loading: editTaskVersionLoading, queryCall: editQueryCall } = useQuery(editTaskVersionAPI); diff --git a/apps/web/app/interfaces/IDataResponse.ts b/apps/web/app/interfaces/IDataResponse.ts index 2acdaa852..90d3154dd 100644 --- a/apps/web/app/interfaces/IDataResponse.ts +++ b/apps/web/app/interfaces/IDataResponse.ts @@ -16,12 +16,12 @@ export type PaginationResponse = { total: number; }; -export type DeleteReponse = { +export type DeleteResponse = { raw: string[]; affected: number; }; -export type CreateReponse = { +export type CreateResponse = { data: T; response: any; }; diff --git a/apps/web/app/services/client/api/email-reset.ts b/apps/web/app/services/client/api/email-reset.ts index 4c02f0be8..94e53c0cb 100644 --- a/apps/web/app/services/client/api/email-reset.ts +++ b/apps/web/app/services/client/api/email-reset.ts @@ -1,11 +1,11 @@ -import { CreateReponse, ISuccessResponse } from '@app/interfaces'; +import { CreateResponse, ISuccessResponse } from '@app/interfaces'; import api from '../axios'; export function emailResetRequestAPI(email: string) { - return api.post>(`/email-reset/request-change-email`, { + return api.post>(`/email-reset/request-change-email`, { email }); } export function verifyChangeEmailRequestAPI(code: string) { - return api.post>(`/email-reset/verify-change-email`, { code }); + return api.post>(`/email-reset/verify-change-email`, { code }); } diff --git a/apps/web/app/services/client/api/index.ts b/apps/web/app/services/client/api/index.ts index 91a6eb620..24d25385a 100644 --- a/apps/web/app/services/client/api/index.ts +++ b/apps/web/app/services/client/api/index.ts @@ -7,7 +7,7 @@ export * from './email-reset'; export * from './languages'; -export * from './taskStatus'; +export * from './task-status'; export * from './task-version'; export * from './task-priorities'; export * from './task-sizes'; diff --git a/apps/web/app/services/client/api/integrations/github.ts b/apps/web/app/services/client/api/integrations/github.ts index 60617f389..8d2777bf0 100644 --- a/apps/web/app/services/client/api/integrations/github.ts +++ b/apps/web/app/services/client/api/integrations/github.ts @@ -1,4 +1,4 @@ -import { CreateReponse, IGithubMetadata, IGithubRepositories } from '@app/interfaces'; +import { CreateResponse, IGithubMetadata, IGithubRepositories } from '@app/interfaces'; import api from '../../axios'; // TODO @@ -12,10 +12,10 @@ export function oAuthEndpointAuthorizationAPI(body: any) { } export function getGithubIntegrationMetadataAPI(integrationId: string) { - return api.get>(`/integration/github/metadata?integrationId=${integrationId}`); + return api.get>(`/integration/github/metadata?integrationId=${integrationId}`); } export function getGithubIntegrationRepositoriesAPI(integrationId: string) { - return api.get>( + return api.get>( `/integration/github/repositories?integrationId=${integrationId}` ); } diff --git a/apps/web/app/services/client/api/integrations/index.ts b/apps/web/app/services/client/api/integrations/index.ts index e0109dfd8..be3eaf20c 100644 --- a/apps/web/app/services/client/api/integrations/index.ts +++ b/apps/web/app/services/client/api/integrations/index.ts @@ -1,8 +1,8 @@ -import { CreateReponse, IIntegration } from '@app/interfaces'; +import { CreateResponse, IIntegration } from '@app/interfaces'; import api from '../../axios'; export function getIntegrationAPI(integrationTypeId: string, searchQuery = '') { - return api.get>( + return api.get>( `/integration?integrationTypeId=${integrationTypeId}&searchQuery=${searchQuery}` ); } diff --git a/apps/web/app/services/client/api/integrations/integration-tenant.ts b/apps/web/app/services/client/api/integrations/integration-tenant.ts index c8e8b1649..2efd0651b 100644 --- a/apps/web/app/services/client/api/integrations/integration-tenant.ts +++ b/apps/web/app/services/client/api/integrations/integration-tenant.ts @@ -1,8 +1,8 @@ -import { IIntegrationTenant, PaginationResponse, CreateReponse } from '@app/interfaces'; +import { IIntegrationTenant, PaginationResponse, CreateResponse } from '@app/interfaces'; import api from '../../axios'; export function getIntegrationTenantAPI(name: string) { - return api.get>>( + return api.get>>( `/integration-tenant/remember/state?name=${name}` ); } diff --git a/apps/web/app/services/client/api/integrations/types.ts b/apps/web/app/services/client/api/integrations/types.ts index 4ebc312c2..5532b170c 100644 --- a/apps/web/app/services/client/api/integrations/types.ts +++ b/apps/web/app/services/client/api/integrations/types.ts @@ -1,6 +1,6 @@ -import { CreateReponse, IIntegrationType } from '@app/interfaces'; +import { CreateResponse, IIntegrationType } from '@app/interfaces'; import api from '../../axios'; export function getIntegrationTypesAPI() { - return api.get>(`/integration/types`); + return api.get>(`/integration/types`); } diff --git a/apps/web/app/services/client/api/invite.ts b/apps/web/app/services/client/api/invite.ts index d603dbd73..7764b1c03 100644 --- a/apps/web/app/services/client/api/invite.ts +++ b/apps/web/app/services/client/api/invite.ts @@ -1,5 +1,5 @@ import { PaginationResponse } from '@app/interfaces/IDataResponse'; -import { IInvitation, IInviteRequest, IMyInvitations, MyInvitationActionEnum, CreateReponse } from '@app/interfaces'; +import { IInvitation, IInviteRequest, IMyInvitations, MyInvitationActionEnum, CreateResponse } from '@app/interfaces'; import api from '../axios'; export function inviteByEmailsAPI(data: IInviteRequest) { @@ -25,5 +25,5 @@ export function getMyInvitationsAPI() { } export function acceptRejectMyInvitationsAPI(invitationId: string, action: MyInvitationActionEnum) { - return api.put>(`/invite/${invitationId}?action=${action}`); + return api.put>(`/invite/${invitationId}?action=${action}`); } diff --git a/apps/web/app/services/client/api/issue-type.ts b/apps/web/app/services/client/api/issue-type.ts index 0e371ad00..709088ff2 100644 --- a/apps/web/app/services/client/api/issue-type.ts +++ b/apps/web/app/services/client/api/issue-type.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, IIssueTypesCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, IIssueTypesCreate } from '@app/interfaces'; import api from '../axios'; export function createIssueTypeAPI(data: IIssueTypesCreate, tenantId?: string) { - return api.post>('/issue-types', data, { + return api.post>('/issue-types', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createIssueTypeAPI(data: IIssueTypesCreate, tenantId?: string) { } export function editIssueTypeAPI(id: string, data: IIssueTypesCreate, tenantId?: string) { - return api.put>(`/issue-types/${id}`, data, { + return api.put>(`/issue-types/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,7 +18,7 @@ export function editIssueTypeAPI(id: string, data: IIssueTypesCreate, tenantId?: } export function deleteIssueTypeAPI(id: string) { - return api.delete(`/issue-types/${id}`); + return api.delete(`/issue-types/${id}`); } export function getIssueTypeList(tenantId: string, organizationId: string, activeTeamId: string | null) { diff --git a/apps/web/app/services/client/api/languages.ts b/apps/web/app/services/client/api/languages.ts index 1f708bd5a..e07c98399 100644 --- a/apps/web/app/services/client/api/languages.ts +++ b/apps/web/app/services/client/api/languages.ts @@ -1,6 +1,6 @@ -import { ILanguageItemList, CreateReponse, PaginationResponse } from '@app/interfaces'; +import { ILanguageItemList, CreateResponse, PaginationResponse } from '@app/interfaces'; import api from '../axios'; export function getLanguageListAPI(is_system: boolean) { - return api.get>>(`/languages?is_system=${is_system}`); + return api.get>>(`/languages?is_system=${is_system}`); } diff --git a/apps/web/app/services/client/api/organization-team-employee.ts b/apps/web/app/services/client/api/organization-team-employee.ts index 73965958f..e14328d1a 100644 --- a/apps/web/app/services/client/api/organization-team-employee.ts +++ b/apps/web/app/services/client/api/organization-team-employee.ts @@ -1,5 +1,5 @@ import { IOrganizationTeamEmployeeUpdate } from '@app/interfaces'; -import { CreateReponse } from '@app/interfaces/IDataResponse'; +import { CreateResponse } from '@app/interfaces/IDataResponse'; import { IOrganizationTeam } from '@app/interfaces/IOrganizationTeam'; import api from '../axios'; @@ -14,20 +14,20 @@ export function deleteOrganizationEmployeeTeamAPI({ organizationId: string; tenantId: string; }) { - return api.delete>( + return api.delete>( `/organization-team-employee/${id}?tenantId=${tenantId}&employeeId=${employeeId}&organizationId=${organizationId}` ); } export function updateOrganizationEmployeeTeamAPI(id: string, data: Partial) { - return api.put>(`/organization-team-employee/${id}`, data); + return api.put>(`/organization-team-employee/${id}`, data); } export function updateOrganizationTeamEmployeeActiveTaskAPI( id: string, data: Partial ) { - return api.put>( + return api.put>( `/organization-team-employee/${id}/active-task`, data ); diff --git a/apps/web/app/services/client/api/organization-team.ts b/apps/web/app/services/client/api/organization-team.ts index 0796fb08e..c50bf9ce5 100644 --- a/apps/web/app/services/client/api/organization-team.ts +++ b/apps/web/app/services/client/api/organization-team.ts @@ -1,4 +1,4 @@ -import { CreateReponse, DeleteReponse, ISuccessResponse, PaginationResponse } from '@app/interfaces/IDataResponse'; +import { CreateResponse, DeleteResponse, ISuccessResponse, PaginationResponse } from '@app/interfaces/IDataResponse'; import { IOrganizationTeamList, @@ -28,12 +28,12 @@ export function updateOrganizationTeamAPI(teamId: string, data: Partial>(`/organization-team/${id}`); + return api.delete>(`/organization-team/${id}`); } export function removeEmployeeOrganizationTeamAPI(employeeId: string) { return api.delete(`/organization-team/employee/${employeeId}`); } export function removeUserFromAllTeamAPI(userId: string) { - return api.delete>(`/organization-team/teams/${userId}`); + return api.delete>(`/organization-team/teams/${userId}`); } diff --git a/apps/web/app/services/client/api/public-organization-team.ts b/apps/web/app/services/client/api/public-organization-team.ts index 3f5e31103..cdef52033 100644 --- a/apps/web/app/services/client/api/public-organization-team.ts +++ b/apps/web/app/services/client/api/public-organization-team.ts @@ -1,14 +1,14 @@ -import { IOrganizationTeamList, CreateReponse, IDataResponse } from '@app/interfaces'; +import { IOrganizationTeamList, CreateResponse, IDataResponse } from '@app/interfaces'; import api from '../axios'; export function getPublicOrganizationTeamsAPI(profile_link: string, team_id: string) { - return api.get | IDataResponse>( + return api.get | IDataResponse>( `/public/team/${profile_link}/${team_id}?type=team` ); } export function getPublicOrganizationTeamsMiscDataAPI(profile_link: string, team_id: string) { - return api.get | IDataResponse>( + return api.get | IDataResponse>( `/public/team/${profile_link}/${team_id}?type=misc` ); } diff --git a/apps/web/app/services/client/api/request-to-join-team.ts b/apps/web/app/services/client/api/request-to-join-team.ts index b50e9329b..9a77927f4 100644 --- a/apps/web/app/services/client/api/request-to-join-team.ts +++ b/apps/web/app/services/client/api/request-to-join-team.ts @@ -4,18 +4,18 @@ import { IDataResponse, ISuccessResponse, IValidateRequestToJoin, - CreateReponse, + CreateResponse, PaginationResponse, IRequestToJoinActionEnum } from '@app/interfaces'; import api from '../axios'; export function requestToJoinAPI(data: IRequestToJoinCreate) { - return api.post>('/organization-team-join', data); + return api.post>('/organization-team-join', data); } export function validateRequestToJoinAPI(data: IValidateRequestToJoin) { - return api.post>>( + return api.post>>( '/organization-team-join/validate', data ); diff --git a/apps/web/app/services/client/api/task-labels.ts b/apps/web/app/services/client/api/task-labels.ts index 101a636a0..6aa94c6c6 100644 --- a/apps/web/app/services/client/api/task-labels.ts +++ b/apps/web/app/services/client/api/task-labels.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, ITaskLabelsCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, ITaskLabelsCreate } from '@app/interfaces'; import api from '../axios'; export function createTaskLabelsAPI(data: ITaskLabelsCreate, tenantId?: string) { - return api.post>('/tags', data, { + return api.post>('/tags', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createTaskLabelsAPI(data: ITaskLabelsCreate, tenantId?: string) } export function editTaskLabelsAPI(id: string, data: ITaskLabelsCreate, tenantId?: string) { - return api.put>(`/tags/${id}`, data, { + return api.put>(`/tags/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,7 +18,7 @@ export function editTaskLabelsAPI(id: string, data: ITaskLabelsCreate, tenantId? } export function deleteTaskLabelsAPI(id: string) { - return api.delete(`/tags/${id}`); + return api.delete(`/tags/${id}`); } export function getTaskLabelsList(tenantId: string, organizationId: string, activeTeamId: string | null) { diff --git a/apps/web/app/services/client/api/task-priorities.ts b/apps/web/app/services/client/api/task-priorities.ts index 31c8ea619..9c299cb4d 100644 --- a/apps/web/app/services/client/api/task-priorities.ts +++ b/apps/web/app/services/client/api/task-priorities.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, ITaskPrioritiesCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, ITaskPrioritiesCreate } from '@app/interfaces'; import api from '../axios'; export function createTaskPrioritiesAPI(data: ITaskPrioritiesCreate, tenantId?: string) { - return api.post>('/task-priorities', data, { + return api.post>('/task-priorities', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createTaskPrioritiesAPI(data: ITaskPrioritiesCreate, tenantId?: } export function editTaskPrioritiesAPI(id: string, data: ITaskPrioritiesCreate, tenantId?: string) { - return api.put>(`/task-priorities/${id}`, data, { + return api.put>(`/task-priorities/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,7 +18,7 @@ export function editTaskPrioritiesAPI(id: string, data: ITaskPrioritiesCreate, t } export function deleteTaskPrioritiesAPI(id: string) { - return api.delete(`/task-priorities/${id}`); + return api.delete(`/task-priorities/${id}`); } export function getTaskPrioritiesList(tenantId: string, organizationId: string, activeTeamId: string | null) { diff --git a/apps/web/app/services/client/api/task-related-issue-type.ts b/apps/web/app/services/client/api/task-related-issue-type.ts index aa5a7a8c9..155e6424d 100644 --- a/apps/web/app/services/client/api/task-related-issue-type.ts +++ b/apps/web/app/services/client/api/task-related-issue-type.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, ITaskRelatedIssueTypeCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, ITaskRelatedIssueTypeCreate } from '@app/interfaces'; import api from '../axios'; export function createTaskRelatedIssueTypeAPI(data: ITaskRelatedIssueTypeCreate, tenantId?: string) { - return api.post>('/task-related-issue-types', data, { + return api.post>('/task-related-issue-types', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createTaskRelatedIssueTypeAPI(data: ITaskRelatedIssueTypeCreate, } export function editTaskRelatedIssueTypeAPI(id: string, data: ITaskRelatedIssueTypeCreate, tenantId?: string) { - return api.put>(`/task-related-issue-types/${id}`, data, { + return api.put>(`/task-related-issue-types/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,7 +18,7 @@ export function editTaskRelatedIssueTypeAPI(id: string, data: ITaskRelatedIssueT } export function deleteTaskRelatedIssueTypeAPI(id: string) { - return api.delete(`/task-related-issue-types/${id}`); + return api.delete(`/task-related-issue-types/${id}`); } export function getTaskRelatedIssueTypeList(tenantId: string, organizationId: string, activeTeamId: string | null) { diff --git a/apps/web/app/services/client/api/task-sizes.ts b/apps/web/app/services/client/api/task-sizes.ts index 70e933abb..15d4a5425 100644 --- a/apps/web/app/services/client/api/task-sizes.ts +++ b/apps/web/app/services/client/api/task-sizes.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, ITaskSizesCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, ITaskSizesCreate } from '@app/interfaces'; import api from '../axios'; export function createTaskSizesAPI(data: ITaskSizesCreate, tenantId?: string) { - return api.post>('/task-sizes', data, { + return api.post>('/task-sizes', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createTaskSizesAPI(data: ITaskSizesCreate, tenantId?: string) { } export function editTaskSizesAPI(id: string, data: ITaskSizesCreate, tenantId?: string) { - return api.put>(`/task-sizes/${id}`, data, { + return api.put>(`/task-sizes/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,7 +18,7 @@ export function editTaskSizesAPI(id: string, data: ITaskSizesCreate, tenantId?: } export function deleteTaskSizesAPI(id: string) { - return api.delete(`/task-sizes/${id}`); + return api.delete(`/task-sizes/${id}`); } export function getTaskSizesList(tenantId: string, organizationId: string, activeTeamId: string | null) { diff --git a/apps/web/app/services/client/api/taskStatus.ts b/apps/web/app/services/client/api/task-status.ts similarity index 58% rename from apps/web/app/services/client/api/taskStatus.ts rename to apps/web/app/services/client/api/task-status.ts index 1d119227b..4dafd1542 100644 --- a/apps/web/app/services/client/api/taskStatus.ts +++ b/apps/web/app/services/client/api/task-status.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, ITaskStatusCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, ITaskStatusCreate } from '@app/interfaces'; import api from '../axios'; export function createTaskStatusAPI(data: ITaskStatusCreate, tenantId?: string) { - return api.post>('/task-statuses', data, { + return api.post>('/task-statuses', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createTaskStatusAPI(data: ITaskStatusCreate, tenantId?: string) } export function editTaskStatusAPI(id: string, data: ITaskStatusCreate, tenantId?: string) { - return api.put>(`/task-statuses/${id}`, data, { + return api.put>(`/task-statuses/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,9 +18,9 @@ export function editTaskStatusAPI(id: string, data: ITaskStatusCreate, tenantId? } export function deleteTaskStatusAPI(id: string) { - return api.delete(`/task-statuses/${id}`); + return api.delete(`/task-statuses/${id}`); } -export function getTaskstatusList(tenantId: string, organizationId: string, activeTeamId: string | null) { +export function getTaskStatusList(tenantId: string, organizationId: string, activeTeamId: string | null) { return api.get(`/task-statuses?tenantId=${tenantId}&organizationId=${organizationId}&activeTeamId=${activeTeamId}`); } diff --git a/apps/web/app/services/client/api/task-version.ts b/apps/web/app/services/client/api/task-version.ts index ba6afa9f3..34e67aad8 100644 --- a/apps/web/app/services/client/api/task-version.ts +++ b/apps/web/app/services/client/api/task-version.ts @@ -1,8 +1,8 @@ -import { CreateReponse, DeleteReponse, ITaskVersionCreate } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, ITaskVersionCreate } from '@app/interfaces'; import api from '../axios'; export function createTaskVersionAPI(data: ITaskVersionCreate, tenantId?: string) { - return api.post>('/task-versions', data, { + return api.post>('/task-versions', data, { headers: { 'Tenant-Id': tenantId } @@ -10,7 +10,7 @@ export function createTaskVersionAPI(data: ITaskVersionCreate, tenantId?: string } export function editTaskVersionAPI(id: string, data: ITaskVersionCreate, tenantId?: string) { - return api.put>(`/task-versions/${id}`, data, { + return api.put>(`/task-versions/${id}`, data, { headers: { 'Tenant-Id': tenantId } @@ -18,9 +18,9 @@ export function editTaskVersionAPI(id: string, data: ITaskVersionCreate, tenantI } export function deleteTaskVersionAPI(id: string) { - return api.delete(`/task-versions/${id}`); + return api.delete(`/task-versions/${id}`); } -export function getTaskversionList(tenantId: string, organizationId: string, activeTeamId: string | null) { +export function getTaskVersionList(tenantId: string, organizationId: string, activeTeamId: string | null) { return api.get(`/task-versions?tenantId=${tenantId}&organizationId=${organizationId}&activeTeamId=${activeTeamId}`); } diff --git a/apps/web/app/services/client/api/tasks.ts b/apps/web/app/services/client/api/tasks.ts index 7af06fad8..d8fedf87d 100644 --- a/apps/web/app/services/client/api/tasks.ts +++ b/apps/web/app/services/client/api/tasks.ts @@ -1,10 +1,10 @@ -import { CreateReponse, DeleteReponse, PaginationResponse } from '@app/interfaces/IDataResponse'; +import { CreateResponse, DeleteResponse, PaginationResponse } from '@app/interfaces/IDataResponse'; import { ICreateTask, ITeamTask } from '@app/interfaces/ITask'; import { ITasksTimesheet } from '@app/interfaces/ITimer'; import api from '../axios'; export function getTasksByIdAPI(taskId: string) { - return api.get>(`/tasks/${taskId}`); + return api.get>(`/tasks/${taskId}`); } export function getTeamTasksAPI() { @@ -12,7 +12,7 @@ export function getTeamTasksAPI() { } export function deleteTaskAPI(taskId: string) { - return api.delete(`/tasks/${taskId}`); + return api.delete(`/tasks/${taskId}`); } export function updateTaskAPI(taskId: string, body: Partial) { @@ -40,5 +40,5 @@ export function allTaskTimesheetStatisticsAPI() { } export function deleteEmployeeFromTasksAPI(employeeId: string, organizationTeamId: string) { - return api.delete(`/tasks/employee/${employeeId}?organizationTeamId=${organizationTeamId}`); + return api.delete(`/tasks/employee/${employeeId}?organizationTeamId=${organizationTeamId}`); } diff --git a/apps/web/app/services/client/api/user.ts b/apps/web/app/services/client/api/user.ts index d8cd3b879..b878f67ba 100644 --- a/apps/web/app/services/client/api/user.ts +++ b/apps/web/app/services/client/api/user.ts @@ -1,10 +1,10 @@ -import { DeleteReponse } from '@app/interfaces'; +import { DeleteResponse } from '@app/interfaces'; import api from '../axios'; export function deleteUserAPI(id: string) { - return api.delete(`/user/${id}`); + return api.delete(`/user/${id}`); } export function resetUserAPI() { - return api.delete(`/user/reset`); + return api.delete(`/user/reset`); } diff --git a/apps/web/app/services/server/requests/tasks.ts b/apps/web/app/services/server/requests/tasks.ts index 5f7ab87e0..a255518fe 100644 --- a/apps/web/app/services/server/requests/tasks.ts +++ b/apps/web/app/services/server/requests/tasks.ts @@ -1,4 +1,4 @@ -import { CreateReponse, DeleteReponse, PaginationResponse, SingleDataResponse } from '@app/interfaces'; +import { CreateResponse, DeleteResponse, PaginationResponse, SingleDataResponse } from '@app/interfaces'; import { ICreateTask, ITeamTask } from '@app/interfaces/ITask'; import { serverFetch } from '../fetch'; import { IUser } from '@app/interfaces'; @@ -6,8 +6,8 @@ import { IUser } from '@app/interfaces'; export function getTeamTasksRequest({ tenantId, organizationId, - // TODO - // projectId, + projectId, + teamId, bearer_token, relations = [ 'tags', @@ -27,15 +27,16 @@ export function getTeamTasksRequest({ bearer_token: string; relations?: string[]; projectId?: string; + teamId: string; }) { const obj = { 'where[organizationId]': organizationId, 'where[tenantId]': tenantId, - // TODO - // 'where[projectId]': projectId, + 'where[projectId]': projectId, 'join[alias]': 'task', 'join[leftJoinAndSelect][members]': 'task.members', - 'join[leftJoinAndSelect][user]': 'members.user' + 'join[leftJoinAndSelect][user]': 'members.user', + 'where[teams][0]': teamId } as Record; relations.forEach((rl, i) => { @@ -91,7 +92,7 @@ export function getTaskByIdRequest({ const query = new URLSearchParams(obj); - return serverFetch>({ + return serverFetch>({ path: `/tasks/${taskId}?${query.toString()}`, method: 'GET', bearer_token, @@ -108,7 +109,7 @@ export function deleteTaskRequest({ taskId: string; bearer_token: string; }) { - return serverFetch({ + return serverFetch({ path: `/tasks/${taskId}?tenantId=${tenantId}`, method: 'DELETE', bearer_token, @@ -153,7 +154,7 @@ export function deleteEmployeeFromTasksRequest({ organizationTeamId: string; bearer_token: string; }) { - return serverFetch({ + return serverFetch({ path: `/tasks/employee/${employeeId}?organizationTeamId=${organizationTeamId}`, method: 'DELETE', bearer_token, diff --git a/apps/web/app/stores/team-tasks.ts b/apps/web/app/stores/team-tasks.ts index fcd722cee..cdcd41cdc 100644 --- a/apps/web/app/stores/team-tasks.ts +++ b/apps/web/app/stores/team-tasks.ts @@ -2,7 +2,6 @@ import moment from 'moment'; import { ITeamTask } from '@app/interfaces/ITask'; import { ITasksTimesheet } from '@app/interfaces/ITimer'; import { atom, selector } from 'recoil'; -import { activeTeamState } from './organization-team'; export const teamTasksState = atom({ key: 'teamTasksState', @@ -28,13 +27,10 @@ export const tasksByTeamState = selector({ key: 'tasksByTeamState', get: ({ get }) => { const tasks = get(teamTasksState); - const activeTeam = get(activeTeamState); return tasks - .filter((task) => { - return task.teams.some((tm) => { - return tm.id === activeTeam?.id; - }); + .filter(() => { + return true }) .sort((a, b) => moment(b.createdAt).diff(a.createdAt)); } diff --git a/apps/web/lib/features/team-members-card-view.tsx b/apps/web/lib/features/team-members-card-view.tsx index 56bf65055..c69fff7c7 100644 --- a/apps/web/lib/features/team-members-card-view.tsx +++ b/apps/web/lib/features/team-members-card-view.tsx @@ -1,4 +1,4 @@ -import { useAuthenticateUser, useModal, useOrganizationTeams, useTeamInvitations } from '@app/hooks'; +import { useAuthenticateUser, useModal, useTeamInvitations } from '@app/hooks'; import { Transition } from '@headlessui/react'; import { InviteFormModal } from './team/invite/invite-form-modal'; import { InvitedCard, InviteUserTeamCard } from './team/invite/user-invite-card'; @@ -9,13 +9,19 @@ interface Props { teamMembers: OT_Member[]; publicTeam: boolean; currentUser: OT_Member | undefined; + teamsFetching: boolean; } -const TeamMembersCardView: React.FC = ({ teamMembers: members, currentUser, publicTeam = false }) => { +const TeamMembersCardView: React.FC = ({ + teamMembers: members, + currentUser, + teamsFetching = false, + publicTeam = false +}) => { const { isTeamManager } = useAuthenticateUser(); - const { teamsFetching } = useOrganizationTeams(); + const { teamInvitations } = useTeamInvitations(); - const $teamsFetching = teamsFetching && members.length === 0; + return (
    {/* Current authenticated user members */} @@ -62,7 +68,7 @@ const TeamMembersCardView: React.FC = ({ teamMembers: members, currentUse {/* Loader skeleton */} m.employee.userId === user?.id); const $members = members.filter((member) => member.id !== currentUser?.id); + const $teamsFetching = teamsFetching && members.length === 0; let teamMembersView; @@ -27,7 +27,7 @@ export function TeamMembers({ publicTeam = false, kabanView = IssuesView.CARDS } case members.length === 0: teamMembersView = (
    -
    +
    @@ -38,12 +38,17 @@ export function TeamMembers({ publicTeam = false, kabanView = IssuesView.CARDS }
    ); break; - case kabanView === IssuesView.CARDS: + case kanbanView === IssuesView.CARDS: teamMembersView = ( - + ); break; - case kabanView === IssuesView.TABLE: + case kanbanView === IssuesView.TABLE: teamMembersView = ( + ); } return teamMembersView; diff --git a/apps/web/lib/i18n/en.ts b/apps/web/lib/i18n/en.ts index cd910228a..ec736c37e 100644 --- a/apps/web/lib/i18n/en.ts +++ b/apps/web/lib/i18n/en.ts @@ -366,7 +366,7 @@ export const en = { layout: { footer: { - RIGHTS_RESERVERD: 'All rights reserved.' + RIGHTS_RESERVED: 'All rights reserved.' } }, diff --git a/apps/web/lib/layout/footer.tsx b/apps/web/lib/layout/footer.tsx index eb3c2a6a4..e2a65f5c4 100644 --- a/apps/web/lib/layout/footer.tsx +++ b/apps/web/lib/layout/footer.tsx @@ -12,7 +12,7 @@ export function Footer({ className }: IClassName) { {t('layout.footer.COPY_RIGHT1', { date: new Date().getFullYear() })}{' '} {t('TITLE')} {t('layout.footer.BY')}{' '} {t('layout.footer.COPY_RIGHT4')}{' '} - {t('layout.footer.RIGHTS_RESERVERD')} + {t('layout.footer.RIGHTS_RESERVED')}

    diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 4f1860fd8..b546ab890 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -3,6 +3,7 @@ const path = require('path'); // eslint-disable-next-line @typescript-eslint/no-var-requires /** @type {import('next').NextConfig} */ const nextConfig = { + output: process.env.NEXT_BUILD_OUTPUT_TYPE === 'standalone' ? 'standalone' : undefined, reactStrictMode: true, swcMinify: true, webpack: (config, { isServer }) => { diff --git a/apps/web/package.json b/apps/web/package.json index 30289d52c..4667c58cd 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -101,5 +101,9 @@ "eslint": "^8.28.0", "eslint-config-next": "^13.1.6", "typescript": "^4.9.4" + }, + "engines": { + "node": ">=16.0.0", + "yarn": ">=1.13.0" } } diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx index 5e517c059..0898e2479 100644 --- a/apps/web/pages/_app.tsx +++ b/apps/web/pages/_app.tsx @@ -1,8 +1,7 @@ /* eslint-disable no-mixed-spaces-and-tabs */ import 'react-loading-skeleton/dist/skeleton.css'; import '../styles/globals.css'; - -import { jitsuConfiguration } from '@app/constants'; +import { GA_MEASUREMENT_ID, jitsuConfiguration } from '@app/constants'; import { JitsuProvider } from '@jitsu/jitsu-react'; import { Analytics } from '@vercel/analytics/react'; import { AppState } from 'lib/app/init-state'; @@ -16,20 +15,27 @@ import { SkeletonTheme } from 'react-loading-skeleton'; import { RecoilRoot } from 'recoil'; import { JitsuAnalytics } from '../lib/components/services/jitsu-analytics'; import i18n from '../ni18n.config'; + const MyApp = ({ Component, pageProps }: AppProps) => { const isJitsuEnvsPresent = jitsuConfiguration.host && jitsuConfiguration.writeKey; return ( <> - + + {GA_MEASUREMENT_ID && ( + <> + + + )} + diff --git a/apps/web/pages/api/tasks/[id].ts b/apps/web/pages/api/tasks/[id].ts index b36f8fd87..87b475407 100644 --- a/apps/web/pages/api/tasks/[id].ts +++ b/apps/web/pages/api/tasks/[id].ts @@ -4,7 +4,7 @@ import { getTeamTasksRequest, updateTaskRequest, getTaskByIdRequest } from '@app import { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { $res, user, tenantId, access_token, organizationId } = await authenticatedGuard(req, res); + const { $res, user, tenantId, access_token, organizationId, projectId, teamId} = await authenticatedGuard(req, res); if (!user) return $res(); const { id: taskId } = req.query; @@ -38,6 +38,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const { data: tasks } = await getTeamTasksRequest({ tenantId, organizationId, + projectId, + teamId, bearer_token: access_token }); diff --git a/apps/web/pages/api/tasks/team.ts b/apps/web/pages/api/tasks/team.ts index 49bd43bc9..afd4ab9dc 100644 --- a/apps/web/pages/api/tasks/team.ts +++ b/apps/web/pages/api/tasks/team.ts @@ -4,7 +4,7 @@ import { createTaskRequest, getTeamTasksRequest } from '@app/services/server/req import { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { $res, user, tenantId, organizationId, access_token, projectId } = await authenticatedGuard(req, res); + const { $res, user, tenantId, organizationId, access_token, projectId, teamId } = await authenticatedGuard(req, res); if (!user) return $res(); if (req.method === 'POST') { @@ -42,6 +42,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) tenantId, organizationId, projectId, + teamId, bearer_token: access_token }); diff --git a/apps/web/pages/index.tsx b/apps/web/pages/index.tsx index 73d058e71..78c95ed4b 100644 --- a/apps/web/pages/index.tsx +++ b/apps/web/pages/index.tsx @@ -79,7 +79,7 @@ function MainPage() {
    - {isTeamMember ? : } + {isTeamMember ? : } ); } diff --git a/apps/web/public/assets/ever-teams.png b/apps/web/public/assets/ever-teams.png new file mode 100644 index 000000000..5beaf9fea Binary files /dev/null and b/apps/web/public/assets/ever-teams.png differ diff --git a/apps/web/public/assets/gauzy-team.png b/apps/web/public/assets/gauzy-team.png deleted file mode 100644 index a939cac8f..000000000 Binary files a/apps/web/public/assets/gauzy-team.png and /dev/null differ diff --git a/apps/web/public/locales/ar/common.json b/apps/web/public/locales/ar/common.json index 4c627d09a..f89afdad2 100644 --- a/apps/web/public/locales/ar/common.json +++ b/apps/web/public/locales/ar/common.json @@ -494,11 +494,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "جميع الحقوق محفوظة.", + "RIGHTS_RESERVED": "جميع الحقوق محفوظة.", "COPY_RIGHT1": "© {{date}}-حاضر،", - "COPY_RIGHT2": "إيفر تيمز", - "COPY_RIGHT3": "فِرق جوزي", - + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "إيفر ش.م.م.", "COMPANY_NAME": "إيفر ش.م.م.", "TERMS": "شروط الخدمة", diff --git a/apps/web/public/locales/bg/common.json b/apps/web/public/locales/bg/common.json index 96a7b498a..35cbd8ebe 100644 --- a/apps/web/public/locales/bg/common.json +++ b/apps/web/public/locales/bg/common.json @@ -499,10 +499,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "Всички права запазени.", + "RIGHTS_RESERVED": "Всички права запазени.", "COPY_RIGHT1": "© {{date}}-настояще,", "COPY_RIGHT2": "Ever Teams", - "COPY_RIGHT3": "Gauzy Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Общи условия", diff --git a/apps/web/public/locales/de/common.json b/apps/web/public/locales/de/common.json index 37c035cd9..05697e933 100644 --- a/apps/web/public/locales/de/common.json +++ b/apps/web/public/locales/de/common.json @@ -488,10 +488,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "Alle Rechte vorbehalten.", + "RIGHTS_RESERVED": "Alle Rechte vorbehalten.", "COPY_RIGHT1": "© {{date}}-Gegenwart,", "COPY_RIGHT2": "Ever Teams", - "COPY_RIGHT3": "Gauzy Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Nutzungsbedingungen", diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json index 6f7cf20b4..657f3e9ea 100644 --- a/apps/web/public/locales/en/common.json +++ b/apps/web/public/locales/en/common.json @@ -489,10 +489,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "All rights reserved.", + "RIGHTS_RESERVED": "All rights reserved.", "COPY_RIGHT1": "© {{date}}-Present,", - "COPY_RIGHT2": "Ever Teams ", - "COPY_RIGHT3": "Gauzy Teams", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Terms of Service", diff --git a/apps/web/public/locales/es/common.json b/apps/web/public/locales/es/common.json index 1f3b2701d..50eedaa10 100644 --- a/apps/web/public/locales/es/common.json +++ b/apps/web/public/locales/es/common.json @@ -473,10 +473,9 @@ }, "layout": { "footer": { - "RIGHTS_RESERVERD": "Todos los derechos reservados.", + "RIGHTS_RESERVED": "Todos los derechos reservados.", "COPY_RIGHT1": "© {{date}}-Presente,", - "COPY_RIGHT2": "Equipos Siempre ", - "COPY_RIGHT3": "Equipos Gauzy", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Términos del servicio", diff --git a/apps/web/public/locales/fr/common.json b/apps/web/public/locales/fr/common.json index 5f77ba207..863e2384c 100644 --- a/apps/web/public/locales/fr/common.json +++ b/apps/web/public/locales/fr/common.json @@ -478,10 +478,9 @@ }, "layout": { "footer": { - "RIGHTS_RESERVERD": "Tous droits réservés.", + "RIGHTS_RESERVED": "Tous droits réservés.", "COPY_RIGHT1": "© {{date}} à nos jours,", - "COPY_RIGHT2": "Ever Teams ", - "COPY_RIGHT3": "Gauzy Teams", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Conditions d'utilisation", diff --git a/apps/web/public/locales/he/common.json b/apps/web/public/locales/he/common.json index 496695d38..62e81f413 100644 --- a/apps/web/public/locales/he/common.json +++ b/apps/web/public/locales/he/common.json @@ -484,10 +484,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "כל הזכויות שמורות.", + "RIGHTS_RESERVED": "כל הזכויות שמורות.", "COPY_RIGHT1": "© {{date}}-היום,", - "COPY_RIGHT2": "Ever צוותים", - "COPY_RIGHT3": "Gauzy צוותים ", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "תנאי שימוש", diff --git a/apps/web/public/locales/it/common.json b/apps/web/public/locales/it/common.json index 4bf8b9183..ab85e8032 100644 --- a/apps/web/public/locales/it/common.json +++ b/apps/web/public/locales/it/common.json @@ -488,10 +488,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "All rights reserved.", + "RIGHTS_RESERVED": "All rights reserved.", "COPY_RIGHT1": "© {{date}}-Present,", - "COPY_RIGHT2": "Ever Teams ", - "COPY_RIGHT3": "Gauzy Teams", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Terms of Service", diff --git a/apps/web/public/locales/nl/common.json b/apps/web/public/locales/nl/common.json index bb66e16bf..cc5ce7e19 100644 --- a/apps/web/public/locales/nl/common.json +++ b/apps/web/public/locales/nl/common.json @@ -488,10 +488,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "Alle rechten voorbehouden.", + "RIGHTS_RESERVED": "Alle rechten voorbehouden.", "COPY_RIGHT1": "© {{date}}-Heden,", "COPY_RIGHT2": "Ever Teams", - "COPY_RIGHT3": "Gauzy Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Gebruiksvoorwaarden", diff --git a/apps/web/public/locales/pl/common.json b/apps/web/public/locales/pl/common.json index 4bf8b9183..ab85e8032 100644 --- a/apps/web/public/locales/pl/common.json +++ b/apps/web/public/locales/pl/common.json @@ -488,10 +488,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "All rights reserved.", + "RIGHTS_RESERVED": "All rights reserved.", "COPY_RIGHT1": "© {{date}}-Present,", - "COPY_RIGHT2": "Ever Teams ", - "COPY_RIGHT3": "Gauzy Teams", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Terms of Service", diff --git a/apps/web/public/locales/pt/common.json b/apps/web/public/locales/pt/common.json index 4bf8b9183..ab85e8032 100644 --- a/apps/web/public/locales/pt/common.json +++ b/apps/web/public/locales/pt/common.json @@ -488,10 +488,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "All rights reserved.", + "RIGHTS_RESERVED": "All rights reserved.", "COPY_RIGHT1": "© {{date}}-Present,", - "COPY_RIGHT2": "Ever Teams ", - "COPY_RIGHT3": "Gauzy Teams", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Terms of Service", diff --git a/apps/web/public/locales/ru/common.json b/apps/web/public/locales/ru/common.json index 4bf8b9183..ab85e8032 100644 --- a/apps/web/public/locales/ru/common.json +++ b/apps/web/public/locales/ru/common.json @@ -488,10 +488,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "All rights reserved.", + "RIGHTS_RESERVED": "All rights reserved.", "COPY_RIGHT1": "© {{date}}-Present,", - "COPY_RIGHT2": "Ever Teams ", - "COPY_RIGHT3": "Gauzy Teams", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "Ever Co.", "COMPANY_NAME": "Ever Co. LTD.", "TERMS": "Terms of Service", diff --git a/apps/web/public/locales/zh/common.json b/apps/web/public/locales/zh/common.json index c1ad6f42d..d04259013 100644 --- a/apps/web/public/locales/zh/common.json +++ b/apps/web/public/locales/zh/common.json @@ -465,10 +465,9 @@ "layout": { "footer": { - "RIGHTS_RESERVERD": "版权所有。", + "RIGHTS_RESERVED": "版权所有。", "COPY_RIGHT1": "© {{date}}-至今,", - "COPY_RIGHT2": "永恒团队", - "COPY_RIGHT3": "Gauzy团队", + "COPY_RIGHT2": "Ever Teams", "COPY_RIGHT4": "永恒公司", "COMPANY_NAME": "永恒有限公司", "TERMS": "服务条款", diff --git a/fly.toml b/fly.toml index 3ed1a5747..080eb74c9 100644 --- a/fly.toml +++ b/fly.toml @@ -9,6 +9,7 @@ kill_signal = "SIGINT" kill_timeout = "5m0s" [build] + image = "ghcr.io/ever-co/ever-teams-webapp:latest" [http_service] internal_port = 3000 diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..a838114cb --- /dev/null +++ b/netlify.toml @@ -0,0 +1,5 @@ +[build] + base = "apps/web" + command = "yarn run build" + functions = "netlify/functions" + publish = ".next" diff --git a/railway.json b/railway.json new file mode 100644 index 000000000..f77c81405 --- /dev/null +++ b/railway.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "DOCKERFILE" + }, + "deploy": { + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 10 + } +} diff --git a/render.yaml b/render.yaml index 3d963a0e4..9b71f90e4 100644 --- a/render.yaml +++ b/render.yaml @@ -1,93 +1,77 @@ services: - - type: web - runtime: node - env: node - name: ever-teams-web - region: oregon - branch: develop - rootDir: ./apps/web - buildCommand: yarn; yarn build - startCommand: yarn start - envVars: - - key: RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED - value: false - - key: NEXT_PUBLIC_GAUZY_API_SERVER_URL - value: https://api.gauzy.co - - key: GAUZY_API_SERVER_URL - value: https://api.gauzy.co/api - - key: INVITE_CALLBACK_URL - value: https://app.ever.team/passcode - - key: NEXT_PUBLIC_GA_MEASUREMENT_ID - value: SOME_MEASUREMENT_ID - #CAPTCHA Settings - - key: NEXT_PUBLIC_CAPTCHA_SITE_KEY - sync: false - - key: CAPTCHA_SECRET_KEY - sync: false - # Invite callback URL - - key: INVITE_CALLBACK_URL - value: https://app.ever.team/auth/passcode - # Verify Email Callback URL - - key: VERIFY_EMAIL_CALLBACK_URL - value: https://app.ever.team/verify-email - # SMTP Mail Configuration - - key: SMTP_FROM_ADDRESS - value: noreply@ever.team - - key: SMTP_HOST - sync: false - - key: SMTP_PORT - sync: false - - key: SMTP_SECURE - sync: false - - key: SMTP_USERNAME - sync: false - - key: SMTP_PASSWORD - sync: false - # Disable auto api refresh - - key: NEXT_PUBLIC_DISABLE_AUTO_REFRESH - value: false - # App Defaults - - key: APP_NAME - value: "Ever Teams" - - key: APP_SIGNATURE - value: "Ever Teams" - - key: APP_LOGO_URL - value: "https://app.ever.team/assets/gauzy-team.png" - # Cookies - - key: NEXT_PUBLIC_COOKIE_DOMAINS - value: ever.team - # Board - - key: NEXT_PUBLIC_BOARD_APP_DOMAIN - value: https://board.ever.team - - key: NEXT_PUBLIC_BOARD_BACKEND_POST_URL - value: "https://jsonboard.ever.team/api/v2/post/" - - key: NEXT_PUBLIC_BOARD_FIREBASE_CONFIG - sync: false - # Meet - - key: NEXT_PUBLIC_MEET_DOMAIN - value: "meet.ever.team" - # Private Variables (Meet) - - key: MEET_JWT_APP_ID - value: ever_teams - - key: MEET_JWT_APP_SECRET - sync: false - # SENTRY - - key: SENTRY_ORG - value: ever-co - - key: SENTRY_PROJECT - value: ever-teams-web - - key: NEXT_PUBLIC_SENTRY_DNS - sync: false - - key: NEXT_PUBLIC_SENTRY_DEBUG - value: false - # JITSU - - key: JITSU_BROWSER_URL - sync: false - - key: JITSU_BROWSER_WRITE_KEY - sync: false - # Github Integration - - key: NEXT_PUBLIC_GITHUB_APP_NAME - value: ever-github - # Chatwoot - - key: NEXT_PUBLIC_CHATWOOT_API_KEY - sync: false \ No newline at end of file + - type: web + runtime: node + env: node + name: ever-teams-web + region: oregon + branch: develop + rootDir: ./apps/web + buildCommand: yarn; yarn build + startCommand: yarn start + envVars: + - key: RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED + value: false + - key: NEXT_PUBLIC_GAUZY_API_SERVER_URL + value: https://api.gauzy.co + - key: GAUZY_API_SERVER_URL + value: https://api.gauzy.co/api + - key: NEXT_PUBLIC_GA_MEASUREMENT_ID + sync: false + - key: NEXT_PUBLIC_CAPTCHA_SITE_KEY + sync: false + - key: CAPTCHA_SECRET_KEY + sync: false + - key: INVITE_CALLBACK_URL + value: https://app.ever.team/auth/passcode + - key: VERIFY_EMAIL_CALLBACK_URL + value: https://app.ever.team/verify-email + - key: SMTP_FROM_ADDRESS + value: noreply@ever.team + - key: SMTP_HOST + sync: false + - key: SMTP_PORT + sync: false + - key: SMTP_SECURE + sync: false + - key: SMTP_USERNAME + sync: false + - key: SMTP_PASSWORD + sync: false + - key: NEXT_PUBLIC_DISABLE_AUTO_REFRESH + value: false + - key: APP_NAME + value: 'Ever Teams' + - key: APP_SIGNATURE + value: 'Ever Teams' + - key: APP_LOGO_URL + value: 'https://app.ever.team/assets/ever-teams.png' + - key: NEXT_PUBLIC_COOKIE_DOMAINS + value: ever.team + - key: NEXT_PUBLIC_BOARD_APP_DOMAIN + value: https://board.ever.team + - key: NEXT_PUBLIC_BOARD_BACKEND_POST_URL + value: 'https://jsonboard.ever.team/api/v2/post/' + - key: NEXT_PUBLIC_BOARD_FIREBASE_CONFIG + sync: false + - key: NEXT_PUBLIC_MEET_DOMAIN + value: 'meet.ever.team' + - key: MEET_JWT_APP_ID + value: ever_teams + - key: MEET_JWT_APP_SECRET + sync: false + - key: SENTRY_ORG + value: ever-co + - key: SENTRY_PROJECT + value: ever-teams-web + - key: NEXT_PUBLIC_SENTRY_DNS + sync: false + - key: NEXT_PUBLIC_SENTRY_DEBUG + value: false + - key: JITSU_BROWSER_URL + sync: false + - key: JITSU_BROWSER_WRITE_KEY + sync: false + - key: NEXT_PUBLIC_GITHUB_APP_NAME + value: ever-github + - key: NEXT_PUBLIC_CHATWOOT_API_KEY + sync: false diff --git a/yarn.lock b/yarn.lock index 4344273a0..322a4b395 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18820,9 +18820,9 @@ streamsearch@^1.1.0: integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== streamx@^2.15.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.1.tgz#396ad286d8bc3eeef8f5cea3f029e81237c024c6" - integrity sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA== + version "2.15.2" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.2.tgz#680eacebdc9c43ede7362c2e6695b34dd413c741" + integrity sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg== dependencies: fast-fifo "^1.1.0" queue-tick "^1.0.1"